diff --git a/README.md b/README.md index e1a7b3b7f..b56df17f1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A frontend for the SameBoy GameBoy emulator, with a focus on music creation. It runs standalone and can be used as an audio plugin (VST) in your favourite DAW! ## Features -- Wraps [SameBoy](https://github.com/LIJI32/SameBoy) v0.14.3 +- Wraps [SameBoy](https://github.com/LIJI32/SameBoy) v0.15.7 - Full MIDI support for [mGB](https://github.com/trash80/mGB) - Syncs [LSDj](https://www.littlesounddj.com) to your DAW - Emulates various [Arduinoboy](https://github.com/trash80/Arduinoboy) modes diff --git a/premake5.lua b/premake5.lua index e43dd633a..54d391f08 100644 --- a/premake5.lua +++ b/premake5.lua @@ -94,7 +94,7 @@ project "RetroPlug" configuration { "not emscripten" } prebuildcommands { - "%{wks.location}/bin/x64/Release/ScriptCompiler ../../src/compiler.config.lua" + "%{wks.location}/bin/%{cfg.platform}/Debug/ScriptCompiler ../../src/compiler.config.lua" } filter { "system:windows", "files:src/retroplug/luawrapper/**" } diff --git a/scripts/sameboy.lua b/scripts/sameboy.lua index 7368aed1c..719c27f66 100644 --- a/scripts/sameboy.lua +++ b/scripts/sameboy.lua @@ -40,7 +40,7 @@ project "SameBoyBootRoms" files { SAMEBOY_DIR .. "BootROMs/**.asm" } prebuildcommands { - 'rgbgfx -h -u -o "%{cfg.objdir}/SameBoyLogo.2bpp" "' .. BOOTROM_DIR .. '/SameBoyLogo.png"', + 'rgbgfx -Z -u -c embedded -o "%{cfg.objdir}/SameBoyLogo.2bpp" "' .. BOOTROM_DIR .. '/SameBoyLogo.png"', '"%{cfg.buildtarget.directory}/pb12" < "%{cfg.objdir}/SameBoyLogo.2bpp" > "%{cfg.objdir}/SameBoyLogo.pb12"' } @@ -57,12 +57,32 @@ project "SameBoyBootRoms" end +local function getVersion() + local file = io.open("../thirdparty/SameBoy/version.mk", "r") + if file == nil then + error("Failed to detect SameBoy version: version.mk could not be opened") + end + + local version = file:read() + local st, en = version:find(":= ") + + if st == nil then + error("Failed to detect SameBoy version: version.mk contains invalid data") + end + + version = version:sub(en + 1) + + file:close() + + return version +end + project "SameBoy" kind "StaticLib" language "C" toolset "clang" - defines { "GB_INTERNAL", "GB_DISABLE_TIMEKEEPING" } + defines { "GB_INTERNAL", "GB_DISABLE_TIMEKEEPING", [[GB_VERSION="]] .. getVersion() .. [["]] } sysincludedirs { SAMEBOY_DIR .. "Core", diff --git a/src/plugs/SameBoyPlug.cpp b/src/plugs/SameBoyPlug.cpp index 2075a8ba2..8a40c383d 100644 --- a/src/plugs/SameBoyPlug.cpp +++ b/src/plugs/SameBoyPlug.cpp @@ -3,10 +3,6 @@ #include #include -extern "C" { - #include -} - #include "retroplug/Constants.h" #include "retroplug/util/SampleConverter.h" #include "generated/bootroms/agb_boot.h" @@ -16,6 +12,10 @@ extern "C" { #include "generated/bootroms/sgb_boot.h" #include "generated/bootroms/sgb2_boot.h" +extern "C" { +#include +} + const size_t LINK_TICKS_MAX = 3907; const size_t MAX_SERIAL_ITEMS = 128; @@ -83,7 +83,7 @@ static uint32_t rgbEncode(GB_gameboy_t* gb, uint8_t r, uint8_t g, uint8_t b) { return 255 << 24 | b << 16 | g << 8 | r; } -static void vblankHandler(GB_gameboy_t* gb) { +static void vblankHandler(GB_gameboy_t* gb, GB_vblank_type_t type) { SameBoyPlugState* state = (SameBoyPlugState*)GB_get_user_data(gb); state->vblankOccurred = true; } @@ -136,7 +136,7 @@ void SameBoyPlug::init(GameboyModel model) { GB_set_serial_transfer_bit_start_callback(_state.gb, serialStart); GB_set_serial_transfer_bit_end_callback(_state.gb, serialEnd); - GB_set_color_correction_mode(_state.gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(_state.gb, GB_COLOR_CORRECTION_MODERN_BALANCED); GB_set_highpass_filter_mode(_state.gb, GB_HIGHPASS_ACCURATE); GB_set_rendering_disabled(_state.gb, true); diff --git a/src/plugs/SameBoyPlug.h b/src/plugs/SameBoyPlug.h index b43dd6fa0..695f33a2c 100644 --- a/src/plugs/SameBoyPlug.h +++ b/src/plugs/SameBoyPlug.h @@ -2,10 +2,12 @@ #include #include -#include #include "retroplug/Messages.h" +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; + const size_t PIXEL_WIDTH = 160; const size_t PIXEL_HEIGHT = 144; const size_t PIXEL_COUNT = (PIXEL_WIDTH * PIXEL_HEIGHT); diff --git a/src/retroplug/luawrapper/UiLuaContext.cpp b/src/retroplug/luawrapper/UiLuaContext.cpp index 37a7c0c7b..fa50f1f95 100644 --- a/src/retroplug/luawrapper/UiLuaContext.cpp +++ b/src/retroplug/luawrapper/UiLuaContext.cpp @@ -18,9 +18,11 @@ #include "platform/Logger.h" #include "Wrappers.h" -//#ifdef COMPILE_LUA_SCRIPTS +#ifdef COMPILE_LUA_SCRIPTS #include "generated/CompiledScripts.h" -//#endif +#endif + +//#define DEBUG_SCRIPTS void UiLuaContext::init(AudioContextProxy* proxy, const std::string& path, const std::string& scriptPath) { _configPath = path; @@ -47,7 +49,7 @@ bool UiLuaContext::onKey(VirtualKey key, bool down) { return res; } - return false; + return false; } void UiLuaContext::onDoubleClick(float x, float y, MouseMod mod) { @@ -91,14 +93,14 @@ void UiLuaContext::reload() { if (_valid) { callFunc(_viewRoot, "onReloadBegin"); } - + shutdown(); setup(false); if (_valid) { callFunc(_viewRoot, "onReloadEnd"); } - + _haltFrameProcessing = !_valid; } @@ -141,13 +143,13 @@ bool UiLuaContext::setup(bool updateProject) { _state = new sol::state(); sol::state& s = *_state; - s.open_libraries( sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::string, + s.open_libraries( sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::string, sol::lib::math, sol::lib::debug, sol::lib::coroutine, sol::lib::io ); std::string packagePath = s["package"]["path"]; packagePath += ";" + _configPath + "/?.lua"; -#ifdef COMPILE_LUA_SCRIPTS +#ifndef DEBUG_SCRIPTS spdlog::info("Using precompiled lua scripts"); s.add_package_loader(CompiledScripts::common::loader); s.add_package_loader(CompiledScripts::ui::loader); @@ -158,7 +160,7 @@ bool UiLuaContext::setup(bool updateProject) { #endif s["package"]["path"] = packagePath; - + luawrappers::registerCommon(s); luawrappers::registerChrono(s); luawrappers::registerLsdj(s); @@ -191,7 +193,7 @@ bool UiLuaContext::setup(bool updateProject) { ); // TODO: Fix naming of this too! - s.create_named_table("nativeutil", + s.create_named_table("nativeutil", "mergeMenu", mergeMenu ); @@ -223,7 +225,7 @@ bool UiLuaContext::setup(bool updateProject) { } // Load the users config settings - // TODO: This should probably happen outside of this class since it may be used by the + // TODO: This should probably happen outside of this class since it may be used by the // audio lua context too. std::string configPath = _configPath + "/config.lua"; bool configValid = false; diff --git a/src/retroplug/model/ProcessingContext.cpp b/src/retroplug/model/ProcessingContext.cpp index c254a62dc..79859e712 100644 --- a/src/retroplug/model/ProcessingContext.cpp +++ b/src/retroplug/model/ProcessingContext.cpp @@ -174,6 +174,9 @@ void ProcessingContext::process(float** outputs, size_t frameCount) { } else { linkedPlugs[linkedPlugCount++] = plug; } + + plug->pressButtons(_buttonPresses[i].data().presses.data(), _buttonPresses[i].getCount()); + _buttonPresses[i].clear(); totalPlugCount++; } diff --git a/thirdparty/SameBoy-old/.gitattributes b/thirdparty/SameBoy-old/.gitattributes new file mode 100644 index 000000000..2149ea1dc --- /dev/null +++ b/thirdparty/SameBoy-old/.gitattributes @@ -0,0 +1,10 @@ +# 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 +SDL/*.h linguist-language=C +Windows/*.h linguist-language=C +Cocoa/*.h linguist-language=Objective-C diff --git a/thirdparty/SameBoy-old/.github/FUNDING.yml b/thirdparty/SameBoy-old/.github/FUNDING.yml new file mode 100644 index 000000000..a4c3a1bbd --- /dev/null +++ b/thirdparty/SameBoy-old/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: LIJI32 diff --git a/thirdparty/SameBoy-old/.github/actions/LICENSE b/thirdparty/SameBoy-old/.github/actions/LICENSE new file mode 100644 index 000000000..8c295a2b8 --- /dev/null +++ b/thirdparty/SameBoy-old/.github/actions/LICENSE @@ -0,0 +1,25 @@ +Blargg's Test ROMs by Shay Green + +Acid2 tests by Matt Currie under MIT: + +MIT License + +Copyright (c) 2020 Matt Currie + +Permission is hereby granted, free of charge, to any person obtaining a copy +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. \ No newline at end of file diff --git a/thirdparty/SameBoy-old/.github/actions/cgb-acid2.gbc b/thirdparty/SameBoy-old/.github/actions/cgb-acid2.gbc new file mode 100644 index 000000000..5f71bd360 Binary files /dev/null and b/thirdparty/SameBoy-old/.github/actions/cgb-acid2.gbc differ diff --git a/thirdparty/SameBoy-old/.github/actions/cgb_sound.gb b/thirdparty/SameBoy-old/.github/actions/cgb_sound.gb new file mode 100644 index 000000000..dc50471b0 Binary files /dev/null and b/thirdparty/SameBoy-old/.github/actions/cgb_sound.gb differ diff --git a/thirdparty/SameBoy-old/.github/actions/dmg-acid2.gb b/thirdparty/SameBoy-old/.github/actions/dmg-acid2.gb new file mode 100644 index 000000000..a25ef9485 Binary files /dev/null and b/thirdparty/SameBoy-old/.github/actions/dmg-acid2.gb differ diff --git a/thirdparty/SameBoy-old/.github/actions/dmg_sound-2.gb b/thirdparty/SameBoy-old/.github/actions/dmg_sound-2.gb new file mode 100644 index 000000000..52dcf70b3 Binary files /dev/null and b/thirdparty/SameBoy-old/.github/actions/dmg_sound-2.gb differ diff --git a/thirdparty/SameBoy-old/.github/actions/install_deps.sh b/thirdparty/SameBoy-old/.github/actions/install_deps.sh new file mode 100644 index 000000000..1c9749efc --- /dev/null +++ b/thirdparty/SameBoy-old/.github/actions/install_deps.sh @@ -0,0 +1,23 @@ +case `echo $1 | cut -d '-' -f 1` in + ubuntu) + sudo apt-get -qq update + sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev + ( + cd `mktemp -d` + curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip + unzip rgbds.zip + cd rgbds-* + make -sj + sudo make install + cd .. + rm -rf * + ) + ;; + macos) + brew install rgbds sdl2 + ;; + *) + echo "Unsupported OS" + exit 1 + ;; +esac \ No newline at end of file diff --git a/thirdparty/SameBoy-old/.github/actions/oam_bug-2.gb b/thirdparty/SameBoy-old/.github/actions/oam_bug-2.gb new file mode 100644 index 000000000..a3f55af10 Binary files /dev/null and b/thirdparty/SameBoy-old/.github/actions/oam_bug-2.gb differ diff --git a/thirdparty/SameBoy-old/.github/actions/sanity_tests.sh b/thirdparty/SameBoy-old/.github/actions/sanity_tests.sh new file mode 100644 index 000000000..1d8b33e2c --- /dev/null +++ b/thirdparty/SameBoy-old/.github/actions/sanity_tests.sh @@ -0,0 +1,33 @@ +set -e + +./build/bin/tester/sameboy_tester --jobs 5 \ + --length 45 .github/actions/cgb_sound.gb \ + --length 10 .github/actions/cgb-acid2.gbc \ + --length 10 .github/actions/dmg-acid2.gb \ +--dmg --length 45 .github/actions/dmg_sound-2.gb \ +--dmg --length 20 .github/actions/oam_bug-2.gb + +mv .github/actions/dmg{,-mode}-acid2.bmp + +./build/bin/tester/sameboy_tester \ +--dmg --length 10 .github/actions/dmg-acid2.gb + +set +e + +FAILED_TESTS=` +shasum .github/actions/*.bmp | grep -E -v \(\ +64c3fd9a5fe9aee40fe15f3371029c0d2f20f5bc\ \ .github/actions/cgb-acid2.bmp\|\ +dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\ +0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\ +fbdb5e342bfdd2edda3ea5601d35d0ca60d18034\ \ .github/actions/dmg-mode-acid2.bmp\|\ +c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\ +f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\ +\)` + +if [ -n "$FAILED_TESTS" ] ; then + echo "Failed the following tests:" + echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort + exit 1 +fi + +echo Passed all tests \ No newline at end of file diff --git a/thirdparty/SameBoy-old/.github/workflows/sanity.yml b/thirdparty/SameBoy-old/.github/workflows/sanity.yml new file mode 100644 index 000000000..ac3732397 --- /dev/null +++ b/thirdparty/SameBoy-old/.github/workflows/sanity.yml @@ -0,0 +1,36 @@ +name: "Bulidability and Sanity" +on: push + +jobs: + sanity: + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, ubuntu-18.04] + cc: [gcc, clang] + include: + - os: macos-latest + cc: clang + extra_target: cocoa + exclude: + - os: macos-latest + cc: gcc + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Install deps + shell: bash + run: | + ./.github/actions/install_deps.sh ${{ matrix.os }} + - name: Build + run: | + ${{ matrix.cc }} -v; (make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} || (echo "==== Build Failed ==="; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }})) + - name: Sanity tests + shell: bash + run: | + ./.github/actions/sanity_tests.sh + - name: Upload binaries + uses: actions/upload-artifact@v1 + with: + name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }} + path: build/bin diff --git a/thirdparty/SameBoy-old/.gitignore b/thirdparty/SameBoy-old/.gitignore new file mode 100644 index 000000000..c795b054e --- /dev/null +++ b/thirdparty/SameBoy-old/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BESS.md b/thirdparty/SameBoy-old/BESS.md new file mode 100644 index 000000000..1807943c6 --- /dev/null +++ b/thirdparty/SameBoy-old/BESS.md @@ -0,0 +1,259 @@ +# BESS – Best Effort Save State 1.0 + +## Motivation + +BESS is a save state format specification designed to allow different emulators, as well as majorly different versions of the same emulator, to import save states from other BESS-compliant save states. BESS works by appending additional, implementation-agnostic information about the emulation state. This allows a single save state file to be read as both a fully-featured, implementation specific save state which includes detailed timing information; and as a portable "best effort" save state that represents a state accurately enough to be restored in casual use-cases. + +## Specification + +Every integer used in the BESS specification is stored in Little Endian encoding. + +### BESS footer + +BESS works by appending a detectable footer at the end of an existing save state format. The footer uses the following format: + +| Offset from end of file | Content | +|-------------------------|-------------------------------------------------------| +| -8 | Offset to the first BESS Block, from the file's start | +| -4 | The ASCII string 'BESS' | + +### BESS blocks + +BESS uses a block format where each block contains the following header: + +| Offset | Content | +|--------|---------------------------------------| +| 0 | A four-letter ASCII identifier | +| 4 | Length of the block, excluding header | + +Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). + +#### NAME block + +The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first. + +The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. + + +#### INFO block + +The INFO block uses the `'INFO'` identifier, and is an optional block that contains information about the ROM this save state originates from. When used, this block should come before `CORE` but after `NAME`. This block is 0x12 bytes long, and it follows this structure: + +| Offset | Content | +|--------|--------------------------------------------------| +| 0x00 | Bytes 0x134-0x143 from the ROM (Title) | +| 0x10 | Bytes 0x14E-0x14F from the ROM (Global checksum) | + +#### CORE block + +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` or `INFO` blocks exist then it must come directly after them. An implementation should not enforce block order on blocks unknown to it for future compatibility. + +The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: + +| Offset | Content | +|--------|----------------------------------------| +| 0x00 | Major BESS version as a 16-bit integer | +| 0x02 | Minor BESS version as a 16-bit integer | + +Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions. + +| Offset | Content | +|--------|----------------------------------------| +| 0x04 | A four-character ASCII model identifier | + +BESS uses a four-character string to identify Game Boy models: + + * The first letter represents mutually-incompatible families of models and is required. The allowed values are `'G'` for the original Game Boy family, `'S'` for the Super Game Boy family, and `'C'` for the Game Boy Color and Advance family. +* The second letter represents a specific model within the family, and is optional (If an implementation does not distinguish between specific models in a family, a space character may be used). The allowed values for family G are `'D'` for DMG and `'M'` for MGB; the allowed values for family S are `'N'` for NTSC, `'P'` for PAL, and `'2'` for SGB2; and the allowed values for family C are `'C'` for CGB, and `'A'` for the various GBA line models. +* The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character. +* The last character is used for padding and must be a space character. + +For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E. + +| Offset | Content | +|--------|--------------------------------------------------------| +| 0x08 | The value of the PC register | +| 0x0A | The value of the AF register | +| 0x0C | The value of the BC register | +| 0x0E | The value of the DE register | +| 0x10 | The value of the HL register | +| 0x12 | The value of the SP register | +| 0x14 | The value of IME (0 or 1) | +| 0x15 | The value of the IE register | +| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | +| 0x17 | Reserved, must be 0 | +| 0x18 | The values of every memory-mapped register (128 bytes) | + +The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: +* Unused registers have Don't-Care values which should be ignored +* Unused register bits have Don't-Care values which should be ignored +* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode + * Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode. +* Object priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect +* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. +* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. +* Implementations should not start a serial transfer when writing the value of SB +* Similarly, no value of NRx4 should trigger a sound pulse on save state load +* And similarly again, implementations should not trigger DMA transfers when writing the values of DMA or HDMA5 +* The value store for DIV will be used to set the internal divisor to `DIV << 8` +* Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers) + +| Offset | Content | +|--------|--------------------------------------------------------------------| +| 0x98 | The size of RAM (32-bit integer) | +| 0x9C | The offset of RAM from file start (32-bit integer) | +| 0xA0 | The size of VRAM (32-bit integer) | +| 0xA4 | The offset of VRAM from file start (32-bit integer) | +| 0xA8 | The size of MBC RAM (32-bit integer) | +| 0xAC | The offset of MBC RAM from file start (32-bit integer) | +| 0xB0 | The size of OAM (=0xA0, 32-bit integer) | +| 0xB4 | The offset of OAM from file start (32-bit integer) | +| 0xB8 | The size of HRAM (=0x7F, 32-bit integer) | +| 0xBC | The offset of HRAM from file start (32-bit integer) | +| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) | +| 0xC4 | The offset of background palettes from file start (32-bit integer) | +| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) | +| 0xCC | The offset of object palettes from file start (32-bit integer) | + +The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color. + +An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros. + +#### XOAM block + +The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block. + + +#### MBC block + +The MBC block uses the `'MBC '` identifier, and is an optional block that is only used when saving states of ROMs that use an MBC. The length of this block is variable and must be divisible by 3. + +This block contains an MBC-specific number of 3-byte-long pairs that represent the values of each MBC register. For example, for MBC5 the contents would look like: + +| Offset | Content | +|--------|---------------------------------------| +| 0x0 | The value 0x0000 as a 16-bit integer | +| 0x2 | 0x0A if RAM is enabled, 0 otherwise | +| 0x3 | The value 0x2000 as a 16-bit integer | +| 0x5 | The lower 8 bits of the ROM bank | +| 0x6 | The value 0x3000 as a 16-bit integer | +| 0x8 | The bit 9 of the ROM bank | +| 0x9 | The value 0x4000 as a 16-bit integer | +| 0xB | The current RAM bank | + +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order) + +#### RTC block +The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. + +The length of this block is 0x30 bytes long and it follows the following structure: + +| Offset | Content | +|--------|------------------------------------------------------------------------| +| 0x00 | Current seconds (1 byte), followed by 3 bytes of padding | +| 0x04 | Current minutes (1 byte), followed by 3 bytes of padding | +| 0x08 | Current hours (1 byte), followed by 3 bytes of padding | +| 0x0C | Current days (1 byte), followed by 3 bytes of padding | +| 0x10 | Current high/overflow/running (1 byte), followed by 3 bytes of padding | +| 0x14 | Latched seconds (1 byte), followed by 3 bytes of padding | +| 0x18 | Latched minutes (1 byte), followed by 3 bytes of padding | +| 0x1C | Latched hours (1 byte), followed by 3 bytes of padding | +| 0x20 | Latched days (1 byte), followed by 3 bytes of padding | +| 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding | +| 0x28 | UNIX timestamp at the time of the save state (64-bit) | + +#### HUC3 block +The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy. + +The length of this block is 0x11 bytes long and it follows the following structure: + +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | UNIX timestamp at the time of the save state (64-bit) | +| 0x08 | RTC minutes (16-bit) | +| 0x0A | RTC days (16-bit) | +| 0x0C | Scheduled alarm time minutes (16-bit) | +| 0x0E | Scheduled alarm time days (16-bit) | +| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) | + +#### TPP1 block +The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC. + +The length of this block is 0x11 bytes long and it follows the following structure: + +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | UNIX timestamp at the time of the save state (64-bit) | +| 0x08 | The current RTC data (4 bytes) | +| 0x0C | The latched RTC data (4 bytes) | +| 0x10 | The value of the MR4 register (8-bits) | + + +#### MBC7 block +The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state. + +The length of this block is 0xA bytes long and it follows the following structure: + +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | Flags (8-bits) | +| 0x01 | Argument bits left (8-bits) | +| 0x02 | Current EEPROM command (16-bits) | +| 0x04 | Pending bits to read (16-bits) | +| 0x06 | Latched gyro X value (16-bits) | +| 0x08 | Latched gyro Y value (16-bits) | + +The meaning of the individual bits in flags are: + * Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X` + * Bit 1: EEPROM DO line + * Bit 2: EEPROM DI line + * Bit 3: EEPROM CLK line + * Bit 4: EEPROM CS line + * Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command + * Bits 6-7: Unused. + +The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`. + +If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0. + +The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones. + +#### SGB block + +The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. + +The length of this block is 0x39 bytes, but implementations should allow and ignore excess data in this block for extensions. The block follows the following structure: + +| Offset | Content | +|--------|--------------------------------------------------------------------------------------------------------------------------| +| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | +| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | +| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | +| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | +| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | +| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | +| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | +| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | +| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | +| 0x2C | The offset of the attribute map (32-bit integer) | +| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | +| 0x34 | The offset of the attribute files (32-bit integer) | +| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) | + +If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. + +#### END block +The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0. + +## Validation and Failures + +Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation: +* Duplicate CORE block +* A known block, other than NAME, appearing before CORE +* An invalid length for the XOAM, RTC, SGB or HUC3 blocks +* An invalid length of MBC (not a multiple of 3) +* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block +* An SGB block on a save state targeting another model +* An END block with non-zero length \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/SameBoyLogo.png b/thirdparty/SameBoy-old/BootROMs/SameBoyLogo.png new file mode 100644 index 000000000..ad1a760c0 Binary files /dev/null and b/thirdparty/SameBoy-old/BootROMs/SameBoyLogo.png differ diff --git a/thirdparty/SameBoy-old/BootROMs/agb_boot.asm b/thirdparty/SameBoy-old/BootROMs/agb_boot.asm new file mode 100644 index 000000000..95a2c783a --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/agb_boot.asm @@ -0,0 +1,2 @@ +AGB EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/cgb0_boot.asm b/thirdparty/SameBoy-old/BootROMs/cgb0_boot.asm new file mode 100644 index 000000000..d49166d89 --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/cgb0_boot.asm @@ -0,0 +1,2 @@ +CGB0 EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/cgb_boot.asm b/thirdparty/SameBoy-old/BootROMs/cgb_boot.asm new file mode 100644 index 000000000..dc055b2ac --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/cgb_boot.asm @@ -0,0 +1,1249 @@ +; SameBoy CGB bootstrap ROM + +INCLUDE "hardware.inc" + +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + call ClearMemoryPage8000 + +; Clear OAM + ld h, $fe + ld c, $a0 +.clearOAMLoop + ldi [hl], a + dec c + jr nz, .clearOAMLoop + +IF !DEF(CGB0) +; Init waveform + ld c, $10 + ld hl, $FF30 +.waveformLoop + ldi [hl], a + cpl + dec c + jr nz, .waveformLoop +ENDC + +; Clear chosen input palette + ldh [InputPalette], a +; Clear title checksum + ldh [TitleChecksum], a + +; Init Audio + ld a, $80 + ldh [rNR52], a + ldh [rNR11], a + ld a, $f3 + ldh [rNR12], a + ldh [rNR51], a + ld a, $77 + ldh [rNR50], a + +; Init BG palette + ld a, $fc + ldh [rBGP], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. +; These tiles are not used, but are required for DMG compatibility. This is done +; by the original CGB Boot ROM as well. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRowTwice + inc de + ld a, e + cp $34 ; End of logo + jr nz, .loadLogoLoop + call ReadTrademarkSymbol + +; Clear the second VRAM bank + ld a, 1 + ldh [rVBK], a + call ClearMemoryPage8000 + call LoadTileset + + ld b, 3 +IF DEF(FAST) + xor a + ldh [rVBK], a +ELSE +; Load Tilemap + ld hl, $98C2 + ld d, 3 + ld a, 8 + +.tilemapLoop + ld c, $10 + +.tilemapRowLoop + + call .write_with_palette + + ; Repeat the 3 tiles common between E and B. This saves 27 bytes after + ; compression, with a cost of 17 bytes of code. + push af + sub $20 + sub $3 + jr nc, .notspecial + add $20 + call .write_with_palette + dec c +.notspecial + pop af + + add d ; d = 3 for SameBoy logo, d = 1 for Nintendo logo + dec c + jr nz, .tilemapRowLoop + sub 44 + push de + ld de, $10 + add hl, de + pop de + dec b + jr nz, .tilemapLoop + + dec d + jr z, .endTilemap + dec d + + ld a, $38 + ld l, $a7 + ld bc, $0107 + jr .tilemapRowLoop + +.write_with_palette + push af + ; Switch to second VRAM Bank + ld a, 1 + ldh [rVBK], a + ld [hl], 8 + ; Switch to back first VRAM Bank + xor a + ldh [rVBK], a + pop af + ldi [hl], a + ret +.endTilemap +ENDC + + ; Expand Palettes + ld de, AnimationColors + ld c, 8 + ld hl, BgPalettes + xor a +.expandPalettesLoop: + cpl + ; One white + ld [hli], a + ld [hli], a + + ; Mixed with white + ld a, [de] + inc e + or $20 + ld b, a + + ld a, [de] + dec e + or $84 + rra + rr b + ld [hl], b + inc l + ld [hli], a + + ; One black + xor a + ld [hli], a + ld [hli], a + + ; One color + ld a, [de] + inc e + ld [hli], a + ld a, [de] + inc e + ld [hli], a + + xor a + dec c + jr nz, .expandPalettesLoop + + call LoadPalettesFromHRAM + + ; Turn on LCD + ld a, $91 + ldh [rLCDC], a + +IF !DEF(FAST) + call DoIntroAnimation + + ld a, 48 ; frames to wait after playing the chime + ldh [WaitLoopCounter], a + ld b, 4 ; frames to wait before playing the chime + call WaitBFrames + + ; Play first sound + ld a, $83 + call PlaySound + ld b, 5 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound + +.waitLoop + call GetInputPaletteIndex + call WaitFrame + ld hl, WaitLoopCounter + dec [hl] + jr nz, .waitLoop +ELSE + ld a, $c1 + call PlaySound +ENDC + call Preboot +IF DEF(AGB) + ld b, 1 +ENDC + jr BootGame + +HDMAData: + db $D0, $00, $98, $A0, $12 + db $D0, $00, $80, $00, $40 + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [rBANK], a ; unmap boot ROM + +SECTION "MoreStuff", ROM0[$200] +; Game Palettes Data +TitleChecksums: + db $00 ; Default + db $88 ; ALLEY WAY + db $16 ; YAKUMAN + db $36 ; BASEBALL, (Game and Watch 2) + db $D1 ; TENNIS + db $DB ; TETRIS + db $F2 ; QIX + db $3C ; DR.MARIO + db $8C ; RADARMISSION + db $92 ; F1RACE + db $3D ; YOSSY NO TAMAGO + db $5C ; + db $58 ; X + db $C9 ; MARIOLAND2 + db $3E ; YOSSY NO COOKIE + db $70 ; ZELDA + db $1D ; + db $59 ; + db $69 ; TETRIS FLASH + db $19 ; DONKEY KONG + db $35 ; MARIO'S PICROSS + db $A8 ; + db $14 ; POKEMON RED, (GAMEBOYCAMERA G) + db $AA ; POKEMON GREEN + db $75 ; PICROSS 2 + db $95 ; YOSSY NO PANEPON + db $99 ; KIRAKIRA KIDS + db $34 ; GAMEBOY GALLERY + db $6F ; POCKETCAMERA + db $15 ; + db $FF ; BALLOON KID + db $97 ; KINGOFTHEZOO + db $4B ; DMG FOOTBALL + db $90 ; WORLD CUP + db $17 ; OTHELLO + db $10 ; SUPER RC PRO-AM + db $39 ; DYNABLASTER + db $F7 ; BOY AND BLOB GB2 + db $F6 ; MEGAMAN + db $A2 ; STAR WARS-NOA + db $49 ; + db $4E ; WAVERACE + db $43 ; + db $68 ; LOLO2 + db $E0 ; YOSHI'S COOKIE + db $8B ; MYSTIC QUEST + db $F0 ; + db $CE ; TOPRANKINGTENNIS + db $0C ; MANSELL + db $29 ; MEGAMAN3 + db $E8 ; SPACE INVADERS + db $B7 ; GAME&WATCH + db $86 ; DONKEYKONGLAND95 + db $9A ; ASTEROIDS/MISCMD + db $52 ; STREET FIGHTER 2 + db $01 ; DEFENDER/JOUST + db $9D ; KILLERINSTINCT95 + db $71 ; TETRIS BLAST + db $9C ; PINOCCHIO + db $BD ; + db $5D ; BA.TOSHINDEN + db $6D ; NETTOU KOF 95 + db $67 ; + db $3F ; TETRIS PLUS + db $6B ; DONKEYKONGLAND 3 +; For these games, the 4th letter is taken into account +FirstChecksumWithDuplicate: + ; Let's play hangman! + db $B3 ; ???[B]???????? + db $46 ; SUP[E]R MARIOLAND + db $28 ; GOL[F] + db $A5 ; SOL[A]RSTRIKER + db $C6 ; GBW[A]RS + db $D3 ; KAE[R]UNOTAMENI + db $27 ; ???[B]???????? + db $61 ; POK[E]MON BLUE + db $18 ; DON[K]EYKONGLAND + db $66 ; GAM[E]BOY GALLERY2 + db $6A ; DON[K]EYKONGLAND 2 + db $BF ; KID[ ]ICARUS + db $0D ; TET[R]IS2 + db $F4 ; ???[-]???????? + db $B3 ; MOG[U]RANYA + db $46 ; ???[R]???????? + db $28 ; GAL[A]GA&GALAXIAN + db $A5 ; BT2[R]AGNAROKWORLD + db $C6 ; KEN[ ]GRIFFEY JR + db $D3 ; ???[I]???????? + db $27 ; MAG[N]ETIC SOCCER + db $61 ; VEG[A]S STAKES + db $18 ; ???[I]???????? + db $66 ; MIL[L]I/CENTI/PEDE + db $6A ; MAR[I]O & YOSHI + db $BF ; SOC[C]ER + db $0D ; POK[E]BOM + db $F4 ; G&W[ ]GALLERY + db $B3 ; TET[R]IS ATTACK +ChecksumsEnd: + +PalettePerChecksum: +palette_index: MACRO ; palette, flags + db ((\1)) | (\2) ; | $80 means game requires DMG boot tilemap +ENDM + palette_index 0, 0 ; Default Palette + palette_index 4, 0 ; ALLEY WAY + palette_index 5, 0 ; YAKUMAN + palette_index 35, 0 ; BASEBALL, (Game and Watch 2) + palette_index 34, 0 ; TENNIS + palette_index 3, 0 ; TETRIS + palette_index 31, 0 ; QIX + palette_index 15, 0 ; DR.MARIO + palette_index 10, 0 ; RADARMISSION + palette_index 5, 0 ; F1RACE + palette_index 19, 0 ; YOSSY NO TAMAGO + palette_index 36, 0 ; + palette_index 7, $80 ; X + palette_index 37, 0 ; MARIOLAND2 + palette_index 30, 0 ; YOSSY NO COOKIE + palette_index 44, 0 ; ZELDA + palette_index 21, 0 ; + palette_index 32, 0 ; + palette_index 31, 0 ; TETRIS FLASH + palette_index 20, 0 ; DONKEY KONG + palette_index 5, 0 ; MARIO'S PICROSS + palette_index 33, 0 ; + palette_index 13, 0 ; POKEMON RED, (GAMEBOYCAMERA G) + palette_index 14, 0 ; POKEMON GREEN + palette_index 5, 0 ; PICROSS 2 + palette_index 29, 0 ; YOSSY NO PANEPON + palette_index 5, 0 ; KIRAKIRA KIDS + palette_index 18, 0 ; GAMEBOY GALLERY + palette_index 9, 0 ; POCKETCAMERA + palette_index 3, 0 ; + palette_index 2, 0 ; BALLOON KID + palette_index 26, 0 ; KINGOFTHEZOO + palette_index 25, 0 ; DMG FOOTBALL + palette_index 25, 0 ; WORLD CUP + palette_index 41, 0 ; OTHELLO + palette_index 42, 0 ; SUPER RC PRO-AM + palette_index 26, 0 ; DYNABLASTER + palette_index 45, 0 ; BOY AND BLOB GB2 + palette_index 42, 0 ; MEGAMAN + palette_index 45, 0 ; STAR WARS-NOA + palette_index 36, 0 ; + palette_index 38, 0 ; WAVERACE + palette_index 26, $80 ; + palette_index 42, 0 ; LOLO2 + palette_index 30, 0 ; YOSHI'S COOKIE + palette_index 41, 0 ; MYSTIC QUEST + palette_index 34, 0 ; + palette_index 34, 0 ; TOPRANKINGTENNIS + palette_index 5, 0 ; MANSELL + palette_index 42, 0 ; MEGAMAN3 + palette_index 6, 0 ; SPACE INVADERS + palette_index 5, 0 ; GAME&WATCH + palette_index 33, 0 ; DONKEYKONGLAND95 + palette_index 25, 0 ; ASTEROIDS/MISCMD + palette_index 42, 0 ; STREET FIGHTER 2 + palette_index 42, 0 ; DEFENDER/JOUST + palette_index 40, 0 ; KILLERINSTINCT95 + palette_index 2, 0 ; TETRIS BLAST + palette_index 16, 0 ; PINOCCHIO + palette_index 25, 0 ; + palette_index 42, 0 ; BA.TOSHINDEN + palette_index 42, 0 ; NETTOU KOF 95 + palette_index 5, 0 ; + palette_index 0, 0 ; TETRIS PLUS + palette_index 39, 0 ; DONKEYKONGLAND 3 + palette_index 36, 0 ; + palette_index 22, 0 ; SUPER MARIOLAND + palette_index 25, 0 ; GOLF + palette_index 6, 0 ; SOLARSTRIKER + palette_index 32, 0 ; GBWARS + palette_index 12, 0 ; KAERUNOTAMENI + palette_index 36, 0 ; + palette_index 11, 0 ; POKEMON BLUE + palette_index 39, 0 ; DONKEYKONGLAND + palette_index 18, 0 ; GAMEBOY GALLERY2 + palette_index 39, 0 ; DONKEYKONGLAND 2 + palette_index 24, 0 ; KID ICARUS + palette_index 31, 0 ; TETRIS2 + palette_index 50, 0 ; + palette_index 17, 0 ; MOGURANYA + palette_index 46, 0 ; + palette_index 6, 0 ; GALAGA&GALAXIAN + palette_index 27, 0 ; BT2RAGNAROKWORLD + palette_index 0, 0 ; KEN GRIFFEY JR + palette_index 47, 0 ; + palette_index 41, 0 ; MAGNETIC SOCCER + palette_index 41, 0 ; VEGAS STAKES + palette_index 0, 0 ; + palette_index 0, 0 ; MILLI/CENTI/PEDE + palette_index 19, 0 ; MARIO & YOSHI + palette_index 34, 0 ; SOCCER + palette_index 23, 0 ; POKEBOM + palette_index 18, 0 ; G&W GALLERY + palette_index 29, 0 ; TETRIS ATTACK + +Dups4thLetterArray: + db "BEFAARBEKEK R-URAR INAILICE R" + +; We assume the last three arrays fit in the same $100 byte page! + +PaletteCombinations: +palette_comb: MACRO ; Obj0, Obj1, Bg + db (\1) * 8, (\2) * 8, (\3) *8 +ENDM +raw_palette_comb: MACRO ; Obj0, Obj1, Bg + db (\1) * 2, (\2) * 2, (\3) * 2 +ENDM + palette_comb 4, 4, 29 + palette_comb 18, 18, 18 + palette_comb 20, 20, 20 + palette_comb 24, 24, 24 + palette_comb 9, 9, 9 + palette_comb 0, 0, 0 + palette_comb 27, 27, 27 + palette_comb 5, 5, 5 + palette_comb 12, 12, 12 + palette_comb 26, 26, 26 + palette_comb 16, 8, 8 + palette_comb 4, 28, 28 + palette_comb 4, 2, 2 + palette_comb 3, 4, 4 + palette_comb 4, 29, 29 + palette_comb 28, 4, 28 + palette_comb 2, 17, 2 + palette_comb 16, 16, 8 + palette_comb 4, 4, 7 + palette_comb 4, 4, 18 + palette_comb 4, 4, 20 + palette_comb 19, 19, 9 + raw_palette_comb 4 * 4 - 1, 4 * 4 - 1, 11 * 4 + palette_comb 17, 17, 2 + palette_comb 4, 4, 2 + palette_comb 4, 4, 3 + palette_comb 28, 28, 0 + palette_comb 3, 3, 0 + palette_comb 0, 0, 1 + palette_comb 18, 22, 18 + palette_comb 20, 22, 20 + palette_comb 24, 22, 24 + palette_comb 16, 22, 8 + 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 + 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 + palette_comb 4, 0, 2 + palette_comb 4, 28, 3 + palette_comb 28, 3, 0 + palette_comb 3, 28, 4 + palette_comb 21, 28, 4 + palette_comb 3, 28, 0 + palette_comb 25, 3, 28 + palette_comb 0, 28, 8 + palette_comb 4, 3, 28 + palette_comb 28, 3, 6 + palette_comb 4, 28, 29 + ; SameBoy "Exclusives" + palette_comb 30, 30, 30 ; CGA + palette_comb 31, 31, 31 ; DMG LCD + palette_comb 28, 4, 1 + palette_comb 0, 0, 2 + +Palettes: + dw $7FFF, $32BF, $00D0, $0000 + dw $639F, $4279, $15B0, $04CB + dw $7FFF, $6E31, $454A, $0000 + dw $7FFF, $1BEF, $0200, $0000 + dw $7FFF, $421F, $1CF2, $0000 + dw $7FFF, $5294, $294A, $0000 + dw $7FFF, $03FF, $012F, $0000 + dw $7FFF, $03EF, $01D6, $0000 + dw $7FFF, $42B5, $3DC8, $0000 + dw $7E74, $03FF, $0180, $0000 + dw $67FF, $77AC, $1A13, $2D6B + dw $7ED6, $4BFF, $2175, $0000 + dw $53FF, $4A5F, $7E52, $0000 + dw $4FFF, $7ED2, $3A4C, $1CE0 + dw $03ED, $7FFF, $255F, $0000 + dw $036A, $021F, $03FF, $7FFF + dw $7FFF, $01DF, $0112, $0000 + dw $231F, $035F, $00F2, $0009 + dw $7FFF, $03EA, $011F, $0000 + dw $299F, $001A, $000C, $0000 + dw $7FFF, $027F, $001F, $0000 + dw $7FFF, $03E0, $0206, $0120 + dw $7FFF, $7EEB, $001F, $7C00 + dw $7FFF, $3FFF, $7E00, $001F + dw $7FFF, $03FF, $001F, $0000 + dw $03FF, $001F, $000C, $0000 + dw $7FFF, $033F, $0193, $0000 + dw $0000, $4200, $037F, $7FFF + dw $7FFF, $7E8C, $7C00, $0000 + dw $7FFF, $1BEF, $6180, $0000 + ; SameBoy "Exclusives" + dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1 + dw $4778, $3290, $1D87, $0861 ; DMG LCD + +KeyCombinationPalettes: + db 1 * 3 ; Right + db 48 * 3 ; Left + db 5 * 3 ; Up + db 8 * 3 ; Down + db 0 * 3 ; Right + A + db 40 * 3 ; Left + A + db 43 * 3 ; Up + A + db 3 * 3 ; Down + A + db 6 * 3 ; Right + B + db 7 * 3 ; Left + B + db 28 * 3 ; Up + B + db 49 * 3 ; Down + B + ; SameBoy "Exclusives" + db 51 * 3 ; Right + A + B + db 52 * 3 ; Left + A + B + db 53 * 3 ; Up + A + B + db 54 * 3 ; Down + A + B + +TrademarkSymbol: + db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SameBoyLogo: + incbin "SameBoyLogo.pb12" + + +AnimationColors: + dw $7FFF ; White + dw $774F ; Cyan + dw $22C7 ; Green + dw $039F ; Yellow + dw $017D ; Orange + dw $241D ; Red + dw $6D38 ; Purple +IF DEF(AGB) + dw $6D60 ; Blue +ELSE + dw $5500 ; Blue +ENDC + +AnimationColorsEnd: + +; Helper Functions +DoubleBitsAndWriteRowTwice: + call .twice +.twice +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +WaitBFrames: + call GetInputPaletteIndex + call WaitFrame + dec b + jr nz, WaitBFrames + ret + +PlaySound: + ldh [rNR13], a + ld a, $87 + ldh [rNR14], a + ret + +ClearMemoryPage8000: + ld hl, $8000 +; Clear from HL to HL | 0x2000 +ClearMemoryPage: + xor a + ldi [hl], a + bit 5, h + jr z, ClearMemoryPage + ret + +ReadTwoTileLines: + call ReadTileLine +; c = $f0 for even lines, $f for odd lines. +ReadTileLine: + ld a, [de] + and c + ld b, a + inc e + inc e + ld a, [de] + dec e + dec e + and c + swap a + or b + bit 0, c + jr z, .dontSwap + swap a +.dontSwap + inc hl + ldi [hl], a + swap c + ret + + +ReadCGBLogoHalfTile: + call .do_twice +.do_twice + call ReadTwoTileLines + inc e + ld a, e + ret + +; LoadTileset using PB12 codec, 2020 Jakub Kądziołka +; (based on PB8 codec, 2019 Damian Yerrick) + +SameBoyLogo_dst = $8080 +SameBoyLogo_length = (128 * 24) / 64 + +LoadTileset: + ld hl, SameBoyLogo + ld de, SameBoyLogo_dst - 1 + ld c, SameBoyLogo_length +.refill + ; Register map for PB12 decompression + ; HL: source address in boot ROM + ; DE: destination address in VRAM + ; A: Current literal value + ; B: Repeat bits, terminated by 1000... + ; Source address in HL lets the repeat bits go straight to B, + ; bypassing A and avoiding spilling registers to the stack. + ld b, [hl] + dec b + jr z, .sameboyLogoEnd + inc b + inc hl + + ; Shift a 1 into lower bit of shift value. Once this bit + ; reaches the carry, B becomes 0 and the byte is over + scf + rl b + +.loop + ; If not a repeat, load a literal byte + jr c, .simple_repeat + sla b + jr c, .shifty_repeat + ld a, [hli] + jr .got_byte +.shifty_repeat + sla b + jr nz, .no_refill_during_shift + ld b, [hl] ; see above. Also, no, factoring it out into a callable + inc hl ; routine doesn't save bytes, even with conditional calls + scf + rl b +.no_refill_during_shift + ld c, a + jr nc, .shift_left + srl a + db $fe ; eat the add a with cp d8 +.shift_left + add a + sla b + jr c, .go_and + or c + db $fe ; eat the and c with cp d8 +.go_and + and c + jr .got_byte +.simple_repeat + sla b + jr c, .got_byte + ; far repeat + dec de + ld a, [de] + inc de +.got_byte + inc de + ld [de], a + sla b + jr nz, .loop + jr .refill + +; End PB12 decoding. The rest uses HL as the destination +.sameboyLogoEnd + ld h, d + ld l, $80 + +; Copy (unresized) ROM logo + ld de, $104 +.CGBROMLogoLoop + ld c, $f0 + call ReadCGBLogoHalfTile + add a, 22 + ld e, a + call ReadCGBLogoHalfTile + sub a, 22 + ld e, a + cp $1c + jr nz, .CGBROMLogoLoop + inc hl + ; fallthrough +ReadTrademarkSymbol: + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + ret + +DoIntroAnimation: + ; Animate the intro + ld a, 1 + ldh [rVBK], a + ld d, 26 +.animationLoop + ld b, 2 + call WaitBFrames + ld hl, $98C0 + ld c, 3 ; Row count +.loop + ld a, [hl] + cp $F ; Already blue + jr z, .nextTile + inc [hl] + and $7 + jr z, .nextLine ; Changed a white tile, go to next line +.nextTile + inc hl + jr .loop +.nextLine + ld a, l + or $1F + ld l, a + inc hl + dec c + jr nz, .loop + dec d + jr nz, .animationLoop + ret + +Preboot: +IF !DEF(FAST) + ld b, 32 ; 32 times to fade +.fadeLoop + ld c, 32 ; 32 colors to fade + ld hl, BgPalettes +.frameLoop + push bc + + ; Brighten Color + ld a, [hli] + ld e, a + ld a, [hld] + ld d, a + ; RGB(1,1,1) + ld bc, $421 + + ; Is blue maxed? + ld a, e + and $1F + cp $1F + jr nz, .blueNotMaxed + dec c +.blueNotMaxed + + ; Is green maxed? + ld a, e + cp $E0 + jr c, .greenNotMaxed + ld a, d + and $3 + cp $3 + jr nz, .greenNotMaxed + res 5, c +.greenNotMaxed + + ; Is red maxed? + ld a, d + and $7C + cp $7C + jr nz, .redNotMaxed + res 2, b +.redNotMaxed + + ld a, e + add c + ld [hli], a + ld a, d + adc b + ld [hli], a + pop bc + + dec c + jr nz, .frameLoop + + call WaitFrame + call LoadPalettesFromHRAM + call WaitFrame + dec b + jr nz, .fadeLoop +ENDC + ld a, 2 + ldh [rSVBK], a + ; Clear RAM Bank 2 (Like the original boot ROM) + ld hl, $D000 + call ClearMemoryPage + inc a + call ClearVRAMViaHDMA + call _ClearVRAMViaHDMA + call ClearVRAMViaHDMA ; A = $40, so it's bank 0 + xor a + ldh [rSVBK], a + cpl + ldh [rJOYP], a + + ; Final values for CGB mode + ld d, a + ld e, c + ld l, $0d + + ld a, [$143] + bit 7, a + call z, EmulateDMG + bit 7, a + + ldh [rKEY0], a ; write CGB compatibility byte, CGB mode + ldh a, [TitleChecksum] + ld b, a + + jr z, .skipDMGForCGBCheck + ldh a, [InputPalette] + and a + jr nz, .emulateDMGForCGBGame +.skipDMGForCGBCheck +IF DEF(AGB) + ; Set registers to match the original AGB-CGB boot + ; AF = $1100, C = 0 + xor a + ld c, a + add a, $11 + ld h, c + ; B is set to 1 after ret +ELSE + ; Set registers to match the original CGB boot + ; AF = $1180, C = 0 + xor a + ld c, a + ld a, $11 + ld h, c + ; B is set to the title checksum +ENDC + ret + +.emulateDMGForCGBGame + call EmulateDMG + ldh [rKEY0], a ; write $04, DMG emulation mode + ld a, $1 + ret + +GetKeyComboPalette: + ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + ret + +EmulateDMG: + ld a, 1 + ldh [rOPRI], a ; DMG Emulation sprite priority + 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 + call GetKeyComboPalette + jr .paletteFromKeys +.nothingDown + ld a, b +.paletteFromKeys + call WaitFrame + call LoadPalettesFromIndex + ld a, 4 + ; Set the final values for DMG mode + ld de, 8 + ld l, $7c + ret + +GetPaletteIndex: + ld hl, $14B + ld a, [hl] ; Old Licensee + cp $33 + jr z, .newLicensee + dec a ; 1 = Nintendo + jr nz, .notNintendo + jr .doChecksum +.newLicensee + ld l, $44 + ld a, [hli] + cp "0" + jr nz, .notNintendo + ld a, [hl] + cp "1" + jr nz, .notNintendo + +.doChecksum + ld l, $34 + ld c, $10 + xor a + +.checksumLoop + add [hl] + inc l + dec c + jr nz, .checksumLoop + ld b, a + + ; c = 0 + ld hl, TitleChecksums + +.searchLoop + ld a, l + sub LOW(ChecksumsEnd) ; use sub to zero out a + ret z + ld a, [hli] + cp b + jr nz, .searchLoop + + ; We might have a match, Do duplicate/4th letter check + ld a, l + sub FirstChecksumWithDuplicate - TitleChecksums + 1 + jr c, .match ; Does not have a duplicate, must be a match! + ; Has a duplicate; check 4th letter + push hl + ld a, l + add Dups4thLetterArray - FirstChecksumWithDuplicate - 1 ; -1 since hl was incremented + ld l, a + ld a, [hl] + pop hl + ld c, a + ld a, [$134 + 3] ; Get 4th letter + cp c + jr nz, .searchLoop ; Not a match, continue + +.match + ld a, l + add PalettePerChecksum - TitleChecksums - 1; -1 since hl was incremented + ld l, a + ld a, b + ldh [TitleChecksum], a + ld a, [hl] + ret + +.notNintendo + xor a + ret + +; optimizations in callers rely on this returning with b = 0 +GetPaletteCombo: + ld hl, PaletteCombinations + ld b, 0 + ld c, a + add hl, bc + ret + +LoadPalettesFromIndex: ; a = index of combination + call GetPaletteCombo + + ; Obj Palettes + ld e, 0 +.loadObjPalette + ld a, [hli] + push hl + ld hl, Palettes + ; b is already 0 + ld c, a + add hl, bc + ld d, 8 + ld c, $6A + call LoadPalettes + pop hl + bit 3, e + jr nz, .loadBGPalette + ld e, 8 + jr .loadObjPalette +.loadBGPalette + ;BG Palette + ld c, [hl] + ; b is already 0 + ld hl, Palettes + add hl, bc + ld d, 8 + jr LoadBGPalettes + +LoadPalettesFromHRAM: + ld hl, BgPalettes + ld d, 64 + +LoadBGPalettes: + ld e, 0 + ld c, LOW(rBGPI) + +LoadPalettes: + ld a, $80 + or e + ld [c], a + inc c +.loop + ld a, [hli] + ld [c], a + dec d + jr nz, .loop + ret + +ClearVRAMViaHDMA: + ldh [rVBK], a + ld hl, HDMAData +_ClearVRAMViaHDMA: + call WaitFrame ; Wait for vblank + ld c, $51 + ld b, 5 +.loop + ld a, [hli] + ldh [c], a + inc c + dec b + jr nz, .loop + ret + +; clobbers AF and HL +GetInputPaletteIndex: + ld a, $20 ; Select directions + ldh [rJOYP], a + ldh a, [rJOYP] + cpl + and $F + ret z ; No direction keys pressed, no palette + + ld l, 0 +.directionLoop + inc l + rra + jr nc, .directionLoop + + ; c = 1: Right, 2: Left, 3: Up, 4: Down + + ld a, $10 ; Select buttons + ldh [rJOYP], a + ldh a, [rJOYP] + cpl + rla + rla + and $C + add l + ld l, a + ldh a, [InputPalette] + cp l + ret z ; No change, don't load + ld a, l + ldh [InputPalette], a + ; Slide into change Animation Palette + +ChangeAnimationPalette: + push bc + push de + call GetKeyComboPalette + call GetPaletteCombo + inc l + inc l + ld c, [hl] + ld hl, Palettes + 1 + add hl, bc + ld a, [hld] + cp $7F ; Is white color? + jr nz, .isWhite + inc hl + inc hl +.isWhite + push af + ld a, [hli] + + push hl + ld hl, BgPalettes ; First color, all palettes + call ReplaceColorInAllPalettes + ld l, LOW(BgPalettes + 2) ; Second color, all palettes + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 6], a ; Fourth color, first palette + + ld a, [hli] + push hl + ld hl, BgPalettes + 1 ; First color, all palettes + call ReplaceColorInAllPalettes + ld l, LOW(BgPalettes + 3) ; Second color, all palettes + call ReplaceColorInAllPalettes + pop hl + ldh [BgPalettes + 7], a ; Fourth color, first palette + + pop af + jr z, .isNotWhite + inc hl + inc hl +.isNotWhite + ; Mixing code by ISSOtm + ldh a, [BgPalettes + 7 * 8 + 2] + and ~$21 + ld b, a + ld a, [hli] + and ~$21 + add a, b + ld b, a + ld a, [BgPalettes + 7 * 8 + 3] + res 2, a ; and ~$04, but not touching carry + ld c, [hl] + res 2, c ; and ~$04, but not touching carry + adc a, c + rra ; Carry sort of "extends" the accumulator, we're bringing that bit back home + ld [BgPalettes + 7 * 8 + 3], a + ld a, b + rra + ld [BgPalettes + 7 * 8 + 2], a + dec l + + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette + ld a, [hli] + ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette + + ld a, [hli] + ldh [BgPalettes + 4], a ; Third color, first palette + ld a, [hli] + ldh [BgPalettes + 5], a ; Third color, first palette + + + call WaitFrame + call LoadPalettesFromHRAM + ; Delay the wait loop while the user is selecting a palette + ld a, 48 + ldh [WaitLoopCounter], a + pop de + pop bc + ret + +ReplaceColorInAllPalettes: + ld de, 8 + ld c, e +.loop + ld [hl], a + add hl, de + dec c + jr nz, .loop + ret + +LoadDMGTilemap: + push af + call WaitFrame + ld a, $19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l, $0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + pop af + ret + +BootEnd: +IF BootEnd > $900 + FAIL "BootROM overflowed: {BootEnd}" +ENDC + +SECTION "HRAM", HRAM[$FF80] +TitleChecksum: + ds 1 +BgPalettes: + ds 8 * 4 * 2 +InputPalette: + ds 1 +WaitLoopCounter: + ds 1 diff --git a/thirdparty/SameBoy-old/BootROMs/cgb_boot_fast.asm b/thirdparty/SameBoy-old/BootROMs/cgb_boot_fast.asm new file mode 100644 index 000000000..cddb47503 --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/cgb_boot_fast.asm @@ -0,0 +1,2 @@ +FAST EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/dmg_boot.asm b/thirdparty/SameBoy-old/BootROMs/dmg_boot.asm new file mode 100644 index 000000000..1d282744b --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/dmg_boot.asm @@ -0,0 +1,183 @@ +; SameBoy DMG bootstrap ROM + +INCLUDE "hardware.inc" + +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + +.clearVRAMLoop + ldi [hl], a + bit 5, h + jr z, .clearVRAMLoop + +; Init Audio + ld a, $80 + ldh [rNR52], a + ldh [rNR11], a + ld a, $f3 + ldh [rNR12], a + ldh [rNR51], a + ld a, $77 + ldh [rNR50], a + +; Init BG palette + ld a, $54 + ldh [rBGP], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ld a, 30 + ldh [rSCY], a + + ; Turn on LCD + ld a, $91 + ldh [rLCDC], a + + ld d, (-119) & $FF + ld c, 15 + +.animate + call WaitFrame + ld a, d + sra a + sra a + ldh [rSCY], a + ld a, d + add c + ld d, a + ld a, c + cp 8 + jr nz, .noPaletteChange + ld a, $A8 + ldh [rBGP], a +.noPaletteChange + dec c + jr nz, .animate + ld a, $fc + ldh [rBGP], a + + ; Play first sound + ld a, $83 + call PlaySound + ld b, 5 + call WaitBFrames + ; Play second sound + ld a, $c1 + call PlaySound + + + +; Wait ~1 second + ld b, 60 + 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 + ld bc, $0013 + ld de, $00D8 + +; Boot the game + jp BootGame + + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +WaitBFrames: + call WaitFrame + dec b + jr nz, WaitBFrames + ret + +PlaySound: + ldh [rNR13], a + ld a, $87 + ldh [rNR14], a + ret + + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [rBANK], a ; unmap boot ROM \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/hardware.inc b/thirdparty/SameBoy-old/BootROMs/hardware.inc new file mode 100644 index 000000000..ca57bcd6a --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/hardware.inc @@ -0,0 +1,919 @@ +;* +;* Gameboy Hardware definitions +;* +;* Based on Jones' hardware.inc +;* And based on Carsten Sorensen's ideas. +;* +;* Rev 1.1 - 15-Jul-97 : Added define check +;* Rev 1.2 - 18-Jul-97 : Added revision check macro +;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 +;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes +;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines +;* : and Nintendo Logo +;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC +;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 +;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC +;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format +;* Rev 2.0 - : Added GBC registers +;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines +;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates +;* Rev 2.3 - : Fixed incorrect _HRAM equate +;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) +;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) +;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) +;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) +;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta) +;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants +;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) + +; If all of these are already defined, don't do it again. + + IF !DEF(HARDWARE_INC) +HARDWARE_INC = 1 + +rev_Check_hardware_inc : MACRO +;NOTE: REVISION NUMBER CHANGES MUST BE ADDED +;TO SECOND PARAMETER IN FOLLOWING LINE. + IF \1 > 3.0 ;PUT REVISION NUMBER HERE + WARN "Version \1 or later of 'hardware.inc' is required." + ENDC +ENDM + +_VRAM EQU $8000 ; $8000->$9FFF +_VRAM8000 EQU _VRAM +_VRAM8800 EQU _VRAM+$800 +_VRAM9000 EQU _VRAM+$1000 +_SCRN0 EQU $9800 ; $9800->$9BFF +_SCRN1 EQU $9C00 ; $9C00->$9FFF +_SRAM EQU $A000 ; $A000->$BFFF +_RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF +_RAMBANK EQU $D000 ; $D000->$DFFF +_OAMRAM EQU $FE00 ; $FE00->$FE9F +_IO EQU $FF00 ; $FF00->$FF7F,$FFFF +_AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F +_HRAM EQU $FF80 ; $FF80->$FFFE + +; *** MBC5 Equates *** + +rRAMG EQU $0000 ; $0000->$1fff +rROMB0 EQU $2000 ; $2000->$2fff +rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. +rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + +;*************************************************************************** +;* +;* Custom registers +;* +;*************************************************************************** + +; -- +; -- P1 ($FF00) +; -- Register for reading joy pad info. (R/W) +; -- +rP1 EQU $FF00 + +P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons +P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad +P1F_3 EQU %00001000 ; P13 in port +P1F_2 EQU %00000100 ; P12 in port +P1F_1 EQU %00000010 ; P11 in port +P1F_0 EQU %00000001 ; P10 in port + +P1F_GET_DPAD EQU P1F_5 +P1F_GET_BTN EQU P1F_4 +P1F_GET_NONE EQU P1F_4 | P1F_5 + + +; -- +; -- SB ($FF01) +; -- Serial Transfer Data (R/W) +; -- +rSB EQU $FF01 + + +; -- +; -- SC ($FF02) +; -- Serial I/O Control (R/W) +; -- +rSC EQU $FF02 + + +; -- +; -- DIV ($FF04) +; -- Divider register (R/W) +; -- +rDIV EQU $FF04 + + +; -- +; -- TIMA ($FF05) +; -- Timer counter (R/W) +; -- +rTIMA EQU $FF05 + + +; -- +; -- TMA ($FF06) +; -- Timer modulo (R/W) +; -- +rTMA EQU $FF06 + + +; -- +; -- TAC ($FF07) +; -- Timer control (R/W) +; -- +rTAC EQU $FF07 + +TACF_START EQU %00000100 +TACF_STOP EQU %00000000 +TACF_4KHZ EQU %00000000 +TACF_16KHZ EQU %00000011 +TACF_65KHZ EQU %00000010 +TACF_262KHZ EQU %00000001 + + +; -- +; -- IF ($FF0F) +; -- Interrupt Flag (R/W) +; -- +rIF EQU $FF0F + + +; -- +; -- AUD1SWEEP/NR10 ($FF10) +; -- Sweep register (R/W) +; -- +; -- Bit 6-4 - Sweep Time +; -- Bit 3 - Sweep Increase/Decrease +; -- 0: Addition (frequency increases???) +; -- 1: Subtraction (frequency increases???) +; -- Bit 2-0 - Number of sweep shift (# 0-7) +; -- Sweep Time: (n*7.8ms) +; -- +rNR10 EQU $FF10 +rAUD1SWEEP EQU rNR10 + +AUD1SWEEP_UP EQU %00000000 +AUD1SWEEP_DOWN EQU %00001000 + + +; -- +; -- AUD1LEN/NR11 ($FF11) +; -- Sound length/Wave pattern duty (R/W) +; -- +; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR11 EQU $FF11 +rAUD1LEN EQU rNR11 + + +; -- +; -- AUD1ENV/NR12 ($FF12) +; -- Envelope (R/W) +; -- +; -- Bit 7-4 - Initial value of envelope +; -- Bit 3 - Envelope UP/DOWN +; -- 0: Decrease +; -- 1: Range of increase +; -- Bit 2-0 - Number of envelope sweep (# 0-7) +; -- +rNR12 EQU $FF12 +rAUD1ENV EQU rNR12 + + +; -- +; -- AUD1LOW/NR13 ($FF13) +; -- Frequency low byte (W) +; -- +rNR13 EQU $FF13 +rAUD1LOW EQU rNR13 + + +; -- +; -- AUD1HIGH/NR14 ($FF14) +; -- Frequency high byte (W) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- Bit 2-0 - Frequency's higher 3 bits +; -- +rNR14 EQU $FF14 +rAUD1HIGH EQU rNR14 + + +; -- +; -- AUD2LEN/NR21 ($FF16) +; -- Sound Length; Wave Pattern Duty (R/W) +; -- +; -- see AUD1LEN for info +; -- +rNR21 EQU $FF16 +rAUD2LEN EQU rNR21 + + +; -- +; -- AUD2ENV/NR22 ($FF17) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR22 EQU $FF17 +rAUD2ENV EQU rNR22 + + +; -- +; -- AUD2LOW/NR23 ($FF18) +; -- Frequency low byte (W) +; -- +rNR23 EQU $FF18 +rAUD2LOW EQU rNR23 + + +; -- +; -- AUD2HIGH/NR24 ($FF19) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR24 EQU $FF19 +rAUD2HIGH EQU rNR24 + + +; -- +; -- AUD3ENA/NR30 ($FF1A) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) +; -- +rNR30 EQU $FF1A +rAUD3ENA EQU rNR30 + + +; -- +; -- AUD3LEN/NR31 ($FF1B) +; -- Sound length (R/W) +; -- +; -- Bit 7-0 - Sound length +; -- +rNR31 EQU $FF1B +rAUD3LEN EQU rNR31 + + +; -- +; -- AUD3LEVEL/NR32 ($FF1C) +; -- Select output level +; -- +; -- Bit 6-5 - Select output level +; -- 00: 0/1 (mute) +; -- 01: 1/1 +; -- 10: 1/2 +; -- 11: 1/4 +; -- +rNR32 EQU $FF1C +rAUD3LEVEL EQU rNR32 + + +; -- +; -- AUD3LOW/NR33 ($FF1D) +; -- Frequency low byte (W) +; -- +; -- see AUD1LOW for info +; -- +rNR33 EQU $FF1D +rAUD3LOW EQU rNR33 + + +; -- +; -- AUD3HIGH/NR34 ($FF1E) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR34 EQU $FF1E +rAUD3HIGH EQU rNR34 + + +; -- +; -- AUD4LEN/NR41 ($FF20) +; -- Sound length (R/W) +; -- +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR41 EQU $FF20 +rAUD4LEN EQU rNR41 + + +; -- +; -- AUD4ENV/NR42 ($FF21) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR42 EQU $FF21 +rAUD4ENV EQU rNR42 + + +; -- +; -- AUD4POLY/NR43 ($FF22) +; -- Polynomial counter (R/W) +; -- +; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) +; -- polynomial counter (0000-1101) +; -- freq=drf*1/2^scf (not sure) +; -- Bit 3 - Selection of the polynomial counter's step +; -- 0: 15 steps +; -- 1: 7 steps +; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) +; -- 000: f/4 001: f/8 010: f/16 011: f/24 +; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz) +; -- +rNR43 EQU $FF22 +rAUD4POLY EQU rNR43 + + +; -- +; -- AUD4GO/NR44 ($FF23) +; -- +; -- Bit 7 - Inital +; -- Bit 6 - Counter/consecutive selection +; -- +rNR44 EQU $FF23 +rAUD4GO EQU rNR44 + + +; -- +; -- AUDVOL/NR50 ($FF24) +; -- Channel control / ON-OFF / Volume (R/W) +; -- +; -- Bit 7 - Vin->SO2 ON/OFF (Vin??) +; -- Bit 6-4 - SO2 output level (volume) (# 0-7) +; -- Bit 3 - Vin->SO1 ON/OFF (Vin??) +; -- Bit 2-0 - SO1 output level (volume) (# 0-7) +; -- +rNR50 EQU $FF24 +rAUDVOL EQU rNR50 + +AUDVOL_VIN_LEFT EQU %10000000 ; SO2 +AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 + + +; -- +; -- AUDTERM/NR51 ($FF25) +; -- Selection of Sound output terminal (R/W) +; -- +; -- Bit 7 - Output sound 4 to SO2 terminal +; -- Bit 6 - Output sound 3 to SO2 terminal +; -- Bit 5 - Output sound 2 to SO2 terminal +; -- Bit 4 - Output sound 1 to SO2 terminal +; -- Bit 3 - Output sound 4 to SO1 terminal +; -- Bit 2 - Output sound 3 to SO1 terminal +; -- Bit 1 - Output sound 2 to SO1 terminal +; -- Bit 0 - Output sound 0 to SO1 terminal +; -- +rNR51 EQU $FF25 +rAUDTERM EQU rNR51 + +; SO2 +AUDTERM_4_LEFT EQU %10000000 +AUDTERM_3_LEFT EQU %01000000 +AUDTERM_2_LEFT EQU %00100000 +AUDTERM_1_LEFT EQU %00010000 +; SO1 +AUDTERM_4_RIGHT EQU %00001000 +AUDTERM_3_RIGHT EQU %00000100 +AUDTERM_2_RIGHT EQU %00000010 +AUDTERM_1_RIGHT EQU %00000001 + + +; -- +; -- AUDENA/NR52 ($FF26) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - All sound on/off (sets all audio regs to 0!) +; -- Bit 3 - Sound 4 ON flag (read only) +; -- Bit 2 - Sound 3 ON flag (read only) +; -- Bit 1 - Sound 2 ON flag (read only) +; -- Bit 0 - Sound 1 ON flag (read only) +; -- +rNR52 EQU $FF26 +rAUDENA EQU rNR52 + +AUDENA_ON EQU %10000000 +AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! + + +; -- +; -- LCDC ($FF40) +; -- LCD Control (R/W) +; -- +rLCDC EQU $FF40 + +LCDCF_OFF EQU %00000000 ; LCD Control Operation +LCDCF_ON EQU %10000000 ; LCD Control Operation +LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select +LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select +LCDCF_WINOFF EQU %00000000 ; Window Display +LCDCF_WINON EQU %00100000 ; Window Display +LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select +LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select +LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select +LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select +LCDCF_OBJ8 EQU %00000000 ; OBJ Construction +LCDCF_OBJ16 EQU %00000100 ; OBJ Construction +LCDCF_OBJOFF EQU %00000000 ; OBJ Display +LCDCF_OBJON EQU %00000010 ; OBJ Display +LCDCF_BGOFF EQU %00000000 ; BG Display +LCDCF_BGON EQU %00000001 ; BG Display +; "Window Character Data Select" follows BG + + +; -- +; -- STAT ($FF41) +; -- LCDC Status (R/W) +; -- +rSTAT EQU $FF41 + +STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable) +STATF_MODE10 EQU %00100000 ; Mode 10 +STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) +STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) +STATF_LYCF EQU %00000100 ; Coincidence Flag +STATF_HBL EQU %00000000 ; H-Blank +STATF_VBL EQU %00000001 ; V-Blank +STATF_OAM EQU %00000010 ; OAM-RAM is used by system +STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system +STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe + + +; -- +; -- SCY ($FF42) +; -- Scroll Y (R/W) +; -- +rSCY EQU $FF42 + + +; -- +; -- SCY ($FF43) +; -- Scroll X (R/W) +; -- +rSCX EQU $FF43 + + +; -- +; -- LY ($FF44) +; -- LCDC Y-Coordinate (R) +; -- +; -- Values range from 0->153. 144->153 is the VBlank period. +; -- +rLY EQU $FF44 + + +; -- +; -- LYC ($FF45) +; -- LY Compare (R/W) +; -- +; -- When LY==LYC, STATF_LYCF will be set in STAT +; -- +rLYC EQU $FF45 + + +; -- +; -- DMA ($FF46) +; -- DMA Transfer and Start Address (W) +; -- +rDMA EQU $FF46 + + +; -- +; -- BGP ($FF47) +; -- BG Palette Data (W) +; -- +; -- Bit 7-6 - Intensity for %11 +; -- Bit 5-4 - Intensity for %10 +; -- Bit 3-2 - Intensity for %01 +; -- Bit 1-0 - Intensity for %00 +; -- +rBGP EQU $FF47 + + +; -- +; -- OBP0 ($FF48) +; -- Object Palette 0 Data (W) +; -- +; -- See BGP for info +; -- +rOBP0 EQU $FF48 + + +; -- +; -- OBP1 ($FF49) +; -- Object Palette 1 Data (W) +; -- +; -- See BGP for info +; -- +rOBP1 EQU $FF49 + + +; -- +; -- WY ($FF4A) +; -- Window Y Position (R/W) +; -- +; -- 0 <= WY <= 143 +; -- When WY = 0, the window is displayed from the top edge of the LCD screen. +; -- +rWY EQU $FF4A + + +; -- +; -- WX ($FF4B) +; -- Window X Position (R/W) +; -- +; -- 7 <= WX <= 166 +; -- When WX = 7, the window is displayed from the left edge of the LCD screen. +; -- Values of 0-6 and 166 are unreliable due to hardware bugs. +; -- +rWX EQU $FF4B + + +; -- +; -- SPEED ($FF4D) +; -- Select CPU Speed (R/W) +; -- +rKEY1 EQU $FF4D +rSPD EQU rKEY1 + +KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) +KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) + + +; -- +; -- VBK ($FF4F) +; -- Select Video RAM Bank (R/W) +; -- +; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) +; -- +rVBK EQU $FF4F + + +; -- +; -- HDMA1 ($FF51) +; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +rHDMA1 EQU $FF51 + + +; -- +; -- HDMA2 ($FF52) +; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +rHDMA2 EQU $FF52 + + +; -- +; -- HDMA3 ($FF53) +; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +rHDMA3 EQU $FF53 + + +; -- +; -- HDMA4 ($FF54) +; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +rHDMA4 EQU $FF54 + + +; -- +; -- HDMA5 ($FF55) +; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) +; -- CGB Mode Only +; -- +rHDMA5 EQU $FF55 + +HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) +HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) + +; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete +HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) + + +; -- +; -- RP ($FF56) +; -- Infrared Communications Port (R/W) +; -- +rRP EQU $FF56 + + +; -- +; -- BCPS ($FF68) +; -- Background Color Palette Specification (R/W) +; -- +rBCPS EQU $FF68 + +BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- BCPD ($FF69) +; -- Background Color Palette Data (R/W) +; -- +rBCPD EQU $FF69 + + +; -- +; -- OCPS ($FF6A) +; -- Object Color Palette Specification (R/W) +; -- +rOCPS EQU $FF6A + +OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + +; -- +; -- OCPD ($FF6B) +; -- Object Color Palette Data (R/W) +; -- +rOCPD EQU $FF6B + + +; -- +; -- SMBK/SVBK ($FF70) +; -- Select Main RAM Bank (R/W) +; -- +; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) +; -- +rSVBK EQU $FF70 +rSMBK EQU rSVBK + + +; -- +; -- PCM12 ($FF76) +; -- Sound channel 1&2 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude +; -- +rPCM12 EQU $FF76 + + +; -- +; -- PCM34 ($FF77) +; -- Sound channel 3&4 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude +; -- +rPCM34 EQU $FF77 + + +; -- +; -- IE ($FFFF) +; -- Interrupt Enable (R/W) +; -- +rIE EQU $FFFF + +IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 +IEF_SERIAL EQU %00001000 ; Serial I/O transfer end +IEF_TIMER EQU %00000100 ; Timer Overflow +IEF_LCDC EQU %00000010 ; LCDC (see STAT) +IEF_VBLANK EQU %00000001 ; V-Blank + + +;*************************************************************************** +;* +;* Flags common to multiple sound channels +;* +;*************************************************************************** + +; -- +; -- Square wave duty cycle +; -- +; -- Can be used with AUD1LEN and AUD2LEN +; -- See AUD1LEN for more info +; -- +AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% +AUDLEN_DUTY_25 EQU %01000000 ; 25% +AUDLEN_DUTY_50 EQU %10000000 ; 50% +AUDLEN_DUTY_75 EQU %11000000 ; 75% + + +; -- +; -- Audio envelope flags +; -- +; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV +; -- See AUD1ENV for more info +; -- +AUDENV_UP EQU %00001000 +AUDENV_DOWN EQU %00000000 + + +; -- +; -- Audio trigger flags +; -- +; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH +; -- See AUD1HIGH for more info +; -- + +AUDHIGH_RESTART EQU %10000000 +AUDHIGH_LENGTH_ON EQU %01000000 +AUDHIGH_LENGTH_OFF EQU %00000000 + + +;*************************************************************************** +;* +;* CPU values on bootup (a=type, b=qualifier) +;* +;*************************************************************************** + +BOOTUP_A_DMG EQU $01 ; Dot Matrix Game +BOOTUP_A_CGB EQU $11 ; Color GameBoy +BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) + +; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or +; other system running in GBC mode +BOOTUP_B_CGB EQU %00000000 +BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP + + +;*************************************************************************** +;* +;* Cart related +;* +;*************************************************************************** + +; $0143 Color GameBoy compatibility code +CART_COMPATIBLE_DMG EQU $00 +CART_COMPATIBLE_DMG_GBC EQU $80 +CART_COMPATIBLE_GBC EQU $C0 + +; $0146 GameBoy/Super GameBoy indicator +CART_INDICATOR_GB EQU $00 +CART_INDICATOR_SGB EQU $03 + +; $0147 Cartridge type +CART_ROM EQU $00 +CART_ROM_MBC1 EQU $01 +CART_ROM_MBC1_RAM EQU $02 +CART_ROM_MBC1_RAM_BAT EQU $03 +CART_ROM_MBC2 EQU $05 +CART_ROM_MBC2_BAT EQU $06 +CART_ROM_RAM EQU $08 +CART_ROM_RAM_BAT EQU $09 +CART_ROM_MMM01 EQU $0B +CART_ROM_MMM01_RAM EQU $0C +CART_ROM_MMM01_RAM_BAT EQU $0D +CART_ROM_MBC3_BAT_RTC EQU $0F +CART_ROM_MBC3_RAM_BAT_RTC EQU $10 +CART_ROM_MBC3 EQU $11 +CART_ROM_MBC3_RAM EQU $12 +CART_ROM_MBC3_RAM_BAT EQU $13 +CART_ROM_MBC5 EQU $19 +CART_ROM_MBC5_BAT EQU $1A +CART_ROM_MBC5_RAM_BAT EQU $1B +CART_ROM_MBC5_RUMBLE EQU $1C +CART_ROM_MBC5_RAM_RUMBLE EQU $1D +CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E +CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 +CART_ROM_POCKET_CAMERA EQU $FC +CART_ROM_BANDAI_TAMA5 EQU $FD +CART_ROM_HUDSON_HUC3 EQU $FE +CART_ROM_HUDSON_HUC1 EQU $FF + +; $0148 ROM size +; these are kilobytes +CART_ROM_32K EQU $00 ; 2 banks +CART_ROM_64K EQU $01 ; 4 banks +CART_ROM_128K EQU $02 ; 8 banks +CART_ROM_256K EQU $03 ; 16 banks +CART_ROM_512K EQU $04 ; 32 banks +CART_ROM_1024K EQU $05 ; 64 banks +CART_ROM_2048K EQU $06 ; 128 banks +CART_ROM_4096K EQU $07 ; 256 banks +CART_ROM_8192K EQU $08 ; 512 banks +CART_ROM_1152K EQU $52 ; 72 banks +CART_ROM_1280K EQU $53 ; 80 banks +CART_ROM_1536K EQU $54 ; 96 banks + +; $0149 SRAM size +; these are kilobytes +CART_SRAM_NONE EQU 0 +CART_SRAM_2K EQU 1 ; 1 incomplete bank +CART_SRAM_8K EQU 2 ; 1 bank +CART_SRAM_32K EQU 3 ; 4 banks +CART_SRAM_128K EQU 4 ; 16 banks + +CART_SRAM_ENABLE EQU $0A +CART_SRAM_DISABLE EQU $00 + +; $014A Destination code +CART_DEST_JAPANESE EQU $00 +CART_DEST_NON_JAPANESE EQU $01 + + +;*************************************************************************** +;* +;* Keypad related +;* +;*************************************************************************** + +PADF_DOWN EQU $80 +PADF_UP EQU $40 +PADF_LEFT EQU $20 +PADF_RIGHT EQU $10 +PADF_START EQU $08 +PADF_SELECT EQU $04 +PADF_B EQU $02 +PADF_A EQU $01 + +PADB_DOWN EQU $7 +PADB_UP EQU $6 +PADB_LEFT EQU $5 +PADB_RIGHT EQU $4 +PADB_START EQU $3 +PADB_SELECT EQU $2 +PADB_B EQU $1 +PADB_A EQU $0 + + +;*************************************************************************** +;* +;* Screen related +;* +;*************************************************************************** + +SCRN_X EQU 160 ; Width of screen in pixels +SCRN_Y EQU 144 ; Height of screen in pixels +SCRN_X_B EQU 20 ; Width of screen in bytes +SCRN_Y_B EQU 18 ; Height of screen in bytes + +SCRN_VX EQU 256 ; Virtual width of screen in pixels +SCRN_VY EQU 256 ; Virtual height of screen in pixels +SCRN_VX_B EQU 32 ; Virtual width of screen in bytes +SCRN_VY_B EQU 32 ; Virtual height of screen in bytes + + +;*************************************************************************** +;* +;* OAM related +;* +;*************************************************************************** + +; OAM attributes +; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) +RSRESET +OAMA_Y RB 1 ; y pos +OAMA_X RB 1 ; x pos +OAMA_TILEID RB 1 ; tile id +OAMA_FLAGS RB 1 ; flags (see below) +sizeof_OAM_ATTRS RB 0 + +OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM + +; flags +OAMF_PRI EQU %10000000 ; Priority +OAMF_YFLIP EQU %01000000 ; Y flip +OAMF_XFLIP EQU %00100000 ; X flip +OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) +OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) +OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) +OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) + +OAMF_PALMASK EQU %00000111 ; Palette (GBC) + +OAMB_PRI EQU 7 ; Priority +OAMB_YFLIP EQU 6 ; Y flip +OAMB_XFLIP EQU 5 ; X flip +OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) +OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) + + +;* +;* Nintendo scrolling logo +;* (Code won't work on a real GameBoy) +;* (if next lines are altered.) +NINTENDO_LOGO : MACRO + DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D + DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 + DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E +ENDM + +; SameBoy additions +rKEY0 EQU $FF4C +rBANK EQU $FF50 +rOPRI EQU $FF6C + +rJOYP EQU rP1 +rBGPI EQU rBCPS +rBGPD EQU rBCPD +rOBPI EQU rOCPS +rOBPD EQU rOCPD + + ENDC ;HARDWARE_INC + diff --git a/thirdparty/SameBoy-old/BootROMs/mgb_boot.asm b/thirdparty/SameBoy-old/BootROMs/mgb_boot.asm new file mode 100644 index 000000000..3a98aefd8 --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/mgb_boot.asm @@ -0,0 +1,2 @@ +MGB EQU 1 +include "dmg_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/pb12.c b/thirdparty/SameBoy-old/BootROMs/pb12.c new file mode 100644 index 000000000..cfedf6bb1 --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/pb12.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include + +void opts(uint8_t byte, uint8_t *options) +{ + *(options++) = byte | ((byte << 1) & 0xff); + *(options++) = byte & (byte << 1); + *(options++) = byte | ((byte >> 1) & 0xff); + *(options++) = byte & (byte >> 1); +} + +void write_all(int fd, const void *buf, size_t count) { + while (count) { + ssize_t written = write(fd, buf, count); + if (written < 0) { + fprintf(stderr, "write"); + exit(1); + } + count -= written; + buf += written; + } +} + +int main() +{ + static uint8_t source[0x4000]; + size_t size = read(STDIN_FILENO, &source, sizeof(source)); + unsigned pos = 0; + assert(size <= 0x4000); + while (size && source[size - 1] == 0) { + size--; + } + + uint8_t literals[8]; + size_t literals_size = 0; + unsigned bits = 0; + unsigned control = 0; + unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values + + while (true) { + + uint8_t byte = 0; + if (pos == size){ + if (bits == 0) break; + } + else { + byte = source[pos++]; + } + + if (byte == prev[0] || byte == prev[1]) { + bits += 2; + control <<= 1; + control |= 1; + control <<= 1; + if (byte == prev[1]) { + control |= 1; + } + } + else { + bits += 2; + control <<= 2; + uint8_t options[4]; + opts(prev[1], options); + bool found = false; + for (unsigned i = 0; i < 4; i++) { + if (options[i] == byte) { + // 01 = modify + control |= 1; + + bits += 2; + control <<= 2; + control |= i; + found = true; + break; + } + } + if (!found) { + literals[literals_size++] = byte; + } + } + + prev[0] = prev[1]; + prev[1] = byte; + if (bits >= 8) { + uint8_t outctl = control >> (bits - 8); + assert(outctl != 1); + write_all(STDOUT_FILENO, &outctl, 1); + write_all(STDOUT_FILENO, literals, literals_size); + bits -= 8; + control &= (1 << bits) - 1; + literals_size = 0; + } + } + uint8_t end_byte = 1; + write_all(STDOUT_FILENO, &end_byte, 1); + + return 0; +} diff --git a/thirdparty/SameBoy-old/BootROMs/sgb2_boot.asm b/thirdparty/SameBoy-old/BootROMs/sgb2_boot.asm new file mode 100644 index 000000000..1c3d85847 --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/sgb2_boot.asm @@ -0,0 +1,2 @@ +SGB2 EQU 1 +include "sgb_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy-old/BootROMs/sgb_boot.asm b/thirdparty/SameBoy-old/BootROMs/sgb_boot.asm new file mode 100644 index 000000000..e0928616a --- /dev/null +++ b/thirdparty/SameBoy-old/BootROMs/sgb_boot.asm @@ -0,0 +1,215 @@ +; SameBoy SGB bootstrap ROM + +INCLUDE "hardware.inc" + +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + +.clearVRAMLoop + ldi [hl], a + bit 5, h + jr z, .clearVRAMLoop + +; Init Audio + ld a, $80 + ldh [rNR52], a + ldh [rNR11], a + ld a, $f3 + ldh [rNR12], a + ldh [rNR51], a + ld a, $77 + ldh [rNR50], a + +; Init BG palette to white + ld a, $0 + ldh [rBGP], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ; Turn on LCD + ld a, $91 + ldh [rLCDC], a + + ld a, $f1 ; Packet magic, increases by 2 for every packet + ldh [_HRAM], a + ld hl, $104 ; Header start + + xor a + ld c, a ; JOYP + +.sendCommand + xor a + ld [c], a + ld a, $30 + ld [c], a + + ldh a, [_HRAM] + call SendByte + push hl + ld b, $e + ld d, 0 + +.checksumLoop + call ReadHeaderByte + add d + ld d, a + dec b + jr nz, .checksumLoop + + ; Send checksum + call SendByte + pop hl + + ld b, $e +.sendLoop + call ReadHeaderByte + call SendByte + dec b + jr nz, .sendLoop + + ; Done bit + ld a, $20 + ld [c], a + ld a, $30 + ld [c], a + + ; Update command + ldh a, [_HRAM] + add 2 + ldh [_HRAM], a + + ld a, $58 + cp l + jr nz, .sendCommand + + ; Write to sound registers for DMG compatibility + ld c, $13 + ld a, $c1 + ld [c], a + inc c + ld a, 7 + ld [c], a + + ; Init BG palette + ld a, $fc + ldh [rBGP], a + +; Set registers to match the original SGB boot +IF DEF(SGB2) + ld a, $FF +ELSE + ld a, 1 +ENDC + ld hl, $c060 + +; Boot the game + jp BootGame + +ReadHeaderByte: + ld a, $4F + cp l + jr c, .zero + ld a, [hli] + ret +.zero: + inc hl + xor a + ret + +SendByte: + ld e, a + ld d, 8 +.loop + ld a, $10 + rr e + jr c, .zeroBit + add a ; 10 -> 20 +.zeroBit + ld [c], a + ld a, $30 + ld [c], a + dec d + ret z + jr .loop + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [rBANK], a \ No newline at end of file diff --git a/thirdparty/SameBoy-old/CHANGES.md b/thirdparty/SameBoy-old/CHANGES.md new file mode 100644 index 000000000..b94aec4c7 --- /dev/null +++ b/thirdparty/SameBoy-old/CHANGES.md @@ -0,0 +1 @@ +See https://sameboy.github.io/changelog/ \ No newline at end of file diff --git a/thirdparty/SameBoy-old/CONTRIBUTING.md b/thirdparty/SameBoy-old/CONTRIBUTING.md new file mode 100644 index 000000000..94627d1a5 --- /dev/null +++ b/thirdparty/SameBoy-old/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. diff --git a/thirdparty/SameBoy-old/Cocoa/AppIcon.icns b/thirdparty/SameBoy-old/Cocoa/AppIcon.icns new file mode 100644 index 000000000..2a85022a9 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/AppIcon.icns differ diff --git a/thirdparty/SameBoy-old/Cocoa/AudioRecordingAccessoryView.xib b/thirdparty/SameBoy-old/Cocoa/AudioRecordingAccessoryView.xib new file mode 100644 index 000000000..6dda38b7d --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/AudioRecordingAccessoryView.xib @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/BigSurToolbar.h b/thirdparty/SameBoy-old/Cocoa/BigSurToolbar.h new file mode 100644 index 000000000..9057d340b --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/BigSurToolbar.h @@ -0,0 +1,30 @@ +#import +#ifndef BigSurToolbar_h +#define BigSurToolbar_h + +/* Backport the toolbarStyle property to allow compilation with older SDKs*/ +#ifndef __MAC_10_16 +typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) { + // The default value. The style will be determined by the window's given configuration + NSWindowToolbarStyleAutomatic, + // The toolbar will appear below the window title + NSWindowToolbarStyleExpanded, + // The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible + NSWindowToolbarStylePreference, + // The window title will appear inline with the toolbar when visible + NSWindowToolbarStyleUnified, + // Same as NSWindowToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window + NSWindowToolbarStyleUnifiedCompact +} API_AVAILABLE(macos(11.0)); + +@interface NSWindow (toolbarStyle) +@property (nonatomic) NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0)); +@end + +@interface NSImage (SFSymbols) ++ (instancetype)imageWithSystemSymbolName:(NSString *)symbolName accessibilityDescription:(NSString *)description API_AVAILABLE(macos(11.0)); +@end + +#endif + +#endif diff --git a/thirdparty/SameBoy-old/Cocoa/CPU.png b/thirdparty/SameBoy-old/Cocoa/CPU.png new file mode 100644 index 000000000..2601ac464 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/CPU@2x.png b/thirdparty/SameBoy-old/Cocoa/CPU@2x.png new file mode 100644 index 000000000..fc94b6dbd Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/CPU~solid.png b/thirdparty/SameBoy-old/Cocoa/CPU~solid.png new file mode 100644 index 000000000..75c96b763 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU~solid.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/CPU~solid@2x.png b/thirdparty/SameBoy-old/Cocoa/CPU~solid@2x.png new file mode 100644 index 000000000..ff17bd84a Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU~solid@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark.png b/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark.png new file mode 100644 index 000000000..eea9a43d7 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark@2x.png b/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark@2x.png new file mode 100644 index 000000000..e7e9653a2 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/CPU~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Cartridge.icns b/thirdparty/SameBoy-old/Cocoa/Cartridge.icns new file mode 100644 index 000000000..1dae2b4a2 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Cartridge.icns differ diff --git a/thirdparty/SameBoy-old/Cocoa/ColorCartridge.icns b/thirdparty/SameBoy-old/Cocoa/ColorCartridge.icns new file mode 100644 index 000000000..35fb29324 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/ColorCartridge.icns differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display.png b/thirdparty/SameBoy-old/Cocoa/Display.png new file mode 100644 index 000000000..c955c4688 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display@2x.png b/thirdparty/SameBoy-old/Cocoa/Display@2x.png new file mode 100644 index 000000000..2cca50e3f Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display~solid.png b/thirdparty/SameBoy-old/Cocoa/Display~solid.png new file mode 100644 index 000000000..2980ad71a Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display~solid.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display~solid@2x.png b/thirdparty/SameBoy-old/Cocoa/Display~solid@2x.png new file mode 100644 index 000000000..a73943778 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display~solid@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display~solid~dark.png b/thirdparty/SameBoy-old/Cocoa/Display~solid~dark.png new file mode 100644 index 000000000..05245deea Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display~solid~dark.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Display~solid~dark@2x.png b/thirdparty/SameBoy-old/Cocoa/Display~solid~dark@2x.png new file mode 100644 index 000000000..3dec49d5d Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Display~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Document.h b/thirdparty/SameBoy-old/Cocoa/Document.h new file mode 100644 index 000000000..19fe0feb4 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/Document.h @@ -0,0 +1,69 @@ +#import +#import "GBView.h" +#import "GBImageView.h" +#import "GBSplitView.h" +#import "GBVisualizerView.h" +#import "GBOSDView.h" + +@class GBCheatWindowController; +@class GBPaletteView; +@class GBObjectView; + +@interface Document : NSDocument +@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 GBPaletteView *paletteView; +@property (nonatomic, strong) IBOutlet GBObjectView *objectView; +@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow; +@property (nonatomic, strong) IBOutlet NSImageView *feedImageView; +@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput; +@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; +@property (strong) IBOutlet NSView *gbsPlayerView; +@property (strong) IBOutlet NSTextField *gbsTitle; +@property (strong) IBOutlet NSTextField *gbsAuthor; +@property (strong) IBOutlet NSTextField *gbsCopyright; +@property (strong) IBOutlet NSPopUpButton *gbsTracks; +@property (strong) IBOutlet NSButton *gbsPlayPauseButton; +@property (strong) IBOutlet NSButton *gbsRewindButton; +@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton; +@property (strong) IBOutlet GBVisualizerView *gbsVisualizer; +@property (strong) IBOutlet GBOSDView *osdView; +@property (readonly) GB_oam_info_t *oamInfo; +@property uint8_t oamCount; +@property uint8_t oamHeight; +@property (strong) IBOutlet NSView *audioRecordingAccessoryView; +@property (strong) IBOutlet NSPopUpButton *audioFormatButton; + ++ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale; +-(uint8_t) readMemory:(uint16_t) addr; +-(void) writeMemory:(uint16_t) addr value:(uint8_t)value; +-(void) performAtomicBlock: (void (^)())block; +-(void) connectLinkCable:(NSMenuItem *)sender; +-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; +@end + diff --git a/thirdparty/SameBoy-old/Cocoa/Document.m b/thirdparty/SameBoy-old/Cocoa/Document.m new file mode 100644 index 000000000..e2b12c0ba --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/Document.m @@ -0,0 +1,2510 @@ +#import +#import +#import +#import "GBAudioClient.h" +#import "Document.h" +#import "GBApp.h" +#import "HexFiend/HexFiend.h" +#import "GBMemoryByteArray.h" +#import "GBWarningPopover.h" +#import "GBCheatWindowController.h" +#import "GBTerminalTextFieldCell.h" +#import "BigSurToolbar.h" +#import "GBPaletteEditorController.h" +#import "GBObjectView.h" +#import "GBPaletteView.h" + +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +@implementation NSString (relativePath) + +- (NSString *)pathRelativeToDirectory:(NSString *)directory +{ + NSMutableArray *baseComponents = [[directory pathComponents] mutableCopy]; + NSMutableArray *selfComponents = [[self pathComponents] mutableCopy]; + + while (baseComponents.count) { + if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) { + break; + } + + [baseComponents removeObjectAtIndex:0]; + [selfComponents removeObjectAtIndex:0]; + } + while (baseComponents.count) { + [baseComponents removeObjectAtIndex:0]; + [selfComponents insertObject:@".." atIndex:0]; + } + return [selfComponents componentsJoinedByString:@"/"]; +} + +@end + +#define GB_MODEL_PAL_BIT_OLD 0x1000 + +/* 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!!! */ + +enum model { + MODEL_NONE, + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_SGB, + MODEL_MGB, +}; + +@interface Document () +@property GBAudioClient *audioClient; +@end + +@implementation Document +{ + GB_gameboy_t gb; + volatile bool running; + volatile bool stopping; + NSConditionLock *has_debugger_input; + NSMutableArray *debugger_input_queue; + + NSMutableAttributedString *pending_console_output; + NSRecursiveLock *console_output_lock; + NSTimer *console_output_timer; + NSTimer *hex_timer; + + bool fullScreen; + bool in_sync_input; + HFController *hex_controller; + + NSString *lastConsoleInput; + HFLineCountingRepresenter *lineRep; + + CVImageBufferRef cameraImage; + AVCaptureSession *cameraSession; + AVCaptureConnection *cameraConnection; + AVCaptureStillImageOutput *cameraOutput; + + GB_oam_info_t _oamInfo[40]; + + NSMutableData *currentPrinterImageData; + enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory; + + bool rom_warning_issued; + + NSMutableString *capturedOutput; + bool logToSideView; + bool shouldClearSideView; + enum model current_model; + + bool rewind; + bool modelsChanging; + + NSCondition *audioLock; + GB_sample_t *audioBuffer; + size_t audioBufferSize; + size_t audioBufferPosition; + size_t audioBufferNeeded; + double _volume; + + bool borderModeChanged; + + /* Link cable*/ + Document *master; + Document *slave; + signed linkOffset; + bool linkCableBit; + + NSSavePanel *_audioSavePanel; + bool _isRecordingAudio; + + void (^ volatile _pendingAtomicBlock)(); +} + +static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self loadBootROM: type]; +} + +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self vblankWithType:type]; +} + +static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self log:string withAttributes: attributes]; +} + +static char *consoleInput(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + return [self getDebuggerInput]; +} + +static char *asyncConsoleInput(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + char *ret = [self getAsyncDebuggerInput]; + return ret; +} + +static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return (r << 0) | (g << 8) | (b << 16) | 0xFF000000; +} + +static void cameraRequestUpdate(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self cameraRequestUpdate]; +} + +static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + return [self cameraGetPixelAtX:x andY:y]; +} + +static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, + uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure]; +} + +static void setWorkboyTime(GB_gameboy_t *gb, time_t t) +{ + [[NSUserDefaults standardUserDefaults] setInteger:time(NULL) - t forKey:@"GBWorkboyTimeOffset"]; +} + +static time_t getWorkboyTime(GB_gameboy_t *gb) +{ + return time(NULL) - [[NSUserDefaults standardUserDefaults] integerForKey:@"GBWorkboyTimeOffset"]; +} + +static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self gotNewSample:sample]; +} + +static void rumbleCallback(GB_gameboy_t *gb, double amp) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self rumbleChanged:amp]; +} + + +static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self linkCableBitStart:bit_to_send]; +} + +static bool linkCableBitEnd(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + return [self linkCableBitEnd]; +} + +static void infraredStateChanged(GB_gameboy_t *gb, bool on) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self infraredStateChanged:on]; +} + + +- (instancetype)init +{ + self = [super init]; + if (self) { + has_debugger_input = [[NSConditionLock alloc] initWithCondition:0]; + debugger_input_queue = [[NSMutableArray alloc] init]; + console_output_lock = [[NSRecursiveLock alloc] init]; + audioLock = [[NSCondition alloc] init]; + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; + } + return self; +} + +- (NSString *)bootROMPathForName:(NSString *)name +{ + NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]; + if (url) { + NSString *path = [url path]; + path = [path stringByAppendingPathComponent:name]; + path = [path stringByAppendingPathExtension:@"bin"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + return path; + } + } + + return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; +} + +- (GB_model_t)internalModel +{ + switch (current_model) { + case MODEL_DMG: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]; + + case MODEL_NONE: + case MODEL_CGB: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]; + + 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_MGB: + return GB_MODEL_MGB; + + case MODEL_AGB: + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBAGBModel"]; + } +} + +- (void) updatePalette +{ + GB_set_palette(&gb, [GBPaletteEditorController userPalette]); +} + +- (void) updateBorderMode +{ + borderModeChanged = true; +} + +- (void) updateRumbleMode +{ + GB_set_rumble_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]); +} + +- (void) initCommon +{ + GB_init(&gb, [self internalModel]); + GB_set_user_data(&gb, (__bridge void *)(self)); + GB_set_boot_rom_load_callback(&gb, (GB_boot_rom_load_callback_t)boot_rom_load); + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog); + GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput); + GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); + GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); + GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); + GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); + [self updatePalette]; + GB_set_rgb_encode_callback(&gb, rgbEncode); + GB_set_camera_get_pixel_callback(&gb, cameraGetPixel); + 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); + [self updateRumbleMode]; +} + +- (void) updateMinSize +{ + self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) || + 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) vblankWithType:(GB_vblank_type_t)type +{ + if (_gbsVisualizer) { + dispatch_async(dispatch_get_main_queue(), ^{ + [_gbsVisualizer setNeedsDisplay:true]; + }); + } + if (type != GB_VBLANK_TYPE_REPEAT) { + [self.view flip]; + if (borderModeChanged) { + dispatch_sync(dispatch_get_main_queue(), ^{ + size_t previous_width = GB_get_screen_width(&gb); + GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); + if (GB_get_screen_width(&gb) != previous_width) { + [self.view screenSizeChanged]; + [self updateMinSize]; + } + }); + borderModeChanged = false; + } + GB_set_pixels_output(&gb, self.view.pixels); + } + + if (self.vramWindow.isVisible) { + dispatch_async(dispatch_get_main_queue(), ^{ + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; + [self reloadVRAMData: nil]; + }); + } + if (self.view.isRewinding) { + rewind = true; + [self.osdView displayText:@"Rewinding..."]; + } +} + +- (void)gotNewSample:(GB_sample_t *)sample +{ + if (_gbsVisualizer) { + [_gbsVisualizer addSample:sample]; + } + [audioLock lock]; + if (_audioClient.isPlaying) { + if (audioBufferPosition == audioBufferSize) { + if (audioBufferSize >= 0x4000) { + audioBufferPosition = 0; + [audioLock unlock]; + return; + } + + if (audioBufferSize == 0) { + audioBufferSize = 512; + } + else { + audioBufferSize += audioBufferSize >> 2; + } + audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize); + } + if (_volume != 1) { + sample->left *= _volume; + sample->right *= _volume; + } + audioBuffer[audioBufferPosition++] = *sample; + } + if (audioBufferPosition == audioBufferNeeded) { + [audioLock signal]; + audioBufferNeeded = 0; + } + [audioLock unlock]; +} + +- (void)rumbleChanged:(double)amp +{ + [_view setRumble:amp]; +} + +- (void) preRun +{ + GB_set_pixels_output(&gb, self.view.pixels); + GB_set_sample_rate(&gb, 96000); + _audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { + [audioLock lock]; + + if (audioBufferPosition < nFrames) { + audioBufferNeeded = nFrames; + [audioLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.125]]; + } + + if (stopping || GB_debugger_is_stopped(&gb)) { + memset(buffer, 0, nFrames * sizeof(*buffer)); + [audioLock unlock]; + return; + } + + 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; + } + else { + memcpy(buffer, audioBuffer + (audioBufferPosition - nFrames), nFrames * sizeof(*buffer)); + audioBufferPosition = 0; + } + [audioLock unlock]; + } andSampleRate:96000]; + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { + [_audioClient start]; + } + 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 */ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + for (NSUserNotification *notification in [center scheduledNotifications]) { + if ([notification.identifier isEqualToString:self.fileURL.path]) { + [center removeScheduledNotification:notification]; + break; + } + } + + for (NSUserNotification *notification in [center deliveredNotifications]) { + if ([notification.identifier isEqualToString:self.fileURL.path]) { + [center removeDeliveredNotification:notification]; + break; + } + } + } +} + +static unsigned *multiplication_table_for_frequency(unsigned frequency) +{ + unsigned *ret = malloc(sizeof(*ret) * 0x100); + for (unsigned i = 0; i < 0x100; i++) { + ret[i] = i * frequency; + } + return ret; +} + +- (void)run +{ + assert(!master); + [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)]; + } + if (unlikely(_pendingAtomicBlock)) { + _pendingAtomicBlock(); + _pendingAtomicBlock = nil; + } + } + 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); + } + if (unlikely(_pendingAtomicBlock)) { + _pendingAtomicBlock(); + _pendingAtomicBlock = nil; + } + } + } + [self postRun]; + stopping = false; +} + +- (void)postRun +{ + [hex_timer invalidate]; + [audioLock lock]; + memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer)); + audioBufferPosition = audioBufferNeeded; + [audioLock signal]; + [audioLock unlock]; + [_audioClient stop]; + _audioClient = nil; + self.view.mouseHidingEnabled = false; + GB_save_battery(&gb, self.savPath.UTF8String); + GB_save_cheats(&gb, self.chtPath.UTF8String); + unsigned time_to_alarm = GB_time_to_alarm(&gb); + + if (time_to_alarm) { + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = (id)[NSApp delegate]; + NSUserNotification *notification = [[NSUserNotification alloc] init]; + 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.fileURL.path; + notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; + notification.soundName = NSUserNotificationDefaultSoundName; + [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; + } + [_view setRumble:0]; +} + +- (void) start +{ + self.gbsPlayPauseButton.state = true; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; + if (master) { + [master start]; + return; + } + if (running) return; + running = true; + [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; +} + +- (void) stop +{ + self.gbsPlayPauseButton.state = false; + 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)) { + [self interruptDebugInputRead]; + } + [audioLock lock]; + stopping = true; + [audioLock signal]; + [audioLock unlock]; + running = false; + while (stopping) { + [audioLock lock]; + [audioLock signal]; + [audioLock unlock]; + } + GB_debugger_set_disabled(&gb, false); +} + +- (void) loadBootROM: (GB_boot_rom_t)type +{ + static NSString *const names[] = { + [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_CGB_0] = @"cgb0_boot", + [GB_BOOT_ROM_CGB] = @"cgb_boot", + [GB_BOOT_ROM_AGB] = @"agb_boot", + }; + GB_load_boot_rom(&gb, [[self bootROMPathForName:names[type]] UTF8String]); +} + +- (IBAction)reset:(id)sender +{ + [self stop]; + size_t old_width = GB_get_screen_width(&gb); + + if ([sender tag] != MODEL_NONE) { + current_model = (enum model)[sender tag]; + } + + GB_switch_model_and_reset(&gb, [self internalModel]); + + if (old_width != GB_get_screen_width(&gb)) { + [self.view screenSizeChanged]; + } + + [self updateMinSize]; + + if ([sender tag] != 0) { + /* User explictly selected a model, save the preference */ + [[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 */ + [self loadROM]; + + [self start]; + + if (hex_controller) { + /* Verify bank sanity, especially when switching models. */ + [(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 +{ + if (master) { + [master togglePause:sender]; + return; + } + if (running) { + [self stop]; + } + else { + [self start]; + } +} + +- (void)dealloc +{ + [cameraSession stopRunning]; + self.view.gb = NULL; + GB_free(&gb); + if (cameraImage) { + CVBufferRelease(cameraImage); + } + if (audioBuffer) { + free(audioBuffer); + } +} + +- (void)windowControllerDidLoadNib:(NSWindowController *)aController +{ + [super windowControllerDidLoadNib:aController]; + // Interface Builder bug? + [self.consoleWindow setContentSize:self.consoleWindow.minSize]; + /* Close Open Panels, if any */ + for (NSWindow *window in [[NSApplication sharedApplication] windows]) { + if ([window isKindOfClass:[NSOpenPanel class]]) { + [(NSOpenPanel *)window cancel:self]; + } + } + + NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; + [paragraph_style setLineSpacing:2]; + + self.debuggerSideViewInput.font = [NSFont userFixedPitchFontOfSize:12]; + self.debuggerSideViewInput.textColor = [NSColor whiteColor]; + self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style; + [self.debuggerSideViewInput setString:@"registers\nbacktrace\n"]; + ((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateSideView) + name:NSTextDidChangeNotification + object:self.debuggerSideViewInput]; + + self.consoleOutput.textContainerInset = NSMakeSize(4, 4); + [self.view becomeFirstResponder]; + self.view.frameBlendingMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; + CGRect window_frame = self.mainWindow.frame; + window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"], + window_frame.size.width); + window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], + window_frame.size.height); + [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:true animate:false]; + + + self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]]; + self.debuggerSplitView.dividerColor = [NSColor clearColor]; + if (@available(macOS 11.0, *)) { + self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded; + self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact; + [self.printerFeedWindow.toolbar removeItemAtIndex:1]; + self.printerFeedWindow.toolbar.items.firstObject.image = + [NSImage imageWithSystemSymbolName:@"square.and.arrow.down" + accessibilityDescription:@"Save"]; + self.printerFeedWindow.toolbar.items.lastObject.image = + [NSImage imageWithSystemSymbolName:@"printer" + accessibilityDescription:@"Print"]; + } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateHighpassFilter) + name:@"GBHighpassFilterChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateColorCorrectionMode) + name:@"GBColorCorrectionChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateLightTemperature) + name:@"GBLightTemperatureChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateInterferenceVolume) + name:@"GBInterferenceVolumeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateFrameBlendingMode) + name:@"GBFrameBlendingModeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updatePalette) + name:@"GBColorPaletteChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateBorderMode) + name:@"GBBorderModeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRumbleMode) + name:@"GBRumbleModeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRewindLength) + name:@"GBRewindLengthChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRTCMode) + name:@"GBRTCModeChanged" + object:nil]; + + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(dmgModelChanged) + name:@"GBDMGModelChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sgbModelChanged) + name:@"GBSGBModelChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(cgbModelChanged) + name:@"GBCGBModelChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(agbModelChanged) + name:@"GBAGBModelChanged" + object:nil]; + + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateVolume) + name:@"GBVolumeChanged" + object:nil]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { + current_model = MODEL_DMG; + } + 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; + } + + [self initCommon]; + self.view.gb = &gb; + self.view.osdView = _osdView; + [self.view screenSizeChanged]; + if ([self loadROM]) { + _mainWindow.alphaValue = 0; // Hack hack ugly hack + dispatch_async(dispatch_get_main_queue(), ^{ + [self close]; + }); + } + else { + [self reset:nil]; + } +} + +- (void) initMemoryView +{ + hex_controller = [[HFController alloc] init]; + [hex_controller setBytesPerColumn:1]; + [hex_controller setEditMode:HFOverwriteMode]; + + [hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]]; + + /* Here we're going to make three representers - one for the hex, one for the ASCII, and one for the scrollbar. To lay these all out properly, we'll use a fourth HFLayoutRepresenter. */ + HFLayoutRepresenter *layoutRep = [[HFLayoutRepresenter alloc] init]; + HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init]; + HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init]; + HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init]; + lineRep = [[HFLineCountingRepresenter alloc] init]; + HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init]; + + lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal; + + /* Add all our reps to the controller. */ + [hex_controller addRepresenter:layoutRep]; + [hex_controller addRepresenter:hexRep]; + [hex_controller addRepresenter:asciiRep]; + [hex_controller addRepresenter:scrollRep]; + [hex_controller addRepresenter:lineRep]; + [hex_controller addRepresenter:statusRep]; + + /* Tell the layout rep which reps it should lay out. */ + [layoutRep addRepresenter:hexRep]; + [layoutRep addRepresenter:scrollRep]; + [layoutRep addRepresenter:asciiRep]; + [layoutRep addRepresenter:lineRep]; + [layoutRep addRepresenter:statusRep]; + + + [(NSView *)[hexRep view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + /* Grab the layout rep's view and stick it into our container. */ + NSView *layoutView = [layoutRep view]; + NSRect layoutViewFrame = self.memoryView.frame; + [layoutView setFrame:layoutViewFrame]; + [layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin]; + [self.memoryView addSubview:layoutView]; + self.memoryView = layoutView; + + self.memoryBankItem.enabled = false; +} + ++ (BOOL)autosavesInPlace +{ + return true; +} + +- (NSString *)windowNibName +{ + // Override returning the nib file name of the document + // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. + return @"Document"; +} + +- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type +{ + return true; +} + +- (IBAction)changeGBSTrack:(id)sender +{ + if (!running) { + [self start]; + } + [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 copy]) { + [view removeFromSuperview]; + } + if (@available(macOS 11, *)) { + [[NSBundle mainBundle] loadNibNamed:@"GBS11" owner:self topLevelObjects:nil]; + } + else { + [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil]; + } + [_mainWindow setContentSize:self.gbsPlayerView.bounds.size]; + _mainWindow.styleMask &= ~NSWindowStyleMaskResizable; + dispatch_async(dispatch_get_main_queue(), ^{ // Cocoa is weird, no clue why it's needed + [_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]; + 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 (!_audioClient.isPlaying) { + [_audioClient start]; + } + + if (@available(macOS 10.10, *)) { + _mainWindow.titlebarAppearsTransparent = true; + } +} + +- (bool)isCartContainer +{ + return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"]; +} + +- (NSString *)savPath +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:@"battery.sav"]; + } + + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path; +} + +- (NSString *)chtPath +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:@"cheats.cht"]; + } + + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path; +} + +- (NSString *)saveStatePath:(unsigned)index +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]]; + } + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path; +} + +- (NSString *)romPath +{ + NSString *fileName = self.fileName; + if (self.isCartContainer) { + NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"] + encoding:NSUTF8StringEncoding + error:nil] componentsSeparatedByString:@"\n"]; + fileName = nil; + bool needsRebuild = false; + for (NSString *path in paths) { + NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL]; + if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { + if (fileName && ![fileName isEqualToString:url.path]) { + needsRebuild = true; + break; + } + fileName = url.path; + } + else { + needsRebuild = true; + } + } + if (fileName && needsRebuild) { + [[NSString stringWithFormat:@"%@\n%@\n%@", + [fileName pathRelativeToDirectory:self.fileName], + fileName, + [[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]] + writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"] + atomically:false + encoding:NSUTF8StringEncoding + error:nil]; + } + } + + return fileName; +} + +static bool is_path_writeable(const char *path) +{ + if (!access(path, W_OK)) return true; + int fd = creat(path, 0644); + if (fd == -1) return false; + close(fd); + unlink(path); + return true; +} + +- (int) loadROM +{ + __block int ret = 0; + NSString *fileName = self.romPath; + if (!fileName) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + return 1; + } + + NSString *rom_warnings = [self captureOutputForBlock:^{ + GB_debugger_clear_symbols(&gb); + if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) { + ret = GB_load_isx(&gb, fileName.UTF8String); + if (!self.isCartContainer) { + GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); + } + } + else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) { + __block GB_gbs_info_t info; + ret = GB_load_gbs(&gb, fileName.UTF8String, &info); + [self prepareGBSInterface:&info]; + } + else { + ret = GB_load_rom(&gb, [fileName UTF8String]); + } + if (GB_save_battery_size(&gb)) { + if (!is_path_writeable(self.savPath.UTF8String)) { + GB_log(&gb, "The save path for this ROM is not writeable, progress will not be saved.\n"); + } + } + GB_load_battery(&gb, self.savPath.UTF8String); + GB_load_cheats(&gb, self.chtPath.UTF8String); + [self.cheatWindowController cheatsUpdated]; + GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); + GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String); + }]; + 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 +{ + [self disconnectLinkCable]; + 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]; + [self.vramWindow close]; + [self.printerFeedWindow close]; + [self.cheatsWindow close]; + [super close]; +} + +- (IBAction) interrupt:(id)sender +{ + [self log:"^C\n"]; + GB_debugger_break(&gb); + [self start]; + [self.consoleWindow makeKeyAndOrderFront:nil]; + [self.consoleInput becomeFirstResponder]; +} + +- (IBAction)mute:(id)sender +{ + if (_audioClient.isPlaying) { + [_audioClient stop]; + } + else { + [_audioClient start]; + if (_volume == 0) { + [GBWarningPopover popoverWithContents:@"Warning: Volume is set to to zero in the preferences panel" onWindow:self.mainWindow]; + } + } + [[NSUserDefaults standardUserDefaults] setBool:!_audioClient.isPlaying forKey:@"Mute"]; +} + +- (BOOL)validateUserInterfaceItem:(id)anItem +{ + if ([anItem action] == @selector(mute:)) { + [(NSMenuItem *)anItem setState:!_audioClient.isPlaying]; + } + else if ([anItem action] == @selector(togglePause:)) { + 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) { + [(NSMenuItem *)anItem setState:anItem.tag == current_model]; + } + else if ([anItem action] == @selector(interrupt:)) { + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { + return false; + } + } + else if ([anItem action] == @selector(disconnectAllAccessories:)) { + [(NSMenuItem *)anItem setState:accessory == GBAccessoryNone]; + } + else if ([anItem action] == @selector(connectPrinter:)) { + [(NSMenuItem *)anItem setState:accessory == GBAccessoryPrinter]; + } + else if ([anItem action] == @selector(connectWorkboy:)) { + [(NSMenuItem *)anItem setState:accessory == GBAccessoryWorkboy]; + } + else if ([anItem action] == @selector(connectLinkCable:)) { + [(NSMenuItem *)anItem setState:[(NSMenuItem *)anItem representedObject] == master || + [(NSMenuItem *)anItem representedObject] == slave]; + } + else if ([anItem action] == @selector(toggleCheats:)) { + [(NSMenuItem *)anItem setState:GB_cheats_enabled(&gb)]; + } + 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)]; + } + else if ([anItem action] == @selector(toggleAudioRecording:)) { + [(NSMenuItem *)anItem setTitle:_isRecordingAudio? @"Stop Audio Recording" : @"Start Audio Recording…"]; + } + + return [super validateUserInterfaceItem:anItem]; +} + + +- (void) windowWillEnterFullScreen:(NSNotification *)notification +{ + fullScreen = true; + self.view.mouseHidingEnabled = running; +} + +- (void) windowWillExitFullScreen:(NSNotification *)notification +{ + fullScreen = false; + self.view.mouseHidingEnabled = false; +} + +- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame +{ + if (fullScreen) { + return newFrame; + } + size_t width = GB_get_screen_width(&gb), + height = GB_get_screen_height(&gb); + + NSRect rect = window.contentView.frame; + + unsigned titlebarSize = window.contentView.superview.frame.size.height - rect.size.height; + unsigned step = width / [[window screen] backingScaleFactor]; + + rect.size.width = floor(rect.size.width / step) * step + step; + rect.size.height = rect.size.width * height / width + titlebarSize; + + if (rect.size.width > newFrame.size.width) { + rect.size.width = width; + rect.size.height = height + titlebarSize; + } + else if (rect.size.height > newFrame.size.height) { + rect.size.width = width; + rect.size.height = height + titlebarSize; + } + + rect.origin = window.frame.origin; + rect.origin.y -= rect.size.height - window.frame.size.height; + + return rect; +} + +- (void) appendPendingOutput +{ + [console_output_lock lock]; + if (shouldClearSideView) { + shouldClearSideView = false; + [self.debuggerSideView setString:@""]; + } + if (pending_console_output) { + NSTextView *textView = logToSideView? self.debuggerSideView : self.consoleOutput; + + [hex_controller reloadData]; + [self reloadVRAMData: nil]; + + [textView.textStorage appendAttributedString:pending_console_output]; + [textView scrollToEndOfDocument:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { + [self.consoleWindow orderFront:nil]; + } + pending_console_output = nil; + } + [console_output_lock unlock]; +} + +- (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes +{ + NSString *nsstring = @(string); // For ref-counting + if (capturedOutput) { + [capturedOutput appendString:nsstring]; + return; + } + + + NSFont *font = [NSFont userFixedPitchFontOfSize:12]; + NSUnderlineStyle underline = NSUnderlineStyleNone; + if (attributes & GB_LOG_BOLD) { + font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask]; + } + + if (attributes & GB_LOG_UNDERLINE_MASK) { + underline = (attributes & GB_LOG_UNDERLINE_MASK) == GB_LOG_DASHED_UNDERLINE? NSUnderlinePatternDot | NSUnderlineStyleSingle : NSUnderlineStyleSingle; + } + + NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init]; + [paragraph_style setLineSpacing:2]; + NSMutableAttributedString *attributed = + [[NSMutableAttributedString alloc] initWithString:nsstring + attributes:@{NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor whiteColor], + NSUnderlineStyleAttributeName: @(underline), + NSParagraphStyleAttributeName: paragraph_style}]; + + [console_output_lock lock]; + if (!pending_console_output) { + pending_console_output = attributed; + } + else { + [pending_console_output appendAttributedString:attributed]; + } + + if (![console_output_timer isValid]) { + 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 = false; +} + +- (IBAction)showConsoleWindow:(id)sender +{ + [self.consoleWindow orderBack:nil]; +} + +- (IBAction)consoleInput:(NSTextField *)sender +{ + NSString *line = [sender stringValue]; + if ([line isEqualToString:@""] && lastConsoleInput) { + line = lastConsoleInput; + } + else if (line) { + lastConsoleInput = line; + } + else { + line = @""; + } + + if (!in_sync_input) { + [self log:">"]; + } + [self log:[line UTF8String]]; + [self log:"\n"]; + [has_debugger_input lock]; + [debugger_input_queue addObject:line]; + [has_debugger_input unlockWithCondition:1]; + + [sender setStringValue:@""]; +} + +- (void) interruptDebugInputRead +{ + [has_debugger_input lock]; + [debugger_input_queue addObject:[NSNull null]]; + [has_debugger_input unlockWithCondition:1]; +} + +- (void) updateSideView +{ + if (!GB_debugger_is_stopped(&gb)) { + return; + } + + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self updateSideView]; + }); + return; + } + + [console_output_lock lock]; + shouldClearSideView = true; + [self appendPendingOutput]; + logToSideView = true; + [console_output_lock unlock]; + + for (NSString *line in [self.debuggerSideViewInput.string componentsSeparatedByString:@"\n"]) { + NSString *stripped = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if ([stripped length]) { + char *dupped = strdup([stripped UTF8String]); + GB_attributed_log(&gb, GB_LOG_BOLD, "%s:\n", dupped); + GB_debugger_execute_command(&gb, dupped); + GB_log(&gb, "\n"); + free(dupped); + } + } + + [console_output_lock lock]; + [self appendPendingOutput]; + logToSideView = false; + [console_output_lock unlock]; +} + +- (char *) getDebuggerInput +{ + bool isPlaying = _audioClient.isPlaying; + if (isPlaying) { + [_audioClient stop]; + } + [audioLock lock]; + [audioLock signal]; + [audioLock unlock]; + [self updateSideView]; + [self log:">"]; + in_sync_input = true; + [has_debugger_input lockWhenCondition:1]; + NSString *input = [debugger_input_queue firstObject]; + [debugger_input_queue removeObjectAtIndex:0]; + [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; + in_sync_input = false; + shouldClearSideView = true; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC / 10)), dispatch_get_main_queue(), ^{ + if (shouldClearSideView) { + shouldClearSideView = false; + [self.debuggerSideView setString:@""]; + } + }); + if (isPlaying) { + [_audioClient start]; + } + if ((id) input == [NSNull null]) { + return NULL; + } + return strdup([input UTF8String]); +} + +- (char *) getAsyncDebuggerInput +{ + [has_debugger_input lock]; + NSString *input = [debugger_input_queue firstObject]; + if (input) { + [debugger_input_queue removeObjectAtIndex:0]; + } + [has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0]; + if ((id)input == [NSNull null]) { + return NULL; + } + return input? strdup([input UTF8String]): NULL; +} + +- (IBAction)saveState:(id)sender +{ + bool __block success = false; + [self performAtomicBlock:^{ + success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0; + }]; + + if (!success) { + [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; +{ + int __block result = false; + NSString *error = + [self captureOutputForBlock:^{ + result = GB_load_state(&gb, path); + }]; + + if (result == ENOENT && noErrorOnFileNotFound) { + return ENOENT; + } + + if (result) { + NSBeep(); + } + else { + [self.osdView displayText:@"State loaded"]; + } + if (error) { + [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; + } + return result; +} + +- (IBAction)loadState:(id)sender +{ + int ret = [self loadStateFile:[self saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true]; + if (ret == ENOENT && !self.isCartContainer) { + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false]; + } +} + +- (IBAction)clearConsole:(id)sender +{ + [self.consoleOutput setString:@""]; +} + +- (void)log:(const char *)log +{ + [self log:log withAttributes:0]; +} + +- (uint8_t) readMemory:(uint16_t)addr +{ + while (!GB_is_inited(&gb)); + return GB_safe_read_memory(&gb, addr); +} + +- (void) writeMemory:(uint16_t)addr value:(uint8_t)value +{ + while (!GB_is_inited(&gb)); + GB_write_memory(&gb, addr, value); +} + +- (void) performAtomicBlock: (void (^)())block +{ + while (!GB_is_inited(&gb)); + bool isRunning = running && !GB_debugger_is_stopped(&gb); + if (master) { + isRunning |= master->running; + } + if (!isRunning) { + block(); + return; + } + + if (master) { + [master performAtomicBlock:block]; + return; + } + + _pendingAtomicBlock = block; + while (_pendingAtomicBlock); +} + +- (NSString *)captureOutputForBlock: (void (^)())block +{ + capturedOutput = [[NSMutableString alloc] init]; + [self performAtomicBlock:block]; + NSString *ret = [capturedOutput stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + capturedOutput = nil; + return [ret length]? ret : nil; +} + ++ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale +{ + + NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(width * scale, height * scale)]; + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:false + isPlanar:false + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:0 + bytesPerRow:4 * width + bitsPerPixel:32]; + memcpy(rep.bitmapData, data.bytes, data.length); + [ret addRepresentation:rep]; + return ret; +} + +- (void) reloadMemoryView +{ + if (self.memoryWindow.isVisible) { + [hex_controller reloadData]; + } +} + +- (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 */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilesetPaletteButton.indexOfSelectedItem; + if (palette_menu_index) { + palette_type = palette_menu_index > 8? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + size_t bufferLength = 256 * 192 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tileset(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 1) & 7); + + self.tilesetImageView.image = [Document imageFromData:data width:256 height:192 scale:1.0]; + self.tilesetImageView.layer.magnificationFilter = kCAFilterNearest; + } + break; + + case 1: + /* Tilemap */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilemapPaletteButton.indexOfSelectedItem; + if (palette_menu_index > 1) { + palette_type = palette_menu_index > 9? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + else if (palette_menu_index == 1) { + palette_type = GB_PALETTE_AUTO; + } + + size_t bufferLength = 256 * 256 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tilemap(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 2) & 7, + (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, + (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); + + 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; + } + break; + + case 2: + /* OAM */ + { + _oamCount = GB_get_oam_info(&gb, _oamInfo, &_oamHeight); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.objectView reloadData:self]; + }); + } + break; + + case 3: + /* Palettes */ + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.paletteView reloadData:self]; + }); + } + break; + } + } +} + +- (IBAction) showMemory:(id)sender +{ + if (!hex_controller) { + [self initMemoryView]; + } + [self.memoryWindow makeKeyAndOrderFront:sender]; +} + +- (IBAction)hexGoTo:(id)sender +{ + NSString *error = [self captureOutputForBlock:^{ + uint16_t addr; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) { + return; + } + addr -= lineRep.valueOffset; + if (addr >= hex_controller.byteArray.length) { + GB_log(&gb, "Value $%04x is out of range.\n", addr); + return; + } + [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; + [hex_controller _ensureVisibilityOfLocation:addr]; + [self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]]; + }]; + if (error) { + NSBeep(); + [GBWarningPopover popoverWithContents:error onView:sender]; + } +} + +- (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors +{ + NSString *error = [self captureOutputForBlock:^{ + uint16_t addr, bank; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) { + return; + } + + if (bank == (uint16_t) -1) { + bank = addr; + } + + uint16_t n_banks = 1; + switch ([(GBMemoryByteArray *)(hex_controller.byteArray) mode]) { + case GBMemoryROM: { + size_t rom_size; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &rom_size, NULL); + n_banks = rom_size / 0x4000; + break; + } + case GBMemoryVRAM: + n_banks = GB_is_cgb(&gb) ? 2 : 1; + break; + case GBMemoryExternalRAM: { + size_t ram_size; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &ram_size, NULL); + n_banks = (ram_size + 0x1FFF) / 0x2000; + break; + } + case GBMemoryRAM: + n_banks = GB_is_cgb(&gb) ? 8 : 1; + break; + case GBMemoryEntireSpace: + break; + } + + bank %= n_banks; + + [sender setStringValue:[NSString stringWithFormat:@"$%x", bank]]; + [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank]; + [hex_controller reloadData]; + }]; + + if (error && !ignore_errors) { + NSBeep(); + [GBWarningPopover popoverWithContents:error onView:sender]; + } +} + +- (IBAction)hexUpdateBank:(NSControl *)sender +{ + [self hexUpdateBank:sender ignoreErrors:false]; +} + +- (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender +{ + self.memoryBankItem.enabled = [sender indexOfSelectedItem] != GBMemoryEntireSpace; + GBMemoryByteArray *byteArray = (GBMemoryByteArray *)(hex_controller.byteArray); + [byteArray setMode:(GB_memory_mode_t)[sender indexOfSelectedItem]]; + uint16_t bank; + switch ((GB_memory_mode_t)[sender indexOfSelectedItem]) { + case GBMemoryEntireSpace: + case GBMemoryROM: + lineRep.valueOffset = 0; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, NULL, &bank); + byteArray.selectedBank = bank; + break; + case GBMemoryVRAM: + lineRep.valueOffset = 0x8000; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, &bank); + byteArray.selectedBank = bank; + break; + case GBMemoryExternalRAM: + lineRep.valueOffset = 0xA000; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, NULL, &bank); + byteArray.selectedBank = bank; + break; + case GBMemoryRAM: + lineRep.valueOffset = 0xC000; + GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, NULL, &bank); + byteArray.selectedBank = bank; + break; + } + [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; + [hex_controller reloadData]; + for (NSView *view in self.memoryView.subviews) { + [view setNeedsDisplay:true]; + } +} + +- (GB_gameboy_t *) gameboy +{ + return &gb; +} + ++ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName +{ + return true; +} + +- (void)cameraRequestUpdate +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @try { + if (!cameraSession) { + if (@available(macOS 10.14, *)) { + switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) { + case AVAuthorizationStatusAuthorized: + break; + case AVAuthorizationStatusNotDetermined: { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + [self cameraRequestUpdate]; + }]; + return; + } + case AVAuthorizationStatusDenied: + case AVAuthorizationStatusRestricted: + GB_camera_updated(&gb); + return; + } + } + + NSError *error; + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error]; + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions([[[device formats] firstObject] formatDescription]); + + if (!input) { + GB_camera_updated(&gb); + return; + } + + cameraOutput = [[AVCaptureStillImageOutput alloc] init]; + /* Greyscale is not widely supported, so we use YUV, whose first element is the brightness. */ + [cameraOutput setOutputSettings: @{(id)kCVPixelBufferPixelFormatTypeKey: @(kYUVSPixelFormat), + (id)kCVPixelBufferWidthKey: @(MAX(128, 112 * dimensions.width / dimensions.height)), + (id)kCVPixelBufferHeightKey: @(MAX(112, 128 * dimensions.height / dimensions.width)),}]; + + + cameraSession = [AVCaptureSession new]; + cameraSession.sessionPreset = AVCaptureSessionPresetPhoto; + + [cameraSession addInput: input]; + [cameraSession addOutput: cameraOutput]; + [cameraSession startRunning]; + cameraConnection = [cameraOutput connectionWithMediaType: AVMediaTypeVideo]; + } + + [cameraOutput captureStillImageAsynchronouslyFromConnection: cameraConnection completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError *error) { + if (error) { + GB_camera_updated(&gb); + } + else { + if (cameraImage) { + CVBufferRelease(cameraImage); + cameraImage = NULL; + } + cameraImage = CVBufferRetain(CMSampleBufferGetImageBuffer(sampleBuffer)); + /* We only need the actual buffer, no need to ever unlock it. */ + CVPixelBufferLockBaseAddress(cameraImage, 0); + } + + GB_camera_updated(&gb); + }]; + } + @catch (NSException *exception) { + /* I have not tested camera support on many devices, so we catch exceptions just in case. */ + GB_camera_updated(&gb); + } + }); +} + +- (uint8_t)cameraGetPixelAtX:(uint8_t)x andY:(uint8_t) y +{ + if (!cameraImage) { + return 0; + } + + uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(cameraImage); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(cameraImage); + uint8_t offsetX = (CVPixelBufferGetWidth(cameraImage) - 128) / 2; + uint8_t offsetY = (CVPixelBufferGetHeight(cameraImage) - 112) / 2; + uint8_t ret = baseAddress[(x + offsetX) * 2 + (y + offsetY) * bytesPerRow]; + + return ret; +} + +- (IBAction)toggleTilesetGrid:(NSButton *)sender +{ + if (sender.state) { + self.tilesetImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:128], + + ]; + self.tilesetImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:64], + ]; + self.tilemapImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + self.tilemapImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + } + else { + self.tilesetImageView.horizontalGrids = nil; + self.tilesetImageView.verticalGrids = nil; + self.tilemapImageView.horizontalGrids = nil; + self.tilemapImageView.verticalGrids = nil; + } +} + +- (IBAction)toggleScrollingDisplay:(NSButton *)sender +{ + self.tilemapImageView.displayScrollRect = sender.state; +} + +- (IBAction)vramTabChanged:(NSSegmentedControl *)sender +{ + [self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]]; + [self reloadVRAMData:sender]; + [self.vramTabView.selectedTabViewItem.view addSubview:self.gridButton]; + self.gridButton.hidden = [sender selectedSegment] >= 2; + + NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; + CGRect window_rect = self.vramWindow.frame; + window_rect.origin.y += window_rect.size.height; + switch ([sender selectedSegment]) { + case 0: + case 2: + window_rect.size.height = 384 + height_diff + 48; + break; + case 1: + window_rect.size.height = 512 + height_diff + 48; + break; + case 3: + window_rect.size.height = 24 * 16 + height_diff; + break; + + default: + break; + } + window_rect.origin.y -= window_rect.size.height; + [self.vramWindow setFrame:window_rect display:true animate:true]; +} + +- (void)mouseDidLeaveImageView:(GBImageView *)view +{ + self.vramStatusLabel.stringValue = @""; +} + +- (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger)y +{ + if (view == self.tilesetImageView) { + uint8_t bank = x >= 128? 1 : 0; + x &= 127; + uint16_t tile = x / 8 + y / 8 * 16; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10]; + } + else if (view == self.tilemapImageView) { + uint16_t map_offset = x / 8 + y / 8 * 32; + uint16_t map_base = 0x1800; + GB_map_type_t map_type = (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem; + GB_tileset_type_t tileset_type = (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem; + uint8_t lcdc = ((uint8_t *)GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC]; + uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL); + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) { + map_base = 0x1C00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + uint8_t tile = vram[map_base + map_offset]; + uint16_t tile_address = 0; + if (tileset_type == GB_TILESET_8000) { + tile_address = 0x8000 + tile * 0x10; + } + else { + tile_address = 0x9000 + (int8_t)tile * 0x10; + } + + if (GB_is_cgb(&gb)) { + uint8_t attributes = vram[map_base + map_offset + 0x2000]; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)", + tile, + attributes & 0x8? 1 : 0, + tile_address, + 0x8000 + map_base + map_offset, + (attributes & 0x80) ? 'P' : '-', + (attributes & 0x40) ? 'V' : '-', + (attributes & 0x20) ? 'H' : '-', + attributes & 0x8? 1 : 0, + attributes & 0x7 + ]; + } + else { + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x ($%04x) at map address $%04x", + tile, + tile_address, + 0x8000 + map_base + map_offset + ]; + } + + } +} + +- (GB_oam_info_t *)oamInfo +{ + return _oamInfo; +} + +- (IBAction)showVRAMViewer:(id)sender +{ + [self.vramWindow makeKeyAndOrderFront:sender]; + [self reloadVRAMData: nil]; +} + +- (void) printImage:(uint32_t *)imageBytes height:(unsigned) height + topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin + exposure:(unsigned) exposure +{ + uint32_t paddedImage[160 * (topMargin + height + bottomMargin)]; + memset(paddedImage, 0xFF, sizeof(paddedImage)); + memcpy(paddedImage + (160 * topMargin), imageBytes, 160 * height * sizeof(imageBytes[0])); + if (!self.printerFeedWindow.isVisible) { + currentPrinterImageData = [[NSMutableData alloc] init]; + } + [currentPrinterImageData appendBytes:paddedImage length:sizeof(paddedImage)]; + /* UI related code must run on main thread. */ + dispatch_async(dispatch_get_main_queue(), ^{ + self.feedImageView.image = [Document imageFromData:currentPrinterImageData + width:160 + height:currentPrinterImageData.length / 160 / sizeof(imageBytes[0]) + scale:2.0]; + NSRect frame = self.printerFeedWindow.frame; + frame.size = self.feedImageView.image.size; + [self.printerFeedWindow setContentMaxSize:frame.size]; + frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height; + [self.printerFeedWindow setFrame:frame display:false animate: self.printerFeedWindow.isVisible]; + [self.printerFeedWindow orderFront:NULL]; + }); + +} + +- (void)printDocument:(id)sender +{ + if (self.feedImageView.image.size.height == 0) { + NSBeep(); return; + } + NSImageView *view = [[NSImageView alloc] initWithFrame:(NSRect){{0,0}, self.feedImageView.image.size}]; + view.image = self.feedImageView.image; + [[NSPrintOperation printOperationWithView:view] runOperationModalForWindow:self.printerFeedWindow delegate:nil didRunSelector:NULL contextInfo:NULL]; +} + +- (IBAction)savePrinterFeed:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:@[@"png"]]; + [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) { + 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:NSBitmapImageFileTypePNG properties:@{}]; + [data writeToURL:savePanel.URL atomically:false]; + [self.printerFeedWindow setIsVisible:false]; + } + if (shouldResume) { + [self start]; + } + }]; +} + +- (IBAction)disconnectAllAccessories:(id)sender +{ + [self disconnectLinkCable]; + [self performAtomicBlock:^{ + accessory = GBAccessoryNone; + GB_disconnect_serial(&gb); + }]; +} + +- (IBAction)connectPrinter:(id)sender +{ + [self disconnectLinkCable]; + [self performAtomicBlock:^{ + accessory = GBAccessoryPrinter; + GB_connect_printer(&gb, printImage); + }]; +} + +- (IBAction)connectWorkboy:(id)sender +{ + [self disconnectLinkCable]; + [self performAtomicBlock:^{ + accessory = GBAccessoryWorkboy; + GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime); + }]; +} + +- (void) updateVolume +{ + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; +} + +- (void) updateHighpassFilter +{ + if (GB_is_inited(&gb)) { + GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); + } +} + +- (void) updateColorCorrectionMode +{ + if (GB_is_inited(&gb)) { + GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + } +} + +- (void) updateLightTemperature +{ + if (GB_is_inited(&gb)) { + GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); + } +} + +- (void) updateInterferenceVolume +{ + if (GB_is_inited(&gb)) { + GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); + } +} + +- (void) updateFrameBlendingMode +{ + self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; +} + +- (void) updateRewindLength +{ + [self performAtomicBlock:^{ + if (GB_is_inited(&gb)) { + GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + } + }]; +} + +- (void) updateRTCMode +{ + if (GB_is_inited(&gb)) { + GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); + } +} + +- (void)dmgModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_DMG) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)sgbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_SGB) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)cgbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_CGB) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)agbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_AGB) { + [self reset:nil]; + } + modelsChanging = false; +} + +- (void)setFileURL:(NSURL *)fileURL +{ + [super setFileURL:fileURL]; + self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]]; + +} + +- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview; +{ + if ([[splitView arrangedSubviews] lastObject] == subview) { + return true; + } + return false; +} + +- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex +{ + return 600; +} + +- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex +{ + return splitView.frame.size.width - 321; +} + +- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view +{ + if ([[splitView arrangedSubviews] lastObject] == view) { + return false; + } + return true; +} + +- (void)splitViewDidResizeSubviews:(NSNotification *)notification +{ + GBSplitView *splitview = notification.object; + if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) { + [splitview setPosition:600 ofDividerAtIndex:0]; + } + /* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an + NSBox-based separator that renders properly so it acts like the split view's separator. */ + NSRect rect = self.debuggerVerticalLine.frame; + rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1; + self.debuggerVerticalLine.frame = rect; +} + +- (IBAction)showCheats:(id)sender +{ + [self.cheatsWindow makeKeyAndOrderFront:nil]; +} + +- (IBAction)toggleCheats:(id)sender +{ + GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb)); +} + +- (void)disconnectLinkCable +{ + bool wasRunning = self->running; + Document *partner = master ?: slave; + if (partner) { + wasRunning |= partner->running; + [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; +} + +- (NSImage *)takeScreenshot +{ + 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 + length:GB_get_screen_width(&gb) * GB_get_screen_height(&gb) * 4 + freeWhenDone:false] + width:GB_get_screen_width(&gb) + height:GB_get_screen_height(&gb) + scale:1.0]; + } + return ret; +} + +- (NSString *)screenshotFilename +{ + NSDate *date = [NSDate date]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateStyle = NSDateFormatterLongStyle; + dateFormatter.timeStyle = NSDateFormatterMediumStyle; + return [[NSString stringWithFormat:@"%@ – %@.png", + self.fileURL.lastPathComponent.stringByDeletingPathExtension, + [dateFormatter stringFromDate:date]] stringByReplacingOccurrencesOfString:@":" withString:@"."]; // Gotta love Mac OS Classic + +} + +- (IBAction)saveScreenshot:(id)sender +{ + NSString *folder = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBScreenshotFolder"]; + BOOL isDirectory = false; + if (folder) { + [[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDirectory]; + } + if (!folder) { + bool shouldResume = running; + [self stop]; + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.canChooseFiles = false; + openPanel.canChooseDirectories = true; + openPanel.message = @"Choose a folder for screenshots"; + [openPanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [[NSUserDefaults standardUserDefaults] setObject:openPanel.URL.path + forKey:@"GBScreenshotFolder"]; + [self saveScreenshot:sender]; + } + if (shouldResume) { + [self start]; + } + + }]; + return; + } + NSImage *image = [self takeScreenshot]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateStyle = NSDateFormatterLongStyle; + dateFormatter.timeStyle = NSDateFormatterMediumStyle; + NSString *filename = [self screenshotFilename]; + filename = [folder stringByAppendingPathComponent:filename]; + unsigned i = 2; + while ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { + filename = [[filename stringByDeletingPathExtension] stringByAppendingFormat:@" %d.png", i++]; + } + + NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; + NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + [data writeToFile:filename atomically:false]; + [self.osdView displayText:@"Screenshot saved"]; +} + +- (IBAction)saveScreenshotAs:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSImage *image = [self takeScreenshot]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setNameFieldStringValue:[self screenshotFilename]]; + [savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [savePanel orderOut:self]; + NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; + NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + [data writeToURL:savePanel.URL atomically:false]; + [[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent + forKey:@"GBScreenshotFolder"]; + } + if (shouldResume) { + [self start]; + } + }]; + [self.osdView displayText:@"Screenshot saved"]; +} + +- (IBAction)copyScreenshot:(id)sender +{ + NSImage *image = [self takeScreenshot]; + [[NSPasteboard generalPasteboard] clearContents]; + [[NSPasteboard generalPasteboard] writeObjects:@[image]]; + [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)); +} + +- (IBAction)newCartridgeInstance:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:@[@"gbcart"]]; + [savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [savePanel orderOut:self]; + NSString *romPath = self.romPath; + [[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil]; + [[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil]; + [[NSString stringWithFormat:@"%@\n%@\n%@", + [romPath pathRelativeToDirectory:savePanel.URL.path], + romPath, + [[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")] + ] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil]; + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil]; + } + if (shouldResume) { + [self start]; + } + }]; +} + +- (IBAction)toggleAudioRecording:(id)sender +{ + + bool shouldResume = running; + [self stop]; + if (_isRecordingAudio) { + _isRecordingAudio = false; + int error = GB_stop_audio_recording(&gb); + if (error) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithFormat:@"Could not finalize recording: %s", strerror(error)]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + } + else { + [self.osdView displayText:@"Audio recording ended"]; + } + if (shouldResume) { + [self start]; + } + return; + } + _audioSavePanel = [NSSavePanel savePanel]; + if (!self.audioRecordingAccessoryView) { + [[NSBundle mainBundle] loadNibNamed:@"AudioRecordingAccessoryView" owner:self topLevelObjects:nil]; + } + _audioSavePanel.accessoryView = self.audioRecordingAccessoryView; + [self audioFormatChanged:self.audioFormatButton]; + + [_audioSavePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [_audioSavePanel orderOut:self]; + int error = GB_start_audio_recording(&gb, _audioSavePanel.URL.fileSystemRepresentation, self.audioFormatButton.selectedTag); + if (error) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithFormat:@"Could not start recording: %s", strerror(error)]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + } + else { + [self.osdView displayText:@"Audio recording started"]; + _isRecordingAudio = true; + } + } + if (shouldResume) { + [self start]; + } + _audioSavePanel = nil; + }]; +} + +- (IBAction)audioFormatChanged:(NSPopUpButton *)sender +{ + switch ((GB_audio_format_t)sender.selectedTag) { + case GB_AUDIO_FORMAT_RAW: + _audioSavePanel.allowedFileTypes = @[@"raw", @"pcm"]; + break; + case GB_AUDIO_FORMAT_AIFF: + _audioSavePanel.allowedFileTypes = @[@"aiff", @"aif", @"aifc"]; + break; + case GB_AUDIO_FORMAT_WAV: + _audioSavePanel.allowedFileTypes = @[@"wav"]; + break; + } +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/Document.xib b/thirdparty/SameBoy-old/Cocoa/Document.xib new file mode 100644 index 000000000..24e2daaf6 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/Document.xib @@ -0,0 +1,883 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/GBApp.h b/thirdparty/SameBoy-old/Cocoa/GBApp.h new file mode 100644 index 000000000..0ad7ac309 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBApp.h @@ -0,0 +1,26 @@ +#import +#import +#import + +@interface GBApp : NSApplication + +@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/thirdparty/SameBoy-old/Cocoa/GBApp.m b/thirdparty/SameBoy-old/Cocoa/GBApp.m new file mode 100644 index 000000000..8c85c520a --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBApp.m @@ -0,0 +1,647 @@ +#import "GBApp.h" +#include "GBButtons.h" +#include "GBView.h" +#include "Document.h" +#include +#import +#import +#import +#import + +#define UPDATE_SERVER "https://sameboy.github.io" + +static uint32_t color_to_int(NSColor *color) +{ + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + return (((unsigned)(color.redComponent * 0xFF)) << 16) | + (((unsigned)(color.greenComponent * 0xFF)) << 8) | + ((unsigned)(color.blueComponent * 0xFF)); +} + +@implementation GBApp +{ + NSArray *_preferencesTabs; + NSString *_lastVersion; + NSString *_updateURL; + NSURLSessionDownloadTask *_updateTask; + enum { + UPDATE_DOWNLOADING, + UPDATE_EXTRACTING, + UPDATE_WAIT_INSTALL, + UPDATE_INSTALLING, + UPDATE_FAILED, + } _updateState; + NSString *_downloadDirectory; + AuthorizationRef _auth; + bool _simulatingMenuEvent; +} + +- (void) applicationDidFinishLaunching:(NSNotification *)notification +{ + // Refresh icon if launched via a software update + [NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + for (unsigned i = 0; i < GBButtonCount; i++) { + if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) { + [defaults removeObjectForKey:button_to_preference_name(i, 0)]; + } + } + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"GBRight": @(kVK_RightArrow), + @"GBLeft": @(kVK_LeftArrow), + @"GBUp": @(kVK_UpArrow), + @"GBDown": @(kVK_DownArrow), + + @"GBA": @(kVK_ANSI_X), + @"GBB": @(kVK_ANSI_Z), + @"GBSelect": @(kVK_Delete), + @"GBStart": @(kVK_Return), + + @"GBTurbo": @(kVK_Space), + @"GBRewind": @(kVK_Tab), + @"GBSlow-Motion": @(kVK_Shift), + + @"GBFilter": @"NearestNeighbor", + @"GBColorCorrection": @(GB_COLOR_CORRECTION_MODERN_BALANCED), + @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), + @"GBRewindLength": @(10), + @"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE), + + @"GBDMGModel": @(GB_MODEL_DMG_B), + @"GBCGBModel": @(GB_MODEL_CGB_E), + @"GBAGBModel": @(GB_MODEL_AGB_A), + @"GBSGBModel": @(GB_MODEL_SGB2), + @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), + + @"GBVolume": @(1.0), + + @"GBMBC7JoystickOverride": @NO, + @"GBMBC7AllowMouse": @YES, + + // Default themes + @"GBThemes": @{ + @"Desert": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8], + @"DisabledLCDColor": @YES, + @"HueBias": @0.10087773904382469, + @"HueBiasStrength": @0.062142056772908363, + @"Manual": @NO, + }, + @"Evening": @{ + @"BrightnessBias": @-0.10168700106441975, + @"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb], + @"DisabledLCDColor": @YES, + @"HueBias": @0.60027079191058874, + @"HueBiasStrength": @0.33816297305747867, + @"Manual": @NO, + }, + @"Fog": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6], + @"DisabledLCDColor": @YES, + @"HueBias": @0.55750435756972117, + @"HueBiasStrength": @0.18424738545816732, + @"Manual": @NO, + }, + @"Magic Eggplant": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2], + @"DisabledLCDColor": @YES, + @"HueBias": @0.87717878486055778, + @"HueBiasStrength": @0.65018052788844627, + @"Manual": @NO, + }, + @"Radioactive Pea": @{ + @"BrightnessBias": @-0.48079556772908372, + @"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1], + @"DisabledLCDColor": @YES, + @"HueBias": @0.3795131972111554, + @"HueBiasStrength": @0.34337649402390436, + @"Manual": @NO, + }, + @"Seaweed": @{ + @"BrightnessBias": @-0.28532744023904377, + @"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.2694067480079681, + @"HueBiasStrength": @0.51565612549800799, + @"Manual": @NO, + }, + @"Twilight": @{ + @"BrightnessBias": @-0.091789093625498031, + @"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.0, + @"HueBiasStrength": @0.49710532868525897, + @"Manual": @NO, + }, + }, + + @"NSToolbarItemForcesStandardSize": @YES, // Forces Monterey to resepect toolbar item sizes + }]; + + [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ + JOYAxes2DEmulateButtonsKey: @YES, + JOYHatsEmulateButtonsKey: @YES, + }]; + + [JOYController registerListener:self]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; + } + + [self askAutoUpdates]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) { + [self checkForUpdates]; + } + + if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { + [NSApp activateIgnoringOtherApps:true]; + } +} + +- (IBAction)toggleDeveloperMode:(id)sender +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"]; +} + +- (IBAction)switchPreferencesTab:(id)sender +{ + for (NSView *view in _preferencesTabs) { + [view removeFromSuperview]; + } + NSView *tab = _preferencesTabs[[sender tag]]; + NSRect old = [_preferencesWindow frame]; + 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:true animate:_preferencesWindow.visible]; + [_preferencesWindow.contentView addSubview:tab]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)anItem +{ + 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; + if (!_preferencesWindow) { + [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; + NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; + _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; + _preferencesTabs = @[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 + if (@available(macOS 11.0, *)) { + [_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0]; + [_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count]; + } + } + [_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]]) { + [(NSOpenPanel *)window makeKeyAndOrderFront:nil]; + return true; + } + } + [[NSDocumentController sharedDocumentController] openDocument:self]; + return true; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true]; +} + +- (void)updateFound +{ + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0]; + if (@available(macOS 10.10, *)) { + linkColor = [NSColor linkColor]; + } + + NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSRange cutoffRange = [changes rangeOfString:@""]; + 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:true]; + }); + 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 (![@GB_VERSION isEqualToString:_lastVersion] && + ![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) { + [self updateFound]; + } + } + }] resume]; +#endif +} + +- (IBAction)userCheckForUpdates:(id)sender +{ + if (self.updateWindow) { + [self.updateWindow makeKeyAndOrderFront:sender]; + } + else { + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"]; + [self checkForUpdates]; + [sender setEnabled:false]; + [self.updatesSpinner startAnimation:sender]; + } +} + +- (void)askAutoUpdates +{ +#ifdef UPDATE_SUPPORT + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Should SameBoy check for updates when launched?"; + alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window."; + [alert addButtonWithTitle:@"Check on Launch"]; + [alert addButtonWithTitle:@"Don't Check on Launch"]; + + [[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"]; + } +#endif +} + +- (IBAction)skipVersion:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"]; + [self.updateWindow performClose:sender]; +} + +- (bool)executePath:(NSString *)path withArguments:(NSArray *)arguments +{ + if (!_auth) { + NSTask *task = [[NSTask alloc] init]; + task.launchPath = path; + task.arguments = arguments; + [task launch]; + [task waitUntilExit]; + return task.terminationStatus == 0 && task.terminationReason == NSTaskTerminationReasonExit; + } + + char *argv[arguments.count + 1]; + argv[arguments.count] = NULL; + for (unsigned i = 0; i < arguments.count; i++) { + argv[i] = (char *)arguments[i].UTF8String; + } + + return AuthorizationExecuteWithPrivileges(_auth, path.UTF8String, kAuthorizationFlagDefaults, argv, NULL) == errAuthorizationSuccess; +} + +- (void)deauthorize +{ + if (_auth) { + AuthorizationFree(_auth, kAuthorizationFlagDefaults); + _auth = nil; + } +} + +- (IBAction)installUpdate:(id)sender +{ + bool needsAuthorization = false; + if ([self executePath:@"/usr/sbin/spctl" withArguments:@[@"--status"]]) { // Succeeds when GateKeeper is on + // GateKeeper is on, we need to --add ourselves as root, else we might get a GateKeeper crash + needsAuthorization = true; + } + else if (access(_dyld_get_image_name(0), W_OK)) { + // We don't have write access, so we need authorization to update as root + needsAuthorization = true; + } + + if (needsAuthorization && !_auth) { + AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed, &_auth); + // Make sure we can modify the bundle + if (![self executePath:@"/usr/sbin/chown" withArguments:@[@"-R", [NSString stringWithFormat:@"%d:%d", getuid(), getgid()], [NSBundle mainBundle].bundlePath]]) { + [self deauthorize]; + return; + } + } + + [self.updateProgressSpinner startAnimation:nil]; + self.updateProgressButton.title = @"Cancel"; + self.updateProgressButton.enabled = true; + self.updateProgressLabel.stringValue = @"Downloading update..."; + _updateState = UPDATE_DOWNLOADING; + _updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + _updateTask = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Extracting update..."; + _updateState = UPDATE_EXTRACTING; + }); + + _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:[[NSBundle mainBundle] bundleURL] + create:true + error:nil] path]; + if (!_downloadDirectory) { + [self deauthorize]; + 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; + } + + NSTask *unzipTask; + unzipTask = [[NSTask alloc] init]; + unzipTask.launchPath = @"/usr/bin/unzip"; + unzipTask.arguments = @[location.path, @"-d", _downloadDirectory]; + [unzipTask launch]; + [unzipTask waitUntilExit]; + if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) { + [self deauthorize]; + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + 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) { + [self deauthorize]; + [[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) { + [self deauthorize]; + [[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)orderFrontAboutPanel:(id)sender +{ + // NSAboutPanelOptionApplicationIcon is not available prior to 10.13, but the key is still there and working. + [[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:@{ + @"ApplicationIcon": [NSImage imageNamed:@"Icon"] + }]; +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + if (!button.isPressed) return; + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; + } + + JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: -1; + if (!mapping && usage >= JOYButtonUsageGeneric0) { + usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3]; + } + + if (usage == GBJoyKitHotkey1 || usage == GBJoyKitHotkey2) { + if (_preferencesWindow && self.keyWindow == _preferencesWindow) { + return; + } + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"] && !self.keyWindow) { + return; + } + + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:usage == GBJoyKitHotkey1? @"GBJoypadHotkey1" : @"GBJoypadHotkey2"]; + NSEventModifierFlags flags = NSEventModifierFlagCommand; + if ([keyEquivalent hasPrefix:@"^"]) { + flags |= NSEventModifierFlagShift; + [keyEquivalent substringFromIndex:1]; + } + _simulatingMenuEvent = true; + [[NSApplication sharedApplication] sendEvent:[NSEvent keyEventWithType:NSEventTypeKeyDown + location:(NSPoint){0,} + modifierFlags:flags + timestamp:0 + windowNumber:0 + context:NULL + characters:keyEquivalent + charactersIgnoringModifiers:keyEquivalent + isARepeat:false + keyCode:0]]; + _simulatingMenuEvent = false; + } +} + +- (NSWindow *)keyWindow +{ + NSWindow *ret = [super keyWindow]; + if (!ret && _simulatingMenuEvent) { + ret = [(Document *)self.orderedDocuments.firstObject mainWindow]; + } + return ret; +} + +- (NSWindow *)mainWindow +{ + NSWindow *ret = [super mainWindow]; + if (!ret && _simulatingMenuEvent) { + ret = [(Document *)self.orderedDocuments.firstObject mainWindow]; + } + return ret; +} + +- (void)dealloc +{ + if (_downloadDirectory) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + } +} + +- (IBAction)nop:(id)sender +{ +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBAudioClient.h b/thirdparty/SameBoy-old/Cocoa/GBAudioClient.h new file mode 100644 index 000000000..03ed70114 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBAudioClient.h @@ -0,0 +1,12 @@ +#import +#import + +@interface GBAudioClient : NSObject +@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 + andSampleRate:(UInt32) rate; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBAudioClient.m b/thirdparty/SameBoy-old/Cocoa/GBAudioClient.m new file mode 100644 index 000000000..81a51fd56 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBAudioClient.m @@ -0,0 +1,111 @@ +#import +#import +#import "GBAudioClient.h" + +static OSStatus render( + GBAudioClient *self, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) + +{ + GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData; + + self.renderBlock(self.rate, inNumberFrames, buffer); + + return noErr; +} + +@implementation GBAudioClient +{ + AudioComponentInstance audioUnit; +} + +-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block + andSampleRate:(UInt32) rate +{ + if (!(self = [super init])) { + return nil; + } + + // Configure the search parameters to find the default playback output unit + // (called the kAudioUnitSubType_RemoteIO on iOS but + // kAudioUnitSubType_DefaultOutput on Mac OS X) + AudioComponentDescription defaultOutputDescription; + defaultOutputDescription.componentType = kAudioUnitType_Output; + defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput; + defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + defaultOutputDescription.componentFlags = 0; + defaultOutputDescription.componentFlagsMask = 0; + + // Get the default playback output unit + AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); + NSAssert(defaultOutput, @"Can't find default output"); + + // Create a new unit based on this that we'll use for output + OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit); + NSAssert1(audioUnit, @"Error creating unit: %hd", err); + + // Set our tone rendering function on the unit + AURenderCallbackStruct input; + input.inputProc = (void*)render; + input.inputProcRefCon = (__bridge void * _Nullable)(self); + err = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + NSAssert1(err == noErr, @"Error setting callback: %hd", err); + + AudioStreamBasicDescription streamFormat; + streamFormat.mSampleRate = rate; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = + kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; + streamFormat.mBytesPerPacket = 4; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = 4; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mBitsPerChannel = 2 * 8; + err = AudioUnitSetProperty (audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + NSAssert1(err == noErr, @"Error setting stream format: %hd", err); + err = AudioUnitInitialize(audioUnit); + NSAssert1(err == noErr, @"Error initializing unit: %hd", err); + + self.renderBlock = block; + _rate = rate; + + return self; +} + +-(void) start +{ + OSErr err = AudioOutputUnitStart(audioUnit); + NSAssert1(err == noErr, @"Error starting unit: %hd", err); + _playing = true; + +} + + +-(void) stop +{ + AudioOutputUnitStop(audioUnit); + _playing = false; +} + +-(void) dealloc +{ + [self stop]; + AudioUnitUninitialize(audioUnit); + AudioComponentInstanceDispose(audioUnit); +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBBorderView.h b/thirdparty/SameBoy-old/Cocoa/GBBorderView.h new file mode 100644 index 000000000..477add17e --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBBorderView.h @@ -0,0 +1,5 @@ +#import + +@interface GBBorderView : NSView + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBBorderView.m b/thirdparty/SameBoy-old/Cocoa/GBBorderView.m new file mode 100644 index 000000000..d992e298a --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBBorderView.m @@ -0,0 +1,26 @@ +#import "GBBorderView.h" + +@implementation GBBorderView + + +- (void)awakeFromNib +{ + self.wantsLayer = true; +} + +- (BOOL)wantsUpdateLayer +{ + return true; +} + +- (void)updateLayer +{ + /* Wonderful, wonderful windowserver(?) bug. Using 0,0,0 here would cause it to render garbage + on fullscreen windows on some High Sierra machines. Any other value, including the one used + here (which is rendered exactly the same due to rounding) works around this bug. */ + self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0 + green:0 + blue:1.0 / 1024.0 + alpha:1.0].CGColor; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBButtons.h b/thirdparty/SameBoy-old/Cocoa/GBButtons.h new file mode 100644 index 000000000..f5c76dab7 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBButtons.h @@ -0,0 +1,41 @@ +#ifndef GBButtons_h +#define GBButtons_h + +typedef enum : NSUInteger { + GBRight, + GBLeft, + GBUp, + GBDown, + GBA, + GBB, + GBSelect, + GBStart, + GBTurbo, + GBRewind, + GBUnderclock, + GBHotkey1, + GBHotkey2, + GBJoypadButtonCount, + GBButtonCount = GBUnderclock + 1, + GBGameBoyButtonCount = GBStart + 1, +} GBButton; + +#define GBJoyKitHotkey1 JOYButtonUsageGeneric0 + 0x100 +#define GBJoyKitHotkey2 JOYButtonUsageGeneric0 + 0x101 + +extern NSString const *GBButtonNames[GBJoypadButtonCount]; + +static inline NSString *n2s(uint64_t number) +{ + return [NSString stringWithFormat:@"%llx", number]; +} + +static inline NSString *button_to_preference_name(GBButton button, unsigned player) +{ + if (player) { + return [NSString stringWithFormat:@"GBPlayer%d%@", player + 1, GBButtonNames[button]]; + } + return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]]; +} + +#endif diff --git a/thirdparty/SameBoy-old/Cocoa/GBButtons.m b/thirdparty/SameBoy-old/Cocoa/GBButtons.m new file mode 100644 index 000000000..ef86738f0 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBButtons.m @@ -0,0 +1,4 @@ +#import +#import "GBButtons.h" + +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion", @"Hotkey 1", @"Hotkey 2"}; diff --git a/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.h b/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.h new file mode 100644 index 000000000..e7fd91779 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBCheatTextFieldCell : NSTextFieldCell +@property (nonatomic) bool usesAddressFormat; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.m b/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.m new file mode 100644 index 000000000..1fdafea04 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCheatTextFieldCell.m @@ -0,0 +1,121 @@ +#import "GBCheatTextFieldCell.h" + +@interface GBCheatTextView : NSTextView +@property bool usesAddressFormat; +@end + +@implementation GBCheatTextView + +- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range +{ + if (range.location == NSNotFound) { + range = self.selectedRange; + } + + NSString *new = [self.string stringByReplacingCharactersInRange:range withString:string]; + if (!self.usesAddressFormat) { + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,2}|[0-9]{1,3})$" options:0 error:NULL]; + if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) { + [super insertText:string replacementRange:range]; + return true; + } + if ([regex numberOfMatchesInString:[@"$" stringByAppendingString:new] options:0 range:NSMakeRange(0, new.length + 1)]) { + [super insertText:string replacementRange:range]; + [super insertText:@"$" replacementRange:NSMakeRange(0, 0)]; + return true; + } + if ([new isEqualToString:@"$"] || [string length] == 0) { + self.string = @"$00"; + self.selectedRange = NSMakeRange(1, 2); + return true; + } + } + else { + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\$[0-9A-Fa-f]{1,3}:)?\\$[0-9a-fA-F]{1,4}$" options:0 error:NULL]; + if ([regex numberOfMatchesInString:new options:0 range:NSMakeRange(0, new.length)]) { + [super insertText:string replacementRange:range]; + return true; + } + if ([string length] == 0) { + NSUInteger index = [new rangeOfString:@":"].location; + if (index != NSNotFound) { + if (range.location > index) { + self.string = [[new componentsSeparatedByString:@":"] firstObject]; + self.selectedRange = NSMakeRange(self.string.length, 0); + return true; + } + self.string = [[new componentsSeparatedByString:@":"] lastObject]; + self.selectedRange = NSMakeRange(0, 0); + return true; + } + else if ([[self.string substringWithRange:range] isEqualToString:@":"]) { + self.string = [[self.string componentsSeparatedByString:@":"] lastObject]; + self.selectedRange = NSMakeRange(0, 0); + return true; + } + } + if ([new isEqualToString:@"$"] || [string length] == 0) { + self.string = @"$0000"; + self.selectedRange = NSMakeRange(1, 4); + return true; + } + if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) { + if ([self _insertText:@"$00:" replacementRange:range]) { + self.selectedRange = NSMakeRange(1, 2); + return true; + } + } + if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) { + if ([self _insertText:@":$0" replacementRange:range]) { + self.selectedRange = NSMakeRange(self.string.length - 2, 2); + return true; + } + } + if ([string isEqualToString:@"$"]) { + if ([self _insertText:@"$0" replacementRange:range]) { + self.selectedRange = NSMakeRange(range.location + 1, 1); + return true; + } + } + } + return false; +} + +- (NSUndoManager *)undoManager +{ + return nil; +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + if (![self _insertText:string replacementRange:replacementRange]) { + NSBeep(); + } +} + +/* Private API, don't tell the police! */ +- (void)_userReplaceRange:(NSRange)range withString:(NSString *)string +{ + [self insertText:string replacementRange:range]; +} + +@end + +@implementation GBCheatTextFieldCell +{ + bool _drawing, _editing; + GBCheatTextView *_fieldEditor; +} + +- (NSTextView *)fieldEditorForView:(NSView *)controlView +{ + if (_fieldEditor) { + _fieldEditor.usesAddressFormat = self.usesAddressFormat; + return _fieldEditor; + } + _fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame]; + _fieldEditor.fieldEditor = true; + _fieldEditor.usesAddressFormat = self.usesAddressFormat; + return _fieldEditor; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.h b/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.h new file mode 100644 index 000000000..eddebc5ac --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.h @@ -0,0 +1,16 @@ +#import +#import +#import "Document.h" + +@interface GBCheatWindowController : NSObject +@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/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.m b/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.m new file mode 100644 index 000000000..5cc8f5959 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCheatWindowController.m @@ -0,0 +1,240 @@ +#import "GBCheatWindowController.h" +#import "GBWarningPopover.h" +#import "GBCheatTextFieldCell.h" + +@implementation GBCheatWindowController + ++ (NSString *)addressStringFromCheat:(const GB_cheat_t *)cheat +{ + if (cheat->bank != GB_CHEAT_ANY_BANK) { + return [NSString stringWithFormat:@"$%x:$%04x", cheat->bank, cheat->address]; + } + return [NSString stringWithFormat:@"$%04x", cheat->address]; +} + ++ (NSString *)actionDescriptionForCheat:(const GB_cheat_t *)cheat +{ + if (cheat->use_old_value) { + return [NSString stringWithFormat:@"[%@]($%02x) = $%02x", [self addressStringFromCheat:cheat], cheat->old_value, cheat->value]; + } + return [NSString stringWithFormat:@"[%@] = $%02x", [self addressStringFromCheat:cheat], cheat->value]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return 0; + size_t cheatCount; + GB_get_cheats(gb, &cheatCount); + return cheatCount + 1; +} + +- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return nil; + size_t cheatCount; + GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (row >= cheatCount && columnIndex == 0) { + return [[NSCell alloc] init]; + } + return nil; +} + +- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + size_t cheatCount; + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return nil; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (row >= cheatCount) { + switch (columnIndex) { + case 0: + return @YES; + + case 1: + return @NO; + + case 2: + return @"Add Cheat..."; + + case 3: + return @""; + } + } + + switch (columnIndex) { + case 0: + return @NO; + + case 1: + return @(cheats[row]->enabled); + + case 2: + return @(cheats[row]->description); + + case 3: + return [GBCheatWindowController actionDescriptionForCheat:cheats[row]]; + } + + return nil; +} + +- (IBAction)importCheat:(id)sender +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + [self.document performAtomicBlock:^{ + if (GB_import_cheat(gb, + self.importCodeField.stringValue.UTF8String, + self.importDescriptionField.stringValue.UTF8String, + true)) { + self.importCodeField.stringValue = @""; + self.importDescriptionField.stringValue = @""; + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; + } + else { + NSBeep(); + [GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField]; + } + }]; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + [self.document performAtomicBlock:^{ + if (columnIndex == 1) { + if (row >= cheatCount) { + GB_add_cheat(gb, "New Cheat", 0, 0, 0, 0, false, true); + } + else { + GB_update_cheat(gb, cheats[row], cheats[row]->description, cheats[row]->address, cheats[row]->bank, cheats[row]->value, cheats[row]->old_value, cheats[row]->use_old_value, !cheats[row]->enabled); + } + } + else if (row < cheatCount) { + GB_remove_cheat(gb, cheats[row]); + } + }]; + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + unsigned row = self.cheatsTable.selectedRow; + const GB_cheat_t *cheat = NULL; + if (row >= cheatCount) { + static const GB_cheat_t template = { + .address = 0, + .bank = 0, + .value = 0, + .old_value = 0, + .use_old_value = false, + .enabled = false, + .description = "New Cheat", + }; + cheat = &template; + } + else { + cheat = cheats[row]; + } + + self.addressField.stringValue = [GBCheatWindowController addressStringFromCheat:cheat]; + self.valueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->value]; + self.oldValueField.stringValue = [NSString stringWithFormat:@"$%02x", cheat->old_value]; + self.oldValueCheckbox.state = cheat->use_old_value; + self.descriptionField.stringValue = @(cheat->description); +} + +- (void)awakeFromNib +{ + [self tableViewSelectionDidChange:nil]; + ((GBCheatTextFieldCell *)self.addressField.cell).usesAddressFormat = true; +} + +- (void)controlTextDidChange:(NSNotification *)obj +{ + [self updateCheat:nil]; +} + +- (IBAction)updateCheat:(id)sender +{ + GB_gameboy_t *gb = self.document.gameboy; + if (!gb) return; + + uint16_t address = 0; + uint16_t bank = GB_CHEAT_ANY_BANK; + if ([self.addressField.stringValue rangeOfString:@":"].location != NSNotFound) { + sscanf(self.addressField.stringValue.UTF8String, "$%hx:$%hx", &bank, &address); + } + else { + sscanf(self.addressField.stringValue.UTF8String, "$%hx", &address); + } + + uint8_t value = 0; + if ([self.valueField.stringValue characterAtIndex:0] == '$') { + sscanf(self.valueField.stringValue.UTF8String, "$%02hhx", &value); + } + else { + sscanf(self.valueField.stringValue.UTF8String, "%hhd", &value); + } + + uint8_t oldValue = 0; + if ([self.oldValueField.stringValue characterAtIndex:0] == '$') { + sscanf(self.oldValueField.stringValue.UTF8String, "$%02hhx", &oldValue); + } + else { + sscanf(self.oldValueField.stringValue.UTF8String, "%hhd", &oldValue); + } + + size_t cheatCount; + const GB_cheat_t *const *cheats = GB_get_cheats(gb, &cheatCount); + unsigned row = self.cheatsTable.selectedRow; + + [self.document performAtomicBlock:^{ + if (row >= cheatCount) { + GB_add_cheat(gb, + self.descriptionField.stringValue.UTF8String, + address, + bank, + value, + oldValue, + self.oldValueCheckbox.state, + false); + } + else { + GB_update_cheat(gb, + cheats[row], + self.descriptionField.stringValue.UTF8String, + address, + bank, + value, + oldValue, + self.oldValueCheckbox.state, + cheats[row]->enabled); + } + }]; + [self.cheatsTable reloadData]; +} + +- (void)cheatsUpdated +{ + [self.cheatsTable reloadData]; + [self tableViewSelectionDidChange:nil]; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.h b/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.h new file mode 100644 index 000000000..24f3ba001 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.h @@ -0,0 +1,7 @@ +#import "Document.h" +#import "HexFiend/HexFiend.h" +#import "HexFiend/HFByteSlice.h" + +@interface GBCompleteByteSlice : HFByteSlice +- (instancetype) initWithByteArray:(HFByteArray *)array; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.m b/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.m new file mode 100644 index 000000000..44e7ee695 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBCompleteByteSlice.m @@ -0,0 +1,26 @@ +#import "GBCompleteByteSlice.h" + +@implementation GBCompleteByteSlice +{ + HFByteArray *_array; +} + +- (instancetype) initWithByteArray:(HFByteArray *)array +{ + if ((self = [super init])) { + _array = array; + } + return self; +} + +- (unsigned long long)length +{ + return [_array length]; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range +{ + [_array copyBytes:dst range:range]; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBGLShader.h b/thirdparty/SameBoy-old/Cocoa/GBGLShader.h new file mode 100644 index 000000000..8e46f93fc --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBGLShader.h @@ -0,0 +1,7 @@ +#import +#import "GBView.h" + +@interface GBGLShader : NSObject +- (instancetype)initWithName:(NSString *) shaderName; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode: (GB_frame_blending_mode_t)blendingMode; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBGLShader.m b/thirdparty/SameBoy-old/Cocoa/GBGLShader.m new file mode 100644 index 000000000..920226b6c --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBGLShader.m @@ -0,0 +1,190 @@ +#import "GBGLShader.h" +#import + +/* + Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial + + This code probably makes no sense after I upgraded it to OpenGL 3, since OpenGL makes aboslute no sense and has zero + helpful documentation. + */ + +static NSString * const vertex_shader = @"\n\ +#version 150 \n\ +in vec4 aPosition;\n\ +void main(void) {\n\ + gl_Position = aPosition;\n\ +}\n\ +"; + +@implementation GBGLShader +{ + GLuint resolution_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint frame_blending_mode_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} + ++ (NSString *) shaderSourceForName:(NSString *) name +{ + return [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name + ofType:@"fsh" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; +} + +- (instancetype)initWithName:(NSString *) shaderName +{ + self = [super init]; + if (self) { + // Program + NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"]; + fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}" + withString:[[self class] shaderSourceForName:shaderName]]; + program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader]; + // Attributes + position_attribute = glGetAttribLocation(program, "aPosition"); + // Uniforms + resolution_uniform = glGetUniformLocation(program, "output_resolution"); + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + texture_uniform = glGetUniformLocation(program, "image"); + + glGenTextures(1, &previous_texture); + glBindTexture(GL_TEXTURE_2D, previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + previous_texture_uniform = glGetUniformLocation(program, "previous_image"); + + frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode"); + + // Configure OpenGL + [self configureOpenGL]; + + } + return self; +} + +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode +{ + glUseProgram(program); + glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(texture_uniform, 0); + glUniform1i(frame_blending_mode_uniform, blendingMode); + if (blendingMode) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(previous_texture_uniform, 1); + } + glBindFragDataLocation(program, 0, "frag_color"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +- (void)configureOpenGL +{ + // Program + + glUseProgram(program); + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint vbo; + glGenBuffers(1, &vbo); + + // Attributes + + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, + }; + + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(position_attribute); + glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); +} + ++ (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh +{ + // Build shaders + GLuint vertex_shader = [self shaderWithContents:vsh type:GL_VERTEX_SHADER]; + GLuint fragment_shader = [self shaderWithContents:fsh type:GL_FRAGMENT_SHADER]; + // Create program + GLuint program = glCreateProgram(); + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + NSLog(@"%@:- GLSL Program Error: %s", self, messages); + } + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +- (void)dealloc +{ + glDeleteProgram(program); + glDeleteTextures(1, &texture); + glDeleteTextures(1, &previous_texture); + + /* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */ + /* We're probably not freeing thing in the right place. */ + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; +} + ++ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type +{ + + const GLchar *source = [contents UTF8String]; + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + NSLog(@"%@:- GLSL Shader Error: %s", self, messages); + } + return shader; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBHueSliderCell.h b/thirdparty/SameBoy-old/Cocoa/GBHueSliderCell.h new file mode 100644 index 000000000..f293124b5 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBHueSliderCell.h @@ -0,0 +1,9 @@ +#import + +@interface NSSlider (GBHueSlider) +-(NSColor *)colorValue; +@end + +@interface GBHueSliderCell : NSSliderCell +-(NSColor *)colorValue; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBHueSliderCell.m b/thirdparty/SameBoy-old/Cocoa/GBHueSliderCell.m new file mode 100644 index 000000000..9b397cb46 --- /dev/null +++ b/thirdparty/SameBoy-old/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; + break; + case 2: + g = 1; + b = t; + break; + case 3: + g = 1 - t; + b = 1; + break; + case 4: + b = 1; + r = t; + break; + case 5: + b = 1 - t; + r = 1; + break; + } + return [NSColor colorWithRed:r green:g blue:b alpha:1.0]; +} + +-(void)drawKnob:(NSRect)knobRect +{ + [super drawKnob:knobRect]; + NSRect peekRect = knobRect; + peekRect.size.width /= 2; + peekRect.size.height = peekRect.size.width; + peekRect.origin.x += peekRect.size.width / 2; + peekRect.origin.y += peekRect.size.height / 2; + NSColor *color = self.colorValue; + if (!self.enabled) { + color = [color colorWithAlphaComponent:0.5]; + } + [color setFill]; + NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect]; + [path fill]; + [[NSColor colorWithWhite:0 alpha:0.25] setStroke]; + [path setLineWidth:0.5]; + [path stroke]; +} + +-(double)_normalizedDoubleValue +{ + if (_drawingTrack) return 0; + return [super _normalizedDoubleValue]; +} + +-(void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped +{ + if (!self.enabled) { + [super drawBarInside:rect flipped:flipped]; + return; + } + + _drawingTrack = true; + [super drawBarInside:rect flipped:flipped]; + _drawingTrack = false; + + NSGradient *gradient = [[NSGradient alloc] initWithColors:@[ + [NSColor redColor], + [NSColor yellowColor], + [NSColor greenColor], + [NSColor cyanColor], + [NSColor blueColor], + [NSColor magentaColor], + [NSColor redColor], + ]]; + + rect.origin.y += rect.size.height / 2 - 0.5; + rect.size.height = 1; + rect.size.width -= 2; + rect.origin.x += 1; + [[NSColor redColor] set]; + NSRectFill(rect); + + rect.size.width -= self.knobThickness + 2; + rect.origin.x += self.knobThickness / 2 - 1; + + [gradient drawInRect:rect angle:0]; +} + +@end + +@implementation NSSlider (GBHueSlider) +- (NSColor *)colorValue +{ + return ((GBHueSliderCell *)self.cell).colorValue; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBImageView.h b/thirdparty/SameBoy-old/Cocoa/GBImageView.h new file mode 100644 index 000000000..c15c4e744 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBImageView.h @@ -0,0 +1,23 @@ +#import + +@protocol GBImageViewDelegate; + +@interface GBImageViewGridConfiguration : NSObject +@property (nonatomic, strong) NSColor *color; +@property (nonatomic) NSUInteger size; +- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size; +@end + +@interface GBImageView : NSImageView +@property (nonatomic, strong) NSArray *horizontalGrids; +@property (nonatomic, strong) NSArray *verticalGrids; +@property (nonatomic) bool displayScrollRect; +@property NSRect scrollRect; +@property (nonatomic, weak) IBOutlet id delegate; +@end + +@protocol GBImageViewDelegate +@optional +- (void) mouseDidLeaveImageView: (GBImageView *)view; +- (void) imageView: (GBImageView *)view mouseMovedToX:(NSUInteger) x Y:(NSUInteger) y; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBImageView.m b/thirdparty/SameBoy-old/Cocoa/GBImageView.m new file mode 100644 index 000000000..c496d9667 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBImageView.m @@ -0,0 +1,166 @@ +#import "GBImageView.h" + +@implementation GBImageViewGridConfiguration +- (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size +{ + self = [super init]; + self.color = color; + self.size = size; + return self; +} +@end + +@interface GBGridView : NSView +@end + +@implementation GBGridView + +- (void)drawRect:(NSRect)dirtyRect +{ + GBImageView *parent = (GBImageView *)self.superview; + + CGFloat y_ratio = parent.frame.size.height / parent.image.size.height; + CGFloat x_ratio = parent.frame.size.width / parent.image.size.width; + for (GBImageViewGridConfiguration *conf in parent.verticalGrids) { + [conf.color set]; + for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(0, y - 0.5)]; + [line lineToPoint:NSMakePoint(self.frame.size.width, y - 0.5)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } + + for (GBImageViewGridConfiguration *conf in parent.horizontalGrids) { + [conf.color set]; + for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(x + 0.5, 0)]; + [line lineToPoint:NSMakePoint(x + 0.5, self.frame.size.height)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } + + if (parent.displayScrollRect) { + NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite]; + for (unsigned x = 0; x < 2; x++) { + for (unsigned y = 0; y < 2; y++) { + NSRect rect = parent.scrollRect; + rect.origin.x *= x_ratio; + rect.origin.y *= y_ratio; + rect.size.width *= x_ratio; + rect.size.height *= y_ratio; + rect.origin.y = self.frame.size.height - rect.origin.y - rect.size.height; + + rect.origin.x -= self.frame.size.width * x; + rect.origin.y += self.frame.size.height * y; + + + NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect]; + [path appendBezierPath:subpath]; + } + } + [path setWindingRule:NSEvenOddWindingRule]; + [path setLineWidth:4.0]; + [path setLineJoinStyle:NSRoundLineJoinStyle]; + [[NSColor colorWithWhite:0.2 alpha:0.5] set]; + [path fill]; + [path addClip]; + [[NSColor colorWithWhite:0.0 alpha:0.6] set]; + [path stroke]; + } +} +@end + +@implementation GBImageView +{ + NSTrackingArea *_trackingArea; + GBGridView *_gridView; + NSRect _scrollRect; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + self.wantsLayer = true; + _gridView = [[GBGridView alloc] initWithFrame:self.bounds]; + _gridView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [self addSubview:_gridView]; + return self; +} + +-(void)viewWillDraw +{ + [super viewWillDraw]; + for (CALayer *layer in self.layer.sublayers) { + layer.magnificationFilter = kCAFilterNearest; + } +} + +- (void)setHorizontalGrids:(NSArray *)horizontalGrids +{ + self->_horizontalGrids = horizontalGrids; + [_gridView setNeedsDisplay:true]; +} + +- (void)setVerticalGrids:(NSArray *)verticalGrids +{ + self->_verticalGrids = verticalGrids; + [_gridView setNeedsDisplay:true]; +} + +- (void)setDisplayScrollRect:(bool)displayScrollRect +{ + self->_displayScrollRect = displayScrollRect; + [_gridView setNeedsDisplay:true]; +} + +- (void)updateTrackingAreas +{ + if (_trackingArea != nil) { + [self removeTrackingArea:_trackingArea]; + } + + _trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(mouseDidLeaveImageView:)]) { + [self.delegate mouseDidLeaveImageView:self]; + } +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(imageView:mouseMovedToX:Y:)]) { + NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil]; + location.x /= self.bounds.size.width; + location.y /= self.bounds.size.height; + location.y = 1 - location.y; + location.x *= self.image.size.width; + location.y *= self.image.size.height; + [self.delegate imageView:self mouseMovedToX:(NSUInteger)location.x Y:(NSUInteger)location.y]; + } +} + +- (void)setScrollRect:(NSRect)scrollRect +{ + if (memcmp(&scrollRect, &_scrollRect, sizeof(scrollRect)) != 0) { + _scrollRect = scrollRect; + [_gridView setNeedsDisplay:true]; + } +} + +- (NSRect)scrollRect +{ + return _scrollRect; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.h b/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.h new file mode 100644 index 000000000..17abe2054 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.h @@ -0,0 +1,17 @@ +#import "Document.h" +#import "HexFiend/HexFiend.h" +#import "HexFiend/HFByteArray.h" + +typedef enum { + GBMemoryEntireSpace, + GBMemoryROM, + GBMemoryVRAM, + GBMemoryExternalRAM, + GBMemoryRAM +} GB_memory_mode_t; + +@interface GBMemoryByteArray : HFByteArray +- (instancetype) initWithDocument:(Document *)document; +@property (nonatomic) uint16_t selectedBank; +@property (nonatomic) GB_memory_mode_t mode; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.m b/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.m new file mode 100644 index 000000000..55e1bd152 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBMemoryByteArray.m @@ -0,0 +1,256 @@ +#import "GBMemoryByteArray.h" +#import "GBCompleteByteSlice.h" + + +@implementation GBMemoryByteArray +{ + Document *_document; +} + +- (instancetype) initWithDocument:(Document *)document +{ + if ((self = [super init])) { + _document = document; + } + return self; +} + +- (unsigned long long)length +{ + switch (_mode) { + case GBMemoryEntireSpace: + return 0x10000; + case GBMemoryROM: + return 0x8000; + case GBMemoryVRAM: + return 0x2000; + case GBMemoryExternalRAM: + return 0x2000; + case GBMemoryRAM: + return 0x2000; + } +} + +- (uint16_t)base +{ + switch (_mode) { + case GBMemoryEntireSpace: return 0; + case GBMemoryROM: return 0; + case GBMemoryVRAM: return 0x8000; + case GBMemoryExternalRAM: return 0xA000; + case GBMemoryRAM: return 0xC000; + } +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range +{ + // Do everything in 0x1000 chunks, never cross a 0x1000 boundary + if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) { + size_t partial = 0x1000 - (range.location & 0xFFF); + [self copyBytes:dst + partial range:HFRangeMake(range.location + partial, range.length - partial)]; + range.length = partial; + } + range.location += self.base; + + GB_gameboy_t *gb = _document.gameboy; + + switch (range.location >> 12) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: { + uint16_t bank; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM0, NULL, &bank); + memcpy(dst, data + bank * 0x4000 + range.location, range.length); + break; + } + case 0x4: + case 0x5: + case 0x6: + case 0x7: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x4000 - 1); + } + memcpy(dst, data + bank * 0x4000 + range.location - 0x4000, range.length); + break; + } + case 0x8: + case 0x9: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x2000 - 1); + } + memcpy(dst, data + bank * 0x2000 + range.location - 0x8000, range.length); + break; + } + case 0xA: + case 0xB: { + // Some carts are special, use memory read directly in full mem mode + if (_mode == GBMemoryEntireSpace) { + case 0xF: + slow_path: + [_document performAtomicBlock:^{ + for (unsigned i = 0; i < range.length; i++) { + dst[i] = GB_safe_read_memory(gb, range.location + i); + } + }]; + break; + } + else { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + bank = self.selectedBank & (size / 0x2000 - 1); + if (size == 0) { + memset(dst, 0xFF, range.length); + } + else if (range.location + range.length - 0xA000 > size) { + goto slow_path; + } + else { + memcpy(dst, data + bank * 0x2000 + range.location - 0xA000, range.length); + } + break; + } + } + case 0xC: + case 0xE: { + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL); + memcpy(dst, data + (range.location & 0xFFF), range.length); + break; + } + + case 0xD: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x1000 - 1); + } + memcpy(dst, data + bank * 0x1000 + range.location - 0xD000, range.length); + break; + } + } +} + +- (NSArray *)byteSlices +{ + return @[[[GBCompleteByteSlice alloc] initWithByteArray:self]]; +} + +- (HFByteArray *)subarrayWithRange:(HFRange)range +{ + unsigned char arr[range.length]; + [self copyBytes:arr range:range]; + HFByteArray *ret = [[HFBTreeByteArray alloc] init]; + HFFullMemoryByteSlice *slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData dataWithBytes:arr length:range.length]]; + [ret insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + return ret; +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)range +{ + if (slice.length != range.length) return; /* Insertion is not allowed, only overwriting. */ + // Do everything in 0x1000 chunks, never cross a 0x1000 boundary + if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) { + size_t partial = 0x1000 - (range.location & 0xFFF); + if (slice.length - partial) { + [self insertByteSlice:[slice subsliceWithRange:HFRangeMake(partial, slice.length - partial)] + inRange:HFRangeMake(range.location + partial, range.length - partial)]; + } + range.length = partial; + } + range.location += self.base; + + GB_gameboy_t *gb = _document.gameboy; + + + switch (range.location >> 12) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: { + return; // ROM not writeable + } + case 0x8: + case 0x9: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x2000 - 1); + } + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + bank * 0x2000 + range.location - 0x8000, sliceData, range.length); + break; + } + case 0xA: + case 0xB: { + // Some carts are special, use memory write directly in full mem mode + if (_mode == GBMemoryEntireSpace) { + case 0xF: + slow_path: + [_document performAtomicBlock:^{ + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + for (unsigned i = 0; i < range.length; i++) { + GB_write_memory(gb, range.location + i, sliceData[i]); + } + }]; + break; + } + else { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + bank = self.selectedBank & (size / 0x2000 - 1); + if (size == 0) { + // Nothing to write to + } + else if (range.location + range.length - 0xA000 > size) { + goto slow_path; + } + else { + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + bank * 0x2000 + range.location - 0xA000, sliceData, range.length); + } + break; + } + } + case 0xC: + case 0xE: { + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL); + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + (range.location & 0xFFF), sliceData, range.length); + break; + } + + case 0xD: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x1000 - 1); + } + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + + memcpy(data + bank * 0x1000 + range.location - 0xD000, sliceData, range.length); + break; + } + } +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBOSDView.h b/thirdparty/SameBoy-old/Cocoa/GBOSDView.h new file mode 100644 index 000000000..4771d2fd6 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBOSDView.h @@ -0,0 +1,6 @@ +#import + +@interface GBOSDView : NSView +@property bool usesSGBScale; +- (void)displayText:(NSString *)text; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBOSDView.m b/thirdparty/SameBoy-old/Cocoa/GBOSDView.m new file mode 100644 index 000000000..710229ecd --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/Cocoa/GBObjectView.h b/thirdparty/SameBoy-old/Cocoa/GBObjectView.h new file mode 100644 index 000000000..2d1c955de --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBObjectView.h @@ -0,0 +1,6 @@ +#import +#import "Document.h" + +@interface GBObjectView : NSView +- (void)reloadData:(Document *)document; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBObjectView.m b/thirdparty/SameBoy-old/Cocoa/GBObjectView.m new file mode 100644 index 000000000..0fb22a158 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBObjectView.m @@ -0,0 +1,124 @@ +#import "GBObjectView.h" + +@interface GBObjectViewItem : NSObject +@property IBOutlet NSView *view; +@property IBOutlet NSImageView *image; +@property IBOutlet NSTextField *oamAddress; +@property IBOutlet NSTextField *position; +@property IBOutlet NSTextField *attributes; +@property IBOutlet NSTextField *tile; +@property IBOutlet NSTextField *tileAddress; +@property IBOutlet NSImageView *warningIcon; +@property IBOutlet NSBox *verticalLine; +@end + +@implementation GBObjectViewItem +{ + @public + uint32_t _lastImageData[128]; + uint8_t _lastHeight; +} +@end + +@implementation GBObjectView +{ + NSMutableArray *_items; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + _items = [NSMutableArray array]; + CGFloat height = self.frame.size.height; + for (unsigned i = 0; i < 40; i++) { + GBObjectViewItem *item = [[GBObjectViewItem alloc] init]; + [_items addObject:item]; + [[NSBundle mainBundle] loadNibNamed:@"GBObjectViewItem" owner:item topLevelObjects:nil]; + item.view.hidden = true; + [self addSubview:item.view]; + [item.view setFrameOrigin:NSMakePoint((i % 4) * 120, height - (i / 4 * 68) - 68)]; + item.oamAddress.toolTip = @"OAM address"; + item.position.toolTip = @"Position"; + item.attributes.toolTip = @"Attributes"; + item.tile.toolTip = @"Tile index"; + item.tileAddress.toolTip = @"Tile address"; + item.warningIcon.toolTip = @"Dropped: too many objects in line"; + if ((i % 4) == 3) { + [item.verticalLine removeFromSuperview]; + } + item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin; + } + return self; +} + +- (void)reloadData:(Document *)document +{ + GB_oam_info_t *info = document.oamInfo; + uint8_t length = document.oamCount; + bool cgb = GB_is_cgb(document.gb); + uint8_t height = document.oamHeight; + for (unsigned i = 0; i < 40; i++) { + GBObjectViewItem *item = _items[i]; + if (i >= length) { + item.view.hidden = true; + } + else { + item.view.hidden = false; + item.oamAddress.stringValue = [NSString stringWithFormat:@"$%04X", info[i].oam_addr]; + item.position.stringValue = [NSString stringWithFormat:@"(%d, %d)", + ((signed)(unsigned)info[i].x) - 8, + ((signed)(unsigned)info[i].y) - 16]; + item.tile.stringValue = [NSString stringWithFormat:@"$%02X", info[i].tile]; + item.tileAddress.stringValue = [NSString stringWithFormat:@"$%04X", 0x8000 + info[i].tile * 0x10]; + item.warningIcon.hidden = !info[i].obscured_by_line_limit; + if (cgb) { + item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d%d", + info[i].flags & 0x80? 'P' : '-', + info[i].flags & 0x40? 'Y' : '-', + info[i].flags & 0x20? 'X' : '-', + info[i].flags & 0x08? 1 : 0, + info[i].flags & 0x07]; + } + else { + item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d", + info[i].flags & 0x80? 'P' : '-', + info[i].flags & 0x40? 'Y' : '-', + info[i].flags & 0x20? 'X' : '-', + info[i].flags & 0x10? 1 : 0]; + } + size_t imageSize = 8 * 4 * height; + if (height == item->_lastHeight && memcmp(item->_lastImageData, info[i].image, imageSize) == 0) { + continue; + } + memcpy(item->_lastImageData, info[i].image, imageSize); + item->_lastHeight = height; + item.image.image = [Document imageFromData:[NSData dataWithBytesNoCopy:info[i].image + length:64 * 4 * 2 + freeWhenDone:false] + width:8 + height:height + scale:32.0 / height]; + } + } + + NSRect frame = self.frame; + CGFloat newHeight = MAX(68 * ((length + 3) / 4), self.superview.frame.size.height); + frame.origin.y -= newHeight - frame.size.height; + frame.size.height = newHeight; + self.frame = frame; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].lastObject setFill]; + } + else { + [[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill]; + } + NSRect frame = self.frame; + for (unsigned i = 1; i <= 5; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 68 * 2, frame.size.width, 68)); + } +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBObjectViewItem.xib b/thirdparty/SameBoy-old/Cocoa/GBObjectViewItem.xib new file mode 100644 index 000000000..85b37820e --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBObjectViewItem.xib @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.h b/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.h new file mode 100644 index 000000000..66001c9d7 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.h @@ -0,0 +1,6 @@ +#import +#import "GBGLShader.h" + +@interface GBOpenGLView : NSOpenGLView +@property (nonatomic) GBGLShader *shader; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.m b/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.m new file mode 100644 index 000000000..2e4eb70f6 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBOpenGLView.m @@ -0,0 +1,39 @@ +#import "GBOpenGLView.h" +#import "GBView.h" +#include + +@implementation GBOpenGLView + +- (void)drawRect:(NSRect)dirtyRect +{ + if (!self.shader) { + self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; + } + + GBView *gbview = (GBView *)self.superview; + double scale = self.window.backingScaleFactor; + glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); + + if (gbview.gb) { + [self.shader renderBitmap:gbview.currentBuffer + previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL + sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) + inSize:self.bounds.size + scale:scale + withBlendingMode:gbview.frameBlendingMode]; + } + glFlush(); +} + +- (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; + return [super initWithFrame:frameRect pixelFormat:format]; +} + +- (void) filterChanged +{ + self.shader = nil; + [self setNeedsDisplay:true]; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.h b/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.h new file mode 100644 index 000000000..135507157 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.h @@ -0,0 +1,6 @@ +#import + +/* Fake interface so the compiler assumes it conforms to NSVisualEffectView */ +@interface GBOptionalVisualEffectView : NSVisualEffectView + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.m b/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.m new file mode 100644 index 000000000..c28eb5957 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBOptionalVisualEffectView.m @@ -0,0 +1,18 @@ +#import + +@interface GBOptionalVisualEffectView : NSView + +@end + +@implementation GBOptionalVisualEffectView + ++ (instancetype)allocWithZone:(struct _NSZone *)zone +{ + Class NSVisualEffectView = NSClassFromString(@"NSVisualEffectView"); + if (NSVisualEffectView) { + return (id)[NSVisualEffectView alloc]; + } + return [super allocWithZone:zone]; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBPaletteEditorController.h b/thirdparty/SameBoy-old/Cocoa/GBPaletteEditorController.h new file mode 100644 index 000000000..fd362ec14 --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/Cocoa/GBPaletteEditorController.m b/thirdparty/SameBoy-old/Cocoa/GBPaletteEditorController.m new file mode 100644 index 000000000..0a613fba4 --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/Cocoa/GBPaletteView.h b/thirdparty/SameBoy-old/Cocoa/GBPaletteView.h new file mode 100644 index 000000000..d92cb5f17 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBPaletteView.h @@ -0,0 +1,6 @@ +#import +#import "Document.h" + +@interface GBPaletteView : NSView +- (void)reloadData:(Document *)document; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBPaletteView.m b/thirdparty/SameBoy-old/Cocoa/GBPaletteView.m new file mode 100644 index 000000000..6aeddc05c --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBPaletteView.m @@ -0,0 +1,91 @@ +#import "GBPaletteView.h" + +@interface GBPaletteViewItem : NSObject +@property IBOutlet NSView *view; +@property (strong) IBOutlet NSTextField *label; +@property (strong) IBOutlet NSTextField *color0; +@property (strong) IBOutlet NSTextField *color1; +@property (strong) IBOutlet NSTextField *color2; +@property (strong) IBOutlet NSTextField *color3; +@end + +@implementation GBPaletteViewItem +@end + +@implementation GBPaletteView +{ + NSMutableArray *_colors; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + _colors = [NSMutableArray array]; + CGFloat height = self.frame.size.height; + for (unsigned i = 0; i < 16; i++) { + GBPaletteViewItem *item = [[GBPaletteViewItem alloc] init]; + [[NSBundle mainBundle] loadNibNamed:@"GBPaletteViewRow" owner:item topLevelObjects:nil]; + [self addSubview:item.view]; + [item.view setFrameOrigin:NSMakePoint(0, height - (i * 24) - 24)]; + item.label.stringValue = [NSString stringWithFormat:@"%@ %d", i < 8? @"Background" : @"Object", i % 8]; + item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin; + [_colors addObject:item.color0]; + [_colors addObject:item.color1]; + [_colors addObject:item.color2]; + [_colors addObject:item.color3]; + + } + return self; +} + +- (void)reloadData:(Document *)document +{ + GB_gameboy_t *gb = document.gb; + uint8_t *bg = GB_get_direct_access(gb, GB_DIRECT_ACCESS_BGP, NULL, NULL); + uint8_t *obj = GB_get_direct_access(gb, GB_DIRECT_ACCESS_OBP, NULL, NULL); + + for (unsigned i = 0; i < 4 * 8 * 2; i++) { + uint8_t index = i % (4 * 8); + uint8_t *palette = i >= 4 * 8 ? obj : bg; + uint16_t color = (palette[(index << 1) + 1] << 8) | palette[(index << 1)]; + uint32_t nativeColor = GB_convert_rgb15(gb, color, false); + + uint8_t r = color & 0x1F, + g = (color >> 5) & 0x1F, + b = (color >> 10) & 0x1F; + + NSTextField *field = _colors[i]; + field.stringValue = [NSString stringWithFormat:@"$%04X", color]; + field.textColor = r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor]; + field.toolTip = [NSString stringWithFormat:@"Red: %d, Green: %d, Blue: %d", r, g, b]; + field.backgroundColor = [NSColor colorWithRed:(nativeColor & 0xFF) / 255.0 + green:((nativeColor >> 8) & 0xFF) / 255.0 + blue:((nativeColor >> 16) & 0xFF) / 255.0 + alpha:1.0]; + } +} + +- (void)drawRect:(NSRect)dirtyRect +{ + NSRect frame = self.frame; + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].lastObject setFill]; + } + else { + [[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill]; + } + for (unsigned i = 1; i <= 8; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2, frame.size.width, 24)); + } + + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].firstObject setFill]; + } + else { + [[NSColor controlBackgroundColor] setFill]; + } + for (unsigned i = 0; i < 8; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2 - 24, frame.size.width, 24)); + } +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBPaletteViewRow.xib b/thirdparty/SameBoy-old/Cocoa/GBPaletteViewRow.xib new file mode 100644 index 000000000..950b199a7 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBPaletteViewRow.xib @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.h b/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.h new file mode 100644 index 000000000..8e11830eb --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.h @@ -0,0 +1,41 @@ +#import +#import +#import "GBPaletteEditorController.h" + +@interface GBPreferencesWindow : NSWindow +@property IBOutlet NSTableView *controlsTableView; +@property IBOutlet NSPopUpButton *graphicsFilterPopupButton; +@property IBOutlet NSButton *analogControlsCheckbox; +@property IBOutlet NSButton *controllersFocusCheckbox; +@property IBOutlet NSButton *aspectRatioCheckbox; +@property IBOutlet NSPopUpButton *highpassFilterPopupButton; +@property IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property IBOutlet NSPopUpButton *frameBlendingModePopupButton; +@property IBOutlet NSPopUpButton *colorPalettePopupButton; +@property IBOutlet NSPopUpButton *displayBorderPopupButton; +@property IBOutlet NSPopUpButton *rewindPopupButton; +@property IBOutlet NSPopUpButton *rtcPopupButton; +@property IBOutlet NSButton *configureJoypadButton; +@property IBOutlet NSButton *skipButton; +@property IBOutlet NSMenuItem *bootROMsFolderItem; +@property IBOutlet NSPopUpButtonCell *bootROMsButton; +@property IBOutlet NSPopUpButton *rumbleModePopupButton; +@property IBOutlet NSPopUpButton *hotkey1PopupButton; +@property IBOutlet NSPopUpButton *hotkey2PopupButton; +@property IBOutlet NSSlider *temperatureSlider; +@property IBOutlet NSSlider *interferenceSlider; +@property IBOutlet NSPopUpButton *dmgPopupButton; +@property IBOutlet NSPopUpButton *sgbPopupButton; +@property IBOutlet NSPopUpButton *cgbPopupButton; +@property IBOutlet NSPopUpButton *agbPopupButton; +@property IBOutlet NSPopUpButton *preferredJoypadButton; +@property IBOutlet NSPopUpButton *playerListButton; +@property IBOutlet NSButton *autoUpdatesCheckbox; +@property IBOutlet NSSlider *volumeSlider; +@property IBOutlet NSButton *OSDCheckbox; +@property IBOutlet NSButton *screenshotFilterCheckbox; +@property IBOutlet GBPaletteEditorController *paletteEditorController; +@property IBOutlet NSWindow *paletteEditor; +@property IBOutlet NSButton *joystickMBC7Checkbox; +@property IBOutlet NSButton *mouseMBC7Checkbox; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.m b/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.m new file mode 100644 index 000000000..a9c2c913a --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBPreferencesWindow.m @@ -0,0 +1,1002 @@ +#import "GBPreferencesWindow.h" +#import "NSString+StringForKey.h" +#import "GBButtons.h" +#import "BigSurToolbar.h" +#import "GBViewMetal.h" +#import "GBWarningPopover.h" +#import + +@implementation GBPreferencesWindow +{ + bool is_button_being_modified; + NSInteger button_being_modified; + signed joystick_configuration_state; + NSString *joystick_being_configured; + bool joypad_wait; + + NSPopUpButton *_graphicsFilterPopupButton; + NSPopUpButton *_highpassFilterPopupButton; + NSPopUpButton *_colorCorrectionPopupButton; + NSPopUpButton *_frameBlendingModePopupButton; + NSPopUpButton *_colorPalettePopupButton; + NSPopUpButton *_displayBorderPopupButton; + NSPopUpButton *_rewindPopupButton; + NSPopUpButton *_rtcPopupButton; + NSButton *_aspectRatioCheckbox; + NSButton *_analogControlsCheckbox; + NSButton *_controllersFocusCheckbox; + NSEventModifierFlags previousModifiers; + + NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton, *_agbPopupButton; + NSPopUpButton *_preferredJoypadButton; + NSPopUpButton *_rumbleModePopupButton; + NSPopUpButton *_hotkey1PopupButton; + NSPopUpButton *_hotkey2PopupButton; + NSSlider *_temperatureSlider; + NSSlider *_interferenceSlider; + NSSlider *_volumeSlider; + NSButton *_autoUpdatesCheckbox; + NSButton *_OSDCheckbox; + NSButton *_screenshotFilterCheckbox; + NSButton *_joystickMBC7Checkbox; + NSButton *_mouseMBC7Checkbox; +} + ++ (NSArray *)filterList +{ + /* The filter list as ordered in the popup button */ + static NSArray * filters = nil; + if (!filters) { + filters = @[ + @"NearestNeighbor", + @"Bilinear", + @"SmoothBilinear", + @"MonoLCD", + @"LCD", + @"CRT", + @"Scale2x", + @"Scale4x", + @"AAScale2x", + @"AAScale4x", + @"HQ2x", + @"OmniScale", + @"OmniScaleLegacy", + @"AAOmniScaleLegacy", + ]; + } + return filters; +} + +- (NSWindowToolbarStyle)toolbarStyle +{ + return NSWindowToolbarStyleExpanded; +} + +- (void)close +{ + joystick_configuration_state = -1; + [self.configureJoypadButton setEnabled:true]; + [self.skipButton setEnabled:false]; + [self.configureJoypadButton setTitle:@"Configure Controller"]; + [super close]; +} + +- (NSPopUpButton *)graphicsFilterPopupButton +{ + return _graphicsFilterPopupButton; +} + +- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton +{ + _graphicsFilterPopupButton = graphicsFilterPopupButton; + NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]; + [_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]]; +} + +- (NSPopUpButton *)highpassFilterPopupButton +{ + return _highpassFilterPopupButton; +} + +- (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButton +{ + _colorCorrectionPopupButton = colorCorrectionPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]; + [_colorCorrectionPopupButton selectItemWithTag:mode]; +} + + +- (NSPopUpButton *)colorCorrectionPopupButton +{ + return _colorCorrectionPopupButton; +} + +- (void)setTemperatureSlider:(NSSlider *)temperatureSlider +{ + _temperatureSlider = temperatureSlider; + [temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256]; +} + +- (NSSlider *)temperatureSlider +{ + return _temperatureSlider; +} + +- (void)setInterferenceSlider:(NSSlider *)interferenceSlider +{ + _interferenceSlider = interferenceSlider; + [interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256]; +} + +- (NSSlider *)interferenceSlider +{ + return _interferenceSlider; +} + +- (void)setVolumeSlider:(NSSlider *)volumeSlider +{ + _volumeSlider = volumeSlider; + [volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256]; +} + +- (NSSlider *)volumeSlider +{ + return _volumeSlider; +} + +- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton +{ + _frameBlendingModePopupButton = frameBlendingModePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; + [_frameBlendingModePopupButton selectItemAtIndex:mode]; +} + +- (NSPopUpButton *)frameBlendingModePopupButton +{ + return _frameBlendingModePopupButton; +} + +- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton +{ + _colorPalettePopupButton = colorPalettePopupButton; + [self updatePalettesMenu]; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]; + if (mode >= 0) { + [_colorPalettePopupButton selectItemWithTag:mode]; + } + else { + [_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""]; + } +} + +- (NSPopUpButton *)colorPalettePopupButton +{ + return _colorPalettePopupButton; +} + +- (void)setDisplayBorderPopupButton:(NSPopUpButton *)displayBorderPopupButton +{ + _displayBorderPopupButton = displayBorderPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]; + [_displayBorderPopupButton selectItemWithTag:mode]; +} + +- (NSPopUpButton *)displayBorderPopupButton +{ + return _displayBorderPopupButton; +} + +- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton +{ + _rumbleModePopupButton = rumbleModePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]; + [_rumbleModePopupButton selectItemWithTag:mode]; +} + +- (NSPopUpButton *)rumbleModePopupButton +{ + return _rumbleModePopupButton; +} + +static inline NSString *keyEquivalentString(NSMenuItem *item) +{ + return [NSString stringWithFormat:@"%s%@", (item.keyEquivalentModifierMask & NSEventModifierFlagShift)? "^":"", item.keyEquivalent]; +} + +- (void)setHotkey1PopupButton:(NSPopUpButton *)hotkey1PopupButton +{ + _hotkey1PopupButton = hotkey1PopupButton; + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey1"]; + for (NSMenuItem *item in _hotkey1PopupButton.menu.itemArray) { + if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) { + [_hotkey1PopupButton selectItem:item]; + break; + } + } +} + +- (NSPopUpButton *)hotkey1PopupButton +{ + return _hotkey1PopupButton; +} + +- (void)setHotkey2PopupButton:(NSPopUpButton *)hotkey2PopupButton +{ + _hotkey2PopupButton = hotkey2PopupButton; + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey2"]; + for (NSMenuItem *item in _hotkey2PopupButton.menu.itemArray) { + if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) { + [_hotkey2PopupButton selectItem:item]; + break; + } + } +} + +- (NSPopUpButton *)hotkey2PopupButton +{ + return _hotkey2PopupButton; +} + +- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton +{ + _rewindPopupButton = rewindPopupButton; + NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]; + [_rewindPopupButton selectItemWithTag:length]; +} + +- (NSPopUpButton *)rewindPopupButton +{ + return _rewindPopupButton; +} + +- (NSPopUpButton *)rtcPopupButton +{ + return _rtcPopupButton; +} + +- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton +{ + _rtcPopupButton = rtcPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]; + [_rtcPopupButton selectItemAtIndex:mode]; +} + +- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton +{ + _highpassFilterPopupButton = highpassFilterPopupButton; + [_highpassFilterPopupButton selectItemAtIndex:[[[NSUserDefaults standardUserDefaults] objectForKey:@"GBHighpassFilter"] unsignedIntegerValue]]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + if (self.playerListButton.selectedTag == 0) { + return GBButtonCount; + } + return GBGameBoyButtonCount; +} + +- (unsigned) usesForKey:(unsigned) key +{ + unsigned ret = 0; + for (unsigned player = 4; player--;) { + for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) { + NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)]; + if (other && [other unsignedIntValue] == key) { + ret++; + } + } + } + return ret; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + if ([tableColumn.identifier isEqualToString:@"keyName"]) { + return GBButtonNames[row]; + } + + if (is_button_being_modified && button_being_modified == row) { + return @"Select a new key..."; + } + + NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)]; + if (key) { + if ([self usesForKey:[key unsignedIntValue]] > 1) { + return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]] + attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0], + NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]] + }]; + } + return [NSString displayStringForKeyCode: [key unsignedIntegerValue]]; + } + + return @""; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + + dispatch_async(dispatch_get_main_queue(), ^{ + is_button_being_modified = true; + button_being_modified = row; + tableView.enabled = false; + self.playerListButton.enabled = false; + [tableView reloadData]; + [self makeFirstResponder:self]; + }); + return false; +} + +-(void)keyDown:(NSEvent *)theEvent +{ + if (!is_button_being_modified) { + if (self.firstResponder != self.controlsTableView && [theEvent type] != NSEventTypeFlagsChanged) { + [super keyDown:theEvent]; + } + return; + } + + is_button_being_modified = false; + + [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode + forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)]; + self.controlsTableView.enabled = true; + self.playerListButton.enabled = true; + [self.controlsTableView reloadData]; + [self makeFirstResponder:self.controlsTableView]; +} + +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + + previousModifiers = event.modifierFlags; +} + +- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]] + forKey:@"GBFilter"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; +} + +- (IBAction)highpassFilterChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBHighpassFilter"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; +} + + +- (IBAction)changeMBC7JoystickOverride:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7JoystickOverride"]; +} + +- (IBAction)changeMBC7AllowMouse:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7AllowMouse"]; +} + +- (IBAction)changeAnalogControls:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAnalogControls"]; +} + +- (IBAction)changeControllerFocus:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAllowBackgroundControllers"]; +} + +- (IBAction)changeAspectRatio:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState + forKey:@"GBAspectRatioUnkept"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil]; +} + +- (IBAction)colorCorrectionChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) + forKey:@"GBColorCorrection"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; +} + +- (IBAction)lightTemperatureChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBLightTemperature"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil]; +} + +- (IBAction)interferenceVolumeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBInterferenceVolume"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil]; +} + +- (IBAction)volumeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBVolume"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBVolumeChanged" object:nil]; +} + +- (IBAction)franeBlendingModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBFrameBlendingMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil]; + +} + +- (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 +{ + 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]; +} + +- (IBAction)displayBorderChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) + forKey:@"GBBorderMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; +} + +- (IBAction)rumbleModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) + forKey:@"GBRumbleMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil]; +} + +- (IBAction)hotkey1Changed:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem]) + forKey:@"GBJoypadHotkey1"]; +} + +- (IBAction)hotkey2Changed:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem]) + forKey:@"GBJoypadHotkey2"]; +} + +- (IBAction)rewindLengthChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBRewindLength"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; +} + +- (IBAction)rtcModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBRTCMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil]; + +} + +- (IBAction)changeAutoUpdates:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAutoUpdatesEnabled"]; +} + +- (IBAction) configureJoypad:(id)sender +{ + [self.configureJoypadButton setEnabled:false]; + [self.skipButton setEnabled:true]; + joystick_being_configured = nil; + [self advanceConfigurationStateMachine]; +} + +- (IBAction) skipButton:(id)sender +{ + [self advanceConfigurationStateMachine]; +} + +- (void) advanceConfigurationStateMachine +{ + joystick_configuration_state++; + if (joystick_configuration_state == GBUnderclock) { + [self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :< + } + else if (joystick_configuration_state < GBJoypadButtonCount) { + [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]]; + } + else { + joystick_configuration_state = -1; + [self.configureJoypadButton setEnabled:true]; + [self.skipButton setEnabled:false]; + [self.configureJoypadButton setTitle:@"Configure Joypad"]; + } +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + /* Debounce */ + if (joypad_wait) return; + joypad_wait = true; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + joypad_wait = false; + }); + + if (!button.isPressed) return; + if (joystick_configuration_state == -1) return; + if (joystick_configuration_state == GBJoypadButtonCount) return; + if (!joystick_being_configured) { + joystick_being_configured = controller.uniqueID; + } + else if (![joystick_being_configured isEqualToString:controller.uniqueID]) { + return; + } + + NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy]; + + NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy]; + + + if (!instance_mappings) { + instance_mappings = [[NSMutableDictionary alloc] init]; + } + + if (!name_mappings) { + name_mappings = [[NSMutableDictionary alloc] init]; + } + + NSMutableDictionary *mapping = nil; + if (joystick_configuration_state != 0) { + mapping = [instance_mappings[controller.uniqueID] mutableCopy]; + } + else { + mapping = [[NSMutableDictionary alloc] init]; + } + + + static const unsigned gb_to_joykit[] = { + [GBRight] = JOYButtonUsageDPadRight, + [GBLeft] = JOYButtonUsageDPadLeft, + [GBUp] = JOYButtonUsageDPadUp, + [GBDown] = JOYButtonUsageDPadDown, + [GBA] = JOYButtonUsageA, + [GBB] = JOYButtonUsageB, + [GBSelect] = JOYButtonUsageSelect, + [GBStart] = JOYButtonUsageStart, + [GBTurbo] = JOYButtonUsageL1, + [GBRewind] = JOYButtonUsageL2, + [GBUnderclock] = JOYButtonUsageR1, + [GBHotkey1] = GBJoyKitHotkey1, + [GBHotkey2] = GBJoyKitHotkey2, + }; + + if (joystick_configuration_state == GBUnderclock) { + mapping[@"AnalogUnderclock"] = nil; + double max = 0; + for (JOYAxis *axis in controller.axes) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { + mapping[@"AnalogUnderclock"] = @(axis.uniqueID); + break; + } + } + } + + if (joystick_configuration_state == GBTurbo) { + mapping[@"AnalogTurbo"] = nil; + double max = 0; + for (JOYAxis *axis in controller.axes) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { + max = axis.value; + mapping[@"AnalogTurbo"] = @(axis.uniqueID); + } + } + } + + mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]); + + instance_mappings[controller.uniqueID] = mapping; + name_mappings[controller.deviceName] = mapping; + [[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"]; + [[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"]; + [self advanceConfigurationStateMachine]; +} + +- (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; +} + +- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox +{ + _analogControlsCheckbox = analogControlsCheckbox; + [_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]]; +} + +- (NSButton *)controllersFocusCheckbox +{ + return _controllersFocusCheckbox; +} + +- (void)setControllersFocusCheckbox:(NSButton *)controllersFocusCheckbox +{ + _controllersFocusCheckbox = controllersFocusCheckbox; + [_controllersFocusCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]]; +} + +- (NSButton *)aspectRatioCheckbox +{ + return _aspectRatioCheckbox; +} + +- (void)setAspectRatioCheckbox:(NSButton *)aspectRatioCheckbox +{ + _aspectRatioCheckbox = aspectRatioCheckbox; + [_aspectRatioCheckbox setState: ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]]; +} + +- (void)awakeFromNib +{ + [super awakeFromNib]; + [self updateBootROMFolderButton]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil]; + [JOYController registerListener:self]; + joystick_configuration_state = -1; +} + +- (void)dealloc +{ + [JOYController unregisterListener:self]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView]; +} + +- (IBAction)selectOtherBootROMFolder:(id)sender +{ + NSOpenPanel *panel = [[NSOpenPanel alloc] init]; + [panel setCanChooseDirectories:true]; + [panel setCanChooseFiles:false]; + [panel setPrompt:@"Select"]; + [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]]; + [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) { + if (result == NSModalResponseOK) { + NSURL *url = [[panel URLs] firstObject]; + [[NSUserDefaults standardUserDefaults] setURL:url forKey:@"GBBootROMsFolder"]; + } + [self updateBootROMFolderButton]; + }]; + +} + +- (void) updateBootROMFolderButton +{ + NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]; + BOOL is_dir = false; + [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&is_dir]; + if (!is_dir) url = nil; + + if (url) { + [self.bootROMsFolderItem setTitle:[url lastPathComponent]]; + NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]]; + [icon setSize:NSMakeSize(16, 16)]; + [self.bootROMsFolderItem setHidden:false]; + [self.bootROMsFolderItem setImage:icon]; + [self.bootROMsButton selectItemAtIndex:1]; + } + else { + [self.bootROMsFolderItem setHidden:true]; + [self.bootROMsButton selectItemAtIndex:0]; + } +} + +- (IBAction)useBuiltinBootROMs:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setURL:nil forKey:@"GBBootROMsFolder"]; + [self updateBootROMFolderButton]; +} + +- (void)setDmgPopupButton:(NSPopUpButton *)dmgPopupButton +{ + _dmgPopupButton = dmgPopupButton; + [_dmgPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]]; +} + +- (NSPopUpButton *)dmgPopupButton +{ + return _dmgPopupButton; +} + +- (void)setSgbPopupButton:(NSPopUpButton *)sgbPopupButton +{ + _sgbPopupButton = sgbPopupButton; + [_sgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]]; +} + +- (NSPopUpButton *)sgbPopupButton +{ + return _sgbPopupButton; +} + +- (void)setCgbPopupButton:(NSPopUpButton *)cgbPopupButton +{ + _cgbPopupButton = cgbPopupButton; + [_cgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]]; +} + +- (NSPopUpButton *)cgbPopupButton +{ + return _cgbPopupButton; +} + +- (void)setAgbPopupButton:(NSPopUpButton *)agbPopupButton +{ + _agbPopupButton = agbPopupButton; + [_agbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBAGBModel"]]; +} + +- (NSPopUpButton *)agbPopupButton +{ + return _agbPopupButton; +} + +- (IBAction)dmgModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBDMGModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil]; + +} + +- (IBAction)sgbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBSGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil]; +} + +- (IBAction)cgbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBCGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil]; +} + +- (IBAction)agbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBAGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAGBModelChanged" object:nil]; +} + +- (IBAction)reloadButtonsData:(id)sender +{ + [self.controlsTableView reloadData]; +} + +- (void)setPreferredJoypadButton:(NSPopUpButton *)preferredJoypadButton +{ + _preferredJoypadButton = preferredJoypadButton; + [self refreshJoypadMenu:nil]; +} + +- (NSPopUpButton *)preferredJoypadButton +{ + return _preferredJoypadButton; +} + +- (void)controllerConnected:(JOYController *)controller +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self refreshJoypadMenu:nil]; + }); +} + +- (void)controllerDisconnected:(JOYController *)controller +{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self refreshJoypadMenu:nil]; + }); +} + +- (IBAction)refreshJoypadMenu:(id)sender +{ + bool preferred_is_connected = false; + NSString *player_string = n2s(self.playerListButton.selectedTag); + NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string]; + + [self.preferredJoypadButton removeAllItems]; + [self.preferredJoypadButton addItemWithTitle:@"None"]; + for (JOYController *controller in [JOYController allControllers]) { + [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]]; + + self.preferredJoypadButton.lastItem.identifier = controller.uniqueID; + + if ([controller.uniqueID isEqualToString:selected_controller]) { + preferred_is_connected = true; + [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem]; + } + } + + if (!preferred_is_connected && selected_controller) { + [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]]; + self.preferredJoypadButton.lastItem.identifier = selected_controller; + [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem]; + } + + + if (!selected_controller) { + [self.preferredJoypadButton selectItemWithTitle:@"None"]; + } + [self.controlsTableView reloadData]; +} + +- (IBAction)changeDefaultJoypad:(id)sender +{ + NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy]; + if (!default_joypads) { + default_joypads = [[NSMutableDictionary alloc] init]; + } + + NSString *player_string = n2s(self.playerListButton.selectedTag); + if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) { + [default_joypads removeObjectForKey:player_string]; + } + else { + default_joypads[player_string] = [[sender selectedItem] identifier]; + } + [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"]; +} + +- (NSButton *)autoUpdatesCheckbox +{ + return _autoUpdatesCheckbox; +} + +- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox +{ + _autoUpdatesCheckbox = autoUpdatesCheckbox; + [_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]]; +} + +- (NSButton *)OSDCheckbox +{ + return _OSDCheckbox; +} + +- (void)setOSDCheckbox:(NSButton *)OSDCheckbox +{ + _OSDCheckbox = OSDCheckbox; + [_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]]; +} + +- (IBAction)changeOSDEnabled:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState + forKey:@"GBOSDEnabled"]; + +} + +- (IBAction)changeFilterScreenshots:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState + forKey:@"GBFilterScreenshots"]; +} + +- (NSButton *)screenshotFilterCheckbox +{ + return _screenshotFilterCheckbox; +} + +- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox +{ + _screenshotFilterCheckbox = screenshotFilterCheckbox; + if (![GBViewMetal isSupported]) { + [_screenshotFilterCheckbox setEnabled:false]; + } + else { + [_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]]; + } +} + +- (IBAction)displayColorCorrectionHelp:(id)sender +{ + [GBWarningPopover popoverWithContents: + (NSString * const[]){ + [GB_COLOR_CORRECTION_DISABLED] = @"Colors are directly interpreted as sRGB, resulting in unbalanced colors and inaccurate hues.", + [GB_COLOR_CORRECTION_CORRECT_CURVES] = @"Colors have their brightness corrected, but hues remain unbalanced.", + [GB_COLOR_CORRECTION_MODERN_BALANCED] = @"Emulates a modern display. Blue contrast is moderately enhanced at the cost of slight hue inaccuracy.", + [GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST] = @"Like Modern – Balanced, but further boosts the contrast of greens and magentas that is lacking on the original hardware.", + [GB_COLOR_CORRECTION_REDUCE_CONTRAST] = @"Slightly reduce the contrast to better represent the tint and contrast of the original display.", + [GB_COLOR_CORRECTION_LOW_CONTRAST] = @"Harshly reduce the contrast to accurately represent the tint low constrast of the original display.", + [GB_COLOR_CORRECTION_MODERN_ACCURATE] = @"Emulates a modern display. Colors have their hues and brightness corrected.", + } [self.colorCorrectionPopupButton.selectedItem.tag] + title:self.colorCorrectionPopupButton.selectedItem.title + onView:sender + timeout:6 + preferredEdge:NSRectEdgeMaxX]; +} + +- (IBAction)displayHighPassHelp:(id)sender +{ + [GBWarningPopover popoverWithContents: + (NSString * const[]){ + [GB_HIGHPASS_OFF] = @"No high-pass filter will be applied. DC offset will be kept, pausing and resuming will trigger audio pops.", + [GB_HIGHPASS_ACCURATE] = @"An accurate high-pass filter will be applied, removing the DC offset while somewhat attenuating the bass.", + [GB_HIGHPASS_REMOVE_DC_OFFSET] = @"A high-pass filter will be applied to the DC offset itself, removing the DC offset while preserving the waveform.", + } [self.highpassFilterPopupButton.indexOfSelectedItem] + title:self.highpassFilterPopupButton.selectedItem.title + onView:sender + timeout:6 + preferredEdge:NSRectEdgeMaxX]; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBS.xib b/thirdparty/SameBoy-old/Cocoa/GBS.xib new file mode 100644 index 000000000..65bd44f82 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBS.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/GBS11.xib b/thirdparty/SameBoy-old/Cocoa/GBS11.xib new file mode 100644 index 000000000..b7a69fd56 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBS11.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/GBSplitView.h b/thirdparty/SameBoy-old/Cocoa/GBSplitView.h new file mode 100644 index 000000000..6ab97cf02 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBSplitView.h @@ -0,0 +1,7 @@ +#import + +@interface GBSplitView : NSSplitView + +-(void) setDividerColor:(NSColor *)color; +- (NSArray *)arrangedSubviews; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBSplitView.m b/thirdparty/SameBoy-old/Cocoa/GBSplitView.m new file mode 100644 index 000000000..d24d58065 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBSplitView.m @@ -0,0 +1,33 @@ +#import "GBSplitView.h" + +@implementation GBSplitView +{ + NSColor *_dividerColor; +} + +- (void)setDividerColor:(NSColor *)color +{ + _dividerColor = color; + [self setNeedsDisplay:true]; +} + +- (NSColor *)dividerColor +{ + if (_dividerColor) { + return _dividerColor; + } + return [super dividerColor]; +} + +/* Mavericks comaptibility */ +- (NSArray *)arrangedSubviews +{ + if (@available(macOS 10.11, *)) { + return [super arrangedSubviews]; + } + else { + return [self subviews]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.h b/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.h new file mode 100644 index 000000000..b76033605 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.h @@ -0,0 +1,6 @@ +#import +#include + +@interface GBTerminalTextFieldCell : NSTextFieldCell +@property (nonatomic) GB_gameboy_t *gb; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.m b/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.m new file mode 100644 index 000000000..e1ba95779 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBTerminalTextFieldCell.m @@ -0,0 +1,231 @@ +#import +#import "GBTerminalTextFieldCell.h" + +@interface GBTerminalTextView : NSTextView +@property GB_gameboy_t *gb; +@end + +@implementation GBTerminalTextFieldCell +{ + GBTerminalTextView *field_editor; +} + +- (NSTextView *)fieldEditorForView:(NSView *)controlView +{ + if (field_editor) { + field_editor.gb = self.gb; + return field_editor; + } + field_editor = [[GBTerminalTextView alloc] init]; + [field_editor setFieldEditor:true]; + field_editor.gb = self.gb; + return field_editor; +} + +@end + +@implementation GBTerminalTextView +{ + NSMutableOrderedSet *lines; + NSUInteger current_line; + bool reverse_search_mode; + NSRange auto_complete_range; + uintptr_t auto_complete_context; +} + +- (instancetype)init +{ + self = [super init]; + if (!self) { + return NULL; + } + lines = [[NSMutableOrderedSet alloc] init]; + return self; +} + +- (void)moveUp:(id)sender +{ + reverse_search_mode = false; + if (current_line != 0) { + current_line--; + [self setString:[lines objectAtIndex:current_line]]; + } + else { + [self setSelectedRange:NSMakeRange(0, 0)]; + NSBeep(); + } +} + +- (void)moveDown:(id)sender +{ + reverse_search_mode = false; + if (current_line == [lines count]) { + [self setString:@""]; + NSBeep(); + return; + } + current_line++; + if (current_line == [lines count]) { + [self setString:@""]; + } + else { + [self setString:[lines objectAtIndex:current_line]]; + } +} + +-(void)insertNewline:(id)sender +{ + if ([self.string length]) { + NSString *string = [self.string copy]; + [lines removeObject:string]; + [lines addObject:string]; + } + [super insertNewline:sender]; + current_line = [lines count]; + reverse_search_mode = false; +} + +- (void)keyDown:(NSEvent *)event +{ + if (event.keyCode == kVK_ANSI_R && (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagControl) { + if ([lines count] == 0) { + NSBeep(); + return; + } + if (!reverse_search_mode) { + [self selectAll:self]; + current_line = [lines count] - 1; + } + else { + if (current_line != 0) { + current_line--; + } + else { + NSBeep(); + } + } + + if (self.string.length) { + [self updateReverseSearch]; + } + else { + [self setNeedsDisplay:true]; + reverse_search_mode = true; + } + + } + else { + [super keyDown:event]; + } +} + +- (void) updateReverseSearch +{ + NSUInteger old_line = current_line; + reverse_search_mode = false; + NSString *substring = [self.string substringWithRange:self.selectedRange]; + do { + NSString *line = [lines objectAtIndex:current_line]; + NSRange range = [line rangeOfString:substring]; + if (range.location != NSNotFound) { + self.string = line; + [self setSelectedRange:range]; + reverse_search_mode = true; + return; + } + } while (current_line--); + current_line = old_line; + reverse_search_mode = true; + NSBeep(); +} + +- (void) insertText:(NSString *)string replacementRange:(NSRange)range +{ + if (reverse_search_mode) { + range = self.selectedRange; + self.string = [[self.string substringWithRange:range] stringByAppendingString:string]; + [self selectAll:nil]; + [self updateReverseSearch]; + } + else { + [super insertText:string replacementRange:range]; + } +} + +-(void)deleteBackward:(id)sender +{ + if (reverse_search_mode && self.string.length) { + NSRange range = self.selectedRange; + range.length--; + self.string = [self.string substringWithRange:range]; + if (range.length) { + [self selectAll:nil]; + [self updateReverseSearch]; + } + else { + reverse_search_mode = true; + current_line = [lines count] - 1; + } + } + else { + [super deleteBackward:sender]; + } +} + +-(void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag +{ + reverse_search_mode = false; + auto_complete_context = 0; + [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; +} + +- (BOOL)resignFirstResponder +{ + reverse_search_mode = false; + return [super resignFirstResponder]; +} + +-(void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + if (reverse_search_mode && [super string].length == 0) { + NSMutableDictionary *attributes = [self.typingAttributes mutableCopy]; + NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5]; + [attributes setObject:color forKey:NSForegroundColorAttributeName]; + [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; + } +} + +/* Todo: lazy design, make it use a delegate instead of having a gb reference*/ + +- (void)insertTab:(id)sender +{ + if (auto_complete_context == 0) { + NSRange selection = self.selectedRange; + if (selection.length) { + [self delete:nil]; + } + auto_complete_range = NSMakeRange(selection.location, 0); + } + char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String); + uintptr_t context = auto_complete_context; + char *completion = GB_debugger_complete_substring(self.gb, substring, &context); + free(substring); + if (completion) { + NSString *ns_completion = @(completion); + free(completion); + if (!ns_completion) { + goto error; + } + self.selectedRange = auto_complete_range; + auto_complete_range.length = ns_completion.length; + [self replaceCharactersInRange:self.selectedRange withString:ns_completion]; + auto_complete_context = context; + return; + } +error: + auto_complete_context = context; + NSBeep(); +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBView.h b/thirdparty/SameBoy-old/Cocoa/GBView.h new file mode 100644 index 000000000..a264d29a4 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBView.h @@ -0,0 +1,32 @@ +#import +#include +#import +#import "GBOSDView.h" + +@class Document; + +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_t; + +@interface GBView : NSView +- (void) flip; +- (uint32_t *) pixels; +@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) bool isRewinding; +@property (nonatomic, strong) NSView *internalView; +@property (weak) GBOSDView *osdView; +- (void) createInternalView; +- (uint32_t *)currentBuffer; +- (uint32_t *)previousBuffer; +- (void)screenSizeChanged; +- (void)setRumble: (double)amp; +- (NSImage *)renderToImage; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBView.m b/thirdparty/SameBoy-old/Cocoa/GBView.m new file mode 100644 index 000000000..cf591e052 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBView.m @@ -0,0 +1,795 @@ +#import +#import +#import "GBView.h" +#import "GBViewGL.h" +#import "GBViewMetal.h" +#import "GBButtons.h" +#import "NSString+StringForKey.h" +#import "Document.h" + +#define JOYSTICK_HIGH 0x4000 +#define JOYSTICK_LOW 0x3800 + +static const uint8_t workboy_ascii_to_key[] = { + ['0'] = GB_WORKBOY_0, + ['`'] = GB_WORKBOY_UMLAUT, + ['1'] = GB_WORKBOY_1, + ['2'] = GB_WORKBOY_2, + ['3'] = GB_WORKBOY_3, + ['4'] = GB_WORKBOY_4, + ['5'] = GB_WORKBOY_5, + ['6'] = GB_WORKBOY_6, + ['7'] = GB_WORKBOY_7, + ['8'] = GB_WORKBOY_8, + ['9'] = GB_WORKBOY_9, + + ['\r'] = GB_WORKBOY_ENTER, + [3] = GB_WORKBOY_ENTER, + + ['!'] = GB_WORKBOY_EXCLAMATION_MARK, + ['$'] = GB_WORKBOY_DOLLAR, + ['#'] = GB_WORKBOY_HASH, + ['~'] = GB_WORKBOY_TILDE, + ['*'] = GB_WORKBOY_ASTERISK, + ['+'] = GB_WORKBOY_PLUS, + ['-'] = GB_WORKBOY_MINUS, + ['('] = GB_WORKBOY_LEFT_PARENTHESIS, + [')'] = GB_WORKBOY_RIGHT_PARENTHESIS, + [';'] = GB_WORKBOY_SEMICOLON, + [':'] = GB_WORKBOY_COLON, + ['%'] = GB_WORKBOY_PERCENT, + ['='] = GB_WORKBOY_EQUAL, + [','] = GB_WORKBOY_COMMA, + ['<'] = GB_WORKBOY_LT, + ['.'] = GB_WORKBOY_DOT, + ['>'] = GB_WORKBOY_GT, + ['/'] = GB_WORKBOY_SLASH, + ['?'] = GB_WORKBOY_QUESTION_MARK, + [' '] = GB_WORKBOY_SPACE, + ['\''] = GB_WORKBOY_QUOTE, + ['@'] = GB_WORKBOY_AT, + + ['q'] = GB_WORKBOY_Q, + ['w'] = GB_WORKBOY_W, + ['e'] = GB_WORKBOY_E, + ['r'] = GB_WORKBOY_R, + ['t'] = GB_WORKBOY_T, + ['y'] = GB_WORKBOY_Y, + ['u'] = GB_WORKBOY_U, + ['i'] = GB_WORKBOY_I, + ['o'] = GB_WORKBOY_O, + ['p'] = GB_WORKBOY_P, + ['a'] = GB_WORKBOY_A, + ['s'] = GB_WORKBOY_S, + ['d'] = GB_WORKBOY_D, + ['f'] = GB_WORKBOY_F, + ['g'] = GB_WORKBOY_G, + ['h'] = GB_WORKBOY_H, + ['j'] = GB_WORKBOY_J, + ['k'] = GB_WORKBOY_K, + ['l'] = GB_WORKBOY_L, + ['z'] = GB_WORKBOY_Z, + ['x'] = GB_WORKBOY_X, + ['c'] = GB_WORKBOY_C, + ['v'] = GB_WORKBOY_V, + ['b'] = GB_WORKBOY_B, + ['n'] = GB_WORKBOY_N, + ['m'] = GB_WORKBOY_M, +}; + +static const uint8_t workboy_vk_to_key[] = { + [kVK_F1] = GB_WORKBOY_CLOCK, + [kVK_F2] = GB_WORKBOY_TEMPERATURE, + [kVK_F3] = GB_WORKBOY_MONEY, + [kVK_F4] = GB_WORKBOY_CALCULATOR, + [kVK_F5] = GB_WORKBOY_DATE, + [kVK_F6] = GB_WORKBOY_CONVERSION, + [kVK_F7] = GB_WORKBOY_RECORD, + [kVK_F8] = GB_WORKBOY_WORLD, + [kVK_F9] = GB_WORKBOY_PHONE, + [kVK_F10] = GB_WORKBOY_UNKNOWN, + [kVK_Delete] = GB_WORKBOY_BACKSPACE, + [kVK_Shift] = GB_WORKBOY_SHIFT_DOWN, + [kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN, + [kVK_UpArrow] = GB_WORKBOY_UP, + [kVK_DownArrow] = GB_WORKBOY_DOWN, + [kVK_LeftArrow] = GB_WORKBOY_LEFT, + [kVK_RightArrow] = GB_WORKBOY_RIGHT, + [kVK_Escape] = GB_WORKBOY_ESCAPE, + [kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT, + [kVK_ANSI_KeypadClear] = GB_WORKBOY_M, + [kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H, + [kVK_ANSI_KeypadDivide] = GB_WORKBOY_J, +}; + +@implementation GBView +{ + uint32_t *image_buffers[3]; + unsigned char current_buffer; + bool mouse_hidden; + NSTrackingArea *tracking_area; + bool _mouseHidingEnabled; + bool axisActive[2]; + bool underclockKeyDown; + double clockMultiplier; + double analogClockMultiplier; + bool analogClockMultiplierValid; + NSEventModifierFlags previousModifiers; + JOYController *lastController; + GB_frame_blending_mode_t _frameBlendingMode; + bool _turbo; + bool _mouseControlEnabled; +} + ++ (instancetype)alloc +{ + return [self allocWithZone:NULL]; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone +{ + if (self == [GBView class]) { + if ([GBViewMetal isSupported]) { + return [GBViewMetal allocWithZone: zone]; + } + return [GBViewGL allocWithZone: zone]; + } + return [super allocWithZone:zone]; +} + +- (void) createInternalView +{ + assert(false && "createInternalView must not be inherited"); +} + +- (void) _init +{ + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; + tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved + owner:self + userInfo:nil]; + [self addTrackingArea:tracking_area]; + clockMultiplier = 1.0; + [self createInternalView]; + [self addSubview:self.internalView]; + self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [JOYController registerListener:self]; + _mouseControlEnabled = true; +} + +- (void)screenSizeChanged +{ + if (image_buffers[0]) free(image_buffers[0]); + if (image_buffers[1]) free(image_buffers[1]); + if (image_buffers[2]) free(image_buffers[2]); + + size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb); + + image_buffers[0] = calloc(1, buffer_size); + image_buffers[1] = calloc(1, buffer_size); + image_buffers[2] = calloc(1, buffer_size); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self setFrame:self.superview.frame]; + }); +} + +- (void) ratioKeepingChanged +{ + [self setFrame:self.superview.frame]; +} + +- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode +{ + _frameBlendingMode = frameBlendingMode; + [self setNeedsDisplay:true]; +} + + +- (GB_frame_blending_mode_t)frameBlendingMode +{ + if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (!_gb || GB_is_sgb(_gb)) { + return GB_FRAME_BLENDING_MODE_SIMPLE; + } + return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + return _frameBlendingMode; +} +- (unsigned char) numberOfBuffers +{ + return _frameBlendingMode? 3 : 2; +} + +- (void)dealloc +{ + free(image_buffers[0]); + free(image_buffers[1]); + free(image_buffers[2]); + if (mouse_hidden) { + mouse_hidden = false; + [NSCursor unhide]; + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self setRumble:0]; + [JOYController unregisterListener:self]; +} +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (!(self = [super initWithCoder:coder])) { + return self; + } + [self _init]; + return self; +} + +- (instancetype)initWithFrame:(NSRect)frameRect +{ + if (!(self = [super initWithFrame:frameRect])) { + return self; + } + [self _init]; + return self; +} + +- (void)setFrame:(NSRect)frame +{ + frame = self.superview.frame; + if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { + double ratio = frame.size.width / frame.size.height; + double width = GB_get_screen_width(_gb); + double height = GB_get_screen_height(_gb); + if (ratio >= width / height) { + double new_width = round(frame.size.height / height * width); + frame.origin.x = floor((frame.size.width - new_width) / 2); + frame.size.width = new_width; + frame.origin.y = 0; + } + else { + double new_height = round(frame.size.width / width * height); + frame.origin.y = floor((frame.size.height - new_height) / 2); + frame.size.height = new_height; + frame.origin.x = 0; + } + } + + [super setFrame:frame]; +} + +- (void) flip +{ + if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { + clockMultiplier = 1.0; + GB_set_clock_multiplier(_gb, analogClockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier); + } + if (analogClockMultiplier == 1.0) { + analogClockMultiplierValid = false; + } + if (analogClockMultiplier < 2.0 && analogClockMultiplier > 1.0) { + GB_set_turbo_mode(_gb, false, false); + if (self.document.partner) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + } + } + else { + if (underclockKeyDown && clockMultiplier > 0.5) { + 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); + } + } + } + if ((!analogClockMultiplierValid && clockMultiplier > 1) || + _turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) { + [self.osdView displayText:@"Fast forwarding..."]; + } + else if ((!analogClockMultiplierValid && clockMultiplier < 1) || + (analogClockMultiplierValid && analogClockMultiplier < 1)) { + [self.osdView displayText:@"Slow motion..."]; + } + current_buffer = (current_buffer + 1) % self.numberOfBuffers; +} + +- (uint32_t *) pixels +{ + return image_buffers[(current_buffer + 1) % self.numberOfBuffers]; +} + +-(void)keyDown:(NSEvent *)theEvent +{ + if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return; + unsigned short keyCode = theEvent.keyCode; + if (GB_workboy_is_enabled(_gb)) { + if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) { + GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]); + return; + } + unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0; + if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) { + GB_workboy_set_key(_gb, workboy_ascii_to_key[c]); + return; + } + } + + bool handled = false; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } + for (unsigned player = 0; player < player_count; player++) { + for (GBButton button = 0; button < GBButtonCount; button++) { + NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; + if (!key) continue; + + if (key.unsignedShortValue == keyCode) { + handled = true; + switch (button) { + case GBTurbo: + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, true, false); + } + else { + GB_set_turbo_mode(_gb, true, self.isRewinding); + } + _turbo = true; + analogClockMultiplierValid = false; + break; + + case GBRewind: + if (!self.document.partner) { + self.isRewinding = true; + GB_set_turbo_mode(_gb, false, false); + _turbo = false; + } + break; + + case GBUnderclock: + underclockKeyDown = true; + analogClockMultiplierValid = false; + break; + + default: + if (self.document.partner) { + if (player == 0) { + GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true); + } + else { + GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true); + } + } + else { + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true); + } + break; + } + } + } + } + + if (!handled && [theEvent type] != NSEventTypeFlagsChanged) { + [super keyDown:theEvent]; + } +} + +-(void)keyUp:(NSEvent *)theEvent +{ + unsigned short keyCode = theEvent.keyCode; + if (GB_workboy_is_enabled(_gb)) { + if (keyCode == kVK_Shift || keyCode == kVK_RightShift) { + GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP); + } + else { + GB_workboy_set_key(_gb, GB_WORKBOY_NONE); + } + + } + bool handled = false; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } + for (unsigned player = 0; player < player_count; player++) { + for (GBButton button = 0; button < GBButtonCount; button++) { + NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; + if (!key) continue; + + if (key.unsignedShortValue == keyCode) { + handled = true; + switch (button) { + case GBTurbo: + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + else { + GB_set_turbo_mode(_gb, false, false); + } + _turbo = false; + analogClockMultiplierValid = false; + break; + + case GBRewind: + self.isRewinding = false; + break; + + case GBUnderclock: + underclockKeyDown = false; + analogClockMultiplierValid = false; + break; + + default: + if (self.document.partner) { + if (player == 0) { + GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false); + } + else { + GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false); + } + } + else { + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false); + } + break; + } + } + } + } + if (!handled && [theEvent type] != NSEventTypeFlagsChanged) { + [super keyUp:theEvent]; + } +} + +- (void)setRumble:(double)amp +{ + [lastController setRumbleAmplitude:amp]; +} + +- (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller +{ + if (!_gb) return false; + if (!GB_has_accelerometer(_gb)) return false; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true; + for (JOYAxes3D *axes in controller.axes3D) { + if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) { + return false; + } + } + return true; +} + +- (bool)allowController +{ + if ([self.window isMainWindow]) return true; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]) { + if ([(Document *)[NSApplication sharedApplication].orderedDocuments.firstObject mainWindow] == self.window) { + return true; + } + } + return false; +} + +- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis +{ + if (!_gb) return; + if (![self allowController]) return; + + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; + } + + if ((axis.usage == JOYAxisUsageR1 && !mapping) || + axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){ + analogClockMultiplier = MIN(MAX(1 - axis.value + 0.05, 1.0 / 3), 1.0); + analogClockMultiplierValid = true; + } + + else if ((axis.usage == JOYAxisUsageL1 && !mapping) || + axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){ + analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.95, 1.0), 3.0); + analogClockMultiplierValid = true; + } +} + +- (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes +{ + if (!_gb) return; + if ([self shouldControllerUseJoystickForMotion:controller]) { + if (!self.mouseControlsActive) { + GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y); + } + } +} + +- (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes +{ + if (!_gb) return; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return; + if (self.mouseControlsActive) return; + + if (axes.usage == JOYAxes3DUsageOrientation) { + for (JOYAxes3D *axes in controller.axes3D) { + // Only use orientation if there's no acceleration axes + if (axes.usage == JOYAxes3DUsageAcceleration) { + return; + } + } + JOYPoint3D point = axes.normalizedValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } + else if (axes.usage == JOYAxes3DUsageAcceleration) { + JOYPoint3D point = axes.gUnitsValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + if (!_gb) return; + if (![self allowController]) return; + _mouseControlEnabled = false; + if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return; + + unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } + + IOPMAssertionID assertionID; + IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID); + + for (unsigned player = 0; player < player_count; player++) { + NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] + objectForKey:n2s(player)]; + if (player_count != 1 && // Single player, accpet inputs from all joypads + !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads + ![preferred_joypad isEqualToString:controller.uniqueID]) { + continue; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [controller setPlayerLEDs:[controller LEDMaskForPlayer:player]]; + }); + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; + } + + JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage; + if (!mapping && usage >= JOYButtonUsageGeneric0) { + 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(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break; + case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break; + case JOYButtonUsageC: break; + case JOYButtonUsageStart: + case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break; + case JOYButtonUsageSelect: + case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break; + case JOYButtonUsageR2: + case JOYButtonUsageL2: + case JOYButtonUsageZ: { + self.isRewinding = button.isPressed; + if (button.isPressed) { + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + else { + GB_set_turbo_mode(_gb, false, false); + } + _turbo = false; + } + break; + } + + case JOYButtonUsageL1: { + if (!analogClockMultiplierValid || analogClockMultiplier == 1.0 || !button.isPressed) { + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); + } + else { + GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); + } + _turbo = button.isPressed; + } + break; + } + + case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break; + case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break; + + default: + break; + } + } +} + +- (BOOL)acceptsFirstResponder +{ + 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 && + !self.mouseControlsActive) { + [NSCursor hide]; + } + } + [super mouseEntered:theEvent]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + if (mouse_hidden) { + mouse_hidden = false; + if (_mouseHidingEnabled) { + [NSCursor unhide]; + } + } + [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; + + _mouseHidingEnabled = mouseHidingEnabled; + + if (mouse_hidden && _mouseHidingEnabled) { + [NSCursor hide]; + } + + if (mouse_hidden && !_mouseHidingEnabled) { + [NSCursor unhide]; + } +} + +- (bool)isMouseHidingEnabled +{ + return _mouseHidingEnabled; +} + +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + else { + [self keyUp:event]; + } + + previousModifiers = event.modifierFlags; +} + +- (uint32_t *)currentBuffer +{ + return image_buffers[current_buffer]; +} + +- (uint32_t *)previousBuffer +{ + 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_save_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 noErrorOnNotFound:false]; + } + + return false; +} + +- (NSImage *)renderToImage; +{ + /* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me + to bother figuring out how the hell something so trivial can be done. */ + return nil; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBViewGL.h b/thirdparty/SameBoy-old/Cocoa/GBViewGL.h new file mode 100644 index 000000000..28db4848c --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBViewGL.h @@ -0,0 +1,5 @@ +#import "GBView.h" + +@interface GBViewGL : GBView + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBViewGL.m b/thirdparty/SameBoy-old/Cocoa/GBViewGL.m new file mode 100644 index 000000000..dda85843e --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBViewGL.m @@ -0,0 +1,35 @@ +#import "GBViewGL.h" +#import "GBOpenGLView.h" + +@implementation GBViewGL + +- (void)createInternalView +{ + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersion3_2Core, + 0 + }; + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + + assert(pf); + + NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; + + self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf]; + ((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = true; + ((GBOpenGLView *)self.internalView).openGLContext = context; +} + +- (void)flip +{ + [super flip]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.internalView setNeedsDisplay:true]; + [self setNeedsDisplay:true]; + }); +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBViewMetal.h b/thirdparty/SameBoy-old/Cocoa/GBViewMetal.h new file mode 100644 index 000000000..521c3c720 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBViewMetal.h @@ -0,0 +1,7 @@ +#import +#import +#import "GBView.h" + +@interface GBViewMetal : GBView ++ (bool) isSupported; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBViewMetal.m b/thirdparty/SameBoy-old/Cocoa/GBViewMetal.m new file mode 100644 index 000000000..ae7443f6b --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBViewMetal.m @@ -0,0 +1,232 @@ +#import +#import "GBViewMetal.h" +#pragma clang diagnostic ignored "-Wpartial-availability" + + +static const vector_float2 rect[] = +{ + {-1, -1}, + { 1, -1}, + {-1, 1}, + { 1, 1}, +}; + +@implementation GBViewMetal +{ + id device; + id texture, previous_texture; + id vertices; + id pipeline_state; + id command_queue; + id frame_blending_mode_buffer; + id output_resolution_buffer; + vector_float2 output_resolution; +} + ++ (bool)isSupported +{ + if (MTLCopyAllDevices) { + return [MTLCopyAllDevices() count]; + } + return false; +} + +- (void) allocateTextures +{ + if (!device) return; + + MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; + + texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; + + texture_descriptor.width = GB_get_screen_width(self.gb); + texture_descriptor.height = GB_get_screen_height(self.gb); + + texture = [device newTextureWithDescriptor:texture_descriptor]; + previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + +} + +- (void)createInternalView +{ + MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; + view.delegate = self; + self.internalView = view; + view.paused = true; + view.enableSetNeedsDisplay = true; + view.framebufferOnly = false; + + vertices = [device newBufferWithBytes:rect + length:sizeof(rect) + options:MTLResourceStorageModeShared]; + + static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; + frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode + length:sizeof(default_blending_mode) + options:MTLResourceStorageModeShared]; + + output_resolution_buffer = [device newBufferWithBytes:&output_resolution + length:sizeof(output_resolution) + options:MTLResourceStorageModeShared]; + + output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height}; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil]; + [self loadShader]; +} + +- (void) loadShader +{ + NSError *error = nil; + NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader" + ofType:@"metal" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; + + NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]; + NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name + ofType:@"fsh" + inDirectory:@"Shaders"] + encoding:NSUTF8StringEncoding + error:nil]; + + shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}" + withString:scaler_source]; + + MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; + options.fastMathEnabled = true; + id library = [device newLibraryWithSource:shader_source + options:options + error:&error]; + if (error) { + NSLog(@"Error: %@", error); + if (!library) { + return; + } + } + + id vertex_function = [library newFunctionWithName:@"vertex_shader"]; + id fragment_function = [library newFunctionWithName:@"fragment_shader"]; + + // Set up a descriptor for creating a pipeline state object + MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipeline_state_descriptor.vertexFunction = vertex_function; + pipeline_state_descriptor.fragmentFunction = fragment_function; + pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat; + + error = nil; + pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor + error:&error]; + if (error) { + NSLog(@"Failed to created pipeline state, error %@", error); + return; + } + + command_queue = [device newCommandQueue]; +} + +- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size +{ + output_resolution = (vector_float2){size.width, size.height}; + dispatch_async(dispatch_get_main_queue(), ^{ + [(MTKView *)self.internalView draw]; + }); +} + +- (void)drawInMTKView:(MTKView *)view +{ + if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; + if (!self.gb) return; + if (texture.width != GB_get_screen_width(self.gb) || + texture.height != GB_get_screen_height(self.gb)) { + [self allocateTextures]; + } + + MTLRegion region = { + {0, 0, 0}, // MTLOrigin + {texture.width, texture.height, 1} // MTLSize + }; + + [texture replaceRegion:region + mipmapLevel:0 + withBytes:[self currentBuffer] + bytesPerRow:texture.width * 4]; + if ([self frameBlendingMode]) { + [previous_texture replaceRegion:region + mipmapLevel:0 + withBytes:[self previousBuffer] + bytesPerRow:texture.width * 4]; + } + + MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; + id command_buffer = [command_queue commandBuffer]; + + if (render_pass_descriptor != nil) { + *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode]; + *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; + + id render_encoder = + [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; + + [render_encoder setViewport:(MTLViewport){0.0, 0.0, + output_resolution.x, + output_resolution.y, + -1.0, 1.0}]; + + [render_encoder setRenderPipelineState:pipeline_state]; + + [render_encoder setVertexBuffer:vertices + offset:0 + atIndex:0]; + + [render_encoder setFragmentBuffer:frame_blending_mode_buffer + offset:0 + atIndex:0]; + + [render_encoder setFragmentBuffer:output_resolution_buffer + offset:0 + atIndex:1]; + + [render_encoder setFragmentTexture:texture + atIndex:0]; + + [render_encoder setFragmentTexture:previous_texture + atIndex:1]; + + [render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip + vertexStart:0 + vertexCount:4]; + + [render_encoder endEncoding]; + + [command_buffer presentDrawable:view.currentDrawable]; + } + + + [command_buffer commit]; +} + +- (void)flip +{ + [super flip]; + dispatch_async(dispatch_get_main_queue(), ^{ + [(MTKView *)self.internalView setNeedsDisplay:true]; + }); +} + +- (NSImage *)renderToImage +{ + CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture] + options:@{ + kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB() + }]; + ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1), + 0, ciImage.extent.size.height)]; + CIContext *context = [CIContext context]; + CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent]; + NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size]; + CGImageRelease(cgImage); + return ret; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.h b/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.h new file mode 100644 index 000000000..5ee4638e5 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.h @@ -0,0 +1,6 @@ +#import +#include + +@interface GBVisualizerView : NSView +- (void)addSample:(GB_sample_t *)sample; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.m b/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.m new file mode 100644 index 000000000..08f6024db --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBVisualizerView.m @@ -0,0 +1,71 @@ +#import "GBVisualizerView.h" +#import "GBPaletteEditorController.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 = [GBPaletteEditorController userPalette]; + NSSize size = self.bounds.size; + + [color_to_effect_color(palette->colors[0]) setFill]; + NSRectFill(self.bounds); + + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(0, size.height / 2)]; + + for (unsigned i = 0; i < SAMPLE_COUNT; i++) { + GB_sample_t *sample = _samples + ((i + _position) % SAMPLE_COUNT); + double volume = ((signed)sample->left + (signed)sample->right) / 32768.0; + [line lineToPoint:NSMakePoint(size.width * (i + 0.5) / SAMPLE_COUNT, + (volume + 1) * size.height / 2)]; + } + + [line lineToPoint:NSMakePoint(size.width, size.height / 2)]; + [line setLineWidth:1.0]; + + [color_to_effect_color(palette->colors[2]) setFill]; + [line fill]; + + [color_to_effect_color(palette->colors[1]) setFill]; + NSRectFill(NSMakeRect(0, size.height / 2 - 0.5, size.width, 1)); + + [color_to_effect_color(palette->colors[3]) setStroke]; + [line stroke]; + + [super drawRect:dirtyRect]; +} + +- (void)addSample:(GB_sample_t *)sample +{ + _samples[_position++] = *sample; + if (_position == SAMPLE_COUNT) { + _position = 0; + } +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.h b/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.h new file mode 100644 index 000000000..953aa3c91 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.h @@ -0,0 +1,9 @@ +#import + +@interface GBWarningPopover : NSPopover + ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view; ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window; ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents title:(NSString *)title onView:(NSView *)view timeout:(double)seconds preferredEdge:(NSRectEdge)preferredEdge; + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.m b/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.m new file mode 100644 index 000000000..b66186ba2 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/GBWarningPopover.m @@ -0,0 +1,63 @@ +#import "GBWarningPopover.h" + +static GBWarningPopover *lastPopover; + +@implementation GBWarningPopover + ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents title:(NSString *)title onView:(NSView *)view timeout:(double)seconds preferredEdge:(NSRectEdge)preferredEdge +{ + [lastPopover close]; + lastPopover = [[self alloc] init]; + + [lastPopover setBehavior:NSPopoverBehaviorApplicationDefined]; + [lastPopover setAnimates:true]; + lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil]; + NSTextField *field = (NSTextField *)lastPopover.contentViewController.view; + if (!title) { + [field setStringValue:contents]; + } + else { + NSMutableAttributedString *fullContents = [[NSMutableAttributedString alloc] initWithString:title + attributes:@{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]}]; + [fullContents appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:contents] + attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}]]; + [field setAttributedStringValue:fullContents]; + + } + NSSize textSize = [field.cell cellSizeForBounds:[field.cell drawingRectForBounds:NSMakeRect(0, 0, 240, CGFLOAT_MAX)]]; + textSize.width = ceil(textSize.width) + 16; + textSize.height = ceil(textSize.height) + 12; + [lastPopover setContentSize:textSize]; + + if (!view.window.isVisible) { + [view.window setIsVisible:true]; + } + + [lastPopover showRelativeToRect:view.bounds + ofView:view + preferredEdge:preferredEdge]; + + NSRect frame = field.frame; + frame.origin.x += 8; + frame.origin.y += 6; + frame.size.width -= 16; + frame.size.height -= 12; + field.frame = frame; + + + [lastPopover performSelector:@selector(close) withObject:nil afterDelay:3.0]; + + return lastPopover; +} + ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view +{ + return [self popoverWithContents:contents title:nil onView:view timeout:3.0 preferredEdge:NSMinYEdge]; +} + ++ (GBWarningPopover *)popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window +{ + return [self popoverWithContents:contents onView:window.contentView.superview.subviews.lastObject]; +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/Icon.png b/thirdparty/SameBoy-old/Cocoa/Icon.png new file mode 100644 index 000000000..a5675ca2c Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Icon.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Icon@2x.png b/thirdparty/SameBoy-old/Cocoa/Icon@2x.png new file mode 100644 index 000000000..b3259ed2a Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Icon@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Info.plist b/thirdparty/SameBoy-old/Cocoa/Info.plist new file mode 100644 index 000000000..97e197dcd --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/Info.plist @@ -0,0 +1,222 @@ + + + + + CFBundleDisplayName + SameBoy + CFBundleDevelopmentRegion + en + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + gb + + CFBundleTypeIconFile + Cartridge + CFBundleTypeName + Game Boy Game + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gb + + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleTypeExtensions + + gbc + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy Color Game + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gbc + + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleTypeExtensions + + isx + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy ISX File + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.isx + + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleTypeExtensions + + gbs + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy Sound File + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gbs + + LSTypeIsPackage + 0 + NSDocumentClass + Document + + + CFBundleTypeExtensions + + gbcart + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy Cartridge + CFBundleTypeRole + Viewer + LSItemContentTypes + + LSTypeIsPackage + 1 + NSDocumentClass + Document + + + CFBundleExecutable + SameBoy + CFBundleIconFile + AppIcon.icns + CFBundleIdentifier + com.github.liji32.sameboy + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SameBoy + CFBundlePackageType + APPL + CFBundleShortVersionString + Version @VERSION + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + LSMinimumSystemVersion + 10.9 + NSHumanReadableCopyright + Copyright © 2015-2022 Lior Halphon + NSMainNibFile + MainMenu + NSPrincipalClass + GBApp + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Game Boy Game + UTTypeIconFile + Cartridge + UTTypeIdentifier + com.github.liji32.sameboy.gb + UTTypeTagSpecification + + public.filename-extension + + gb + + + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Game Boy Color Game + UTTypeIconFile + ColorCartridge + UTTypeIdentifier + com.github.liji32.sameboy.gbc + UTTypeTagSpecification + + public.filename-extension + + gbc + + + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Game Boy ISX File + UTTypeIconFile + ColorCartridge + UTTypeIdentifier + com.github.liji32.sameboy.isx + UTTypeTagSpecification + + public.filename-extension + + isx + + + + + 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 + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad.png b/thirdparty/SameBoy-old/Cocoa/Joypad.png new file mode 100644 index 000000000..46cd64837 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad@2x.png b/thirdparty/SameBoy-old/Cocoa/Joypad@2x.png new file mode 100644 index 000000000..4fcc1f433 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad~dark.png b/thirdparty/SameBoy-old/Cocoa/Joypad~dark.png new file mode 100644 index 000000000..18bdccd95 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad~dark.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad~dark@2x.png b/thirdparty/SameBoy-old/Cocoa/Joypad~dark@2x.png new file mode 100644 index 000000000..aa6b5c006 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad~dark@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad~solid.png b/thirdparty/SameBoy-old/Cocoa/Joypad~solid.png new file mode 100644 index 000000000..fecb8b320 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad~solid.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Joypad~solid@2x.png b/thirdparty/SameBoy-old/Cocoa/Joypad~solid@2x.png new file mode 100644 index 000000000..197155a95 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Joypad~solid@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/KeyboardShortcutPrivateAPIs.h b/thirdparty/SameBoy-old/Cocoa/KeyboardShortcutPrivateAPIs.h new file mode 100644 index 000000000..a80dfde97 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/KeyboardShortcutPrivateAPIs.h @@ -0,0 +1,26 @@ +#ifndef KeyboardShortcutPrivateAPIs_h +#define KeyboardShortcutPrivateAPIs_h + +/* These are private APIs, but they are a very simple and comprehensive way + to convert a key equivalent to its display name. */ + +@interface NSKeyboardShortcut : NSObject + ++ (id)shortcutWithPreferencesEncoding:(NSString *)encoding; ++ (id)shortcutWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask; +- (id)initWithKeyEquivalent:(NSString *)key_equivalent modifierMask:(unsigned long long)mask; + +@property(readonly) unsigned long long modifierMask; +@property(readonly) NSString *keyEquivalent; +@property(readonly) NSString *preferencesEncoding; +@property(readonly) NSString *localizedModifierMaskDisplayName; +@property(readonly) NSString *localizedKeyEquivalentDisplayName; +@property(readonly) NSString *localizedDisplayName; + +@end + +@interface NSPrefPaneUtils : NSObject ++ (id)stringForVirtualKey:(unsigned int)key modifiers:(unsigned int)flags; +@end + +#endif \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Cocoa/License.html b/thirdparty/SameBoy-old/Cocoa/License.html new file mode 100644 index 000000000..c6db03e92 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/License.html @@ -0,0 +1,80 @@ + + + + + + + + + +

SameBoy

+

MIT License

+

Copyright © 2015-2022 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 +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.

+ +

Third-party Libraries

+

HexFiend

+

Copyright © 2005-2009, Peter Ammon +All rights reserved.

+ +

Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met:

+ +
    +
  • Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer.
  • +
  • Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution.
  • +
+ +

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE

+ + \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Cocoa/MainMenu.xib b/thirdparty/SameBoy-old/Cocoa/MainMenu.xib new file mode 100644 index 000000000..386d2e27e --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/MainMenu.xib @@ -0,0 +1,538 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/NSImageNamedDarkSupport.m b/thirdparty/SameBoy-old/Cocoa/NSImageNamedDarkSupport.m new file mode 100644 index 000000000..73e7b64b7 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/NSImageNamedDarkSupport.m @@ -0,0 +1,49 @@ +#import +#import + +@interface NSImageRep(PrivateAPI) +@property(setter=_setAppearanceName:) NSString *_appearanceName; +@end + +static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name); + +@implementation NSImage(DarkHooks) + ++ (NSImage *)imageNamedWithDark:(NSImageName)name +{ + if (@available(macOS 11.0, *)) { + if (![name containsString:@"~solid"]) { + NSImage *solid = [self imageNamed:[name stringByAppendingString:@"~solid"]]; + [solid setTemplate:true]; + if (solid) return solid; + } + } + NSImage *light = imageNamed(self, _cmd, name); + if (@available(macOS 10.14, *)) { + NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]); + if (!dark) { + return light; + } + NSImage *ret = [[NSImage alloc] initWithSize:light.size]; + for (NSImageRep *rep in light.representations) { + [rep _setAppearanceName:NSAppearanceNameAqua]; + [ret addRepresentation:rep]; + } + for (NSImageRep *rep in dark.representations) { + [rep _setAppearanceName:NSAppearanceNameDarkAqua]; + [ret addRepresentation:rep]; + } + return ret; + } + return light; +} + ++(void)load +{ + if (@available(macOS 10.14, *)) { + imageNamed = (void *)[self methodForSelector:@selector(imageNamed:)]; + method_setImplementation(class_getClassMethod(self, @selector(imageNamed:)), + [self methodForSelector:@selector(imageNamedWithDark:)]); + } +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/NSObject+MavericksCompat.m b/thirdparty/SameBoy-old/Cocoa/NSObject+MavericksCompat.m new file mode 100644 index 000000000..6c065143d --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/NSObject+MavericksCompat.m @@ -0,0 +1,7 @@ +#import +@implementation NSObject (MavericksCompat) +- (instancetype)initWithCoder:(NSCoder *)coder +{ + return [self init]; +} +@end diff --git a/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.h b/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.h new file mode 100644 index 000000000..11aabd2d6 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.h @@ -0,0 +1,6 @@ +#import + +@interface NSString (StringForKey) ++ (NSString *) displayStringForKeyString: (NSString *)key_string; ++ (NSString *) displayStringForKeyCode:(unsigned short) keyCode; +@end diff --git a/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.m b/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.m new file mode 100644 index 000000000..f5a9aa324 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/NSString+StringForKey.m @@ -0,0 +1,46 @@ +#import "NSString+StringForKey.h" +#import "KeyboardShortcutPrivateAPIs.h" +#import + +@implementation NSString (StringForKey) + ++ (NSString *) displayStringForKeyString: (NSString *)key_string +{ + return [[NSKeyboardShortcut shortcutWithKeyEquivalent:key_string modifierMask:0] localizedDisplayName]; +} + ++ (NSString *) displayStringForKeyCode:(unsigned short) keyCode +{ + /* These cases are not handled by stringForVirtualKey */ + switch (keyCode) { + + case kVK_Home: return @"↖"; + case kVK_End: return @"↘"; + case kVK_PageUp: return @"⇞"; + case kVK_PageDown: return @"⇟"; + case kVK_Delete: return @"⌫"; + case kVK_ForwardDelete: return @"⌦"; + case kVK_ANSI_KeypadEnter: return @"⌤"; + case kVK_CapsLock: return @"⇪"; + case kVK_Shift: return @"Left ⇧"; + case kVK_Control: return @"Left ⌃"; + case kVK_Option: return @"Left ⌥"; + case kVK_Command: return @"Left ⌘"; + case kVK_RightShift: return @"Right ⇧"; + case kVK_RightControl: return @"Right ⌃"; + case kVK_RightOption: return @"Right ⌥"; + case kVK_RightCommand: return @"Right ⌘"; + case kVK_Function: return @"fn"; + + /* Label Keypad buttons accordingly */ + default: + if ((keyCode < kVK_ANSI_Keypad0 || keyCode > kVK_ANSI_Keypad9)) { + return [NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]; + } + + case kVK_ANSI_KeypadDecimal: case kVK_ANSI_KeypadMultiply: case kVK_ANSI_KeypadPlus: case kVK_ANSI_KeypadDivide: case kVK_ANSI_KeypadMinus: case kVK_ANSI_KeypadEquals: + return [@"Keypad " stringByAppendingString:[NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/Cocoa/Next.png b/thirdparty/SameBoy-old/Cocoa/Next.png new file mode 100644 index 000000000..eb4d13522 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Next.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Next@2x.png b/thirdparty/SameBoy-old/Cocoa/Next@2x.png new file mode 100644 index 000000000..c6b9d3aca Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Next@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Pause.png b/thirdparty/SameBoy-old/Cocoa/Pause.png new file mode 100644 index 000000000..d81a4f632 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Pause.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Pause@2x.png b/thirdparty/SameBoy-old/Cocoa/Pause@2x.png new file mode 100644 index 000000000..965b40f24 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Pause@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/PkgInfo b/thirdparty/SameBoy-old/Cocoa/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Cocoa/Play.png b/thirdparty/SameBoy-old/Cocoa/Play.png new file mode 100644 index 000000000..fd43bc97f Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Play.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Play@2x.png b/thirdparty/SameBoy-old/Cocoa/Play@2x.png new file mode 100644 index 000000000..a0edda659 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Play@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/PopoverView.xib b/thirdparty/SameBoy-old/Cocoa/PopoverView.xib new file mode 100644 index 000000000..7ccdf4967 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/PopoverView.xib @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/Preferences.xib b/thirdparty/SameBoy-old/Cocoa/Preferences.xib new file mode 100644 index 000000000..57a4755c0 --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/Preferences.xib @@ -0,0 +1,1311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/Previous.png b/thirdparty/SameBoy-old/Cocoa/Previous.png new file mode 100644 index 000000000..9dc6141a8 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Previous.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Previous@2x.png b/thirdparty/SameBoy-old/Cocoa/Previous@2x.png new file mode 100644 index 000000000..f0f7f65cf Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Previous@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Rewind.png b/thirdparty/SameBoy-old/Cocoa/Rewind.png new file mode 100644 index 000000000..9ac45228e Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Rewind.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Rewind@2x.png b/thirdparty/SameBoy-old/Cocoa/Rewind@2x.png new file mode 100644 index 000000000..6cb3417ac Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Rewind@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker.png b/thirdparty/SameBoy-old/Cocoa/Speaker.png new file mode 100644 index 000000000..e6464c928 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker@2x.png b/thirdparty/SameBoy-old/Cocoa/Speaker@2x.png new file mode 100644 index 000000000..a07f21b1e Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker~dark.png b/thirdparty/SameBoy-old/Cocoa/Speaker~dark.png new file mode 100644 index 000000000..0b6d9271a Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker~dark.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker~dark@2x.png b/thirdparty/SameBoy-old/Cocoa/Speaker~dark@2x.png new file mode 100644 index 000000000..78f6b396b Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker~dark@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker~solid.png b/thirdparty/SameBoy-old/Cocoa/Speaker~solid.png new file mode 100644 index 000000000..bd9fce9c5 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker~solid.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Speaker~solid@2x.png b/thirdparty/SameBoy-old/Cocoa/Speaker~solid@2x.png new file mode 100644 index 000000000..2b218275d Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Speaker~solid@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/UpdateWindow.xib b/thirdparty/SameBoy-old/Cocoa/UpdateWindow.xib new file mode 100644 index 000000000..0949af4fe --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/UpdateWindow.xib @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy-old/Cocoa/Updates.png b/thirdparty/SameBoy-old/Cocoa/Updates.png new file mode 100644 index 000000000..e6bbbb6a6 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Updates@2x.png b/thirdparty/SameBoy-old/Cocoa/Updates@2x.png new file mode 100644 index 000000000..a5675ca2c Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Updates~solid.png b/thirdparty/SameBoy-old/Cocoa/Updates~solid.png new file mode 100644 index 000000000..0575ccc44 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates~solid.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Updates~solid@2x.png b/thirdparty/SameBoy-old/Cocoa/Updates~solid@2x.png new file mode 100644 index 000000000..df758e392 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates~solid@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark.png b/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark.png new file mode 100644 index 000000000..bb5f21264 Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark@2x.png b/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark@2x.png new file mode 100644 index 000000000..eb35dad9f Binary files /dev/null and b/thirdparty/SameBoy-old/Cocoa/Updates~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy-old/Cocoa/main.m b/thirdparty/SameBoy-old/Cocoa/main.m new file mode 100644 index 000000000..3eb7a378e --- /dev/null +++ b/thirdparty/SameBoy-old/Cocoa/main.m @@ -0,0 +1,6 @@ +#import + +int main(int argc, const char * argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/thirdparty/SameBoy-old/Core/apu.c b/thirdparty/SameBoy-old/Core/apu.c new file mode 100644 index 000000000..e8d82a1b3 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/apu.c @@ -0,0 +1,1740 @@ +#include +#include +#include +#include +#include +#include "gb.h" + +static const uint8_t duties[] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 0, +}; + +static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_offset) +{ + unsigned multiplier = gb->apu_output.cycles_since_render + cycles_offset - gb->apu_output.last_update[index]; + gb->apu_output.summed_samples[index].left += gb->apu_output.current_sample[index].left * multiplier; + gb->apu_output.summed_samples[index].right += gb->apu_output.current_sample[index].right * multiplier; + gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; +} + +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) +{ + if (gb->model > GB_MODEL_CGB_E) { + /* On the AGB, mixing is done digitally, so there are no per-channel + DACs. Instead, all channels are summed digital regardless of + whatever the DAC state would be on a CGB or earlier model. */ + return true; + } + + switch (index) { + case GB_SQUARE_1: + return gb->io_registers[GB_IO_NR12] & 0xF8; + + case GB_SQUARE_2: + return gb->io_registers[GB_IO_NR22] & 0xF8; + + case GB_WAVE: + return gb->apu.wave_channel.enable; + + case GB_NOISE: + return gb->io_registers[GB_IO_NR42] & 0xF8; + + nodefault; + } + + return false; +} + +static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index) +{ + if (!gb->apu.is_active[index]) return 0; + + switch (index) { + case GB_SQUARE_1: + return gb->apu.square_channels[GB_SQUARE_1].current_volume; + case GB_SQUARE_2: + return gb->apu.square_channels[GB_SQUARE_2].current_volume; + case GB_WAVE: + return 0; + case GB_NOISE: + return gb->apu.noise_channel.current_volume; + + nodefault; + } + return 0; +} + +static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) +{ + + if (gb->model > GB_MODEL_CGB_E) { + /* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different. + A channel that is not connected to a terminal is idenitcal to a connected channel + playing PCM sample 0. */ + gb->apu.samples[index] = value; + + if (gb->apu_output.sample_rate) { + unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + + if (index == GB_WAVE) { + /* For some reason, channel 3 is inverted on the AGB */ + value ^= 0xF; + } + + GB_sample_t output; + uint8_t bias = agb_bias_for_channel(gb, index); + + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + output.right = (0xF - value * 2 + bias) * right_volume; + } + else { + output.right = 0xF * right_volume; + } + + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + output.left = (0xF - value * 2 + bias) * left_volume; + } + else { + output.left = 0xF * left_volume; + } + + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } + + return; + } + + if (value == 0 && gb->apu.samples[index] == 0) return; + + if (!GB_apu_is_DAC_enabled(gb, index)) { + value = gb->apu.samples[index]; + } + else { + gb->apu.samples[index] = value; + } + + if (gb->apu_output.sample_rate) { + unsigned right_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + } + unsigned left_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + } + GB_sample_t output = {(0xF - value * 2) * left_volume, (0xF - value * 2) * right_volume}; + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } +} + +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_CGB_E) { + 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_CGB_E) { + 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_CGB_E && (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}; + + unrolled for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + double multiplier = CH_STEP; + + if (gb->model <= GB_MODEL_CGB_E) { + if (!GB_apu_is_DAC_enabled(gb, i)) { + gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] < 0) { + multiplier = 0; + gb->apu_output.dac_discharge[i] = 0; + } + else { + multiplier *= smooth(gb->apu_output.dac_discharge[i]); + } + } + else { + gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] > 1) { + gb->apu_output.dac_discharge[i] = 1; + } + else { + multiplier *= smooth(gb->apu_output.dac_discharge[i]); + } + } + } + + if (likely(gb->apu_output.last_update[i] == 0)) { + output.left += gb->apu_output.current_sample[i].left * multiplier; + output.right += gb->apu_output.current_sample[i].right * multiplier; + } + else { + refresh_channel(gb, i, 0); + output.left += (signed long) gb->apu_output.summed_samples[i].left * multiplier + / gb->apu_output.cycles_since_render; + output.right += (signed long) gb->apu_output.summed_samples[i].right * multiplier + / gb->apu_output.cycles_since_render; + gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0}; + } + gb->apu_output.last_update[i] = 0; + } + gb->apu_output.cycles_since_render = 0; + + if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return; + + GB_sample_t filtered_output = gb->apu_output.highpass_mode? + (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, + output.right - gb->apu_output.highpass_diff.right} : + output; + + switch (gb->apu_output.highpass_mode) { + case GB_HIGHPASS_OFF: + gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0}; + break; + case GB_HIGHPASS_ACCURATE: + gb->apu_output.highpass_diff = (GB_double_sample_t) + {output.left - filtered_output.left * gb->apu_output.highpass_rate, + output.right - filtered_output.right * gb->apu_output.highpass_rate}; + break; + case GB_HIGHPASS_REMOVE_DC_OFFSET: { + unsigned mask = gb->io_registers[GB_IO_NR51]; + unsigned left_volume = 0; + unsigned right_volume = 0; + unrolled for (unsigned i = GB_N_CHANNELS; i--;) { + if (GB_apu_is_DAC_enabled(gb, i)) { + if (mask & 1) { + left_volume += ((gb->io_registers[GB_IO_NR50] & 7) + 1) * CH_STEP * 0xF; + } + if (mask & 0x10) { + right_volume += (((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1) * CH_STEP * 0xF; + } + } + mask >>= 1; + } + gb->apu_output.highpass_diff = (GB_double_sample_t) + {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:; + } + + } + + + 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); + if (unlikely(gb->apu_output.output_file)) { +#ifdef GB_BIG_ENDIAN + if (gb->apu_output.output_format == GB_AUDIO_FORMAT_WAV) { + filtered_output.left = LE16(filtered_output.left); + filtered_output.right = LE16(filtered_output.right); + } +#endif + if (fwrite(&filtered_output, sizeof(filtered_output), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + gb->apu_output.output_error = errno; + } + } +} + +static void update_square_sample(GB_gameboy_t *gb, unsigned index) +{ + if (gb->apu.square_channels[index].sample_surpressed) { + if (gb->model > GB_MODEL_CGB_E) { + update_sample(gb, index, gb->apu.samples[index], 0); + } + return; + } + + uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; + update_sample(gb, index, + duties[gb->apu.square_channels[index].current_sample_index + duty * 8]? + gb->apu.square_channels[index].current_volume : 0, + 0); +} + +static inline void update_wave_sample(GB_gameboy_t *gb, unsigned cycles) +{ + if (gb->apu.wave_channel.current_sample_index & 1) { + update_sample(gb, GB_WAVE, + (gb->apu.wave_channel.current_sample_byte & 0xF) >> gb->apu.wave_channel.shift, + cycles); + } + else { + update_sample(gb, GB_WAVE, + (gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift, + cycles); + } +} + +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) { + *countdown = value & 7; + } + 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 (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 (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; + } +} + +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, countdown, lock); + _nrx2_glitch(volume, value, 0xFF, countdown, lock); + } + else { + _nrx2_glitch(volume, value, old_value, countdown, lock); + } +} + +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].envelope_clock.locked) return; + 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; + } + } + + if (nrx2 & 8) { + if (gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } + else { + gb->apu.square_channels[index].envelope_clock.locked = true; + } + } + else { + if (gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } + else { + gb->apu.square_channels[index].envelope_clock.locked = true; + } + } + + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); + } +} + +static void tick_noise_envelope(GB_gameboy_t *gb) +{ + uint8_t nr42 = gb->io_registers[GB_IO_NR42]; + + if (gb->apu.noise_channel.envelope_clock.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) { + if (gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } + else { + gb->apu.noise_channel.envelope_clock.locked = true; + } + } + else { + if (gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } + else { + gb->apu.noise_channel.envelope_clock.locked = true; + } + } + + 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); + } +} + +static void trigger_sweep_calculation(GB_gameboy_t *gb) +{ + if ((gb->io_registers[GB_IO_NR10] & 0x70) && gb->apu.square_sweep_countdown == 7) { + if (gb->io_registers[GB_IO_NR10] & 0x07) { + gb->apu.square_channels[GB_SQUARE_1].sample_length = + gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8); + gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF; + } + if (gb->apu.channel_1_restart_hold == 0) { + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); + } + + /* Recalculation and overflow check only occurs after a delay */ + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + 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; + } +} + +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; + return; + } + if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIPPED) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_INACTIVE; + } + else { + gb->apu.div_divider++; + } + + if ((gb->apu.div_divider & 7) == 7) { + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { + 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_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_channels[i].envelope_clock.clock) { + tick_square_envelope(gb, i); + gb->apu.square_channels[i].envelope_clock.clock = false; + } + } + + if (gb->apu.noise_channel.envelope_clock.clock) { + tick_noise_envelope(gb); + gb->apu.noise_channel.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) { + if (gb->apu.square_channels[i].pulse_length) { + if (!--gb->apu.square_channels[i].pulse_length) { + gb->apu.is_active[i] = false; + update_sample(gb, i, 0, 0); + } + } + } + } + + if (gb->apu.wave_channel.length_enabled) { + if (gb->apu.wave_channel.pulse_length) { + if (!--gb->apu.wave_channel.pulse_length) { + if (gb->apu.is_active[GB_WAVE] && gb->model > GB_MODEL_CGB_E) { + if (gb->apu.wave_channel.sample_countdown == 0) { + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)]; + } + else if (gb->apu.wave_channel.sample_countdown == 9) { + // TODO: wtf? + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START]; + } + } + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + } + } + + if (gb->apu.noise_channel.length_enabled) { + if (gb->apu.noise_channel.pulse_length) { + if (!--gb->apu.noise_channel.pulse_length) { + gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); + } + } + } + } + + if ((gb->apu.div_divider & 3) == 3) { + gb->apu.square_sweep_countdown++; + gb->apu.square_sweep_countdown &= 7; + trigger_sweep_calculation(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) { + 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_channel.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; + 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.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.noise_channel.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + cycles_offset); + } +} + +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_CGB_E && (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. */ + 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)) { + uint16_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; + if (likely(!gb->stopped || GB_is_cgb(gb))) { + 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.noise_channel.dmg_delayed_start > cycles) { + gb->apu.noise_channel.dmg_delayed_start -= cycles; + } + else { + /* 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, true); + } + } + /* 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 <= 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; + } + else { + /* APU bug: sweep frequency is checked after adding the sweep delta twice */ + if (gb->apu.channel_1_restart_hold == 0) { + gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; + } + if (gb->io_registers[GB_IO_NR10] & 8) { + gb->apu.sweep_length_addend ^= 0x7FF; + } + if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); + } + gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend; + + 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; + } + } + + unrolled for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { + if (gb->apu.is_active[i]) { + uint16_t cycles_left = cycles; + if (unlikely(gb->apu.square_channels[i].delay)) { + if (gb->apu.square_channels[i].delay < cycles_left) { + gb->apu.square_channels[i].delay = 0; + } + else { + gb->apu.square_channels[i].delay -= cycles_left; + } + } + while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { + cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; + gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; + 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; + } + gb->apu.square_channels[i].did_tick = true; + update_square_sample(gb, i); + } + if (cycles_left) { + gb->apu.square_channels[i].sample_countdown -= cycles_left; + } + } + } + + gb->apu.wave_channel.wave_form_just_read = false; + if (gb->apu.is_active[GB_WAVE]) { + 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; + gb->apu.wave_channel.current_sample_index++; + gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (gb->apu.wave_channel.current_sample_index >> 1)]; + update_wave_sample(gb, cycles - cycles_left); + gb->apu.wave_channel.wave_form_just_read = true; + } + if (cycles_left) { + gb->apu.wave_channel.sample_countdown -= cycles_left; + gb->apu.wave_channel.wave_form_just_read = false; + } + } + else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model <= GB_MODEL_CGB_E) { + uint16_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + cycles_left -= gb->apu.wave_channel.sample_countdown + 1; + gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; + if (cycles_left) { + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; + } + else { + 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 + if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) { + 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; + } + // 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; + 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 */ + if (new_bit && !old_bit) { + 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) { + gb->apu.noise_channel.counter_countdown -= cycles_left; + gb->apu.noise_channel.countdown_reloaded = false; + } + else { + gb->apu.noise_channel.countdown_reloaded = true; + } + } + } + + if (gb->apu_output.sample_rate) { + gb->apu_output.cycles_since_render += cycles; + + if (gb->apu_output.sample_cycles >= clock_rate) { + gb->apu_output.sample_cycles -= clock_rate; + 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) +{ + memset(&gb->apu, 0, sizeof(gb->apu)); + gb->apu.lf_div = 1; + gb->apu.wave_channel.shift = 4; + /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, + the first DIV/APU event is skipped. */ + if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP; + gb->apu.div_divider = 1; + } + gb->apu.square_channels[GB_SQUARE_1].sample_countdown = -1; + gb->apu.square_channels[GB_SQUARE_2].sample_countdown = -1; +} + +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) +{ + GB_apu_run(gb, true); + if (reg == GB_IO_NR52) { + uint8_t value = 0; + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + value >>= 1; + if (gb->apu.is_active[i]) { + value |= 0x8; + } + } + if (gb->apu.global_enable) { + value |= 0x80; + } + value |= 0x70; + return value; + } + + static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = { + /* NRX0 NRX1 NRX2 NRX3 NRX4 */ + 0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X + 0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X + 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X + 0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X + 0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused + // Wave RAM + 0, /* ... */ + }; + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { + if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { + return 0xFF; + } + if (gb->model > GB_MODEL_CGB_E) { + return 0xFF; + } + reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; + } + + return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; +} + +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) { + /* 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. */ + 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; // TODO: Only my CGB-B does that! Others behave like C! + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + if (effective_counter & 0x2000) { + effective_counter |= 0x20; + } + break; + case GB_MODEL_DMG_B: + case GB_MODEL_MGB: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_C: + if (effective_counter & 8) { + effective_counter |= 0xE; // Sometimes F on some instances + } + 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) { + if ((gb->io_registers[GB_IO_NR43] & 8)) { + effective_counter |= 0x400; + } + effective_counter |= 0x8; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + if (effective_counter & 0x2000) { + effective_counter |= 0x20; + } + break; + case GB_MODEL_CGB_D: + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird + 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; + case GB_MODEL_CGB_E: + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird + effective_counter |= 0xFF; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + break; + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + /* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple + pattern like the other revisions. */ + /* For the most part, AGS seems to do: + 0x20 -> 0xA0 + 0x200 -> 0xA00 + 0x1000 -> 0x1010, but only if wide + */ + break; + } + return effective_counter; +} + +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 && + reg != GB_IO_NR21 && + reg != GB_IO_NR31 && + reg != GB_IO_NR41 + ) + )) { + return; + } + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { + if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model > GB_MODEL_CGB_E) { + return; + } + reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; + } + + /* Todo: this can and should be rewritten with a function table. */ + switch (reg) { + /* Globals */ + case GB_IO_NR50: + case GB_IO_NR51: + gb->io_registers[reg] = value; + /* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/ + /* We call update_samples with the current value so the APU output is updated with the new outputs */ + for (unsigned i = GB_N_CHANNELS; i--;) { + int8_t sample = gb->apu.samples[i]; + gb->apu.samples[i] = 0x10; // Invalidate to force update + update_sample(gb, i, sample, 0); + } + break; + case GB_IO_NR52: { + + 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); + gb->apu.global_enable = true; + } + else if (!(value & 0x80) && gb->apu.global_enable) { + for (unsigned i = GB_N_CHANNELS; i--;) { + update_sample(gb, i, 0, 0); + } + memset(&gb->apu, 0, sizeof(gb->apu)); + memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); + gb->apu.global_enable = false; + } + + if (!GB_is_cgb(gb) && (value & 0x80)) { + gb->apu.square_channels[0].pulse_length = old_pulse_lengths[0]; + gb->apu.square_channels[1].pulse_length = old_pulse_lengths[1]; + gb->apu.wave_channel.pulse_length = old_pulse_lengths[2]; + gb->apu.noise_channel.pulse_length = old_pulse_lengths[3]; + } + } + break; + + /* Square channels */ + case GB_IO_NR10:{ + 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.channel1_completed_addend + old_negate > 0x7FF && + !(value & 8)) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, 0); + } + trigger_sweep_calculation(gb); + break; + } + + case GB_IO_NR11: + case GB_IO_NR21: { + unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; + gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3F)); + if (!gb->apu.global_enable) { + value &= 0x3F; + } + break; + } + + case GB_IO_NR12: + case GB_IO_NR22: { + unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; + if ((value & 0xF8) == 0) { + /* This disables the DAC */ + gb->io_registers[reg] = value; + gb->apu.is_active[index] = false; + 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], &gb->apu.square_channels[index].volume_countdown, + &gb->apu.square_channels[index].envelope_clock); + update_square_sample(gb, index); + } + + break; + } + + case GB_IO_NR13: + case GB_IO_NR23: { + unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; + gb->apu.square_channels[index].sample_length &= ~0xFF; + gb->apu.square_channels[index].sample_length |= value & 0xFF; + break; + } + + 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 from ≥$700 to <$700, 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. */ + if ((value & 0x80) == 0 && gb->apu.is_active[index] && (gb->io_registers[reg] & 0x7) == 7 && (value & 7) != 7) { + /* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on + double speed. */ + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) { + if (gb->apu.square_channels[index].did_tick && + gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) { + gb->apu.square_channels[index].current_sample_index--; + gb->apu.square_channels[index].current_sample_index &= 7; + gb->apu.square_channels[index].sample_surpressed = false; + } + } + } + + 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) { + /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by + turning the APU off. */ + gb->apu.square_channels[index].envelope_clock.locked = false; + gb->apu.square_channels[index].envelope_clock.clock = false; + gb->apu.square_channels[index].did_tick = false; + bool force_unsurpressed = false; + if (!gb->apu.is_active[index]) { + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) { + if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - gb->apu.square_channels[index].delay) / 2) & 0x400)) { + gb->apu.square_channels[index].current_sample_index++; + gb->apu.square_channels[index].current_sample_index &= 0x7; + force_unsurpressed = true; + } + } + gb->apu.square_channels[index].delay = 6 - gb->apu.lf_div; + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.square_channels[index].sample_countdown += 2; + gb->apu.square_channels[index].delay += 2; + } + } + else { + unsigned extra_delay = 0; + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) { + if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1 - gb->apu.square_channels[index].delay) / 2) & 0x400)) { + gb->apu.square_channels[index].current_sample_index++; + gb->apu.square_channels[index].current_sample_index &= 0x7; + gb->apu.square_channels[index].sample_surpressed = false; + } + /* 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].sample_surpressed)) { + extra_delay += 2; + } + } + /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ + gb->apu.square_channels[index].delay = 4 - gb->apu.lf_div + extra_delay; + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.square_channels[index].sample_countdown += 2; + gb->apu.square_channels[index].delay += 2; + } + } + gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; + /* 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[index]) { + update_square_sample(gb, index); + } + + gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7; + + if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { + gb->apu.is_active[index] = true; + update_sample(gb, index, 0, 0); + gb->apu.square_channels[index].sample_surpressed = true && !force_unsurpressed; + } + if (gb->apu.square_channels[index].pulse_length == 0) { + gb->apu.square_channels[index].pulse_length = 0x40; + gb->apu.square_channels[index].length_enabled = false; + } + + 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; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + /* 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; + if (!was_active) { + gb->apu.square_sweep_calculate_countdown += 2; + } + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); + } + else { + gb->apu.sweep_length_addend = 0; + } + 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; + } + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + 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) { + gb->apu.square_channels[index].pulse_length--; + if (gb->apu.square_channels[index].pulse_length == 0) { + if (value & 0x80) { + gb->apu.square_channels[index].pulse_length = 0x3F; + } + else { + gb->apu.is_active[index] = false; + update_sample(gb, index, 0, 0); + } + } + } + gb->apu.square_channels[index].length_enabled = value & 0x40; + break; + } + + /* Wave channel */ + case GB_IO_NR30: + gb->apu.wave_channel.enable = value & 0x80; + if (!gb->apu.wave_channel.enable) { + 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_CGB_E) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->pc & 0xF)]; + } + else if (gb->apu.wave_channel.wave_form_just_read && gb->model <= GB_MODEL_CGB_C) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (GB_IO_NR30 & 0xF)]; + } + } + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + break; + case GB_IO_NR31: + gb->apu.wave_channel.pulse_length = (0x100 - value); + break; + case GB_IO_NR32: + gb->apu.wave_channel.shift = (const uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; + if (gb->apu.is_active[GB_WAVE]) { + update_wave_sample(gb, 0); + } + break; + case GB_IO_NR33: + gb->apu.wave_channel.sample_length &= ~0xFF; + gb->apu.wave_channel.sample_length |= value & 0xFF; + break; + case GB_IO_NR34: + gb->apu.wave_channel.sample_length &= 0xFF; + gb->apu.wave_channel.sample_length |= (value & 7) << 8; + if (value & 0x80) { + 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) && + gb->apu.is_active[GB_WAVE] && + gb->apu.wave_channel.sample_countdown == 0) { + unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; + + /* This glitch varies between models and even specific instances: + 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 && gb->model != GB_MODEL_MGB) { + gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; + } + else { + memcpy(gb->io_registers + GB_IO_WAV_START, + gb->io_registers + GB_IO_WAV_START + (offset & ~3), + 4); + } + } + gb->apu.wave_channel.current_sample_index = 0; + if (gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START]; + } + if (gb->apu.wave_channel.enable) { + gb->apu.is_active[GB_WAVE] = true; + update_sample(gb, GB_WAVE, + (gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift, + 0); + } + gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; + if (gb->apu.wave_channel.pulse_length == 0) { + gb->apu.wave_channel.pulse_length = 0x100; + gb->apu.wave_channel.length_enabled = false; + } + /* Note that we don't change the sample just yet! This was verified on hardware. */ + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + 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) { + gb->apu.wave_channel.pulse_length--; + if (gb->apu.wave_channel.pulse_length == 0) { + if (value & 0x80) { + gb->apu.wave_channel.pulse_length = 0xFF; + } + else { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + } + } + gb->apu.wave_channel.length_enabled = value & 0x40; + + break; + + /* Noise Channel */ + + case GB_IO_NR41: { + gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3F)); + break; + } + + case GB_IO_NR42: { + if ((value & 0xF8) == 0) { + /* This disables the DAC */ + gb->io_registers[reg] = value; + gb->apu.is_active[GB_NOISE] = false; + 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], &gb->apu.noise_channel.volume_countdown, + &gb->apu.noise_channel.envelope_clock); + update_sample(gb, GB_NOISE, + gb->apu.noise_channel.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + break; + } + + case GB_IO_NR43: { + gb->apu.noise_channel.narrow = value & 8; + 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.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) { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (const uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + else { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (const uint8_t[]){2, 1, 4, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + gb->apu.noise_channel.delta = 0; + } + /* Step LFSR */ + if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { + 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; + } + + case GB_IO_NR44: { + if (value & 0x80) { + 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.noise_channel.dmg_delayed_start = 6; + } + else { + unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; + if (!divisor) divisor = 2; + gb->apu.noise_channel.delta = 0; + gb->apu.noise_channel.counter_countdown = divisor + 4; + if (divisor == 2) { + 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 { + if (gb->model <= GB_MODEL_CGB_C) { + gb->apu.noise_channel.counter_countdown += (const uint8_t[]){2, 1, 4, 3}[gb->apu.noise_channel.alignment & 3]; + } + else { + gb->apu.noise_channel.counter_countdown += (const 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.noise_channel.delta = 2; + } + else { + gb->apu.noise_channel.counter_countdown -= 4; + } + } + } + + /* 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 { + gb->apu.noise_channel.counter_countdown -= 2; + } + } + + 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.noise_channel.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + gb->apu.noise_channel.lfsr = 0; + 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) { + 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; + } + } + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + if ((value & 0x40) && + !gb->apu.noise_channel.length_enabled && + (gb->apu.div_divider & 1) && + gb->apu.noise_channel.pulse_length) { + gb->apu.noise_channel.pulse_length--; + if (gb->apu.noise_channel.pulse_length == 0) { + if (value & 0x80) { + gb->apu.noise_channel.pulse_length = 0x3F; + } + else { + gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); + } + } + } + gb->apu.noise_channel.length_enabled = value & 0x40; + break; + } + } + gb->io_registers[reg] = value; +} + +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) +{ + + gb->apu_output.sample_rate = sample_rate; + if (sample_rate) { + gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); + } +} + +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample) +{ + + if (cycles_per_sample == 0) { + GB_set_sample_rate(gb, 0); + return; + } + 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); +} + +unsigned GB_get_sample_rate(GB_gameboy_t *gb) +{ + return gb->apu_output.sample_rate; +} + +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) +{ + gb->apu_output.sample_callback = callback; +} + +void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) +{ + gb->apu_output.highpass_mode = mode; +} + +void GB_set_interference_volume(GB_gameboy_t *gb, double volume) +{ + gb->apu_output.interference_volume = volume; +} + +typedef struct __attribute__((packed)) { + uint32_t format_chunk; // = BE32('FORM') + uint32_t size; // = BE32(file size - 8) + uint32_t format; // = BE32('AIFC') + + uint32_t fver_chunk; // = BE32('FVER') + uint32_t fver_size; // = BE32(4) + uint32_t fver; + + uint32_t comm_chunk; // = BE32('COMM') + uint32_t comm_size; // = BE32(0x18) + + uint16_t channels; // = BE16(2) + uint32_t samples_per_channel; // = BE32(total number of samples / 2) + uint16_t bit_depth; // = BE16(16) + uint16_t frequency_exponent; + uint64_t frequency_significand; + uint32_t compression_type; // = 'NONE' (BE) or 'twos' (LE) + uint16_t compression_name; // = 0 + + uint32_t ssnd_chunk; // = BE32('SSND') + uint32_t ssnd_size; // = BE32(length of samples - 8) + uint32_t ssnd_offset; // = 0 + uint32_t ssnd_block; // = 0 +} aiff_header_t; + +typedef struct __attribute__((packed)) { + uint32_t marker; // = BE32('RIFF') + uint32_t size; // = LE32(file size - 8) + uint32_t type; // = BE32('WAVE') + + uint32_t fmt_chunk; // = BE32('fmt ') + uint32_t fmt_size; // = LE16(16) + uint16_t format; // = LE16(1) + uint16_t channels; // = LE16(2) + uint32_t sample_rate; // = LE32(sample_rate) + uint32_t byte_rate; // = LE32(sample_rate * 4) + uint16_t frame_size; // = LE32(4) + uint16_t bit_depth; // = LE16(16) + + uint32_t data_chunk; // = BE32('data') + uint32_t data_size; // = LE32(length of samples) +} wav_header_t; + + +int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format) +{ + if (gb->apu_output.sample_rate == 0) { + return EINVAL; + } + + if (gb->apu_output.output_file) { + GB_stop_audio_recording(gb); + } + gb->apu_output.output_file = fopen(path, "wb"); + if (!gb->apu_output.output_file) return errno; + + gb->apu_output.output_format = format; + switch (format) { + case GB_AUDIO_FORMAT_RAW: + return 0; + case GB_AUDIO_FORMAT_AIFF: { + aiff_header_t header = {0,}; + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return errno; + } + return 0; + } + case GB_AUDIO_FORMAT_WAV: { + wav_header_t header = {0,}; + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return errno; + } + return 0; + } + default: + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return EINVAL; + } +} +int GB_stop_audio_recording(GB_gameboy_t *gb) +{ + if (!gb->apu_output.output_file) { + int ret = gb->apu_output.output_error ?: -1; + gb->apu_output.output_error = 0; + return ret; + } + gb->apu_output.output_error = 0; + switch (gb->apu_output.output_format) { + case GB_AUDIO_FORMAT_RAW: + break; + case GB_AUDIO_FORMAT_AIFF: { + size_t file_size = ftell(gb->apu_output.output_file); + size_t frames = (file_size - sizeof(aiff_header_t)) / sizeof(GB_sample_t); + aiff_header_t header = { + .format_chunk = BE32('FORM'), + .size = BE32(file_size - 8), + .format = BE32('AIFC'), + + .fver_chunk = BE32('FVER'), + .fver_size = BE32(4), + .fver = BE32(0xA2805140), + + .comm_chunk = BE32('COMM'), + .comm_size = BE32(0x18), + .channels = BE16(2), + .samples_per_channel = BE32(frames), + .bit_depth = BE16(16), +#ifdef GB_BIG_ENDIAN + .compression_type = 'NONE', +#else + .compression_type = 'twos', +#endif + .compression_name = 0, + .ssnd_chunk = BE32('SSND'), + .ssnd_size = BE32(frames * sizeof(GB_sample_t) - 8), + .ssnd_offset = 0, + .ssnd_block = 0, + }; + + uint64_t significand = gb->apu_output.sample_rate; + uint16_t exponent = 0x403E; + while ((int64_t)significand > 0) { + significand <<= 1; + exponent--; + } + header.frequency_exponent = BE16(exponent); + header.frequency_significand = BE64(significand); + + fseek(gb->apu_output.output_file, 0, SEEK_SET); + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + gb->apu_output.output_error = errno; + } + break; + } + case GB_AUDIO_FORMAT_WAV: { + size_t file_size = ftell(gb->apu_output.output_file); + size_t frames = (file_size - sizeof(wav_header_t)) / sizeof(GB_sample_t); + wav_header_t header = { + .marker = BE32('RIFF'), + .size = LE32(file_size - 8), + .type = BE32('WAVE'), + + .fmt_chunk = BE32('fmt '), + .fmt_size = LE16(16), + .format = LE16(1), + .channels = LE16(2), + .sample_rate = LE32(gb->apu_output.sample_rate), + .byte_rate = LE32(gb->apu_output.sample_rate * 4), + .frame_size = LE32(4), + .bit_depth = LE16(16), + + .data_chunk = BE32('data'), + .data_size = LE32(frames * sizeof(GB_sample_t)), + }; + + fseek(gb->apu_output.output_file, 0, SEEK_SET); + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + gb->apu_output.output_error = errno; + } + break; + } + } + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + + int ret = gb->apu_output.output_error; + gb->apu_output.output_error = 0; + return ret; +} diff --git a/thirdparty/SameBoy-old/Core/apu.h b/thirdparty/SameBoy-old/Core/apu.h new file mode 100644 index 000000000..5cbfbf2f9 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/apu.h @@ -0,0 +1,196 @@ +#ifndef apu_h +#define apu_h +#include +#include +#include +#include +#include "defs.h" + +#ifdef GB_INTERNAL +/* Speed = 1 / Length (in seconds) */ +#define DAC_DECAY_SPEED 20000 +#define DAC_ATTACK_SPEED 20000 + + +/* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ +#ifdef WIIU +/* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/ +#define MAX_CH_AMP (0xFF0 / 2) +#else +#define MAX_CH_AMP 0xFF0 +#endif +#define CH_STEP (MAX_CH_AMP/0xF/8) +#endif + + + +/* APU ticks are 2MHz, triggered by an internal APU clock. */ + +typedef struct +{ + int16_t left; + int16_t right; +} GB_sample_t; + +typedef struct +{ + double left; + double right; +} GB_double_sample_t; + +enum GB_CHANNELS { + GB_SQUARE_1, + GB_SQUARE_2, + GB_WAVE, + GB_NOISE, + 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 +{ + bool global_enable; + uint16_t apu_cycles; + + uint8_t samples[GB_N_CHANNELS]; + bool is_active[GB_N_CHANNELS]; + + uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided + // once more to generate 128Hz and 64Hz clocks + + uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide + // need to divide the signal. + + uint8_t square_sweep_countdown; // In 128Hz + uint8_t square_sweep_calculate_countdown; // In 2 MHz + uint16_t sweep_length_addend; + 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; + 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; + uint8_t delay; // Hack for CGB D/E phantom step due to how sample_countdown is implemented in SameBoy + bool did_tick; + } square_channels[2]; + + struct { + bool enable; // NR30 + uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks + uint8_t shift; // NR32 + uint16_t sample_length; // NR33, NR34, in APU ticks + bool length_enabled; // NR34 + + 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. + bool wave_form_just_read; + bool pulsed; + uint8_t bugged_read_countdown; + } wave_channel; + + struct { + uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks + uint8_t current_volume; // Reloaded from NR42 + uint8_t volume_countdown; // Reloaded from NR42 + uint16_t lfsr; + bool narrow; + + uint8_t counter_countdown; // Counts from 0-7 to 0 to tick counter (Scaled from 512KHz to 2MHz) + 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; + + 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 +} GB_apu_t; + +typedef enum { + GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset + GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware + GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform + GB_HIGHPASS_MAX +} GB_highpass_mode_t; + +typedef enum { + GB_AUDIO_FORMAT_RAW, // Native endian + GB_AUDIO_FORMAT_AIFF, // Native endian + GB_AUDIO_FORMAT_WAV, +} GB_audio_format_t; + +typedef struct { + unsigned sample_rate; + + 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; + unsigned last_update[GB_N_CHANNELS]; + GB_sample_t current_sample[GB_N_CHANNELS]; + GB_sample_t summed_samples[GB_N_CHANNELS]; + double dac_discharge[GB_N_CHANNELS]; + + GB_highpass_mode_t highpass_mode; + double highpass_rate; + GB_double_sample_t highpass_diff; + + GB_sample_callback_t sample_callback; + + double interference_volume; + double interference_highpass; + + FILE *output_file; + GB_audio_format_t output_format; + int output_error; +} GB_apu_output_t; + +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); +unsigned GB_get_sample_rate(GB_gameboy_t *gb); +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ +void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); +void GB_set_interference_volume(GB_gameboy_t *gb, double volume); +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); +int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format); +int GB_stop_audio_recording(GB_gameboy_t *gb); +#ifdef GB_INTERNAL +internal bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); +internal void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); +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, bool force); +#endif + +#endif /* apu_h */ diff --git a/thirdparty/SameBoy-old/Core/camera.c b/thirdparty/SameBoy-old/Core/camera.c new file mode 100644 index 000000000..22ecf5de0 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/camera.c @@ -0,0 +1,164 @@ +#include "gb.h" + +static uint32_t noise_seed = 0; + +/* This is not a complete emulation of the camera chip. Only the features used by the Game Boy Camera ROMs are supported. + We also do not emulate the timing of the real cart when a webcam is used, as it might be actually faster than the webcam. */ + +static uint8_t generate_noise(uint8_t x, uint8_t y) +{ + uint32_t value = (x * 151 + y * 149) ^ noise_seed; + uint32_t hash = 0; + + while (value) { + hash <<= 1; + if (hash & 0x100) { + hash ^= 0x101; + } + if (value & 0x80000000) { + hash ^= 0xA1; + } + value <<= 1; + } + return hash; +} + +static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + if (x >= 128) { + x = 0; + } + if (y >= 112) { + y = 0; + } + + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y)); + + static const double gain_values[] = + {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + /* Multiply color by gain value */ + color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F]; + + + /* Color is multiplied by the exposure register to simulate exposure. */ + color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; + + return color; +} + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) +{ + uint8_t tile_x = addr / 0x10 % 0x10; + uint8_t tile_y = addr / 0x10 / 0x10; + + uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8; + uint8_t bit = addr & 1; + + uint8_t ret = 0; + + for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { + + long color = get_processed_color(gb, x, y); + + static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5}; + double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7]; + if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) { + color += (color * 4) * edge_enhancement_ratio; + color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio; + } + + + /* The camera's registers are used as a threshold pattern, which defines the dithering */ + uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; + + if (color < gb->camera_registers[pattern_base]) { + color = 3; + } + else if (color < gb->camera_registers[pattern_base + 1]) { + color = 2; + } + else if (color < gb->camera_registers[pattern_base + 2]) { + color = 1; + } + else { + color = 0; + } + + ret <<= 1; + ret |= (color >> bit) & 1; + } + + return ret; +} + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback) +{ + gb->camera_get_pixel_callback = callback; +} + +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback) +{ + if (gb->camera_countdown > 0 && callback) { + GB_log(gb, "Camera update request callback set while camera was proccessing, clearing camera countdown.\n"); + gb->camera_countdown = 0; + GB_camera_updated(gb); + } + + gb->camera_update_request_callback = callback; +} + +void GB_camera_updated(GB_gameboy_t *gb) +{ + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1; +} + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + addr &= 0x7F; + if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { + value &= 0x7; + noise_seed = GB_random(); + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + if (gb->camera_update_request_callback) { + gb->camera_update_request_callback(gb); + } + else { + /* If no callback is set, wait the amount of time the real camera would take before clearing the busy bit */ + uint16_t exposure = (gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) | gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]; + gb->camera_countdown = 129792 + ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x80)? 0 : 2048) + (exposure * 64) + (gb->camera_alignment & 4); + } + } + + if (!(value & 1) && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + /* We don't support cancelling a camera shoot */ + GB_log(gb, "ROM attempted to cancel camera shoot, which is currently not supported. The camera shoot will not be cancelled.\n"); + value |= 1; + } + + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] = value; + } + else { + if (addr >= 0x36) { + GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value); + return; + } + gb->camera_registers[addr] = value; + } +} +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) +{ + if ((addr & 0x7F) == 0) { + return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS]; + } + return 0; +} diff --git a/thirdparty/SameBoy-old/Core/camera.h b/thirdparty/SameBoy-old/Core/camera.h new file mode 100644 index 000000000..1461f3adf --- /dev/null +++ b/thirdparty/SameBoy-old/Core/camera.h @@ -0,0 +1,29 @@ +#ifndef camera_h +#define camera_h +#include +#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); + +enum { + GB_CAMERA_SHOOT_AND_1D_FLAGS = 0, + GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1, + GB_CAMERA_EXPOSURE_HIGH = 2, + GB_CAMERA_EXPOSURE_LOW = 3, + GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4, + GB_CAMERA_DITHERING_PATTERN_START = 6, + GB_CAMERA_DITHERING_PATTERN_END = 0x35, +}; + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr); + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback); +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback); + +void GB_camera_updated(GB_gameboy_t *gb); + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr); + +#endif diff --git a/thirdparty/SameBoy-old/Core/cheats.c b/thirdparty/SameBoy-old/Core/cheats.c new file mode 100644 index 000000000..8b5a7a0ee --- /dev/null +++ b/thirdparty/SameBoy-old/Core/cheats.c @@ -0,0 +1,314 @@ +#include "gb.h" +#include "cheats.h" +#include +#include +#include + +static inline uint8_t hash_addr(uint16_t addr) +{ + return addr; +} + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) +{ + 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 (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)) { + if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) { + *value = cheat->value; + break; + } + } + } + } +} + +bool GB_cheats_enabled(GB_gameboy_t *gb) +{ + return gb->cheat_enabled; +} + +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled) +{ + gb->cheat_enabled = enabled; +} + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = malloc(sizeof(*cheat)); + cheat->address = address; + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + 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(gb->cheats[0])); + gb->cheats[gb->cheat_count - 1] = cheat; + + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } +} + +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size) +{ + *size = gb->cheat_count; + return (void *)gb->cheats; +} +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) +{ + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == cheat) { + gb->cheats[i] = gb->cheats[--gb->cheat_count]; + if (gb->cheat_count == 0) { + free(gb->cheats); + gb->cheats = NULL; + } + else { + gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0])); + } + break; + } + } + + 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]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + + free((void *)cheat); +} + +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled) +{ + uint8_t dummy; + /* GameShark */ + { + uint8_t bank; + uint8_t value; + uint16_t address; + if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { + if (bank >= 0x80) { + bank &= 0xF; + } + GB_add_cheat(gb, description, address, bank, value, 0, false, enabled); + return true; + } + } + + /* GameGenie */ + { + char stripped_cheat[10] = {0,}; + for (unsigned i = 0; i < 9 && *cheat; i++) { + stripped_cheat[i] = *(cheat++); + while (*cheat == '-') { + cheat++; + } + } + + // Delete the 7th character; + stripped_cheat[7] = stripped_cheat[8]; + stripped_cheat[8] = 0; + + uint8_t old_value; + uint8_t value; + uint16_t address; + if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6); + old_value ^= 0xBA; + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled); + return true; + } + + if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled); + return true; + } + } + return false; +} + +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = NULL; + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == _cheat) { + cheat = gb->cheats[i]; + break; + } + } + + assert(cheat); + + if (cheat->address != address) { + /* Remove from old bucket */ + 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]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + cheat->address = address; + + /* Add to new bucket */ + hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } + } + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + cheat->enabled = enabled; + if (description != cheat->description) { + strncpy(cheat->description, description, sizeof(cheat->description)); + cheat->description[sizeof(cheat->description) - 1] = 0; + } +} + +#define CHEAT_MAGIC 'SBCh' + +void GB_load_cheats(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + uint32_t magic = 0; + uint32_t struct_size = 0; + fread(&magic, sizeof(magic), 1, f); + fread(&struct_size, sizeof(struct_size), 1, f); + if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) { + GB_log(gb, "The file is not a SameBoy cheat database"); + return; + } + + if (struct_size != sizeof(GB_cheat_t)) { + GB_log(gb, "This cheat database is not compatible with this version of SameBoy"); + return; + } + + // Remove all cheats first + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } + + GB_cheat_t cheat; + while (fread(&cheat, sizeof(cheat), 1, f)) { + if (magic != CHEAT_MAGIC) { + cheat.address = __builtin_bswap16(cheat.address); + cheat.bank = __builtin_bswap16(cheat.bank); + } + cheat.description[sizeof(cheat.description) - 1] = 0; + GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled); + } + + return; +} + +int GB_save_cheats(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cheat_count) return 0; // Nothing to save. + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno)); + return errno; + } + + uint32_t magic = CHEAT_MAGIC; + uint32_t struct_size = sizeof(GB_cheat_t); + + if (fwrite(&magic, sizeof(magic), 1, f) != 1) { + fclose(f); + return EIO; + } + + if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) { + fclose(f); + return EIO; + } + + for (size_t i = 0; i cheat_count; i++) { + if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) { + fclose(f); + return EIO; + } + } + + errno = 0; + fclose(f); + return errno; +} diff --git a/thirdparty/SameBoy-old/Core/cheats.h b/thirdparty/SameBoy-old/Core/cheats.h new file mode 100644 index 000000000..f9c076c64 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/cheats.h @@ -0,0 +1,42 @@ +#ifndef cheats_h +#define cheats_h +#include "defs.h" + +#define GB_CHEAT_ANY_BANK 0xFFFF + +typedef struct GB_cheat_s GB_cheat_t; + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled); +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); +bool GB_cheats_enabled(GB_gameboy_t *gb); +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled); +void GB_load_cheats(GB_gameboy_t *gb, const char *path); +int GB_save_cheats(GB_gameboy_t *gb, const char *path); + +#ifdef GB_INTERNAL +#ifdef GB_DISABLE_CHEATS +#define GB_apply_cheat(...) +#else +internal void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); +#endif +#endif + +typedef struct { + size_t size; + GB_cheat_t *cheats[]; +} GB_cheat_hash_t; + +struct GB_cheat_s { + uint16_t address; + uint16_t bank; + uint8_t value; + uint8_t old_value; + bool use_old_value; + bool enabled; + char description[128]; +}; + +#endif diff --git a/thirdparty/SameBoy-old/Core/debugger.c b/thirdparty/SameBoy-old/Core/debugger.c new file mode 100644 index 000000000..2bec9e65f --- /dev/null +++ b/thirdparty/SameBoy-old/Core/debugger.c @@ -0,0 +1,2750 @@ +#include +#include +#include +#include "gb.h" + +typedef struct { + bool has_bank; + uint16_t bank:9; + uint16_t value; +} value_t; + +typedef struct { + enum { + LVALUE_MEMORY, + LVALUE_MEMORY16, + LVALUE_REG16, + LVALUE_REG_H, + LVALUE_REG_L, + } kind; + union { + uint16_t *register_address; + value_t memory_address; + }; +} lvalue_t; + +#define VALUE_16(x) ((value_t){false, 0, (x)}) + +struct GB_breakpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; + bool is_jump_to; +}; + +#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +#define GB_WATCHPOINT_R (1) +#define GB_WATCHPOINT_W (2) + +struct GB_watchpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; + uint8_t flags; +}; + +#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +typedef struct { + uint16_t rom0_bank; + uint16_t rom_bank; + uint8_t mbc_ram_bank; + bool mbc_ram_enable; + uint8_t ram_bank; + uint8_t vram_bank; +} banking_state_t; + +static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + state->rom0_bank = gb->mbc_rom0_bank; + state->rom_bank = gb->mbc_rom_bank; + state->mbc_ram_bank = gb->mbc_ram_bank; + state->mbc_ram_enable = gb->mbc_ram_enable; + state->ram_bank = gb->cgb_ram_bank; + state->vram_bank = gb->cgb_vram_bank; +} + +static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + + gb->mbc_rom0_bank = state->rom0_bank; + gb->mbc_rom_bank = state->rom_bank; + gb->mbc_ram_bank = state->mbc_ram_bank; + gb->mbc_ram_enable = state->mbc_ram_enable; + gb->cgb_ram_bank = state->ram_bank; + gb->cgb_vram_bank = state->vram_bank; +} + +static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) +{ + gb->mbc_rom0_bank = bank; + gb->mbc_rom_bank = bank; + gb->mbc_ram_bank = bank; + gb->mbc_ram_enable = true; + if (GB_is_cgb(gb)) { + gb->cgb_ram_bank = bank & 7; + gb->cgb_vram_bank = bank & 1; + if (gb->cgb_ram_bank == 0) { + gb->cgb_ram_bank = 1; + } + } +} + +static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) +{ + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); + + if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + if (!symbol) { + snprintf(output, sizeof(output), "$%04x", value); + } + + else if (symbol->addr == value) { + if (prefer_name) { + snprintf(output, sizeof(output), "%s ($%04x)", symbol->name, value); + } + else { + snprintf(output, sizeof(output), "$%04x (%s)", value, symbol->name); + } + } + + else { + if (prefer_name) { + snprintf(output, sizeof(output), "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + } + else { + snprintf(output, sizeof(output), "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + } + } + return output; +} + +static GB_symbol_map_t *get_symbol_map(GB_gameboy_t *gb, uint16_t bank) +{ + if (bank >= gb->n_symbol_maps) { + return NULL; + } + return gb->bank_symbols[bank]; +} + +static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) +{ + if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); + + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value); + + if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + if (!symbol) { + snprintf(output, sizeof(output), "$%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); + } + else { + snprintf(output, sizeof(output), "$%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); + } + else { + snprintf(output, sizeof(output), "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + } + } + return output; +} + +static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +{ + /* Not used until we add support for operators like += */ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + + case LVALUE_REG16: + return VALUE_16(*lvalue.register_address); + + case LVALUE_REG_L: + return VALUE_16(*lvalue.register_address & 0x00FF); + + case LVALUE_REG_H: + return VALUE_16(*lvalue.register_address >> 8); + } + + return VALUE_16(0); +} + +static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) +{ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + return; + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); + return; + + case LVALUE_REG16: + *lvalue.register_address = value; + return; + + case LVALUE_REG_L: + *lvalue.register_address &= 0xFF00; + *lvalue.register_address |= value & 0xFF; + return; + + case LVALUE_REG_H: + *lvalue.register_address &= 0x00FF; + *lvalue.register_address |= value << 8; + return; + } +} + +/* 16 bit value 16 bit value = 16 bit value + 25 bit address 16 bit value = 25 bit address + 16 bit value 25 bit address = 25 bit address + 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) + + Boolean operators always return a 16-bit value + */ +#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) + +static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} +static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} +static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} +static value_t _div(value_t a, value_t b) +{ + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value / b.value); +}; +static value_t mod(value_t a, value_t b) +{ + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value % b.value); +}; +static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);} +static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);} +static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);} +static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);} +static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);} +static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) +{ + write_lvalue(gb, a, b); + return read_lvalue(gb, a); +} + +static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);} +static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);} +static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);} +static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);} +static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);} +static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);} +static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);} +static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);} +static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};} + + +static struct { + const char *string; + int8_t priority; + value_t (*operator)(value_t, value_t); + value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); +} operators[] = +{ + // Yes. This is not C-like. But it makes much more sense. + // Deal with it. + {"+", 0, add}, + {"-", 0, sub}, + {"||", 0, bool_or}, + {"|", 0, or}, + {"*", 1, mul}, + {"/", 1, _div}, + {"%", 1, mod}, + {"&&", 1, bool_and}, + {"&", 1, and}, + {"^", 1, xor}, + {"<<", 2, shleft}, + {"<=", 3, lower_equals}, + {"<", 3, lower}, + {">>", 2, shright}, + {">=", 3, greater_equals}, + {">", 3, greater}, + {"==", 3, equals}, + {"=", -1, NULL, assign}, + {"!=", 3, different}, + {":", 4, bank}, +}; + +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); + +static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +{ + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) { + GB_log(gb, "Expected expression.\n"); + *error = true; + return (lvalue_t){0,}; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '{') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == '}') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->af}; + case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->af}; + case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->bc}; + case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->bc}; + case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->de}; + case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->de}; + case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->hl}; + case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->hl}; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->af}; + case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->bc}; + case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->de}; + case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->hl}; + case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->sp}; + case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; + } + } + GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string); + *error = true; + return (lvalue_t){0,}; + } + + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string); + *error = true; + return (lvalue_t){0,}; +} + +#define ERROR ((value_t){0,}) +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 evaluating expressions */ + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + value_t ret = ERROR; + + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) { + GB_log(gb, "Expected expression.\n"); + *error = true; + goto exit; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) { + ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + goto exit; + } + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } + } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '{') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == '}') depth--; + } + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value) | (GB_read_memory(gb, addr.value + 1) * 0x100)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } + } + // Search for lowest priority operator + signed depth = 0; + unsigned operator_index = -1; + unsigned operator_pos = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + else if (string[i] == ')') depth--; + else if (string[i] == '[') depth++; + else if (string[i] == ']') depth--; + else if (depth == 0) { + for (unsigned j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { + unsigned operator_length = strlen(operators[j].string); + if (operator_length > length - i) continue; // Operator too long + + if (memcmp(string + i, operators[j].string, operator_length) == 0) { + if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) { + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; + } + // Found an operator! + operator_pos = i; + operator_index = j; + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; + } + } + } + } + if (operator_index != -1) { + unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string)); + value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + if (operators[operator_index].lvalue_operator) { + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].lvalue_operator(gb, left, right.value); + goto exit; + } + value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].operator(left, right); + goto exit; + } + + // Not an expression - must be a register or a literal + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': ret = VALUE_16(gb->af >> 8); goto exit; + case 'f': ret = VALUE_16(gb->af & 0xFF); goto exit; + case 'b': ret = VALUE_16(gb->bc >> 8); goto exit; + case 'c': ret = VALUE_16(gb->bc & 0xFF); goto exit; + case 'd': ret = VALUE_16(gb->de >> 8); goto exit; + case 'e': ret = VALUE_16(gb->de & 0xFF); goto exit; + case 'h': ret = VALUE_16(gb->hl >> 8); goto exit; + case 'l': ret = VALUE_16(gb->hl & 0xFF); goto exit; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->af); goto exit;} + case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->bc); goto exit;} + case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->de); goto exit;} + case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->hl); goto exit;} + case 's': if (string[1] == 'p') {ret = VALUE_16(gb->sp); goto exit;} + case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} + } + } + else if (length == 3) { + if (watchpoint_address && memcmp(string, "old", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + + if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(*watchpoint_new_value); + goto exit; + } + + /* $new is identical to $old in read conditions */ + if (watchpoint_address && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + } + + char symbol_name[length + 1]; + memcpy(symbol_name, string, length); + symbol_name[length] = 0; + const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); + if (symbol) { + ret = (value_t){true, symbol->bank, symbol->addr}; + goto exit; + } + + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string); + *error = true; + goto exit; + } + + char *end; + unsigned base = 10; + if (string[0] == '$') { + string++; + base = 16; + length--; + } + uint16_t literal = (uint16_t) (strtol(string, &end, base)); + if (end != string + length) { + GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string); + *error = true; + goto exit; + } + ret = VALUE_16(literal); +exit: + gb->n_watchpoints = n_watchpoints; + return ret; +} + +struct debugger_command_s; +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); +typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context); + +typedef struct debugger_command_s { + const char *command; + uint8_t min_length; + debugger_command_imp_t *implementation; + const char *help_string; // Null if should not appear in help + const char *arguments_format; // For usage message + const char *modifiers_format; // For usage message + debugger_completer_imp_t *argument_completer; + debugger_completer_imp_t *modifiers_completer; +} debugger_command_t; + +static const char *lstrip(const char *str) +{ + while (*str == ' ' || *str == '\t') { + str++; + } + return str; +} + +#define STOPPED_ONLY \ +if (!gb->debug_stopped) { \ +GB_log(gb, "Program is running. \n"); \ +return false; \ +} + +#define NO_MODIFIERS \ +if (modifiers) { \ +print_usage(gb, command); \ +return true; \ +} + +static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_log(gb, "Usage: %s", command->command); + + if (command->modifiers_format) { + GB_log(gb, "[/%s]", command->modifiers_format); + } + + if (command->arguments_format) { + GB_log(gb, " %s", command->arguments_format); + } + + GB_log(gb, "\n"); +} + +static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + return false; +} + +static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + return false; +} + +static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_fin_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + + GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->af, /* AF can't really be an address */ + (gb->f & GB_CARRY_FLAG)? 'C' : '-', + (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', + (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', + (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false)); + GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); + return true; +} + +static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"on", "off"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +/* Enable or disable software breakpoints */ +static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { + gb->has_software_breakpoints = true; + } + else if (strcmp(lstrip(arguments), "off") == 0) { + gb->has_software_breakpoints = false; + } + else { + print_usage(gb, command); + } + + return true; +} + +/* Find the index of the closest breakpoint equal or greater to addr */ +static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->breakpoints) { + return 0; + } + + uint32_t key = BP_KEY(addr); + + unsigned min = 0; + unsigned max = gb->n_breakpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->breakpoints[pivot].key == key) return pivot; + if (gb->breakpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static inline bool is_legal_symbol_char(char c) +{ + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= 'a' && c <= 'z') return true; + if (c == '_') return true; + if (c == '.') return true; + return false; +} + +static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context) +{ + const char *symbol_prefix = string; + while (*string) { + if (!is_legal_symbol_char(*string)) { + symbol_prefix = string + 1; + } + string++; + } + + if (*symbol_prefix == '$') { + return NULL; + } + + struct { + uint16_t bank; + uint32_t symbol; + } *context = (void *)_context; + + + size_t length = strlen(symbol_prefix); + while (context->bank < 0x200) { + GB_symbol_map_t *map = get_symbol_map(gb, context->bank); + if (map == NULL || + context->symbol >= map->n_symbols) { + context->bank++; + context->symbol = 0; + continue; + } + const char *candidate = map->symbols[context->symbol++].name; + if (memcmp(symbol_prefix, candidate, length) == 0) { + return strdup(candidate + length); + } + } + return NULL; +} + +static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"j"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + bool is_jump_to = true; + if (!modifiers) { + is_jump_to = false; + } + else if (strcmp(modifiers, "j") != 0) { + print_usage(gb, command); + return true; + } + + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) { + GB_log(gb, "Too many breakpoints set\n"); + return true; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = find_breakpoint(gb, result); + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (!gb->breakpoints[index].condition && condition) { + GB_log(gb, "Added condition to breakpoint\n"); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && condition) { + GB_log(gb, "Replaced breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && !condition) { + GB_log(gb, "Removed breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = NULL; + } + return true; + } + + gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->breakpoints[index].key = key; + + if (condition) { + gb->breakpoints[index].condition = strdup(condition); + } + else { + gb->breakpoints[index].condition = NULL; + } + gb->n_breakpoints++; + + gb->breakpoints[index].is_jump_to = is_jump_to; + + if (is_jump_to) { + gb->has_jump_to_breakpoints = true; + } + + GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_breakpoints; i--;) { + if (gb->breakpoints[i].condition) { + free(gb->breakpoints[i].condition); + } + } + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (gb->breakpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->breakpoints[i].addr == result.value && result.has_bank != (gb->breakpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_breakpoints) { + GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + result.bank = gb->breakpoints[index].bank; + result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1; + + if (gb->breakpoints[index].condition) { + free(gb->breakpoints[index].condition); + } + + if (gb->breakpoints[index].is_jump_to) { + gb->has_jump_to_breakpoints = false; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (i == index) continue; + if (gb->breakpoints[i].is_jump_to) { + gb->has_jump_to_breakpoints = true; + break; + } + } + } + + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); + gb->n_breakpoints--; + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + + GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +/* Find the index of the closest watchpoint equal or greater to addr */ +static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->watchpoints) { + return 0; + } + uint32_t key = WP_KEY(addr); + unsigned min = 0; + unsigned max = gb->n_watchpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->watchpoints[pivot].key == key) return pivot; + if (gb->watchpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"r", "rw", "w"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { +print_usage: + print_usage(gb, command); + return true; + } + + if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) { + GB_log(gb, "Too many watchpoints set\n"); + return true; + } + + if (!modifiers) { + modifiers = "w"; + } + + uint8_t flags = 0; + while (*modifiers) { + switch (*modifiers) { + case 'r': + flags |= GB_WATCHPOINT_R; + break; + case 'w': + flags |= GB_WATCHPOINT_W; + break; + default: + goto print_usage; + } + modifiers++; + } + + if (!flags) { + goto print_usage; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + /* To make $new and $old legal */ + uint16_t dummy = 0; + uint8_t dummy2 = 0; + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (gb->watchpoints[index].flags != flags) { + GB_log(gb, "Modified watchpoint type\n"); + gb->watchpoints[index].flags = flags; + } + if (!gb->watchpoints[index].condition && condition) { + GB_log(gb, "Added condition to watchpoint\n"); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && condition) { + GB_log(gb, "Replaced watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && !condition) { + GB_log(gb, "Removed watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = NULL; + } + return true; + } + + gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); + memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); + gb->watchpoints[index].key = key; + gb->watchpoints[index].flags = flags; + if (condition) { + gb->watchpoints[index].condition = strdup(condition); + } + else { + gb->watchpoints[index].condition = NULL; + } + gb->n_watchpoints++; + + GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_watchpoints; i--;) { + if (gb->watchpoints[i].condition) { + free(gb->watchpoints[i].condition); + } + } + free(gb->watchpoints); + gb->watchpoints = NULL; + gb->n_watchpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_watchpoints; i++) { + if (gb->watchpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->watchpoints[i].addr == result.value && result.has_bank != (gb->watchpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_watchpoints) { + GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + result.bank = gb->watchpoints[index].bank; + result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1; + + if (gb->watchpoints[index].condition) { + free(gb->watchpoints[index].condition); + } + + memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); + gb->n_watchpoints--; + gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints *sizeof(gb->watchpoints[0])); + + GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool list(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->n_breakpoints == 0) { + GB_log(gb, "No breakpoints set.\n"); + } + else { + GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; + if (gb->breakpoints[i].condition) { + GB_log(gb, " %d. %s (%sCondition: %s)\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? "Jump to, ": "", + gb->breakpoints[i].condition); + } + else { + GB_log(gb, " %d. %s%s\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? " (Jump to)" : ""); + } + } + } + + if (gb->n_watchpoints == 0) { + GB_log(gb, "No watchpoints set.\n"); + } + else { + GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); + for (uint16_t i = 0; i < gb->n_watchpoints; i++) { + value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr}; + if (gb->watchpoints[i].condition) { + GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', + gb->watchpoints[i].condition); + } + else { + GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); + } + } + } + + return true; +} + +static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to) +{ + uint16_t index = find_breakpoint(gb, addr); + uint32_t key = BP_KEY(addr); + + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key && gb->breakpoints[index].is_jump_to == jump_to) { + if (!gb->breakpoints[index].condition) { + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, + (unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return true; + } + return condition; + } + return false; +} + +static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) +{ + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_should_break(gb, full_addr, jump_to)) return true; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + return _should_break(gb, full_addr, jump_to); +} + +static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"a", "b", "d", "o", "x"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (!modifiers || !modifiers[0]) { + modifiers = "a"; + } + else if (modifiers[1]) { + print_usage(gb, command); + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + if (!error) { + switch (modifiers[0]) { + case 'a': + GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); + break; + case 'd': + GB_log(gb, "=%d\n", result.value); + break; + case 'x': + GB_log(gb, "=$%x\n", result.value); + break; + case 'o': + GB_log(gb, "=0%o\n", result.value); + break; + case 'b': + { + if (!result.value) { + GB_log(gb, "=%%0\n"); + break; + } + char binary[17]; + binary[16] = 0; + char *ptr = &binary[16]; + while (result.value) { + *(--ptr) = (result.value & 1)? '1' : '0'; + result.value >>= 1; + } + GB_log(gb, "=%%%s\n", ptr); + break; + } + default: + break; + } + } + return true; +} + +static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint16_t count = 32; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + while (count) { + GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); + for (unsigned i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + + restore_banking_state(gb, &old_state); + } + else { + while (count) { + GB_log(gb, "%04x: ", addr.value); + for (unsigned i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + } + } + return true; +} + +static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + arguments = "pc"; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint16_t count = 5; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + GB_cpu_disassemble(gb, addr.value, count); + + restore_banking_state(gb, &old_state); + } + else { + GB_cpu_disassemble(gb, addr.value, count); + } + } + return true; +} + +static bool mbc(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; + } + + const GB_cartridge_t *cartridge = gb->cartridge_type; + + if (cartridge->has_ram) { + 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"); + } + + if (cartridge->mbc_type) { + if (gb->is_mbc30) { + GB_log(gb, "MBC30\n"); + } + else { + static const char *const mapper_names[] = { + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_MBC7] = "MBC7", + [GB_MMM01] = "MMM01", + [GB_HUC1] = "HUC-1", + [GB_HUC3] = "HUC-3", + [GB_CAMERA] = "MAC-GBD", + + }; + GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); + } + if (cartridge->mbc_type == GB_MMM01 || cartridge->mbc_type == GB_MBC1) { + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + } + GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); + if (cartridge->has_ram) { + GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); + if (gb->cartridge_type->mbc_type != GB_HUC1) { + GB_log(gb, "RAM is currently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + } + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) { + GB_log(gb, "MBC1 uses MBC1M wiring. \n"); + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled"); + } + + } + else { + GB_log(gb, "No MBC\n"); + } + + 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"); + } + + if (cartridge->has_rtc) { + GB_log(gb, "Cart contains a real time clock\n"); + } + + return true; +} + +static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); + for (unsigned i = gb->backtrace_size; i--;) { + GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); + } + + return true; +} + +static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_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; +} + + +static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!GB_is_cgb(gb)) { + GB_log(gb, "Not available on a DMG.\n"); + return true; + } + + GB_log(gb, "Background palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + GB_log(gb, "Object palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->object_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + return true; +} + +static bool dma(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!GB_is_dma_active(gb)) { + GB_log(gb, "DMA is inactive\n"); + return true; + } + + if (gb->dma_current_dest == 0xFF) { + GB_log(gb, "DMA warming up\n"); // Shouldn't actually happen, as it only lasts 2 T-cycles + return true; + } + + GB_log(gb, "Next DMA write: [$FE%02X] = [$%04X]\n", gb->dma_current_dest, gb->dma_current_src); + + return true; +} + +static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + 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? "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"); + GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800"); + GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); + GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); + + GB_log(gb, "\nSTAT:\n"); + static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; + GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); + GB_log(gb, " LYC flag: %s\n", (gb->io_registers[GB_IO_STAT] & 4)? "On" : "Off"); + GB_log(gb, " H-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 8)? "Enabled" : "Disabled"); + GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); + GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); + + + + GB_log(gb, "\nCurrent line: %d\n", gb->current_line); + GB_log(gb, "Current state: "); + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + GB_log(gb, "Off\n"); + } + else if (gb->display_state == 7 || gb->display_state == 8) { + GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0); + } + else if (gb->display_state <= 3 || gb->display_state == 24 || gb->display_state == 31) { + GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2); + } + else if (gb->mode_for_interrupt == 3) { + if (((uint8_t)(gb->position_in_line + 16) < 8)) { + GB_log(gb, "Adjusting for scrolling (%d/%d)\n", gb->position_in_line & 7, gb->io_registers[GB_IO_SCX] & 7); + } + else { + signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; + GB_log(gb, "Rendering pixel (%d/160)\n", pixel); + } + } + else { + GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2); + } + GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); + GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); + GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7, gb->io_registers[GB_IO_WY]); + GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); + + return true; +} + +static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + const char *stripped = lstrip(arguments); + if (strlen(stripped)) { + if (stripped[0] != 0 && (stripped[0] < '1' || stripped[0] > '5')) { + print_usage(gb, command); + return true; + } + } + + if (stripped[0] == 0 || stripped[0] == '5') { + GB_log(gb, "Current state: "); + if (!gb->apu.global_enable) { + GB_log(gb, "Disabled\n"); + } + else { + GB_log(gb, "Enabled\n"); + for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { + GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, + gb->apu.is_active[channel] ? "active " : "inactive", + GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", + gb->apu.samples[channel]); + } + } + + GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); + if (gb->io_registers[GB_IO_NR51] & 0x0F) { + for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + + GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); + if (gb->io_registers[GB_IO_NR51] & 0xF0) { + for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + } + + + for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { + if (stripped[0] != 0 && stripped[0] != ('1') + channel) continue; + + GB_log(gb, "\nCH%u:\n", channel + 1); + GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.square_channels[channel].current_volume, + (gb->apu.square_channels[channel].sample_length ^ 0x7FF) * 2 + 1, + gb->apu.square_channels[channel].sample_countdown); + + uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.square_channels[channel].volume_countdown, + nrx2 & 8 ? "in" : "de", + nrx2 & 7); + + uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; + GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", + duty > 3? "" : (const char *const[]){"12.5", " 25", " 50", " 75"}[duty], + duty > 3? "" : (const char *const[]){"_______-", "-______-", "-____---", "_------_"}[duty], + 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", + ((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) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.square_channels[channel].pulse_length); + } + } + + if (stripped[0] == 0 || stripped[0] == '3') { + GB_log(gb, "\nCH3:\n"); + GB_log(gb, " Wave:"); + for (uint8_t i = 0; i < 16; i++) { + GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4); + GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF); + } + GB_log(gb, "\n"); + GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); + + GB_log(gb, " Volume %s (right-shifted %u times)\n", + gb->apu.wave_channel.shift > 4? "" : (const char *const[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift], + gb->apu.wave_channel.shift); + + GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.wave_channel.sample_length ^ 0x7FF, + gb->apu.wave_channel.sample_countdown); + + if (gb->apu.wave_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.wave_channel.pulse_length); + } + } + + + if (stripped[0] == 0 || stripped[0] == '4') { + GB_log(gb, "\nCH4:\n"); + GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n", + gb->apu.noise_channel.current_volume, + gb->apu.noise_channel.counter, + gb->apu.noise_channel.counter_countdown); + + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.noise_channel.volume_countdown, + gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", + gb->io_registers[GB_IO_NR42] & 7); + + GB_log(gb, " LFSR in %u-step mode, current value ", + gb->apu.noise_channel.narrow? 7 : 15); + for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { + GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); + } + + if (gb->apu.noise_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.noise_channel.pulse_length); + } + } + + + GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n"); + + return true; +} + +static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"c", "f", "l"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) { + print_usage(gb, command); + return true; + } + + uint8_t shift_amount = 1, mask; + if (modifiers) { + switch (modifiers[0]) { + case 'c': + shift_amount = 2; + break; + case 'l': + shift_amount = 8; + break; + } + } + mask = (0xF << (shift_amount - 1)) & 0xF; + + for (int8_t cur_val = 0xF & mask; cur_val >= 0; cur_val -= shift_amount) { + for (uint8_t i = 0; i < 32; i++) { + uint8_t sample = i & 1? + (gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) : + (gb->io_registers[GB_IO_WAV_START + i / 2] >> 4); + if ((sample & mask) == cur_val) { + GB_log(gb, "%X", sample); + } + else { + GB_log(gb, "%c", i % 4 == 2 ? '-' : ' '); + } + } + GB_log(gb, "\n"); + } + + return true; +} + +static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!gb->undo_label) { + GB_log(gb, "No undo state available\n"); + return true; + } + uint16_t pc = gb->pc; + GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size_no_bess(gb)); + GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label); + if (pc != gb->pc) { + GB_cpu_disassemble(gb, gb->pc, 5); + } + gb->undo_label = NULL; + + return true; +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); + +#define HELP_NEWLINE "\n " + +/* Commands without implementations are aliases of the previous non-alias commands */ +static const debugger_command_t commands[] = { + {"continue", 1, cont, "Continue running until next stop"}, + {"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, "Revert the last command"}, + {"registers", 1, registers, "Print values of processor registers and other important registers"}, + {"backtrace", 2, backtrace, "Display the current call stack"}, + {"bt", 2, }, /* Alias */ + {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE + "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE + "decimal (d), hexadecimal (x), octal (o) or binary (b).", + "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, + {"eval", 2, }, /* Alias */ + {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, + {"x", 1, }, /* Alias */ + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE + "Can also modify the condition of existing breakpoints." HELP_NEWLINE + "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE + "jumping to the target.", + "[ if ]", "j", + .argument_completer = symbol_completer, .modifiers_completer = j_completer}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]", .argument_completer = symbol_completer}, + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE + "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE + "Default watchpoint type is write-only.", + "[ if ]", "(r|w|rw)", + .argument_completer = symbol_completer, .modifiers_completer = rw_completer + }, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]", .argument_completer = symbol_completer}, + {"softbreak", 2, softbreak, "Enable or disable software breakpoints ('ld b, b' opcodes)", "(on|off)", .argument_completer = on_off_completer}, + {"list", 1, list, "List all set breakpoints and watchpoints"}, + {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE + "used"}, + {"cartridge", 2, mbc, "Display information about the MBC and cartridge"}, + {"mbc", 3, }, /* Alias */ + {"apu", 3, apu, "Display information about the current state of the audio processing unit", "[channel (1-4, 5 for NR5x)]"}, + {"wave", 3, wave, "Print a visual representation of the wave RAM." HELP_NEWLINE + "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, + {"lcd", 3, lcd, "Display information about the current state of the LCD controller"}, + {"palettes", 3, palettes, "Display the current CGB palettes"}, + {"dma", 3, dma, "Display the current OAM DMA status"}, + + {"help", 1, help, "List available commands or show help for the specified command", "[]"}, + {NULL,}, /* Null terminator */ +}; + +static const debugger_command_t *find_command(const char *string) +{ + size_t length = strlen(string); + for (const debugger_command_t *command = commands; command->command; command++) { + if (command->min_length > length) continue; + if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ + /* Aliases */ + while (!command->implementation) { + command--; + } + return command; + } + } + + return NULL; +} + +static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); + GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command + command->min_length); +} + +static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) +{ + print_command_shortcut(gb, command); + GB_log(gb, ": "); + GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) +{ + const debugger_command_t *command = find_command(arguments); + if (command) { + print_command_description(gb, command); + GB_log(gb, "\n"); + print_usage(gb, command); + + command++; + if (command->command && !command->implementation) { /* Command has aliases*/ + GB_log(gb, "\nAliases: "); + do { + print_command_shortcut(gb, command); + GB_log(gb, " "); + command++; + } while (command->command && !command->implementation); + GB_log(gb, "\n"); + } + return true; + } + for (command = commands; command->command; command++) { + if (command->help_string) { + print_command_description(gb, command); + } + } + return true; +} + +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) +{ + /* Called just after the CPU calls a function/enters an interrupt/etc... */ + + if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) { + gb->backtrace_size--; + } + else { + break; + } + } + + gb->backtrace_sps[gb->backtrace_size] = gb->sp; + gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); + gb->backtrace_returns[gb->backtrace_size].addr = call_addr; + gb->backtrace_size++; + } + + gb->debug_call_depth++; +} + +void GB_debugger_ret_hook(GB_gameboy_t *gb) +{ + /* Called just before the CPU runs ret/reti */ + + gb->debug_call_depth--; + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) { + gb->backtrace_size--; + } + else { + break; + } + } +} + +static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + } + return false; +} + +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_write_watchpoint(gb, full_addr, value); +} + +static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + } + return false; +} + +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_read_watchpoint(gb, full_addr); +} + +/* Returns true if debugger waits for more commands */ +bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) +{ + if (!input[0]) { + return true; + } + + GB_display_sync(gb); + GB_apu_run(gb, true); + + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + else { + arguments = ""; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command) { + uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, old_state); + bool ret = command->implementation(gb, arguments, modifiers, command); + if (!ret) { // Command continues, save state in any case + free(gb->undo_state); + gb->undo_state = old_state; + gb->undo_label = command->command; + } + else { + uint8_t *new_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, new_state); + if (memcmp(new_state, old_state, GB_get_save_state_size_no_bess(gb)) != 0) { + // State changed, save the old state as the new undo state + free(gb->undo_state); + gb->undo_state = old_state; + gb->undo_label = command->command; + } + else { + // Nothing changed, just free the old state + free(old_state); + } + free(new_state); + } + return ret; + } + else { + GB_log(gb, "%s: no such command.\n", command_string); + return true; + } +} + +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context) +{ + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command && command->implementation == help && arguments) { + command_string = arguments; + arguments = NULL; + } + + /* No commands and no modifiers, complete the command */ + if (!arguments && !modifiers) { + size_t length = strlen(command_string); + if (*context >= sizeof(commands) / sizeof(commands[0])) { + return NULL; + } + for (const debugger_command_t *command = &commands[*context]; command->command; command++) { + (*context)++; + if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */ + return strdup(command->command + length); + } + } + return NULL; + } + + if (command) { + if (arguments) { + if (command->argument_completer) { + return command->argument_completer(gb, arguments, context); + } + return NULL; + } + + if (modifiers) { + if (command->modifiers_completer) { + return command->modifiers_completer(gb, modifiers, context); + } + return NULL; + } + } + return NULL; +} + +typedef enum { + JUMP_TO_NONE, + JUMP_TO_BREAK, + JUMP_TO_NONTRIVIAL, +} jump_to_return_t; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address); + +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_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, gb->undo_state); + } + + char *input = NULL; + if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { + gb->debug_stopped = true; + } + if (gb->debug_fin_command && gb->debug_call_depth == -1) { + gb->debug_stopped = true; + } + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } +next_command: + if (input) { + free(input); + } + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) { + gb->debug_stopped = true; + GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + } + + if (gb->breakpoints && !gb->debug_stopped) { + uint16_t address = 0; + jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); + + bool should_delete_state = true; + if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { + if (gb->non_trivial_jump_breakpoint_occured) { + gb->non_trivial_jump_breakpoint_occured = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1); + gb->debug_stopped = true; + } + } + else if (jump_to_result == JUMP_TO_BREAK) { + gb->debug_stopped = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, address, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + gb->non_trivial_jump_breakpoint_occured = false; + } + else if (jump_to_result == JUMP_TO_NONTRIVIAL) { + if (!gb->nontrivial_jump_state) { + gb->nontrivial_jump_state = malloc(GB_get_save_state_size_no_bess(gb)); + } + GB_save_state_to_buffer_no_bess(gb, gb->nontrivial_jump_state); + gb->non_trivial_jump_breakpoint_occured = false; + should_delete_state = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = false; + } + + if (should_delete_state) { + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + } + } + + if (gb->debug_stopped && !gb->debug_disable) { + gb->debug_next_command = false; + gb->debug_fin_command = false; + input = gb->input_callback(gb); + + if (input == NULL) { + /* Debugging is no currently available, continue running */ + gb->debug_stopped = false; + return; + } + + if (GB_debugger_execute_command(gb, input)) { + goto next_command; + } + + free(input); + } +} + +void GB_debugger_handle_async_commands(GB_gameboy_t *gb) +{ + char *input = NULL; + + while (gb->async_input_callback && (input = gb->async_input_callback(gb))) { + GB_debugger_execute_command(gb, input); + free(input); + } +} + +void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol) +{ + if (bank >= gb->n_symbol_maps) { + gb->bank_symbols = realloc(gb->bank_symbols, (bank + 1) * sizeof(*gb->bank_symbols)); + while (bank >= gb->n_symbol_maps) { + gb->bank_symbols[gb->n_symbol_maps++] = NULL; + } + } + + if (!gb->bank_symbols[bank]) { + gb->bank_symbols[bank] = GB_map_alloc(); + } + GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + if (allocated_symbol) { + GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); + } +} + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return; + + char *line = NULL; + size_t size = 0; + size_t length = 0; + while ((length = getline(&line, &size, f)) != -1) { + for (unsigned i = 0; i < length; i++) { + if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') { + line[i] = 0; + length = i; + break; + } + } + if (length == 0) continue; + + unsigned bank, address; + char symbol[length]; + + if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) { + GB_debugger_add_symbol(gb, bank, address, symbol); + } + } + free(line); + fclose(f); +} + +void GB_debugger_clear_symbols(GB_gameboy_t *gb) +{ + for (unsigned i = gb->n_symbol_maps; i--;) { + if (gb->bank_symbols[i]) { + GB_map_free(gb->bank_symbols[i]); + gb->bank_symbols[i] = 0; + } + } + for (unsigned i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) { + while (gb->reversed_symbol_map.buckets[i]) { + GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; + free(gb->reversed_symbol_map.buckets[i]); + gb->reversed_symbol_map.buckets[i] = next; + } + } + gb->n_symbol_maps = 0; + if (gb->bank_symbols) { + free(gb->bank_symbols); + gb->bank_symbols = NULL; + } +} + +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) +{ + uint16_t bank = bank_for_addr(gb, addr); + + const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr); + if (symbol) return symbol; + if (bank != 0) return GB_map_find_symbol(get_symbol_map(gb, 0), addr); /* Maybe the symbol incorrectly uses bank 0? */ + + return NULL; +} + +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) +{ + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); + if (symbol && symbol->addr == addr) return symbol->name; + return NULL; +} + +/* The public version of debugger_evaluate */ +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) +{ + bool error = false; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + if (result) { + *result = value.value; + } + if (result_bank) { + *result_bank = value.has_bank? value.value : -1; + } + return error; +} + +void GB_debugger_break(GB_gameboy_t *gb) +{ + gb->debug_stopped = true; +} + +bool GB_debugger_is_stopped(GB_gameboy_t *gb) +{ + return gb->debug_stopped; +} + +void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->debug_disable = disabled; +} + +/* Jump-to breakpoints */ + +static bool is_in_trivial_memory(uint16_t addr) +{ + /* ROM */ + if (addr < 0x8000) { + return true; + } + + /* HRAM */ + if (addr >= 0xFF80 && addr < 0xFFFF) { + return true; + } + + /* RAM */ + if (addr >= 0xC000 && addr < 0xE000) { + return true; + } + + return false; +} + +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) +{ + return gb->pc + 1; +} + +uint16_t trivial_2(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2; +} + +uint16_t trivial_3(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 3; +} + +static uint16_t jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->af & GB_ZERO_FLAG); + case 1: + return (gb->af & GB_ZERO_FLAG); + case 2: + return !(gb->af & GB_CARRY_FLAG); + case 3: + return (gb->af & GB_CARRY_FLAG); + } + + return false; +} + +static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (!condition_code(gb, opcode)) { + return gb->pc + 2; + } + + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->sp) | + (GB_read_memory(gb, gb->sp + 1) << 8); +} + + +static uint16_t ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return ret(gb, opcode); + } + else { + return gb->pc + 1; + } +} + +static uint16_t jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->pc + 1) | + (GB_read_memory(gb, gb->pc + 2) << 8); +} + +static uint16_t jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return jp_a16(gb, opcode); + } + else { + return gb->pc + 3; + } +} + +static uint16_t rst(GB_gameboy_t *gb, uint8_t opcode) +{ + return opcode ^ 0xC7; +} + +static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->hl; +} + +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 */ + trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_2, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 1X */ + jr_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 2X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 3X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 4X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 5X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 6X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, NULL, trivial_1, /* 7X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 8X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 9X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* aX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* bX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + ret_cc, trivial_1, jp_cc_a16, jp_a16, jp_cc_a16, trivial_1, trivial_2, rst, /* cX */ + ret_cc, ret, jp_cc_a16, trivial_2, jp_cc_a16, jp_a16, trivial_2, rst, + ret_cc, trivial_1, jp_cc_a16, NULL, jp_cc_a16, trivial_1, trivial_2, rst, /* dX */ + ret_cc, ret, jp_cc_a16, NULL, jp_cc_a16, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, NULL, NULL, trivial_1, trivial_2, rst, /* eX */ + trivial_2, jp_hl, trivial_3, NULL, NULL, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, trivial_1, NULL, trivial_1, trivial_2, rst, /* fX */ + trivial_2, trivial_1, trivial_3, trivial_1, NULL, NULL, trivial_2, rst, +}; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) +{ + if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; + + if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || + !is_in_trivial_memory(gb->sp) || !is_in_trivial_memory(gb->sp + 1)) { + return JUMP_TO_NONTRIVIAL; + } + + /* Interrupts */ + if (gb->ime) { + for (unsigned i = 0; i < 5; i++) { + if ((gb->interrupt_enable & (1 << i)) && (gb->io_registers[GB_IO_IF] & (1 << i))) { + if (should_break(gb, 0x40 + i * 8, true)) { + if (address) { + *address = 0x40 + i * 8; + } + return JUMP_TO_BREAK; + } + } + } + } + + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + uint8_t opcode = GB_read_memory(gb, gb->pc); + + if (opcode == 0x76) { + gb->n_watchpoints = n_watchpoints; + if (gb->ime) { /* Already handled in above */ + return JUMP_TO_NONE; + } + + if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { + return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ + } + + return JUMP_TO_NONE; + } + + opcode_address_getter_t *getter = opcodes[opcode]; + if (!getter) { + gb->n_watchpoints = n_watchpoints; + return JUMP_TO_NONE; + } + + uint16_t new_pc = getter(gb, opcode); + + gb->n_watchpoints = n_watchpoints; + + if (address) { + *address = new_pc; + } + + return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; +} diff --git a/thirdparty/SameBoy-old/Core/debugger.h b/thirdparty/SameBoy-old/Core/debugger.h new file mode 100644 index 000000000..3d77b7a8c --- /dev/null +++ b/thirdparty/SameBoy-old/Core/debugger.h @@ -0,0 +1,46 @@ +#ifndef debugger_h +#define debugger_h +#include +#include +#include "defs.h" +#include "symbol_hash.h" + + +#ifdef GB_INTERNAL +#ifdef GB_DISABLE_DEBUGGER +#define GB_debugger_run(gb) (void)0 +#define GB_debugger_handle_async_commands(gb) (void)0 +#define GB_debugger_ret_hook(gb) (void)0 +#define GB_debugger_call_hook(gb, addr) (void)addr +#define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value) +#define GB_debugger_test_read_watchpoint(gb, addr) (void)addr +#define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol) + +#else +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 + +#ifdef GB_INTERNAL +bool /* Returns true if debugger waits for more commands. Not relevant for non-GB_INTERNAL */ +#else +void +#endif +GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */ + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */ +void GB_debugger_break(GB_gameboy_t *gb); +bool GB_debugger_is_stopped(GB_gameboy_t *gb); +void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled); +void GB_debugger_clear_symbols(GB_gameboy_t *gb); +#endif /* debugger_h */ diff --git a/thirdparty/SameBoy-old/Core/defs.h b/thirdparty/SameBoy-old/Core/defs.h new file mode 100644 index 000000000..81b3f8f99 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/defs.h @@ -0,0 +1,50 @@ +#ifndef defs_h +#define defs_h + +#define GB_likely(x) __builtin_expect((bool)(x), 1) +#define GB_unlikely(x) __builtin_expect((bool)(x), 0) + +#ifdef GB_INTERNAL + +// "Keyword" definitions +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +//#define internal __attribute__((visibility("internal"))) +#define internal + +#if __clang__ +#define unrolled _Pragma("unroll") +#elif __GNUC__ >= 8 +#define unrolled _Pragma("GCC unroll 8") +#else +#define unrolled +#endif + +#define unreachable() __builtin_unreachable(); +#define nodefault default: unreachable() + +#ifdef GB_BIG_ENDIAN +#define LE16(x) __builtin_bswap16(x) +#define LE32(x) __builtin_bswap32(x) +#define LE64(x) __builtin_bswap64(x) +#define BE16(x) (x) +#define BE32(x) (x) +#define BE64(x) (x) +#else +#define LE16(x) (x) +#define LE32(x) (x) +#define LE64(x) (x) +#define BE16(x) __builtin_bswap16(x) +#define BE32(x) __builtin_bswap32(x) +#define BE64(x) __builtin_bswap64(x) +#endif +#endif + +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) +#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) +#endif + +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +#endif diff --git a/thirdparty/SameBoy-old/Core/display.c b/thirdparty/SameBoy-old/Core/display.c new file mode 100644 index 000000000..f0ae1486c --- /dev/null +++ b/thirdparty/SameBoy-old/Core/display.c @@ -0,0 +1,2276 @@ +#include +#include +#include +#include +#include +#include "gb.h" + +/* FIFO functions */ + +static inline unsigned fifo_size(GB_fifo_t *fifo) +{ + return fifo->size; +} + +static void fifo_clear(GB_fifo_t *fifo) +{ + fifo->read_end = fifo->size = 0; +} + +static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) +{ + assert(fifo->size); + GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end]; + fifo->read_end++; + fifo->read_end &= (GB_FIFO_LENGTH - 1); + fifo->size--; + return ret; +} + +static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) +{ + assert(fifo->size == 0); + fifo->size = 8; + if (!flip_x) { + unrolled for (unsigned i = 0; i < 8; i++) { + fifo->fifo[i] = (GB_fifo_item_t) { + (lower >> 7) | ((upper >> 7) << 1), + palette, + 0, + bg_priority, + }; + lower <<= 1; + upper <<= 1; + } + } + else { + unrolled for (unsigned i = 0; i < 8; i++) { + fifo->fifo[i] = (GB_fifo_item_t) { + (lower & 1) | ((upper & 1) << 1), + palette, + 0, + bg_priority, + }; + lower >>= 1; + upper >>= 1; + } + } +} + +static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x) +{ + while (fifo->size < GB_FIFO_LENGTH) { + fifo->fifo[(fifo->read_end + fifo->size) & (GB_FIFO_LENGTH - 1)] = (GB_fifo_item_t) {0,}; + fifo->size++; + } + + uint8_t flip_xor = flip_x? 0: 0x7; + + 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)) { + target->pixel = pixel; + target->palette = palette; + target->bg_priority = bg_priority; + target->priority = priority; + } + lower <<= 1; + upper <<= 1; + } +} + + +/* + 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 + + Mode 1 is VBlank + */ + +#define MODE2_LENGTH (80) +#define LINE_LENGTH (456) +#define LINES (144) +#define WIDTH (160) +#define BORDERED_WIDTH 256 +#define BORDERED_HEIGHT 224 +#define FRAME_LENGTH (LCDC_PERIOD) +#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 + +typedef struct __attribute__((packed)) { + uint8_t y; + uint8_t x; + uint8_t tile; + uint8_t flags; +} object_t; + +void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + 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)) { + GB_sgb_render(gb); + } + + if (gb->turbo) { + if (GB_timing_sync_turbo(gb)) { + return; + } + } + + if (GB_is_cgb(gb) && type == GB_VBLANK_TYPE_NORMAL_FRAME && gb->frame_repeat_countdown > 0 && gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + GB_handle_rumble(gb); + + if (gb->vblank_callback) { + gb->vblank_callback(gb, GB_VBLANK_TYPE_REPEAT); + } + GB_timing_sync(gb); + return; + } + + bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; + + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + /* 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; + if (GB_is_cgb(gb)) { + color = GB_convert_rgb15(gb, 0x7FFF, false); + } + else { + color = is_ppu_stopped ? + gb->background_palettes_rgb[0] : + gb->background_palettes_rgb[4]; + } + if (gb->border_mode == GB_BORDER_ALWAYS) { + for (unsigned y = 0; y < LINES; y++) { + for (unsigned x = 0; x < WIDTH; x++) { + gb ->screen[x + y * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH] = color; + } + } + } + else { + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = color; + } + } + } + } + + 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]; + + if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E) { + uint16_t colors[] = { + 0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648, + 0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C, + 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, + }; + unsigned index = gb->rom? gb->rom[0x14E] % 5 : 0; + 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 A was only available in red! + } + 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, LE16(gb->borrowed_border.palette[i]), true); + } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + continue; + } + uint16_t tile = LE16(gb->borrowed_border.map[tile_x + tile_y * 32]); + 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 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]; + } + else { + *output = border_colors[color + palette * 16]; + } + } + } + } + } + } + GB_handle_rumble(gb); + + if (gb->vblank_callback) { + gb->vblank_callback(gb, type); + } + 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); +} + +static inline uint8_t scale_channel_with_curve(uint8_t x) +{ + return (const 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 (const 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) +{ + return (const uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x]; +} + + +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) +{ + uint8_t r = (color) & 0x1F; + uint8_t g = (color >> 5) & 0x1F; + uint8_t b = (color >> 10) & 0x1F; + + if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED || (for_border && !gb->has_sgb_border)) { + r = scale_channel(r); + 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 { + bool agb = gb->model > GB_MODEL_CGB_E; + r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r); + g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g); + b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b); + + if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { + uint8_t new_r, new_g, new_b; + if (g != b) { // Minor optimization + double gamma = 2.2; + if (gb->color_correction_mode < GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + /* Don't use absolutely gamma-correct mixing for the high-contrast + modes, to prevent the blue hues from being too washed out */ + gamma = 1.6; + } + + // TODO: Optimze pow out using a LUT + if (agb) { + new_g = round(pow((pow(g / 255.0, gamma) * 5 + pow(b / 255.0, gamma)) / 6, 1 / gamma) * 255); + } + else { + new_g = round(pow((pow(g / 255.0, gamma) * 3 + pow(b / 255.0, gamma)) / 4, 1 / gamma) * 255); + } + } + else { + new_g = g; + } + + new_r = r; + new_b = b; + if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + r = new_r; + g = new_g; + b = new_b; + + new_r = new_r * 15 / 16 + ( g + b) / 32; + new_g = new_g * 15 / 16 + (r + b) / 32; + new_b = new_b * 15 / 16 + (r + g ) / 32; + + if (agb) { + new_r = new_r * (224 - 20) / 255 + 20; + new_g = new_g * (220 - 18) / 255 + 18; + new_b = new_b * (216 - 16) / 255 + 16; + } + else { + new_r = new_r * (220 - 40) / 255 + 40; + new_g = new_g * (224 - 36) / 255 + 36; + new_b = new_b * (216 - 32) / 255 + 32; + } + } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) { + r = new_r; + g = new_g; + b = new_b; + + new_r = new_r * 15 / 16 + ( g + b) / 32; + new_g = new_g * 15 / 16 + (r + b) / 32; + new_b = new_b * 15 / 16 + (r + g ) / 32; + + if (agb) { + new_r = new_r * (167 - 27) / 255 + 27; + new_g = new_g * (165 - 24) / 255 + 24; + new_b = new_b * (157 - 22) / 255 + 22; + } + else { + new_r = new_r * (162 - 45) / 255 + 45; + new_g = new_g * (167 - 41) / 255 + 41; + new_b = new_b * (157 - 38) / 255 + 38; + } + } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST) { + uint8_t old_max = MAX(r, MAX(g, b)); + uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); + + if (new_max != 0) { + new_r = new_r * old_max / new_max; + new_g = new_g * old_max / new_max; + new_b = new_b * old_max / new_max; + } + + uint8_t old_min = MIN(r, MIN(g, b)); + uint8_t new_min = MIN(new_r, MIN(new_g, new_b)); + + if (new_min != 0xFF) { + new_r = 0xFF - (0xFF - new_r) * (0xFF - old_min) / (0xFF - new_min); + new_g = 0xFF - (0xFF - new_g) * (0xFF - old_min) / (0xFF - new_min); + new_b = 0xFF - (0xFF - new_b) * (0xFF - old_min) / (0xFF - new_min); + } + } + r = new_r; + g = new_g; + b = new_b; + } + } + + 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); +} + +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->object_palettes_data; + uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); + + (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) +{ + gb->color_correction_mode = mode; + 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); + } + } +} + +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 + + General timing is based on GiiBiiAdvance's documents: + https://github.com/AntonioND/giibiiadvance + + */ + +void GB_STAT_update(GB_gameboy_t *gb) +{ + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; + if (GB_is_dma_active(gb) && (gb->io_registers[GB_IO_STAT] & 3) == 2) { + gb->io_registers[GB_IO_STAT] &= ~3; + } + + bool previous_interrupt_line = gb->stat_interrupt_line; + /* Set LY=LYC bit */ + /* TODO: This behavior might not be correct for CGB revisions other than C and E */ + if (gb->ly_for_comparison != (uint16_t)-1 || gb->model <= GB_MODEL_CGB_C) { + if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { + gb->lyc_interrupt_line = true; + gb->io_registers[GB_IO_STAT] |= 4; + } + else { + if (gb->ly_for_comparison != (uint16_t)-1) { + gb->lyc_interrupt_line = false; + } + gb->io_registers[GB_IO_STAT] &= ~4; + } + } + + switch (gb->mode_for_interrupt) { + case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break; + case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; + case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + default: gb->stat_interrupt_line = false; + } + + /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ + if ((gb->io_registers[GB_IO_STAT] & 0x40) && gb->lyc_interrupt_line) { + gb->stat_interrupt_line = true; + } + + if (gb->stat_interrupt_line && !previous_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; + } +} + +void GB_lcd_off(GB_gameboy_t *gb) +{ + gb->cycles_for_line = 0; + gb->display_state = 0; + gb->display_cycles = 0; + /* When the LCD is disabled, state is constant */ + + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3)) { + gb->hdma_on = true; + } + + /* When the LCD is off, LY is 0 and STAT mode is 0. */ + gb->io_registers[GB_IO_LY] = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + + + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + + gb->current_line = 0; + gb->ly_for_comparison = 0; + + gb->accessed_oam_row = -1; + gb->wy_triggered = false; + + if (unlikely(gb->lcd_line_callback)) { + gb->lcd_line_callback(gb, 0); + } +} + +static inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr) +{ + if (unlikely(gb->oam_ppu_blocked)) { + return 0xFF; + } + if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles? + if (gb->hdma_in_progress) { + return GB_read_oam(gb, (gb->hdma_current_src & ~1) | (addr & 1)); + } + if (gb->dma_current_dest != 0xA0) { + return gb->oam[(gb->dma_current_dest & ~1) | (addr & 1)]; + } + } + return gb->oam[addr]; +} + +static void add_object_from_index(GB_gameboy_t *gb, unsigned index) +{ + if (likely(!GB_is_dma_active(gb) || gb->halted || gb->stopped)) { + gb->mode2_y_bus = oam_read(gb, index * 4); + gb->mode2_x_bus = oam_read(gb, index * 4 + 1); + } + + if (gb->n_visible_objs == 10) return; + + /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */ + if (unlikely(GB_is_dma_active(gb) && (gb->halted || gb->stopped))) { + if (gb->model < GB_MODEL_CGB_E) { + return; + } + /* CGB-0 to CGB-D: Halted DMA blocks Mode 2; + Pre-CGB: Unit specific behavior, some units read FFs, some units read using + several different corruption pattterns. For simplicity, we emulate + FFs. */ + } + + if (unlikely(gb->oam_ppu_blocked)) { + return; + } + + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; + signed y = gb->mode2_y_bus - 16; + /* This reverse sorts the visible objects by location and priority */ + if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { + unsigned j = 0; + for (; j < gb->n_visible_objs; j++) { + if (gb->objects_x[j] <= gb->mode2_x_bus) break; + } + memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); + memmove(gb->objects_x + j + 1, gb->objects_x + j, gb->n_visible_objs - j); + memmove(gb->objects_y + j + 1, gb->objects_y + j, gb->n_visible_objs - j); + gb->visible_objs[j] = index; + gb->objects_x[j] = gb->mode2_x_bus; + gb->objects_y[j] = gb->mode2_y_bus; + gb->n_visible_objs++; + } +} + +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) { + 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; +} + + +static void render_pixel_if_possible(GB_gameboy_t *gb) +{ + 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; + + // Rendering (including scrolling adjustment) does not occur as long as an object at x=0 is pending + if (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && + gb->objects_x[gb->n_visible_objs - 1] == 0) { + return; + } + + if (unlikely(gb->wx_triggered && !fifo_size(&gb->bg_fifo))) return; + + fifo_item = fifo_pop(&gb->bg_fifo); + bg_priority = fifo_item->bg_priority; + + if (fifo_size(&gb->oam_fifo)) { + oam_fifo_item = fifo_pop(&gb->oam_fifo); + if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) { + draw_oam = true; + bg_priority |= oam_fifo_item->bg_priority; + } + } + + // (gb->position_in_line + 16 < 8) is (gb->position_in_line < -8) in unsigned logic + if (((uint8_t)(gb->position_in_line + 16) < 8)) { + if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) { + gb->position_in_line = -8; + } + else if (gb->position_in_line == (uint8_t) -9) { + gb->position_in_line = -16; + return; + } + } + + /* Drop pixels for scrollings */ + if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { + gb->position_in_line++; + return; + } + + /* Mixing */ + + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (gb->cgb_mode) { + bg_priority = false; + } + else { + 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; + if (!gb->sgb) { + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + } + + { + uint8_t pixel = bg_enabled? fifo_item->pixel : 0; + if (pixel && bg_priority) { + draw_oam = false; + } + if (!gb->cgb_mode) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + if (gb->sgb) { + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + } + } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + icd_pixel = pixel; + } + } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } + else { + *dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + } + } + + if (draw_oam) { + uint8_t pixel = oam_fifo_item->pixel; + if (!gb->cgb_mode) { + /* Todo: Verify access timings */ + pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); + } + if (gb->sgb) { + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + } + } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + icd_pixel = pixel; + } + } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } + else { + *dest = gb->object_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + } + } + + if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + gb->icd_pixel_callback(gb, icd_pixel); + } + } + + gb->position_in_line++; + gb->lcd_x++; + gb->window_is_being_fetched = false; +} + +static inline void dma_sync(GB_gameboy_t *gb, unsigned *cycles) +{ + if (unlikely(GB_is_dma_active(gb))) { + unsigned offset = *cycles - gb->display_cycles; // Time passed in 8MHz ticks + if (offset) { + *cycles = gb->display_cycles; + if (!gb->cgb_double_speed) { + offset >>= 1; // Convert to T-cycles + } + unsigned old = gb->dma_cycles; + gb->dma_cycles = offset; + GB_dma_run(gb); + gb->dma_cycles = old - offset; + } + } +} + +/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have + slightly different timings than CPUs <= C. + + Todo: Add support to CPU C and older */ + +static inline uint8_t fetcher_y(GB_gameboy_t *gb) +{ + return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY]; +} + +static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr) +{ + if (unlikely(gb->vram_ppu_blocked)) { + return 0xFF; + } + if (unlikely(gb->hdma_in_progress)) { + gb->addr_for_hdma_conflict = addr; + return 0; + } + // TODO: what if both? + else if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0 && (gb->dma_current_src & 0xE000) == 0x8000)) { // TODO: what happens in the last and first M cycles? + // DMAing from VRAM! + /* TODO: AGS has its own, very different pattern, but AGS is not currently a supported model */ + /* TODO: Research this when researching odd modes */ + /* TODO: probably not 100% on the first few reads during halt/stop modes*/ + unsigned offset = 1 - (gb->halted || gb->stopped); + if (GB_is_cgb(gb)) { + if (gb->dma_ppu_vram_conflict) { + addr = (gb->dma_ppu_vram_conflict_addr & 0x1FFF) | (addr & 0x2000); + } + else if (gb->dma_cycles_modulo && !gb->halted && !gb->stopped) { + addr &= 0x2000; + addr |= ((gb->dma_current_src - offset) & 0x1FFF); + } + else { + addr &= 0x2000 | ((gb->dma_current_src - offset) & 0x1FFF); + gb->dma_ppu_vram_conflict_addr = addr; + gb->dma_ppu_vram_conflict = !gb->halted && !gb->stopped; + } + } + else { + addr |= ((gb->dma_current_src - offset) & 0x1FFF); + } + gb->oam[gb->dma_current_dest - offset] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)]; + } + return gb->vram[addr]; +} + +static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) +{ + typedef enum { + GB_FETCHER_GET_TILE, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_PUSH, + GB_FETCHER_SLEEP, + } fetcher_step_t; + + static const fetcher_step_t fetcher_state_machine [8] = { + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_PUSH, + GB_FETCHER_PUSH, + }; + switch (fetcher_state_machine[gb->fetcher_state & 7]) { + case GB_FETCHER_GET_TILE: { + dma_sync(gb, cycles); + uint16_t map = 0x1800; + + if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) { + gb->wx_triggered = false; + gb->wx166_glitch = false; + } + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) { + map = 0x1C00; + } + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) { + map = 0x1C00; + } + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + uint8_t y = fetcher_y(gb); + uint8_t x = 0; + if (gb->wx_triggered) { + x = gb->window_tile_x; + } + else if ((uint8_t)(gb->position_in_line + 16) < 8) { + x = gb->io_registers[GB_IO_SCX] >> 3; + } + else { + /* TODO: There is some CGB timing error around here. + Adjusting SCX by 7 or less shouldn't have an effect on a CGB, + but SameBoy is affected by a change of both 7 and 6 (but not less). */ + x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8) / 8) & 0x1F; + } + if (gb->model > GB_MODEL_CGB_C) { + /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ + gb->fetcher_y = y; + } + gb->last_tile_index_address = map + x + y / 8 * 32; + gb->current_tile = vram_read(gb, gb->last_tile_index_address); + if (GB_is_cgb(gb)) { + /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. + This probably means the CGB has a 16-bit data bus for the VRAM. */ + gb->current_tile_attributes = vram_read(gb, gb->last_tile_index_address + 0x2000); + } + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_LOWER: { + dma_sync(gb, cycles); + bool use_glitched = false; + bool cgb_d_glitch = false; + if (gb->tile_sel_glitch) { + 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; + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + if (!use_glitched) { + gb->current_tile_data[0] = + vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + + } + if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = + vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + } + else if (cgb_d_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2); + } + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_HIGH: { + dma_sync(gb, cycles); + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + + bool use_glitched = false; + bool cgb_d_glitch = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); + } + + uint16_t tile_address = 0; + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); + + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + uint8_t y_flip = 0; + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1 - cgb_d_glitch; + if (!use_glitched) { + gb->current_tile_data[1] = + vram_read(gb, gb->last_tile_data_address); + } + if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address); + + } + else if (cgb_d_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1); + + } + } + if (gb->wx_triggered) { + gb->window_tile_x++; + gb->window_tile_x &= 0x1F; + } + + // fallthrough + case GB_FETCHER_PUSH: { + if (gb->fetcher_state < 7) { + gb->fetcher_state++; + } + if (fifo_size(&gb->bg_fifo) > 0) break; + + if (unlikely(gb->wy_triggered && !(gb->io_registers[GB_IO_LCDC] & 0x20) && !GB_is_cgb(gb))) { + /* See https://github.com/LIJI32/SameBoy/issues/278 for documentation */ + uint8_t logical_position = gb->position_in_line + 7; + if (logical_position > 167) { + logical_position = 0; + } + if (gb->io_registers[GB_IO_WX] == logical_position) { + gb->bg_fifo.read_end--; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; + gb->bg_fifo.size = 1; + break; + } + } + + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], + gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); + gb->fetcher_state = 0; + } + break; + + case GB_FETCHER_SLEEP: + { + gb->fetcher_state++; + } + break; + + nodefault; + } +} + +static uint16_t get_object_line_address(GB_gameboy_t *gb, uint8_t y, uint8_t tile, uint8_t flags) +{ + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ + uint8_t tile_y = (gb->current_line - y) & (height_16? 0xF : 7); + + if (flags & 0x40) { /* Flip Y */ + tile_y ^= height_16? 0xF : 7; + } + + /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ + uint16_t line_address = (height_16? tile & 0xFE : tile) * 0x10 + tile_y * 2; + + if (gb->cgb_mode && (flags & 0x8)) { /* Use VRAM bank 2 */ + line_address += 0x2000; + } + 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 + 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; + const object_t *object = &objects[object_index]; + gb->n_visible_objs--; + + uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (gb->n_visible_objs == 0) { + gb->data_for_sel_glitch = data1; + } + 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++; + } + } + } + else { + object_buffer = (const void *)empty_object_buffer; + } + + + 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 + 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]]; + gb->n_visible_objs--; + + uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (object->flags & 0x20) { + 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++; + } + } + } + else { + object_buffer = (const void *)empty_object_buffer; + } + + + 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->position_in_line != (uint8_t)-16) return 0; + if (gb->model & GB_MODEL_NO_SFC_BIT) return 0; + if (gb->hdma_on) return 0; + if (gb->stopped) return 0; + if (GB_is_dma_active(gb)) return 0; + if (gb->wy_triggered) { + if (gb->io_registers[GB_IO_LCDC] & 0x20) { + if ((gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) { + return 0; + } + } + else { + if (gb->io_registers[GB_IO_WX] < 167 && !GB_is_cgb(gb)) { + 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; +} + +static inline uint8_t x_for_object_match(GB_gameboy_t *gb) +{ + uint8_t ret = gb->position_in_line + 8; + if (ret > (uint8_t)-16) return 0; + return ret; +} + +/* + TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles. + The PPU logic can be greatly simplified if that delay is simply emulated. + */ +void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) +{ + if (unlikely((gb->io_registers[GB_IO_LCDC] & 0x80) && (signed)(gb->cycles_for_line * 2 + cycles + gb->display_cycles) > LINE_LENGTH * 2)) { + unsigned first_batch = (LINE_LENGTH * 2 - gb->cycles_for_line * 2 + gb->display_cycles); + GB_display_run(gb, first_batch, force); + cycles -= first_batch; + if (gb->display_state == 22) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + GB_STAT_update(gb); + } + gb->display_state = 9; + gb->display_cycles = 0; + } + if (unlikely(gb->delayed_glitch_hblank_interrupt && cycles && gb->current_line < LINES)) { + gb->delayed_glitch_hblank_interrupt = false; + gb->mode_for_interrupt = 0; + GB_STAT_update(gb); + gb->mode_for_interrupt = 3; + } + gb->cycles_since_vblank_callback += cycles / 2; + + if (cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= cycles; + } + else { + gb->frame_repeat_countdown = 0; + } + + /* The PPU does not advance while in STOP mode on the DMG */ + if (gb->stopped && !GB_is_cgb(gb)) { + if (gb->cycles_since_vblank_callback >= LCDC_PERIOD) { + GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL); + } + return; + } + + 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, 6); + GB_STATE(gb, display, 7); + GB_STATE(gb, display, 8); + GB_STATE(gb, display, 9); + GB_STATE(gb, display, 10); + GB_STATE(gb, display, 11); + GB_STATE(gb, display, 12); + GB_STATE(gb, display, 13); + GB_STATE(gb, display, 14); + GB_STATE(gb, display, 15); + GB_STATE(gb, display, 16); + GB_STATE(gb, display, 17); + GB_STATE(gb, display, 19); + GB_STATE(gb, display, 20); + GB_STATE(gb, display, 21); + GB_STATE(gb, display, 22); + GB_STATE(gb, display, 23); + GB_STATE(gb, display, 24); + GB_STATE(gb, display, 26); + GB_STATE(gb, display, 27); + GB_STATE(gb, display, 28); + GB_STATE(gb, display, 29); + GB_STATE(gb, display, 30); + GB_STATE(gb, display, 31); + GB_STATE(gb, display, 32); + GB_STATE(gb, display, 33); + GB_STATE(gb, display, 34); + GB_STATE(gb, display, 35); + GB_STATE(gb, display, 36); + GB_STATE(gb, display, 37); + GB_STATE(gb, display, 38); + GB_STATE(gb, display, 39); + GB_STATE(gb, display, 40); + GB_STATE(gb, display, 41); + GB_STATE(gb, display, 42); + GB_STATE(gb, display, 43); + } + + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + while (true) { + 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_VBLANK_TYPE_LCD_OFF); + } + return; + } + + gb->is_odd_frame = false; + + if (!GB_is_cgb(gb)) { + GB_SLEEP(gb, display, 23, 1); + } + + /* Handle mode 2 on the very first line 0 */ + gb->current_line = 0; + gb->window_y = -1; + gb->wy_triggered = false; + gb->position_in_line = -16; + + gb->ly_for_comparison = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = -1; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + gb->cycles_for_line = MODE2_LENGTH - 4; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4); + + gb->oam_write_blocked = true; + gb->cycles_for_line += 2; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 34, 2); + + gb->n_visible_objs = 0; + gb->orig_n_visible_objs = 0; + gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles. + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; + + gb->oam_write_blocked = true; + gb->oam_read_blocked = true; + gb->vram_read_blocked = gb->cgb_double_speed; + gb->vram_write_blocked = gb->cgb_double_speed; + if (!GB_is_cgb(gb)) { + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + } + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 37, 2); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3; + GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3); + + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + gb->wx_triggered = false; + gb->wx166_glitch = false; + goto mode_3_start; + + // Mode 3 abort, state 9 + display9: { + // TODO: Timing of things in this scenario is almost completely untested + if (gb->current_line < LINES && !GB_is_sgb(gb) && !gb->disable_rendering) { + GB_log(gb, "The ROM is preventing line %d from fully rendering, this could damage a real device's LCD display.\n", gb->current_line); + uint32_t *dest = NULL; + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + uint32_t color = GB_is_cgb(gb)? GB_convert_rgb15(gb, 0x7FFF, false) : gb->background_palettes_rgb[4]; + while (gb->lcd_x < 160) { + *(dest++) = color; + gb->lcd_x++; + } + } + gb->n_visible_objs = gb->orig_n_visible_objs; + gb->current_line++; + gb->cycles_for_line = 0; + if (gb->current_line != LINES) { + gb->cycles_for_line = 2; + GB_SLEEP(gb, display, 28, 2); + gb->io_registers[GB_IO_LY] = gb->current_line; + if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) { + gb->delayed_glitch_hblank_interrupt = true; + } + GB_STAT_update(gb); + gb->position_in_line = -15; + goto mode_3_start; + } + else { + if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) { + gb->delayed_glitch_hblank_interrupt = true; + } + gb->position_in_line = -16; + } + } + + while (true) { + /* Lines 0 - 143 */ + gb->window_y = -1; + 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; + + GB_SLEEP(gb, display, 35, 2); + gb->oam_write_blocked = GB_is_cgb(gb); + + GB_SLEEP(gb, display, 6, 1); + gb->io_registers[GB_IO_LY] = gb->current_line; + gb->oam_read_blocked = true; + gb->ly_for_comparison = gb->current_line? -1 : 0; + + /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. + PPU glitch? */ + if (gb->current_line != 0) { + gb->mode_for_interrupt = 2; + gb->io_registers[GB_IO_STAT] &= ~3; + } + else if (!GB_is_cgb(gb)) { + gb->io_registers[GB_IO_STAT] &= ~3; + } + GB_STAT_update(gb); + + GB_SLEEP(gb, display, 7, 1); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + gb->mode_for_interrupt = 2; + gb->oam_write_blocked = true; + gb->ly_for_comparison = gb->current_line; + GB_STAT_update(gb); + gb->mode_for_interrupt = -1; + GB_STAT_update(gb); + gb->n_visible_objs = 0; + gb->orig_n_visible_objs = 0; + + if (!GB_is_dma_active(gb) && !gb->oam_ppu_blocked) { + GB_BATCHPOINT(gb, display, 5, 80); + } + for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { + if (GB_is_cgb(gb)) { + add_object_from_index(gb, gb->oam_search_index); + /* The CGB does not care about the accessed OAM row as there's no OAM bug */ + } + GB_SLEEP(gb, display, 8, 2); + if (!GB_is_cgb(gb)) { + add_object_from_index(gb, gb->oam_search_index); + gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8; + } + if (gb->oam_search_index == 37) { + gb->vram_read_blocked = !GB_is_cgb(gb); + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + gb->oam_write_blocked = GB_is_cgb(gb); + } + } + gb->cycles_for_line = MODE2_LENGTH + 4; + gb->orig_n_visible_objs = gb->n_visible_objs; + gb->accessed_oam_row = -1; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + gb->cgb_palettes_blocked = false; + gb->oam_write_blocked = true; + gb->oam_read_blocked = true; + + GB_STAT_update(gb); + + + uint8_t idle_cycles = 3; + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + idle_cycles = 2; + } + gb->cycles_for_line += idle_cycles; + GB_SLEEP(gb, display, 10, idle_cycles); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 32, 2); + mode_3_start: + /* TODO: Timing seems incorrect, might need an access conflict handling. */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + gb->io_registers[GB_IO_WY] == gb->current_line) { + gb->wy_triggered = true; + } + + fifo_clear(&gb->bg_fifo); + fifo_clear(&gb->oam_fifo); + /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */ + fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); + gb->lcd_x = 0; + + /* 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, + WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166 + has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at + that point. The code should be updated to represent this, and this will fix the time travel hack in + WX's access conflict code. */ + + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { + bool should_activate_window = false; + if (gb->io_registers[GB_IO_WX] == 0) { + static const uint8_t scx_to_wx0_comparisons[] = {-7, -1, -2, -3, -4, -5, -6, -6}; + if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; + } + } + else if (gb->wx166_glitch) { + static const uint8_t scx_to_wx166_comparisons[] = {-16, -1, -2, -3, -4, -5, -6, -7}; + if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; + } + } + else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { + if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { + should_activate_window = true; + } + else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) { + should_activate_window = true; + /* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them. + This doesn't seem to be CPU revision dependent, but most revisions */ + if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) { + if (gb->lcd_x > 0) { + gb->lcd_x--; + } + } + } + } + + if (should_activate_window) { + gb->window_y++; + /* TODO: Verify fetcher access timings in this case */ + if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 42, 1); + } + gb->wx_triggered = true; + gb->window_tile_x = 0; + fifo_clear(&gb->bg_fifo); + gb->fetcher_state = 0; + gb->window_is_being_fetched = true; + } + else if (!GB_is_cgb(gb) && gb->io_registers[GB_IO_WX] == 166 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { + gb->window_y++; + } + } + + /* TODO: What happens when WX=0? When the fifo is full? */ + if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && + gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) && gb->bg_fifo.size == 8) { + // Insert a pixel right at the FIFO's end + gb->bg_fifo.read_end--; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; + gb->bg_fifo.size++; + gb->window_is_being_fetched = false; + } + + /* Handle objects */ + /* When the object enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. + On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ + + while (gb->n_visible_objs != 0 && + gb->objects_x[gb->n_visible_objs - 1] < x_for_object_match(gb)) { + gb->n_visible_objs--; + } + + gb->during_object_fetch = true; + while (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && + gb->objects_x[gb->n_visible_objs - 1] == x_for_object_match(gb)) { + + while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) { + advance_fetcher_state_machine(gb, &cycles); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 27, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + } + + /* TODO: Can this be deleted? { */ + advance_fetcher_state_machine(gb, &cycles); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 41, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + /* } */ + + advance_fetcher_state_machine(gb, &cycles); + dma_sync(gb, &cycles); + gb->mode2_y_bus = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 2); + gb->object_flags = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 3); + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 20, 2); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + + /* TODO: timing not verified */ + dma_sync(gb, &cycles); + gb->object_low_line_address = get_object_line_address(gb, + gb->objects_y[gb->n_visible_objs - 1], + gb->mode2_y_bus, + gb->object_flags); + gb->object_tile_data[0] = vram_read(gb, gb->object_low_line_address); + + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 39, 2); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + + gb->during_object_fetch = false; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 40, 1); + + /* TODO: timing not verified */ + dma_sync(gb, &cycles); + gb->object_tile_data[1] = vram_read(gb, get_object_line_address(gb, + gb->objects_y[gb->n_visible_objs - 1], + gb->mode2_y_bus, + gb->object_flags) + 1); + + + uint8_t palette = (gb->object_flags & 0x10) ? 1 : 0; + if (gb->cgb_mode) { + palette = gb->object_flags & 0x7; + } + fifo_overlay_object_row(&gb->oam_fifo, + gb->object_tile_data[0], + gb->object_tile_data[1], + palette, + gb->object_flags & 0x80, + gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, + gb->object_flags & 0x20); + + gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address + 1]; + gb->n_visible_objs--; + } + +abort_fetching_object: + gb->object_fetch_aborted = false; + gb->during_object_fetch = false; + + render_pixel_if_possible(gb); + advance_fetcher_state_machine(gb, &cycles); + + if (gb->position_in_line == 160) break; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 21, 1); + } +skip_slow_mode_3: + gb->position_in_line = -16; + + /* TODO: This seems incorrect (glitches Tesserae), verify further */ + /* + if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { + 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; + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + *dest = gb->background_palettes_rgb[0]; + gb->lcd_x++; + + } + + /* TODO: Verify timing */ + if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { + gb->wx166_glitch = true; + } + else { + gb->wx166_glitch = false; + } + gb->wx_triggered = false; + + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 30, 1); + } + + if (!gb->cgb_double_speed) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + + gb->cycles_for_line++; + GB_SLEEP(gb, display, 22, 1); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + GB_STAT_update(gb); + + /* Todo: Measure this value */ + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 33, 2); + gb->cgb_palettes_blocked = !gb->cgb_double_speed; + + if (gb->hdma_on_hblank && !gb->halted && !gb->stopped) { + gb->hdma_on = true; + } + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 36, 2); + gb->cgb_palettes_blocked = false; + + if (gb->cycles_for_line > LINE_LENGTH - 2) { + gb->cycles_for_line = 0; + GB_SLEEP(gb, display, 43, LINE_LENGTH - gb->cycles_for_line); + goto display9; + } + + { + uint16_t cycles_for_line = gb->cycles_for_line; + gb->cycles_for_line = 0; + GB_SLEEP(gb, display, 11, LINE_LENGTH - cycles_for_line - 2); + } + /* + TODO: Verify double speed timing + TODO: Timing differs on a DMG + */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + (gb->io_registers[GB_IO_WY] == gb->current_line)) { + gb->wy_triggered = true; + } + gb->cycles_for_line = 0; + GB_SLEEP(gb, display, 31, 2); + if (gb->current_line != LINES - 1) { + gb->mode_for_interrupt = 2; + } + + // Todo: unverified timing + gb->current_lcd_line++; + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); + } + + if (gb->icd_hreset_callback) { + gb->icd_hreset_callback(gb); + } + } + gb->wx166_glitch = false; + /* Lines 144 - 152 */ + for (; gb->current_line < VIRTUAL_LINES - 1; 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; + } + GB_SLEEP(gb, display, 12, 2); + if (gb->delayed_glitch_hblank_interrupt) { + gb->delayed_glitch_hblank_interrupt = false; + gb->mode_for_interrupt = 0; + } + gb->ly_for_comparison = gb->current_line; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 24, 1); + + if (gb->current_line == LINES) { + /* Entering VBlank state triggers the OAM interrupt */ + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 1; + gb->io_registers[GB_IO_IF] |= 1; + if (!gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) { + gb->io_registers[GB_IO_IF] |= 2; + } + gb->mode_for_interrupt = 1; + GB_STAT_update(gb); + + if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + if (GB_is_cgb(gb)) { + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; + } + else { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); + } + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; + } + } + else { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); + } + } + } + + /* 3640 is just a few cycles less than 4 lines, no clue where the + AGB constant comes from (These are measured and confirmed) */ + gb->frame_repeat_countdown = LINES * LINE_LENGTH * 2 + (gb->model > GB_MODEL_CGB_E? 5982 : 3640); // 8MHz units + if (gb->display_cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= gb->display_cycles; + } + else { + gb->frame_repeat_countdown = 0; + } + + GB_SLEEP(gb, display, 13, LINE_LENGTH - 5); + } + + /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */ + /* Lines 153 */ + gb->ly_for_comparison = -1; + GB_STAT_update(gb); + 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->model <= GB_MODEL_CGB_C && !gb->cgb_double_speed) { + gb->io_registers[GB_IO_LY] = 0; + } + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 15, (gb->model > GB_MODEL_CGB_C)? 4: 2); + + gb->io_registers[GB_IO_LY] = 0; + gb->ly_for_comparison = (gb->model > GB_MODEL_CGB_C)? 153 : -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 16, 4); + + gb->ly_for_comparison = 0; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 29, 12); /* Writing to LYC during this period on a CGB has side effects */ + GB_SLEEP(gb, display, 17, LINE_LENGTH - 24); + + + gb->current_line = 0; + gb->wy_triggered = false; + + // TODO: not the correct timing + gb->current_lcd_line = 0; + if (gb->icd_vreset_callback) { + gb->icd_vreset_callback(gb); + } + } +} + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { + default: + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->object_palettes_rgb + (4 * (palette_index & 7)); + break; + } + + for (unsigned y = 0; y < 192; y++) { + for (unsigned x = 0; x < 256; x++) { + if (x >= 128 && !GB_is_cgb(gb)) { + *(dest++) = gb->background_palettes_rgb[0]; + continue; + } + uint16_t tile = (x % 128) / 8 + y / 8 * 16; + uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0); + uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1); + + if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_BACKGROUND) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + else if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_OAM) { + pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3); + } + } + } + + + *(dest++) = palette[pixel]; + } + } +} + +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) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + uint16_t map = 0x1800; + + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->object_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_AUTO: + break; + } + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { + map = 0x1C00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + for (unsigned y = 0; y < 256; y++) { + for (unsigned x = 0; x < 256; x++) { + uint8_t tile = gb->vram[map + x/8 + y/8 * 32]; + uint16_t tile_address; + uint8_t attributes = 0; + + if (tileset_type == GB_TILESET_8800) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x8) { + tile_address += 0x2000; + } + + uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) | + ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1); + + if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + + if (palette) { + *(dest++) = palette[pixel]; + } + else { + *(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel]; + } + } + } +} + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_height) +{ + uint8_t count = 0; + *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 *object = (object_t *) &gb->oam; + uint8_t objects_in_line = 0; + bool obscured = false; + for (uint8_t i = 0; i < 40; i++, object++) { + signed object_y = object->y - 16; + // 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 = 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(*object); + } + else { + info = dest + oam_to_dest_index[i] - 1; + } + info->obscured_by_line_limit |= obscured; + } + } + + for (unsigned i = 0; i < count; i++) { + uint16_t vram_address = dest[i].tile * 0x10; + uint8_t flags = dest[i].flags; + uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); + if (GB_is_cgb(gb) && (flags & 0x8)) { + vram_address += 0x2000; + } + + 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 ); + + 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)?*object_height - 1 -y:y) * 8] = gb->object_palettes_rgb[palette * 4 + color]; + } + vram_address += 2; + } + } + return count; +} + + +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/thirdparty/SameBoy-old/Core/display.h b/thirdparty/SameBoy-old/Core/display.h new file mode 100644 index 000000000..38105bb3c --- /dev/null +++ b/thirdparty/SameBoy-old/Core/display.h @@ -0,0 +1,83 @@ +#ifndef display_h +#define display_h + +#include "gb.h" +#include +#include + +typedef enum { + GB_VBLANK_TYPE_NORMAL_FRAME, // An actual Vblank-triggered frame + GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off + GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason + GB_VBLANK_TYPE_REPEAT, // A frame that would not render on actual hardware, but the screen should retain the previous frame +} GB_vblank_type_t; + +#ifdef GB_INTERNAL +internal void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force); +internal void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); +internal void GB_STAT_update(GB_gameboy_t *gb); +internal void GB_lcd_off(GB_gameboy_t *gb); +internal void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type); +#define GB_display_sync(gb) GB_display_run(gb, 0, true) + +enum { + GB_OBJECT_PRIORITY_X, + GB_OBJECT_PRIORITY_INDEX, +}; + +#endif + +typedef enum { + GB_PALETTE_NONE, + GB_PALETTE_BACKGROUND, + GB_PALETTE_OAM, + GB_PALETTE_AUTO, +} GB_palette_type_t; + +typedef enum { + GB_MAP_AUTO, + GB_MAP_9800, + GB_MAP_9C00, +} GB_map_type_t; + +typedef enum { + GB_TILESET_AUTO, + GB_TILESET_8800, + GB_TILESET_8000, +} GB_tileset_type_t; + +typedef struct { + uint32_t image[128]; + uint8_t x, y, tile, flags; + uint16_t oam_addr; + bool obscured_by_line_limit; +} GB_oam_info_t; + +typedef enum { + GB_COLOR_CORRECTION_DISABLED, + GB_COLOR_CORRECTION_CORRECT_CURVES, + GB_COLOR_CORRECTION_MODERN_BALANCED, + GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST, + GB_COLOR_CORRECTION_REDUCE_CONTRAST, + GB_COLOR_CORRECTION_LOW_CONTRAST, + GB_COLOR_CORRECTION_MODERN_ACCURATE, +} GB_color_correction_mode_t; + +static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BALANCED instead"))) GB_COLOR_CORRECTION_EMULATE_HARDWARE = GB_COLOR_CORRECTION_MODERN_BALANCED; +static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST instead"))) GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST; + +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 *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); +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/thirdparty/SameBoy-old/Core/gb.c b/thirdparty/SameBoy-old/Core/gb.c new file mode 100644 index 000000000..923a97f8d --- /dev/null +++ b/thirdparty/SameBoy-old/Core/gb.c @@ -0,0 +1,1992 @@ +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include "random.h" +#include "gb.h" + + +#ifdef GB_DISABLE_REWIND +#define GB_rewind_free(...) +#define GB_rewind_push(...) +#endif + + +void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + if (string) { + if (gb->log_callback) { + gb->log_callback(gb, string, attributes); + } + else { + /* Todo: Add ANSI escape sequences for attributed text */ + printf("%s", string); + } + } + free(string); +} + +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, attributes, fmt, args); + va_end(args); +} + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, 0, fmt, args); + va_end(args); +} + +#ifndef GB_DISABLE_DEBUGGER +static char *default_input_callback(GB_gameboy_t *gb) +{ + char *expression = NULL; + size_t size = 0; + if (gb->debug_stopped) { + printf(">"); + } + + if (getline(&expression, &size, stdin) == -1) { + /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */ + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return strdup("c"); + } + + if (!expression) { + return strdup(""); + } + + size_t length = strlen(expression); + if (expression[length - 1] == '\n') { + expression[length - 1] = 0; + } + + if (expression[0] == '\x03') { + gb->debug_stopped = true; + free(expression); + return strdup(""); + } + return expression; +} + +static char *default_async_input_callback(GB_gameboy_t *gb) +{ +#ifndef _WIN32 + fd_set set; + FD_ZERO(&set); + FD_SET(STDIN_FILENO, &set); + struct timeval time = {0,}; + if (select(1, &set, NULL, NULL, &time) == 1) { + if (feof(stdin)) { + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return NULL; + } + return default_input_callback(gb); + } +#endif + return NULL; +} +#endif + +static void load_default_border(GB_gameboy_t *gb) +{ + if (gb->has_sgb_border) return; + + #define LOAD_BORDER() do { \ + memcpy(gb->borrowed_border.map, tilemap, sizeof(tilemap));\ + memcpy(gb->borrowed_border.palette, palette, sizeof(palette));\ + memcpy(gb->borrowed_border.tiles, tiles, sizeof(tiles));\ + } 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_CGB_E) { + #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(); + } + else { + #include "graphics/dmg_border.inc" + LOAD_BORDER(); + } +} + +void GB_init(GB_gameboy_t *gb, GB_model_t model) +{ + memset(gb, 0, sizeof(*gb)); + gb->model = model; + if (GB_is_cgb(gb)) { + gb->ram = malloc(gb->ram_size = 0x1000 * 8); + gb->vram = malloc(gb->vram_size = 0x2000 * 2); + } + else { + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + } + +#ifndef GB_DISABLE_DEBUGGER + gb->input_callback = default_input_callback; + gb->async_input_callback = default_async_input_callback; +#endif + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->clock_multiplier = 1.0; + + if (model & GB_MODEL_NO_SFC_BIT) { + /* Disable time syncing. Timing should be done by the SFC emulator. */ + gb->turbo = true; + } + + GB_reset(gb); + load_default_border(gb); +} + +GB_model_t GB_get_model(GB_gameboy_t *gb) +{ + return gb->model; +} + +void GB_free(GB_gameboy_t *gb) +{ + gb->magic = 0; + if (gb->ram) { + free(gb->ram); + } + if (gb->vram) { + free(gb->vram); + } + if (gb->mbc_ram) { + free(gb->mbc_ram); + } + if (gb->rom) { + free(gb->rom); + } + if (gb->breakpoints) { + free(gb->breakpoints); + } + if (gb->sgb) { + free(gb->sgb); + } + 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 + GB_rewind_free(gb); +#ifndef GB_DISABLE_CHEATS + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } +#endif + GB_stop_audio_recording(gb); + memset(gb, 0, sizeof(*gb)); +} + +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno)); + return errno; + } + fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); + fclose(f); + return 0; +} + +void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size) +{ + if (size > sizeof(gb->boot_rom)) { + size = sizeof(gb->boot_rom); + } + memset(gb->boot_rom, 0xFF, sizeof(gb->boot_rom)); + memcpy(gb->boot_rom, buffer, size); +} + +void GB_borrow_sgb_border(GB_gameboy_t *gb) +{ + if (GB_is_sgb(gb)) return; + if (gb->border_mode != GB_BORDER_ALWAYS) return; + if (gb->tried_loading_sgb_border) return; + gb->tried_loading_sgb_border = true; + if (gb->rom && gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow + if (!gb->boot_rom_load_callback) return; // Can't borrow a border without this callback + GB_gameboy_t sgb; + GB_init(&sgb, GB_MODEL_SGB); + sgb.cartridge_type = gb->cartridge_type; + sgb.rom = gb->rom; + sgb.rom_size = gb->rom_size; + sgb.turbo = true; + sgb.turbo_dont_skip = true; + // sgb.disable_rendering = true; + + /* Load the boot ROM using the existing gb object */ + typeof(gb->boot_rom) boot_rom_backup; + memcpy(boot_rom_backup, gb->boot_rom, sizeof(gb->boot_rom)); + gb->boot_rom_load_callback(gb, GB_BOOT_ROM_SGB); + memcpy(sgb.boot_rom, gb->boot_rom, sizeof(gb->boot_rom)); + memcpy(gb->boot_rom, boot_rom_backup, sizeof(gb->boot_rom)); + sgb.sgb->intro_animation = -1; + + for (unsigned i = 600; i--;) { + GB_run_frame(&sgb); + if (sgb.sgb->border_animation) { + gb->has_sgb_border = true; + memcpy(&gb->borrowed_border, &sgb.sgb->pending_border, sizeof(gb->borrowed_border)); + gb->borrowed_border.palette[0] = sgb.sgb->effective_palettes[0]; + break; + } + } + + sgb.rom = NULL; + sgb.rom_size = 0; + GB_free(&sgb); +} + +int GB_load_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open ROM: %s.\n", strerror(errno)); + return errno; + } + fseek(f, 0, SEEK_END); + gb->rom_size = (ftell(f) + 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 < 0x8000) { + gb->rom_size = 0x8000; + } + fseek(f, 0, 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, 1, gb->rom_size, f); + fclose(f); + GB_configure_cart(gb); + gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); + return 0; +} + +#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) +{ + 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 + } + 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) { + 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_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info) +{ + 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)); + + 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"); + return -1; + } + + 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)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + + if (gb->rom_size < 0x8000) { + gb->rom_size = 0x8000; + } + + if (gb->rom) { + free(gb->rom); + } + + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ + 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) { + free(gb->mbc_ram); + gb->mbc_ram = NULL; + gb->mbc_ram_size = 0; + } + + 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); + } + + bool has_interrupts = gb->gbs_header.TAC & 0x40; + + 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); + } + + + 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_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 = 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); + 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"); + if (!f) { + GB_log(gb, "Could not open ISX file: %s.\n", strerror(errno)); + return errno; + } + char magic[4]; +#define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error + fread(magic, 1, sizeof(magic), f); + + + bool extended = *(uint32_t *)&magic == BE32('ISX '); + + fseek(f, extended? 0x20 : 0, SEEK_SET); + + + uint8_t *old_rom = gb->rom; + uint32_t old_size = gb->rom_size; + gb->rom = NULL; + gb->rom_size = 0; + + while (true) { + uint8_t record_type = 0; + if (fread(&record_type, sizeof(record_type), 1, f) != 1) break; + switch (record_type) { + case 0x01: { // Binary + uint16_t bank; + uint16_t address; + uint16_t length; + uint8_t byte; + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); + address = LE16(address); + address &= 0x3FFF; + + READ(length); + length = LE16(length); + + size_t needed_size = bank * 0x4000 + address + length; + if (needed_size > 1024 * 1024 * 32) goto error; + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) goto error; + + break; + } + + case 0x11: { // Extended Binary + uint32_t address; + uint32_t length; + + READ(address); + address = LE32(address); + + READ(length); + length = LE32(length); + size_t needed_size = address + length; + if (needed_size > 1024 * 1024 * 32) goto error; + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + address, length, 1, f) != 1) goto error; + + break; + } + + case 0x04: { // Symbol + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint16_t bank; + uint16_t address; + uint8_t byte; + READ(count); + count = LE16(count); + while (count--) { + READ(length); + if (fread(name, length, 1, f) != 1) goto error; + name[length] = 0; + READ(flag); // unused + + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); + address = LE16(address); + GB_debugger_add_symbol(gb, bank, address, name); + } + break; + } + + case 0x14: { // Extended Binary + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint32_t address; + READ(count); + count = LE16(count); + while (count--) { + READ(length); + if (fread(name, length + 1, 1, f) != 1) goto error; + name[length] = 0; + READ(flag); // unused + + READ(address); + address = LE32(address); + // TODO: How to convert 32-bit addresses to Bank:Address? Needs to tell RAM and ROM apart + } + break; + } + + default: + goto done; + } + } +done:; +#undef READ + if (gb->rom_size == 0) goto error; + + size_t needed_size = (gb->rom_size + 0x3FFF) & ~0x3FFF; /* Round to bank */ + + /* And then round to a power of two */ + while (needed_size & (needed_size - 1)) { + /* I promise this works. */ + needed_size |= needed_size >> 1; + needed_size++; + } + + if (needed_size < 0x8000) { + needed_size = 0x8000; + } + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + GB_configure_cart(gb); + + // Fix a common wrong MBC error + if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery + bool needs_fix = false; + if (gb->rom_size >= 0x21 * 0x4000) { + for (unsigned i = 0x20 * 0x4000; i < 0x21 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (!needs_fix && gb->rom_size >= 0x41 * 0x4000) { + for (unsigned i = 0x40 * 0x4000; i < 0x41 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (!needs_fix && gb->rom_size >= 0x61 * 0x4000) { + for (unsigned i = 0x60 * 0x4000; i < 0x61 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (needs_fix) { + gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery + GB_configure_cart(gb); + gb->rom[0x147] = 0x3; + GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n"); + } + } + + if (old_rom) { + free(old_rom); + } + + return 0; +error: + GB_log(gb, "Invalid or unsupported ISX file.\n"); + if (gb->rom) { + free(gb->rom); + gb->rom = old_rom; + gb->rom_size = old_size; + } + fclose(f); + gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); + return -1; +} + +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + gb->rom_size = (size + 0x3FFF) & ~0x3FFF; + while (gb->rom_size & (gb->rom_size - 1)) { + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } + if (gb->rom) { + free(gb->rom); + } + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); + memcpy(gb->rom, buffer, size); + GB_configure_cart(gb); + gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); +} + +typedef 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]; +} 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]; +} tpp1_rtc_save_t; + +typedef union { + struct __attribute__((packed)) { + GB_rtc_time_t rtc_real; + time_t last_rtc_second; /* Platform specific endianess and size */ + } sameboy_legacy; + struct { + /* Used by VBA versions with 32-bit timestamp*/ + 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*/ + vba_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; /* Always little endian */ + } vba64; +} rtc_save_t; + +static void fill_tpp1_save_data(GB_gameboy_t *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. + 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) { + 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(tpp1_rtc_save_t); + } + + rtc_save_t rtc_save_size; + return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); +} + +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; + + memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + buffer += gb->mbc_ram_size; + 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) { + buffer += gb->mbc_ram_size; + + GB_huc3_rtc_time_t rtc_save = { + 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, + }; + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->has_rtc) { + 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; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + 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; + rtc_save.vba64.last_rtc_second = LE64(time(NULL)); + memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); + } + + errno = 0; + return errno; +} + +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) { + GB_log(gb, "Could not open battery save: %s.\n", strerror(errno)); + return errno; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + 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); + return EIO; + } + } + else if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_t rtc_save = { + 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 (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->has_rtc) { + 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; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + 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; + 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; + } + + } + + errno = 0; + fclose(f); + return errno; +} + +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--;) { + 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)); + if (size <= gb->mbc_ram_size) { + goto reset_rtc; + } + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + 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)); + + 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)) { + goto reset_rtc; + } + memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); + 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; + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + + 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): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + 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; + gb->last_rtc_second = LE32(rtc_save.vba32.last_rtc_second); + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + 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; + gb->last_rtc_second = LE64(rtc_save.vba64.last_rtc_second); + break; + + default: + goto reset_rtc; + } + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +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. */ + if (gb->cartridge_type->mbc_type == GB_HUC3) { + gb->huc3.days = 0xFFFF; + gb->huc3.minutes = 0xFFF; + gb->huc3.alarm_enabled = false; + } +exit: + return; +} + +/* Loading will silently stop if the format is incomplete */ +void GB_load_battery(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + goto reset_rtc; + } + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + tpp1_rtc_save_t rtc_save; + if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + goto reset_rtc; + } + + 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) { + goto reset_rtc; + } + 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; + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + + 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)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + 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; + gb->last_rtc_second = LE32(rtc_save.vba32.last_rtc_second); + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + 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; + gb->last_rtc_second = LE64(rtc_save.vba64.last_rtc_second); + break; + + default: + goto reset_rtc; + } + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +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. */ + 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; +} + +unsigned GB_run(GB_gameboy_t *gb) +{ + gb->vblank_just_occured = false; + + if (gb->sgb && gb->sgb->intro_animation < 96) { + /* On the SGB, the GB is halted after finishing the boot ROM. + Then, after the boot animation is almost done, it's reset. + Since the SGB HLE does not perform any header validity checks, + 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, true); + gb->cycles_since_last_sync += 228; + return 228; + } + + GB_debugger_run(gb); + gb->cycles_since_run = 0; + GB_cpu_run(gb); + if (gb->vblank_just_occured) { + 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; +} + +uint64_t GB_run_frame(GB_gameboy_t *gb) +{ + /* Configure turbo temporarily, the user wants to handle FPS capping manually. */ + bool old_turbo = gb->turbo; + bool old_dont_skip = gb->turbo_dont_skip; + gb->turbo = true; + gb->turbo_dont_skip = true; + + gb->cycles_since_last_sync = 0; + while (true) { + GB_run(gb); + if (gb->vblank_just_occured) { + break; + } + } + gb->turbo = old_turbo; + gb->turbo_dont_skip = old_dont_skip; + return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ +} + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) +{ + gb->screen = output; +} + +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb) +{ + return gb->screen; +} + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) +{ + gb->vblank_callback = callback; +} + +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) +{ + gb->log_callback = callback; +} + +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ +#ifndef GB_DISABLE_DEBUGGER + if (gb->input_callback == default_input_callback) { + gb->async_input_callback = NULL; + } + gb->input_callback = callback; +#endif +} + +void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ +#ifndef GB_DISABLE_DEBUGGER + gb->async_input_callback = callback; +#endif +} + +void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback) +{ + 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; +} + +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_callback_t callback) +{ + gb->lcd_status_callback = callback; +} + +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xAA, 0xAA, 0xAA}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xA5, 0x63}, {0xC6, 0xDE, 0x8C}, {0xD2, 0xE6, 0xA6}}}; +const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0E}, {0x3A, 0x4C, 0x3A}, {0x81, 0x8D, 0x66}, {0xC2, 0xCE, 0x93}, {0xCF, 0xDA, 0xAC}}}; +const GB_palette_t GB_PALETTE_GBL = {{{0x0A, 0x1C, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xB4, 0x95}, {0x7F, 0xE2, 0xC3}, {0x91, 0xEA, 0xD0}}}; + +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->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->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->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->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 + gb->background_palettes_rgb[4] = + gb->rgb_encode_callback(gb, palette->colors[4].r, palette->colors[4].g, palette->colors[4].b); + } +} + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette) +{ + gb->dmg_palette = 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) +{ + + gb->rgb_encode_callback = callback; + update_dmg_palette(gb); + + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } +} + +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) +{ + gb->infrared_callback = callback; +} + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state) +{ + gb->infrared_input = state; +} + +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) +{ + gb->rumble_callback = callback; +} + +void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback) +{ + gb->serial_transfer_bit_start_callback = callback; +} + +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback) +{ + gb->serial_transfer_bit_end_callback = callback; +} + +bool GB_serial_get_data_bit(GB_gameboy_t *gb) +{ + if (!(gb->io_registers[GB_IO_SC] & 0x80)) { + /* Disabled serial returns 0 bits */ + return false; + } + + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial read request while using internal clock. \n"); + return true; + } + return gb->io_registers[GB_IO_SB] & 0x80; +} + +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data) +{ + if (!(gb->io_registers[GB_IO_SC] & 0x80)) { + /* Serial disabled */ + return; + } + + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial write request while using internal clock. \n"); + return; + } + + gb->io_registers[GB_IO_SB] <<= 1; + gb->io_registers[GB_IO_SB] |= data; + gb->serial_count++; + if (gb->serial_count == 8) { + gb->io_registers[GB_IO_IF] |= 8; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->serial_count = 0; + } +} + +void GB_disconnect_serial(GB_gameboy_t *gb) +{ + gb->serial_transfer_bit_start_callback = NULL; + gb->serial_transfer_bit_end_callback = NULL; + + /* Reset any internally-emulated device. */ + memset(&gb->printer, 0, sizeof(gb->printer)); + memset(&gb->workboy, 0, sizeof(gb->workboy)); +} + +bool GB_is_inited(GB_gameboy_t *gb) +{ + return gb->magic == state_magic(); +} + +bool GB_is_cgb(const GB_gameboy_t *gb) +{ + return gb->model >= GB_MODEL_CGB_0; +} + +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; +} + +bool GB_is_hle_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2; +} + +void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) +{ + gb->turbo = on; + gb->turbo_dont_skip = no_frame_skip; +} + +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->disable_rendering = disabled; +} + +void *GB_get_user_data(GB_gameboy_t *gb) +{ + return gb->user_data; +} + +void GB_set_user_data(GB_gameboy_t *gb, void *data) +{ + gb->user_data = 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_A: /* Unverified */ + case GB_MODEL_GBP_A: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = GB_random(); + } + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = GB_random(); + if (i & 0x100) { + gb->ram[i] &= GB_random(); + } + else { + gb->ram[i] |= GB_random(); + } + } + break; + + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = 0x55; + gb->ram[i] ^= GB_random() & GB_random() & GB_random(); + } + 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++) { + if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) { + gb->ram[i] = 0; + } + else { + gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random() | GB_random(); + } + } + 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_0: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_D: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + gb->hram[i] = GB_random(); + } + 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 */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + if (i & 1) { + gb->hram[i] = GB_random() | GB_random() | GB_random(); + } + else { + gb->hram[i] = GB_random() & GB_random() & GB_random(); + } + } + break; + } + + /* 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: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + /* Zero'd out by boot ROM anyway */ + 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 */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < 8; i++) { + if (i & 2) { + gb->oam[i] = GB_random() & GB_random() & GB_random(); + } + else { + gb->oam[i] = GB_random() | GB_random() | GB_random(); + } + } + for (unsigned i = 8; i < sizeof(gb->oam); i++) { + gb->oam[i] = gb->oam[i - 8]; + } + break; + } + + /* 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: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + /* 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 */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: { + 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() & GB_random(); + } + else { + gb->io_registers[GB_IO_WAV_START + i] = GB_random() | GB_random() | GB_random(); + } + } + break; + } + } + + for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { + gb->extra_oam[i] = GB_random(); + } + + 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->object_palettes_data[i] = GB_random(); + } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } + } +} + +static void request_boot_rom(GB_gameboy_t *gb) +{ + if (gb->boot_rom_load_callback) { + GB_boot_rom_t type = 0; + switch (gb->model) { + 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: + case GB_MODEL_SGB_PAL_NO_SFC: + type = GB_BOOT_ROM_SGB; + break; + case GB_MODEL_SGB2: + 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_A: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_D: + case GB_MODEL_CGB_E: + type = GB_BOOT_ROM_CGB; + break; + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + type = GB_BOOT_ROM_AGB; + break; + } + gb->boot_rom_load_callback(gb, type); + } +} + +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)); + memcpy(GB_GET_SECTION(gb, rtc), rtc_section, sizeof(rtc_section)); + gb->model = model; + gb->version = GB_STRUCT_VERSION; + + GB_reset_mbc(gb); + + gb->last_rtc_second = time(NULL); + gb->cgb_ram_bank = 1; + gb->io_registers[GB_IO_JOYP] = 0xCF; + gb->mbc_ram_size = mbc_ram_size; + if (GB_is_cgb(gb)) { + gb->ram_size = 0x1000 * 8; + gb->vram_size = 0x2000 * 2; + memset(gb->vram, 0, gb->vram_size); + gb->cgb_mode = true; + gb->object_priority = GB_OBJECT_PRIORITY_INDEX; + } + else { + gb->ram_size = 0x2000; + gb->vram_size = 0x2000; + memset(gb->vram, 0, gb->vram_size); + gb->object_priority = GB_OBJECT_PRIORITY_X; + + update_dmg_palette(gb); + } + reset_ram(gb); + + gb->serial_mask = 0x80; + gb->io_registers[GB_IO_SC] = 0x7E; + + /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ + gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; + + gb->accessed_oam_row = -1; + gb->dma_current_dest = 0xA1; + + if (GB_is_hle_sgb(gb)) { + if (!gb->sgb) { + gb->sgb = malloc(sizeof(*gb->sgb)); + } + memset(gb->sgb, 0, sizeof(*gb->sgb)); + memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases)); + gb->sgb_intro_sweep_phase = 0; + gb->sgb_intro_sweep_previous_sample = 0; + gb->sgb->intro_animation = -10; + + gb->sgb->player_count = 1; + GB_sgb_load_default_data(gb); + + } + else { + if (gb->sgb) { + free(gb->sgb); + gb->sgb = NULL; + } + } + + GB_set_internal_div_counter(gb, 8); + + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + + gb->magic = state_magic(); + request_boot_rom(gb); +} + +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) +{ + gb->model = model; + if (GB_is_cgb(gb)) { + gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8); + gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2); + } + else { + 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); +} + +void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank) +{ + /* Set size and bank to dummy pointers if not set */ + size_t dummy_size; + uint16_t dummy_bank; + if (!size) { + size = &dummy_size; + } + + if (!bank) { + bank = &dummy_bank; + } + + + switch (access) { + case GB_DIRECT_ACCESS_ROM: + *size = gb->rom_size; + *bank = gb->mbc_rom_bank & (gb->rom_size / 0x4000 - 1); + return gb->rom; + case GB_DIRECT_ACCESS_ROM0: + *size = gb->rom_size; + *bank = gb->mbc_rom0_bank & (gb->rom_size / 0x4000 - 1); + return gb->rom; + case GB_DIRECT_ACCESS_RAM: + *size = gb->ram_size; + *bank = gb->cgb_ram_bank; + return gb->ram; + case GB_DIRECT_ACCESS_CART_RAM: + *size = gb->mbc_ram_size; + *bank = gb->mbc_ram_bank & (gb->mbc_ram_size / 0x2000 - 1); + return gb->mbc_ram; + case GB_DIRECT_ACCESS_VRAM: + *size = gb->vram_size; + *bank = gb->cgb_vram_bank; + return gb->vram; + case GB_DIRECT_ACCESS_HRAM: + *size = sizeof(gb->hram); + *bank = 0; + return &gb->hram; + case GB_DIRECT_ACCESS_IO: + *size = sizeof(gb->io_registers); + *bank = 0; + return &gb->io_registers; + case GB_DIRECT_ACCESS_BOOTROM: + *size = GB_is_cgb(gb)? sizeof(gb->boot_rom) : 0x100; + *bank = 0; + return &gb->boot_rom; + case GB_DIRECT_ACCESS_OAM: + *size = sizeof(gb->oam); + *bank = 0; + return &gb->oam; + case GB_DIRECT_ACCESS_BGP: + *size = sizeof(gb->background_palettes_data); + *bank = 0; + return &gb->background_palettes_data; + case GB_DIRECT_ACCESS_OBP: + *size = sizeof(gb->object_palettes_data); + *bank = 0; + return &gb->object_palettes_data; + case GB_DIRECT_ACCESS_IE: + *size = sizeof(gb->interrupt_enable); + *bank = 0; + return &gb->interrupt_enable; + default: + *size = 0; + *bank = 0; + return NULL; + } +} + +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; + GB_update_clock_rate(gb); +} + +uint32_t GB_get_clock_rate(GB_gameboy_t *gb) +{ + return gb->clock_rate; +} + +uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb) +{ + 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; + gb->border_mode = border_mode; +} + +unsigned GB_get_screen_width(GB_gameboy_t *gb) +{ + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 256 : 160; + case GB_BORDER_NEVER: + return 160; + case GB_BORDER_ALWAYS: + return 256; + } +} + +unsigned GB_get_screen_height(GB_gameboy_t *gb) +{ + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 224 : 144; + case GB_BORDER_NEVER: + return 144; + case GB_BORDER_ALWAYS: + return 224; + } +} + +unsigned GB_get_player_count(GB_gameboy_t *gb) +{ + return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1; +} + +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback) +{ + gb->update_input_hint_callback = callback; +} + +double GB_get_usual_frame_rate(GB_gameboy_t *gb) +{ + return GB_get_clock_rate(gb) / (double)LCDC_PERIOD; +} + +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback) +{ + gb->joyp_write_callback = callback; +} + +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback) +{ + gb->icd_pixel_callback = callback; +} + +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback) +{ + gb->icd_hreset_callback = callback; +} + + +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback) +{ + gb->icd_vreset_callback = callback; +} + +void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback) +{ + gb->boot_rom_load_callback = callback; + request_boot_rom(gb); +} + +unsigned GB_time_to_alarm(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3) return 0; + if (!gb->huc3.alarm_enabled) return 0; + if (!(gb->huc3.alarm_days & 0x2000)) return 0; + unsigned current_time = (gb->huc3.days & 0x1FFF) * 24 * 60 * 60 + gb->huc3.minutes * 60 + (time(NULL) % 60); + unsigned alarm_time = (gb->huc3.alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3.alarm_minutes * 60; + if (current_time > alarm_time) return 0; + return alarm_time - current_time; +} + +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); + if (gb->rom_size >= 0x4000) { + for (unsigned i = 0; i < 0x10; i++) { + if (gb->rom[0x134 + i] < 0x20 || gb->rom[0x134 + i] >= 0x80) break; + title[i] = gb->rom[0x134 + i]; + } + } +} + +uint32_t GB_get_rom_crc32(GB_gameboy_t *gb) +{ + static const uint32_t table[] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + const uint8_t *byte = gb->rom; + uint32_t size = gb->rom_size; + uint32_t ret = 0xFFFFFFFF; + while (size--) { + ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8); + } + return ~ret; +} diff --git a/thirdparty/SameBoy-old/Core/gb.h b/thirdparty/SameBoy-old/Core/gb.h new file mode 100644 index 000000000..f3086133f --- /dev/null +++ b/thirdparty/SameBoy-old/Core/gb.h @@ -0,0 +1,966 @@ +#ifndef GB_h +#define GB_h +#define typeof __typeof__ +#include +#include +#include +#include + +#include "defs.h" +#include "save_state.h" + +#include "apu.h" +#include "camera.h" +#include "debugger.h" +#include "display.h" +#include "joypad.h" +#include "mbc.h" +#include "memory.h" +#include "printer.h" +#include "timing.h" +#include "rewind.h" +#include "sm83_cpu.h" +#include "symbol_hash.h" +#include "sgb.h" +#include "cheats.h" +#include "rumble.h" +#include "workboy.h" +#include "random.h" + +#define GB_STRUCT_VERSION 14 + +#define GB_MODEL_FAMILY_MASK 0xF00 +#define GB_MODEL_DMG_FAMILY 0x000 +#define GB_MODEL_MGB_FAMILY 0x100 +#define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_GBP_BIT 0x20 +#define GB_MODEL_PAL_BIT 0x40 +#define GB_MODEL_NO_SFC_BIT 0x80 + +#define GB_REWIND_FRAMES_PER_KEY 255 + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define GB_BIG_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define GB_LITTLE_ENDIAN +#else +#error Unable to detect endianess +#endif + +#ifdef GB_BIG_ENDIAN +#define GB_REGISTER_ORDER a, f, \ + b, c, \ + d, e, \ + h, l +#else +#define GB_REGISTER_ORDER f, a, \ + c, b, \ + e, d, \ + l, h +#endif + +typedef struct { + struct GB_color_s { + uint8_t r, g, b; + } colors[5]; +} GB_palette_t; + +extern const GB_palette_t GB_PALETTE_GREY; +extern const GB_palette_t GB_PALETTE_DMG; +extern const GB_palette_t GB_PALETTE_MGB; +extern const GB_palette_t GB_PALETTE_GBL; + +typedef union { + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + 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; + +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, + GB_MODEL_DMG_B = 0x002, + // GB_MODEL_DMG_C = 0x003, + GB_MODEL_SGB = 0x004, + GB_MODEL_SGB_NTSC = GB_MODEL_SGB, + GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, + GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, + GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, + GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, + 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_A = 0x201, + GB_MODEL_CGB_B = 0x202, + GB_MODEL_CGB_C = 0x203, + GB_MODEL_CGB_D = 0x204, + GB_MODEL_CGB_E = 0x205, + // GB_MODEL_AGB_0 = 0x206, + GB_MODEL_AGB_A = 0x207, + GB_MODEL_GBP_A = GB_MODEL_AGB_A | GB_MODEL_GBP_BIT, // AGB-A inside a Game Boy Player + GB_MODEL_AGB = GB_MODEL_AGB_A, + GB_MODEL_GBP = GB_MODEL_GBP_A, + //GB_MODEL_AGB_B = 0x208 + //GB_MODEL_AGB_E = 0x209 + //GB_MODEL_GBP_E = GB_MODEL_AGB_E | GB_MODEL_GBP_BIT, // AGB-E inside a Game Boy Player +} GB_model_t; + +enum { + GB_REGISTER_AF, + GB_REGISTER_BC, + GB_REGISTER_DE, + GB_REGISTER_HL, + GB_REGISTER_SP, + GB_REGISTER_PC, + GB_REGISTERS_16_BIT /* Count */ +}; + +/* Todo: Actually use these! */ +enum { + GB_CARRY_FLAG = 16, + GB_HALF_CARRY_FLAG = 32, + GB_SUBTRACT_FLAG = 64, + GB_ZERO_FLAG = 128, +}; + +typedef enum { + GB_BORDER_SGB, + GB_BORDER_NEVER, + GB_BORDER_ALWAYS, +} GB_border_mode_t; + +enum { + /* Joypad and Serial */ + GB_IO_JOYP = 0x00, // Joypad (R/W) + GB_IO_SB = 0x01, // Serial transfer data (R/W) + GB_IO_SC = 0x02, // Serial Transfer Control (R/W) + + /* Missing */ + + /* Timers */ + GB_IO_DIV = 0x04, // Divider Register (R/W) + GB_IO_TIMA = 0x05, // Timer counter (R/W) + GB_IO_TMA = 0x06, // Timer Modulo (R/W) + GB_IO_TAC = 0x07, // Timer Control (R/W) + + /* Missing */ + + GB_IO_IF = 0x0F, // Interrupt Flag (R/W) + + /* Sound */ + GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W) + GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W) + GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W) + GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only) + GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W) + /* NR20 does not exist */ + GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W) + GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W) + GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W) + GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W) + GB_IO_NR30 = 0x1A, // Channel 3 Sound on/off (R/W) + GB_IO_NR31 = 0x1B, // Channel 3 Sound Length + GB_IO_NR32 = 0x1C, // Channel 3 Select output level (R/W) + GB_IO_NR33 = 0x1D, // Channel 3 Frequency's lower data (W) + GB_IO_NR34 = 0x1E, // Channel 3 Frequency's higher data (R/W) + /* NR40 does not exist */ + GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W) + GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W) + GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W) + GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W) + GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W) + GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W) + GB_IO_NR52 = 0x26, // Sound on/off + + /* Missing */ + + GB_IO_WAV_START = 0x30, // Wave pattern start + GB_IO_WAV_END = 0x3F, // Wave pattern end + + /* Graphics */ + GB_IO_LCDC = 0x40, // LCD Control (R/W) + GB_IO_STAT = 0x41, // LCDC Status (R/W) + GB_IO_SCY = 0x42, // Scroll Y (R/W) + GB_IO_SCX = 0x43, // Scroll X (R/W) + GB_IO_LY = 0x44, // LCDC Y-Coordinate (R) + GB_IO_LYC = 0x45, // LY Compare (R/W) + GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W) + GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only + GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only + GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only + GB_IO_WY = 0x4A, // Window Y Position (R/W) + GB_IO_WX = 0x4B, // Window X Position minus 7 (R/W) + // Controls DMG mode and PGB mode + GB_IO_KEY0 = 0x4C, + + /* General CGB features */ + GB_IO_KEY1 = 0x4D, // CGB Mode Only - Prepare Speed Switch + + /* Missing */ + + GB_IO_VBK = 0x4F, // CGB Mode Only - VRAM Bank + GB_IO_BANK = 0x50, // Write to disable the BIOS mapping + + /* CGB DMA */ + GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High + GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low + GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High + GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low + GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start + + /* IR */ + GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port + + /* Missing */ + + /* CGB Palettes */ + GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index + GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data + GB_IO_OBPI = 0x6A, // CGB Mode Only - Object Palette Index + GB_IO_OBPD = 0x6B, // CGB Mode Only - Object Palette Data + GB_IO_OPRI = 0x6C, // Affects object priority (X based or index based) + + /* Missing */ + + GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank + GB_IO_PSM = 0x71, // Palette Selection Mode, controls the PSW and key combo + GB_IO_PSWX = 0x72, // X position of the palette switching window + GB_IO_PSWY = 0x73, // Y position of the palette switching window + GB_IO_PSW = 0x74, // Key combo to trigger the palette switching window + GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) + GB_IO_PCM12 = 0x76, // Channels 1 and 2 amplitudes + GB_IO_PCM34 = 0x77, // Channels 3 and 4 amplitudes +}; + +typedef enum { + GB_LOG_BOLD = 1, + GB_LOG_DASHED_UNDERLINE = 2, + GB_LOG_UNDERLINE = 4, + GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE +} GB_log_attributes; + +typedef enum { + GB_BOOT_ROM_DMG_0, + GB_BOOT_ROM_DMG, + GB_BOOT_ROM_MGB, + GB_BOOT_ROM_SGB, + GB_BOOT_ROM_SGB2, + GB_BOOT_ROM_CGB_0, + GB_BOOT_ROM_CGB, + GB_BOOT_ROM_AGB, +} GB_boot_rom_t; + +#ifdef GB_INTERNAL +#define LCDC_PERIOD 70224 +#define CPU_FREQUENCY 0x400000 +#define SGB_NTSC_FREQUENCY (21477272 / 5) +#define SGB_PAL_FREQUENCY (21281370 / 5) +#define DIV_CYCLES (0x100) + +#if !defined(MIN) +#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) +#define MAX(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#endif +#endif + +typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type); +typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); +typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); +typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); +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); +typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); +typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row); +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); +typedef void (*GB_lcd_line_callback_t)(GB_gameboy_t *gb, uint8_t line); +typedef void (*GB_lcd_status_callback_t)(GB_gameboy_t *gb, bool on); + +struct GB_breakpoint_s; +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; // 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 8 +typedef struct { + GB_fifo_item_t fifo[GB_FIFO_LENGTH]; + uint8_t read_end; + uint8_t size; +} GB_fifo_t; + +typedef struct { + uint32_t magic; + uint8_t track_count; + uint8_t first_track; + uint16_t load_address; + uint16_t init_address; + uint16_t play_address; + uint16_t sp; + uint8_t TMA; + uint8_t TAC; + char title[32]; + char author[32]; + char copyright[32]; +} GB_gbs_header_t; + +typedef struct { + uint8_t track_count; + uint8_t first_track; + char title[33]; + char author[33]; + char copyright[33]; +} GB_gbs_info_t; + +/* Duplicated so it can remain anonymous in GB_gameboy_t */ +typedef union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp, + pc; + }; + struct { + uint8_t GB_REGISTER_ORDER; + }; +} 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. + This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 + bit platforms. */ + +#ifdef GB_INTERNAL +struct GB_gameboy_s { +#else +struct GB_gameboy_internal_s { +#endif + GB_SECTION(header, + /* The magic makes sure a state file is: + - Indeed a SameBoy state file. + - Has the same endianess has the current platform. */ + volatile uint32_t magic; + /* The version field makes sure we don't load save state files with a completely different structure. + This happens when struct fields are removed/resized in an backward incompatible manner. */ + uint32_t version; + ) + + GB_SECTION(core_state, + /* Registers */ + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp, + pc; + }; + struct { + uint8_t GB_REGISTER_ORDER; + }; + }; + uint8_t ime; + uint8_t interrupt_enable; + uint8_t cgb_ram_bank; + + /* CPU and General Hardware Flags*/ + GB_model_t model; + bool cgb_mode; + bool cgb_double_speed; + bool halted; + bool stopped; + bool boot_rom_finished; + bool ime_toggle; /* ei has delayed a effect.*/ + bool halt_bug; + bool just_halted; + + /* Misc state */ + bool infrared_input; + GB_printer_t printer; + uint8_t extra_oam[0xFF00 - 0xFEA0]; + uint32_t ram_size; // Different between CGB and DMG + GB_workboy_t workboy; + + int32_t ir_sensor; + bool effective_ir_input; + uint16_t address_bus; + ) + + /* DMA and HDMA */ + GB_SECTION(dma, + bool hdma_on; + bool hdma_on_hblank; + uint8_t hdma_steps_left; + uint16_t hdma_current_src, hdma_current_dest; + + uint8_t dma_current_dest; + uint8_t last_dma_read; + uint16_t dma_current_src; + uint16_t dma_cycles; + int8_t dma_cycles_modulo; + bool dma_ppu_vram_conflict; + uint16_t dma_ppu_vram_conflict_addr; + uint8_t hdma_open_bus; /* Required to emulate HDMA reads from Exxx */ + bool allow_hdma_on_wake; + ) + + /* MBC */ + GB_SECTION(mbc, + uint16_t mbc_rom_bank; + uint16_t mbc_rom0_bank; /* For multicart mappings . */ + uint8_t mbc_ram_bank; + uint32_t mbc_ram_size; + bool mbc_ram_enable; + union { + struct { + uint8_t bank_low:5; + uint8_t bank_high:2; + uint8_t mode:1; + } mbc1; + + struct { + uint8_t rom_bank:4; + } mbc2; + + struct { + uint8_t rom_bank:8; + uint8_t ram_bank:3; + bool rtc_mapped:1; + } mbc3; + + struct { + uint8_t rom_bank_low; + uint8_t rom_bank_high:1; + uint8_t ram_bank:4; + } mbc5; // Also used for GB_CAMERA + + struct { + uint8_t rom_bank; + uint16_t x_latch; + uint16_t y_latch; + bool latch_ready:1; + bool eeprom_do:1; + bool eeprom_di:1; + bool eeprom_clk:1; + bool eeprom_cs:1; + uint16_t eeprom_command:11; + uint16_t read_bits; + uint8_t argument_bits_left:5; + bool secondary_ram_enable:1; + bool eeprom_write_enabled:1; + } mbc7; + + struct { + uint8_t rom_bank_low:5; + uint8_t rom_bank_mid:2; + bool mbc1_mode:1; + + uint8_t rom_bank_mask:4; + uint8_t rom_bank_high:2; + uint8_t ram_bank_low:2; + + uint8_t ram_bank_high:2; + uint8_t ram_bank_mask:2; + + bool locked:1; + bool mbc1_mode_disable:1; + bool multiplex_mode:1; + } mmm01; + + struct { + uint8_t bank_low:6; + uint8_t bank_high:3; + bool ir_mode; + } huc1; + + struct { + 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; + }; + bool camera_registers_mapped; + uint8_t camera_registers[0x36]; + uint8_t rumble_strength; + bool cart_ir; + uint8_t camera_alignment; + int32_t camera_countdown; + ) + + /* HRAM and HW Registers */ + GB_SECTION(hram, + uint8_t hram[0xFFFF - 0xFF80]; + uint8_t io_registers[0x80]; + ) + + /* Timing */ + GB_SECTION(timing, + GB_UNIT(display); + GB_UNIT(div); + uint16_t div_counter; + uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ + bool serial_master_clock; + uint8_t serial_mask; + 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 + /* 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; + uint8_t joyp_switching_delay; + uint8_t joyp_switch_value; + uint16_t key_bounce_timing[GB_KEY_MAX]; + ) + + /* APU */ + GB_SECTION(apu, + GB_apu_t apu; + ) + + /* RTC */ + GB_SECTION(rtc, + GB_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; + uint32_t rtc_cycles; + uint8_t tpp1_mr4; + ) + + /* Video Display */ + GB_SECTION(video, + uint32_t vram_size; // Different between CGB and DMG + bool cgb_vram_bank; + uint8_t oam[0xA0]; + uint8_t background_palettes_data[0x40]; + uint8_t object_palettes_data[0x40]; + uint8_t position_in_line; + bool stat_interrupt_line; + uint8_t window_y; + /* The LCDC will skip the first frame it renders after turning it on. + On the CGB, a frame is not skipped if the previous frame was skipped as well. + See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ + + /* TODO: Drop this and properly emulate the dropped vreset signal*/ + enum { + GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, + // on a CGB, the previous frame is repeated (which might be + // blank if the LCD was off for more than a few cycles) + GB_FRAMESKIP_FIRST_FRAME_SKIPPED__DEPRECATED, + GB_FRAMESKIP_FIRST_FRAME_RENDERED, + } frame_skip_state; + bool oam_read_blocked; + bool vram_read_blocked; + bool oam_write_blocked; + bool vram_write_blocked; + uint8_t current_line; + uint16_t ly_for_comparison; + GB_fifo_t bg_fifo, oam_fifo; + uint8_t fetcher_y; + uint16_t cycles_for_line; + uint8_t current_tile; + uint8_t current_tile_attributes; + uint8_t current_tile_data[2]; + uint8_t fetcher_state; + bool window_is_being_fetched; + bool wx166_glitch; + bool wx_triggered; + uint8_t visible_objs[10]; + uint8_t objects_x[10]; + uint8_t objects_y[10]; + uint8_t object_tile_data[2]; + uint8_t mode2_y_bus; + // They're the same bus + union { + uint8_t mode2_x_bus; + uint8_t object_flags; + }; + uint8_t n_visible_objs; + uint8_t orig_n_visible_objs; + uint8_t oam_search_index; + uint8_t accessed_oam_row; + uint8_t mode_for_interrupt; + 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. + uint8_t object_priority; + bool oam_ppu_blocked; + bool vram_ppu_blocked; + bool cgb_palettes_ppu_blocked; + bool object_fetch_aborted; + bool during_object_fetch; + uint16_t object_low_line_address; + bool wy_triggered; + uint8_t window_tile_x; + uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. + bool is_odd_frame; + uint16_t last_tile_data_address; + uint16_t last_tile_index_address; + GB_PADDING(bool, cgb_repeated_a_frame); + uint8_t data_for_sel_glitch; + bool delayed_glitch_hblank_interrupt; + uint32_t frame_repeat_countdown; + ) + + /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ + /* This data is reserved on reset and must come last in the struct */ + GB_SECTION(unsaved, + /* ROM */ + uint8_t *rom; + uint32_t rom_size; + const GB_cartridge_t *cartridge_type; + enum { + GB_STANDARD_MBC1_WIRING, + GB_MBC1M_WIRING, + } mbc1_wiring; + bool is_mbc30; + + unsigned pending_cycles; + + /* Various RAMs */ + uint8_t *ram; + uint8_t *vram; + uint8_t *mbc_ram; + + /* I/O */ + uint32_t *screen; + uint32_t background_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; + 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; + bool has_sgb_border; + bool objects_disabled; + bool background_disabled; + bool joyp_accessed; + bool illegal_inputs_allowed; + bool no_bouncing_emulation; + bool joypad_is_stable; + + /* Timing */ + uint64_t last_sync; + 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; + + /* Callbacks */ + void *user_data; + GB_log_callback_t log_callback; + GB_input_callback_t input_callback; + GB_input_callback_t async_input_callback; + GB_rgb_encode_callback_t rgb_encode_callback; + GB_vblank_callback_t vblank_callback; + GB_infrared_callback_t infrared_callback; + GB_camera_get_pixel_callback_t camera_get_pixel_callback; + GB_camera_update_request_callback_t camera_update_request_callback; + GB_rumble_callback_t rumble_callback; + GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback; + GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; + GB_update_input_hint_callback_t update_input_hint_callback; + GB_joyp_write_callback_t joyp_write_callback; + GB_icd_pixel_callback_t icd_pixel_callback; + 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; + GB_workboy_get_time_callback workboy_get_time_callback; + GB_execution_callback_t execution_callback; + GB_lcd_line_callback_t lcd_line_callback; + GB_lcd_status_callback_t lcd_status_callback; + /*** Debugger ***/ + volatile bool debug_stopped, debug_disable; + bool debug_fin_command, debug_next_command; + + /* Breakpoints */ + uint16_t n_breakpoints; + struct GB_breakpoint_s *breakpoints; + bool has_jump_to_breakpoints, has_software_breakpoints; + void *nontrivial_jump_state; + bool non_trivial_jump_breakpoint_occured; + + signed debug_call_depth; + uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ + uint16_t addr_for_call_depth[0x200]; + + /* Backtrace */ + unsigned backtrace_size; + uint16_t backtrace_sps[0x200]; + struct { + uint16_t bank; + uint16_t addr; + } backtrace_returns[0x200]; + + /* Watchpoints */ + uint16_t n_watchpoints; + struct GB_watchpoint_s *watchpoints; + + /* Symbol tables */ + GB_symbol_map_t **bank_symbols; + size_t n_symbol_maps; + GB_reversed_symbol_map_t reversed_symbol_map; + + /* Ticks command */ + uint64_t debugger_ticks; + uint64_t absolute_debugger_ticks; + + /* Undo */ + uint8_t *undo_state; + const char *undo_label; + + /* Rewind */ + size_t rewind_buffer_length; + struct { + uint8_t *key_state; + uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY]; + unsigned pos; + } *rewind_sequences; // lasts about 4 seconds + size_t rewind_pos; + + /* SGB - saved and allocated optionally */ + GB_sgb_t *sgb; + + double sgb_intro_jingle_phases[7]; + double sgb_intro_sweep_phase; + double sgb_intro_sweep_previous_sample; + + /* Cheats */ + bool cheat_enabled; + size_t cheat_count; + GB_cheat_t **cheats; + GB_cheat_hash_t *cheat_hash[256]; + + /* Misc */ + bool turbo; + bool turbo_dont_skip; + bool disable_rendering; + uint8_t boot_rom[0x900]; + bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank + unsigned cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units + double clock_multiplier; + GB_rumble_mode_t rumble_mode; + uint32_t rumble_on_cycles; + uint32_t rumble_off_cycles; + + /* Temporary state */ + bool wx_just_changed; + bool tile_sel_glitch; + bool disable_oam_corruption; // For safe memory reads + bool in_dma_read; + bool hdma_in_progress; + uint16_t addr_for_hdma_conflict; + + GB_gbs_header_t gbs_header; + ) +}; + +#ifndef GB_INTERNAL +struct GB_gameboy_s { + alignas(struct GB_gameboy_internal_s) uint8_t __internal[sizeof(struct GB_gameboy_internal_s)]; +}; +#endif + + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void GB_init(GB_gameboy_t *gb, GB_model_t model); +bool GB_is_inited(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 +GB_model_t GB_get_model(GB_gameboy_t *gb); +void GB_free(GB_gameboy_t *gb); +void GB_reset(GB_gameboy_t *gb); +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model); + +/* Returns the time passed, in 8MHz ticks. */ +unsigned GB_run(GB_gameboy_t *gb); +/* Returns the time passed since the last frame, in nanoseconds */ +uint64_t GB_run_frame(GB_gameboy_t *gb); + +typedef enum { + GB_DIRECT_ACCESS_ROM, + GB_DIRECT_ACCESS_RAM, + GB_DIRECT_ACCESS_CART_RAM, + GB_DIRECT_ACCESS_VRAM, + GB_DIRECT_ACCESS_HRAM, + GB_DIRECT_ACCESS_IO, /* Warning: Some registers can only be read/written correctly via GB_memory_read/write. */ + GB_DIRECT_ACCESS_BOOTROM, + GB_DIRECT_ACCESS_OAM, + GB_DIRECT_ACCESS_BGP, + GB_DIRECT_ACCESS_OBP, + GB_DIRECT_ACCESS_IE, + GB_DIRECT_ACCESS_ROM0, // Identical to ROM, but returns the correct rom0 bank in the bank output argument +} GB_direct_access_t; + +/* 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); + +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); +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); +int GB_load_isx(GB_gameboy_t *gb, const char *path); +int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info); +int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info); +void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track); + +int GB_save_battery_size(GB_gameboy_t *gb); +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); +int GB_save_battery(GB_gameboy_t *gb, const char *path); + +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); +void GB_load_battery(GB_gameboy_t *gb, const char *path); + +void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled); + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb); +void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state); + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback); +/* 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_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback); +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_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); +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback); + +/* These APIs are used when using external clock */ +bool GB_serial_get_data_bit(GB_gameboy_t *gb); +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); + +void GB_disconnect_serial(GB_gameboy_t *gb); + +/* For cartridges with an alarm clock */ +unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm + +/* For 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 */ +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback); +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); + +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); +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); + +/* Handy ROM info APIs */ +// `title` must be at least 17 bytes in size +void GB_get_rom_title(GB_gameboy_t *gb, char *title); +uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); + +#ifdef GB_INTERNAL +internal void GB_borrow_sgb_border(GB_gameboy_t *gb); +internal void GB_update_clock_rate(GB_gameboy_t *gb); +#endif + +#endif /* GB_h */ diff --git a/thirdparty/SameBoy-old/Core/graphics/agb_border.inc b/thirdparty/SameBoy-old/Core/graphics/agb_border.inc new file mode 100644 index 000000000..dd4ebbe88 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/graphics/agb_border.inc @@ -0,0 +1,522 @@ +static const uint16_t palette[] = { + 0x410A, 0x0421, 0x35AD, 0x4A52, 0x7FFF, 0x2D49, 0x0C42, 0x1484, + 0x18A5, 0x20C6, 0x6718, 0x5D6E, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0004, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0004, 0x0004, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0006, 0x0007, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x4007, 0x4006, 0x0000, + 0x0000, 0x0009, 0x0008, 0x0008, 0x0008, 0x000A, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x400A, 0x0008, 0x0008, 0x0008, 0xC009, 0x0000, + 0x0000, 0x000C, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400C, 0x0000, + 0x0000, 0x000E, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC00E, 0x0000, + 0x0000, 0x000F, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400F, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0012, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4012, 0x0000, + 0x0000, 0x0013, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC013, 0x0000, + 0x0014, 0x0015, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4015, 0x4014, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0018, 0x0019, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4019, 0x4018, + 0x001A, 0x001B, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC01B, 0xC01A, + 0x001C, 0x001D, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x401D, 0x401C, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001F, 0x801D, 0x0008, 0x0008, 0x0008, 0x0020, 0x0021, 0x0022, + 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, + 0x002B, 0x002C, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, + 0x002E, 0x0021, 0x4020, 0x0008, 0x0008, 0x0008, 0xC01D, 0x401F, + 0x002F, 0x0030, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0031, + 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, + 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, + 0x0042, 0x0043, 0x0008, 0x0008, 0x0008, 0x0008, 0x4030, 0x402F, + 0x0044, 0x0045, 0x0046, 0x0047, 0x0008, 0x0008, 0x0048, 0x0049, + 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, + 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005A, 0x005B, 0x0008, 0x0008, 0x4047, 0x4046, 0x4045, 0x4044, + 0x0000, 0x0000, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, + 0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, + 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x4062, 0x0061, + 0x0061, 0x4060, 0x405F, 0x405E, 0x405D, 0x405C, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x01, + 0x7F, 0x1F, 0xFF, 0x7E, 0xFF, 0xE1, 0xFF, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x1E, + 0x1F, 0x60, 0x7F, 0x80, 0xFF, 0x00, 0xBF, 0x40, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0x01, 0x07, 0x03, 0x07, 0x03, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x01, 0x02, 0x03, 0x04, 0x03, 0x04, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 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, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, + 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, 0xFF, 0x00, 0x00, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x0E, 0x01, 0x0E, 0x01, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x01, 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, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0xFF, 0xE7, 0xF8, 0xDF, 0xE3, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x00, 0xE4, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xCE, 0x3F, 0xF5, 0x8E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0x00, 0x4E, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xEE, 0x1F, 0xB5, 0x4E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0x00, 0x0E, 0xA0, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xFB, 0x07, 0x04, 0x73, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x03, 0x88, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0xFF, 0x7F, 0x80, 0x82, 0x39, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x01, 0x44, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0xFE, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x83, 0x7C, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x42, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xBB, 0x7C, 0x4F, 0xB0, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x00, 0xB1, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xF9, 0x06, 0xE7, 0xF8, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x06, 0x00, 0x08, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0E, 0xFF, 0xF5, 0x0E, 0x9B, 0x74, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0E, 0x00, 0x94, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xF7, 0x0F, 0xBF, 0x47, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0x00, 0xA7, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 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, 0x01, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0x03, 0x04, 0x01, 0x02, 0x01, 0x02, 0x00, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xDF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0xBF, 0x40, 0xBF, 0x40, 0xDF, 0x20, + 0xB0, 0xD8, 0xA0, 0xD3, 0x67, 0x84, 0x47, 0xA4, + 0x61, 0x81, 0xA0, 0xD0, 0xB4, 0xCA, 0x7E, 0x81, + 0xD7, 0x08, 0xCC, 0x13, 0x98, 0x20, 0x98, 0x00, + 0x9E, 0x20, 0xCF, 0x00, 0xCD, 0x02, 0x80, 0x01, + 0x32, 0x2D, 0x13, 0x6D, 0x34, 0x48, 0xFC, 0x02, + 0x7C, 0x00, 0x78, 0x05, 0x30, 0x49, 0x20, 0x50, + 0xCD, 0x00, 0xAC, 0x40, 0x49, 0x82, 0x01, 0x02, + 0x07, 0x80, 0xC2, 0x05, 0x86, 0x41, 0x9F, 0x40, + 0x15, 0x2E, 0x09, 0x06, 0x09, 0x16, 0x0B, 0xD4, + 0xC6, 0x49, 0x8E, 0x40, 0xCF, 0xC8, 0x06, 0x01, + 0xCE, 0x20, 0xE6, 0x10, 0xE6, 0x00, 0x24, 0xD0, + 0x39, 0x80, 0x38, 0x01, 0x31, 0x00, 0xF8, 0x00, + 0x0C, 0x8B, 0x85, 0x8A, 0x03, 0x84, 0x27, 0x20, + 0x22, 0x35, 0x12, 0x34, 0x20, 0x12, 0x10, 0x20, + 0x73, 0x00, 0x72, 0x08, 0x7C, 0x80, 0xDC, 0x01, + 0xC8, 0x11, 0xC9, 0x06, 0xCD, 0x22, 0xEF, 0x10, + 0x83, 0x44, 0x86, 0x01, 0x03, 0x85, 0x26, 0x21, + 0x46, 0x69, 0x46, 0x68, 0x8E, 0xCA, 0x86, 0x88, + 0x39, 0x40, 0x78, 0x84, 0x7C, 0x80, 0xD8, 0x01, + 0x90, 0x29, 0xD1, 0x28, 0x73, 0x00, 0xB3, 0x40, + 0x00, 0x01, 0x01, 0x00, 0x3F, 0x00, 0x3F, 0x40, + 0x03, 0x02, 0x01, 0x02, 0x41, 0x7C, 0x7F, 0x00, + 0xFE, 0x00, 0xFF, 0x00, 0xC0, 0x00, 0x80, 0x40, + 0xFC, 0x00, 0xFC, 0x00, 0x80, 0x02, 0xC0, 0x00, + 0xC0, 0x00, 0x80, 0x4C, 0xCC, 0x43, 0x8E, 0x52, + 0x80, 0x4C, 0x80, 0x00, 0x12, 0x1E, 0x9E, 0x00, + 0x7F, 0x00, 0x33, 0x0C, 0x32, 0x01, 0x23, 0x50, + 0x33, 0x4C, 0x7F, 0x00, 0x61, 0x80, 0xF1, 0x00, + 0x7C, 0x02, 0x30, 0x48, 0x31, 0x40, 0x61, 0x50, + 0x87, 0xE4, 0xE3, 0x84, 0x23, 0x44, 0x43, 0x44, + 0x85, 0x42, 0x87, 0x40, 0x8F, 0x50, 0x8C, 0x12, + 0x78, 0x00, 0x18, 0x20, 0xB8, 0x00, 0x98, 0x24, + 0x03, 0x04, 0x03, 0xE0, 0xF1, 0x12, 0xF0, 0x09, + 0xF9, 0x09, 0xF9, 0x08, 0xE1, 0x12, 0xF1, 0x12, + 0xF8, 0x00, 0x1E, 0xE0, 0x0C, 0x02, 0x07, 0x08, + 0x07, 0x00, 0x06, 0x00, 0x1C, 0x02, 0x0C, 0x00, + 0x9F, 0x91, 0x86, 0x88, 0xC4, 0x4C, 0x80, 0x4C, + 0xE1, 0x20, 0xC1, 0x22, 0x23, 0xD4, 0x22, 0xD5, + 0x60, 0x00, 0xFB, 0x00, 0x37, 0x00, 0x73, 0x0C, + 0x1F, 0x00, 0x3C, 0x00, 0xC8, 0x14, 0xC9, 0x14, + 0x16, 0x2F, 0x76, 0x4F, 0x2D, 0xDE, 0xDD, 0xBE, + 0xBA, 0x7D, 0x7A, 0xFD, 0x7A, 0xFD, 0xF4, 0xF8, + 0xCF, 0x00, 0x8F, 0x00, 0x5E, 0x80, 0xBE, 0x00, + 0x7D, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xF9, 0x02, + 0xFF, 0x00, 0xBF, 0x78, 0x86, 0x09, 0x86, 0x89, + 0x06, 0x25, 0x02, 0x25, 0x42, 0x45, 0x60, 0x11, + 0x00, 0x00, 0x09, 0x00, 0x70, 0x81, 0x70, 0x09, + 0xDC, 0x21, 0xD8, 0x01, 0x98, 0x25, 0xCC, 0x13, + 0xFF, 0x00, 0xF3, 0xF8, 0x02, 0x03, 0x01, 0x30, + 0x39, 0x09, 0x30, 0x09, 0x31, 0x09, 0x20, 0x19, + 0x00, 0x00, 0x01, 0x04, 0xFC, 0x00, 0xCF, 0x30, + 0xE6, 0x00, 0xE6, 0x01, 0xE6, 0x00, 0xF6, 0x08, + 0xFF, 0x00, 0xFA, 0xC7, 0x18, 0x21, 0x09, 0x10, + 0x88, 0x99, 0x93, 0x1A, 0x83, 0x11, 0xC2, 0x41, + 0x00, 0x00, 0x00, 0x20, 0xC6, 0x21, 0xFF, 0x00, + 0x67, 0x00, 0xE4, 0x08, 0x6F, 0x10, 0x3C, 0x00, + 0xFD, 0x02, 0xB5, 0x3A, 0xC7, 0x44, 0x03, 0x84, + 0x83, 0x24, 0x21, 0xB0, 0x21, 0x12, 0x21, 0x02, + 0x02, 0x00, 0x02, 0x40, 0x3C, 0x00, 0xF8, 0x00, + 0xD8, 0x24, 0x4C, 0x92, 0xEC, 0x00, 0xCC, 0x12, + 0xFF, 0x00, 0xFF, 0xF3, 0x1C, 0x14, 0x0C, 0x04, + 0x00, 0x0C, 0x04, 0x24, 0x00, 0x24, 0x10, 0x30, + 0x00, 0x00, 0x10, 0x04, 0xE3, 0x00, 0xFB, 0x00, + 0xF3, 0x08, 0xDB, 0x20, 0xDB, 0x04, 0xCF, 0x00, + 0xFF, 0x00, 0xEC, 0x3E, 0xC1, 0x01, 0x01, 0x8E, + 0x8F, 0x10, 0x0F, 0x90, 0x0F, 0x90, 0x0D, 0x09, + 0x00, 0x00, 0x20, 0x01, 0x7E, 0x00, 0xF1, 0x0E, + 0xE0, 0x10, 0x60, 0x10, 0x60, 0x10, 0x79, 0x82, + 0xFF, 0x00, 0x7F, 0xFC, 0x03, 0x82, 0x01, 0x9E, + 0x13, 0x80, 0x03, 0x80, 0x03, 0x9C, 0x0F, 0x90, + 0x00, 0x00, 0x02, 0x00, 0x7C, 0x80, 0x60, 0x9C, + 0x60, 0x9C, 0x7C, 0x80, 0x60, 0x9C, 0x70, 0x80, + 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xEF, 0xFF, 0xF7, 0x7F, 0x7B, 0x3F, 0x3C, + 0x1F, 0x1F, 0x0F, 0x0F, 0x03, 0x03, 0x00, 0x00, + 0xEF, 0x10, 0x77, 0x88, 0x3B, 0x44, 0x1C, 0x23, + 0x0F, 0x10, 0x03, 0x0C, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3F, 0xC0, 0xC3, 0x3C, 0xFC, 0x03, 0x3F, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC1, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0xC1, 0x3E, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 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, 0xFF, 0x00, + 0xEA, 0x14, 0xC0, 0x00, 0x80, 0x21, 0x7F, 0x92, + 0x9F, 0xE0, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x27, 0x18, 0x7F, 0x00, 0x1E, 0x61, 0x9A, 0x04, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x73, 0x53, 0x47, 0x44, 0x46, 0x25, 0xFD, 0x03, + 0xF9, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x00, 0xD8, 0x20, 0x1D, 0xA0, 0x03, 0x00, + 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0xE1, 0xE6, 0x05, 0x42, 0xA5, 0xBF, 0xC0, + 0x9F, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x18, 0x24, 0x38, 0x01, 0xB8, 0x05, 0xC0, 0x00, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x21, 0x11, 0x31, 0x49, 0x33, 0x4A, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDE, 0x00, 0x87, 0x48, 0x84, 0x48, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xCC, 0x02, 0x8E, 0x4A, 0xCC, 0x42, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x71, 0x08, 0x39, 0x00, 0x31, 0x02, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3D, 0x40, 0x03, 0x02, 0x03, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBC, 0x02, 0xFC, 0x00, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x12, 0x82, 0x80, 0x80, 0x01, 0x83, 0xFF, 0x00, + 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x61, 0x1C, 0x7F, 0x00, 0x7C, 0x82, 0x00, 0x00, + 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x22, 0x52, 0x30, 0xC0, 0x58, 0xA4, 0x8F, 0x72, + 0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x11, 0x4F, 0x90, 0xA3, 0x0C, 0x73, 0x00, + 0xFC, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x23, 0xA4, 0x06, 0x0D, 0x05, 0x1B, 0xBB, 0x07, + 0xE7, 0x1F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x98, 0x44, 0xF5, 0x08, 0xEB, 0x00, 0x87, 0x40, + 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x66, 0x85, 0xE2, 0xA5, 0x66, 0x81, 0xBF, 0xC1, + 0x99, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x99, 0x00, 0xB9, 0x00, 0x9D, 0x20, 0xC1, 0x00, + 0xE7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF6, 0xFA, 0xFC, 0xF2, 0xF7, 0xF8, 0xFB, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF9, 0x00, 0xF1, 0x02, 0xF8, 0x00, 0xFC, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x52, 0x53, 0x30, 0x23, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x21, 0xCC, 0x13, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x03, 0x06, 0xFE, 0x01, 0xF9, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFD, 0x02, 0xFA, 0x04, 0x01, 0x00, 0x07, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x86, 0x05, 0x46, 0xA0, 0x5F, 0xB8, 0xBF, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x38, 0x41, 0x99, 0x26, 0xB8, 0x00, 0xC0, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x30, 0x28, 0x09, 0x09, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC6, 0x09, 0xE6, 0x10, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x20, 0x38, 0x38, 0x20, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xD7, 0x08, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x41, 0xA1, 0x61, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3E, 0x40, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x82, 0x01, 0x82, 0xFF, 0x00, 0xFC, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7C, 0x82, 0x7C, 0x82, 0x00, 0x00, 0x03, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 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, 0xFF, 0x00, + 0x3F, 0x3F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x3C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFF, 0x3F, 0x3F, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0x01, 0x3F, 0xC0, 0x01, 0x3E, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, 0x3F, 0xC0, + 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, + 0x3F, 0xC0, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0xFC, 0x03, + 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, + 0xFC, 0x03, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, +}; diff --git a/thirdparty/SameBoy-old/Core/graphics/cgb_border.inc b/thirdparty/SameBoy-old/Core/graphics/cgb_border.inc new file mode 100644 index 000000000..755312a43 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/graphics/cgb_border.inc @@ -0,0 +1,446 @@ +static const uint16_t palette[] = { + 0x7C1A, 0x0000, 0x0011, 0x3DEF, 0x6318, 0x7FFF, 0x1EBA, 0x19AF, + 0x1EAF, 0x4648, 0x7FC0, 0x2507, 0x1484, 0x5129, 0x5010, 0x2095, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0007, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x4007, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x000A, 0x000B, 0x400A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x800A, 0x000C, 0xC00A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x000D, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x000E, + 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, + 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, + 0x001F, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x400D, 0x0000, + 0x0000, 0x0020, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, + 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, + 0x0032, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4020, 0x0000, + 0x0000, 0x0033, 0x0034, 0x0035, 0x0036, 0x0005, 0x0005, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0005, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, + 0x0047, 0x0005, 0x0005, 0x4036, 0x4035, 0x4034, 0x4033, 0x0000, + 0x0000, 0x0000, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004E, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x404F, 0x004E, 0x004E, + 0x404D, 0x004C, 0x404B, 0x404A, 0x4049, 0x4048, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +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, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, + 0x01, 0x11, 0x06, 0x26, 0x04, 0x24, 0x08, 0x48, + 0x00, 0x00, 0x01, 0x01, 0x07, 0x07, 0x0F, 0x0F, + 0x1E, 0x1F, 0x39, 0x3F, 0x3B, 0x3F, 0x77, 0x7F, + 0x00, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x7F, 0x7F, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x08, 0x48, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x77, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, + 0xBD, 0xBD, 0x7E, 0x66, 0x7E, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x7E, 0xFF, 0xFF, 0xE7, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0xFF, 0x3C, 0xFF, 0x81, 0xFF, 0xC3, 0xFF, + 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0x7E, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x81, + 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xDF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xC7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xE3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x9F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0xE1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xD8, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC2, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xE0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x84, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x08, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x08, 0x48, 0x04, 0x24, 0x04, 0x24, 0x02, 0x12, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x37, 0x7F, 0x1B, 0x3F, 0x1B, 0x3F, 0x0D, 0x1F, + 0x0F, 0x08, 0x0E, 0x00, 0x1E, 0x12, 0x1E, 0x12, + 0x1F, 0x10, 0x0F, 0x08, 0x02, 0x02, 0x00, 0x00, + 0xF7, 0xF8, 0xFF, 0xF0, 0xED, 0xE3, 0xED, 0xE1, + 0xEF, 0xE0, 0xF7, 0xF0, 0xFD, 0xFC, 0xFF, 0xFF, + 0xF0, 0x10, 0x40, 0x00, 0x41, 0x41, 0x00, 0x00, + 0x83, 0x82, 0xE3, 0x20, 0xC7, 0x04, 0xC7, 0x00, + 0xEF, 0x1F, 0xFF, 0x1F, 0xBE, 0xFF, 0xFF, 0xFE, + 0x7D, 0x7E, 0xDF, 0x3C, 0xFB, 0x18, 0xFF, 0x18, + 0x60, 0x00, 0x70, 0x00, 0xF8, 0x08, 0xB0, 0x00, + 0xD8, 0x40, 0x3C, 0x24, 0x5C, 0x44, 0xFC, 0x00, + 0xFF, 0x8F, 0xFF, 0x0F, 0xF7, 0x07, 0xFF, 0x07, + 0xBF, 0x47, 0xDB, 0x47, 0xBB, 0x03, 0xFF, 0x03, + 0x3C, 0x04, 0x78, 0x00, 0x78, 0x00, 0xEC, 0x80, + 0xFE, 0x92, 0xE7, 0x83, 0xE5, 0x80, 0x4F, 0x08, + 0xFB, 0x83, 0xFF, 0x83, 0xFF, 0x83, 0x7F, 0x83, + 0x6D, 0x93, 0x7C, 0x10, 0x7F, 0x10, 0xF7, 0x10, + 0x3C, 0x00, 0x7C, 0x40, 0x78, 0x00, 0xC8, 0x80, + 0xFC, 0x24, 0xBC, 0x24, 0xFD, 0x65, 0x3D, 0x25, + 0xFF, 0xC3, 0xBF, 0x83, 0xFF, 0x83, 0x7F, 0x03, + 0xDB, 0x23, 0xDB, 0x23, 0x9A, 0x67, 0xDA, 0x47, + 0xFF, 0x80, 0xFF, 0x80, 0xE0, 0x80, 0x40, 0x00, + 0xFF, 0x01, 0xFF, 0x01, 0xDF, 0x1F, 0xE0, 0x20, + 0x7F, 0x80, 0x7F, 0x00, 0x7F, 0x1F, 0xFF, 0x1F, + 0xFE, 0x00, 0xFE, 0x00, 0xE0, 0x01, 0xDF, 0x3F, + 0xBF, 0xA0, 0xB9, 0xA0, 0x10, 0x00, 0x11, 0x01, + 0x3B, 0x00, 0x3F, 0x00, 0x7E, 0x4E, 0x78, 0x48, + 0x5F, 0x40, 0x5F, 0xC0, 0xFF, 0xC7, 0xFE, 0xC7, + 0xFF, 0xC0, 0xFF, 0xC0, 0xB1, 0xC4, 0xB7, 0x8F, + 0xE3, 0x22, 0xC7, 0x04, 0xCE, 0x08, 0xE6, 0x20, + 0xCE, 0x42, 0xDE, 0x52, 0xFE, 0x32, 0xFC, 0x30, + 0xDD, 0x3E, 0xFB, 0x18, 0xF7, 0x18, 0xDF, 0x31, + 0xBD, 0x31, 0xAD, 0x23, 0xCD, 0x31, 0xCF, 0x11, + 0xFE, 0x02, 0x9E, 0x00, 0x86, 0x80, 0x06, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x07, 0x01, 0x07, 0x01, + 0xFD, 0x03, 0xFF, 0x01, 0x7F, 0xF0, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFE, 0xF8, 0xFE, 0xF1, + 0x38, 0x08, 0x71, 0x41, 0x1F, 0x06, 0x39, 0x20, + 0x0F, 0x00, 0x0F, 0x01, 0x04, 0x00, 0x0C, 0x00, + 0xF7, 0x87, 0xBE, 0xC6, 0xF9, 0xC6, 0xDF, 0xE0, + 0xFF, 0xE0, 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, + 0x70, 0x10, 0xE0, 0x20, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEF, 0x1F, 0xDF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x7F, 0x7F, 0x78, 0x78, 0xF0, 0xF0, + 0xF0, 0xF0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0, + 0xDF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0xE7, 0xE0, 0xFF, 0xF0, 0xFE, 0xF0, 0x1C, 0x00, + 0x3C, 0x20, 0x3C, 0x24, 0x3C, 0x24, 0x3C, 0x20, + 0xFF, 0xFF, 0xEF, 0xFF, 0x6F, 0xFF, 0xFF, 0xFF, + 0xDF, 0xFF, 0xDB, 0xFF, 0xDB, 0xFF, 0xDF, 0xFF, + 0xF8, 0x00, 0xFC, 0xC0, 0x1F, 0x03, 0x1F, 0x13, + 0x1F, 0x13, 0x1E, 0x02, 0x1E, 0x02, 0x3E, 0x02, + 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0xFF, 0xEC, 0xFF, + 0xED, 0xFE, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21, + 0x20, 0x21, 0x00, 0x01, 0x00, 0x01, 0x40, 0x41, + 0x8F, 0x7F, 0x1F, 0xFF, 0x1F, 0xFF, 0x3F, 0xDE, + 0x1F, 0xFE, 0x3F, 0xFE, 0x3F, 0xFE, 0x7F, 0xBE, + 0x40, 0x7F, 0x84, 0xFF, 0x11, 0xF1, 0x20, 0xE0, + 0x20, 0xE0, 0x01, 0xC1, 0x01, 0xC1, 0x22, 0xE3, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x0E, 0xFF, 0x1F, + 0xDF, 0x3F, 0xFE, 0x3F, 0xFF, 0x3E, 0xFD, 0x1E, + 0x47, 0xC0, 0x27, 0xE0, 0x2F, 0xE8, 0x0F, 0xE9, + 0x0F, 0xE1, 0x0F, 0xE0, 0x3F, 0xF0, 0x3F, 0xF1, + 0xF8, 0x3F, 0xF8, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, + 0xF0, 0x1F, 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, + 0xFC, 0x00, 0xFE, 0xE2, 0x1E, 0x12, 0x1E, 0x12, + 0x3E, 0x22, 0xFC, 0x00, 0xF8, 0x08, 0xF0, 0x10, + 0x03, 0xFF, 0x01, 0xFF, 0xE1, 0xFF, 0xE1, 0xFF, + 0xC1, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF, + 0x01, 0x11, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x1F, 0x07, 0x0F, 0x03, 0x07, 0x01, 0x03, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x40, 0x40, 0x30, 0x30, + 0x0C, 0x0C, 0x03, 0xC3, 0x00, 0x30, 0x00, 0x0C, + 0xFF, 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xCF, 0xFF, + 0xF3, 0xFF, 0x3C, 0xFF, 0x0F, 0x3F, 0x03, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0x3C, 0x3C, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x15, 0x15, 0x3F, 0x20, 0x2F, 0x20, 0x06, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEA, 0xE6, 0xDF, 0xC0, 0xDF, 0xE0, 0xF9, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE6, 0x20, 0x9E, 0x12, 0x0C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xED, 0x31, 0xFF, 0x63, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x0D, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0x03, 0xED, 0xE1, 0xFE, 0xF1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4F, 0x08, 0xE6, 0x20, 0xE7, 0x21, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x18, 0xDF, 0x18, 0xDE, 0x18, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xB9, 0xA1, 0x11, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5E, 0x46, 0xFE, 0xC6, 0xFF, 0xC6, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0x01, 0xFF, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC0, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7E, 0x4E, 0x3F, 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB1, 0x8E, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEE, 0x20, 0x8F, 0x08, 0x85, 0x84, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xF7, 0x38, 0x7B, 0x7C, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xAE, 0xA2, 0xF8, 0x00, 0xE8, 0x08, 0xC0, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5D, 0xE1, 0xFF, 0x03, 0xF7, 0x0F, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x1E, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0xF1, 0xED, 0xF1, 0xED, 0xE3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFB, 0x7F, 0x7F, 0x3F, 0x3F, 0x0C, 0x0C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDF, 0xC1, 0xDF, 0xD0, 0x8F, 0x88, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xEF, 0xFF, 0x77, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFA, 0x82, 0xF8, 0x08, 0xE0, 0x00, 0x81, 0x81, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0xFD, 0xF4, 0xFF, 0xFC, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7D, 0x7D, 0x02, 0x02, 0x02, 0x02, 0xFC, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFE, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x1C, 0xFF, 0x00, 0xFF, 0x41, 0x7F, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x08, 0xFF, 0x00, 0xFF, 0x80, 0xE7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x5E, 0xC2, 0x9C, 0x80, 0x1C, 0x04, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE1, 0x3F, 0xE3, 0x7F, 0xE3, 0xFF, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x80, 0x78, 0x08, 0x78, 0x48, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFF, 0x87, 0xFF, 0x87, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0x03, 0x3F, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1C, 0x1C, 0x03, 0x03, 0x00, 0xE0, 0x00, 0x1C, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE3, 0xFF, 0xFC, 0xFF, 0x1F, 0xFF, 0x03, 0x1F, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFC, 0x03, 0x03, 0x00, 0x00, + 0x00, 0xFC, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, + 0x03, 0xFF, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, + 0xC0, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF +}; diff --git a/thirdparty/SameBoy-old/Core/graphics/dmg_border.inc b/thirdparty/SameBoy-old/Core/graphics/dmg_border.inc new file mode 100644 index 000000000..7db0673a4 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/graphics/dmg_border.inc @@ -0,0 +1,558 @@ +static const uint16_t palette[] = { + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4A32, 0x2033, 0x20EC, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0001, 0x0003, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x4003, 0x0001, + 0x0001, 0x0006, 0x0007, 0x0007, 0x0007, 0x0008, 0x0009, 0x000A, + 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, + 0x0013, 0x0014, 0x0015, 0x000E, 0x0016, 0x0017, 0x0018, 0x0019, + 0x001A, 0x001B, 0x001C, 0x0007, 0x0007, 0x0007, 0x4006, 0x0001, + 0x0001, 0x001D, 0x001E, 0x001E, 0x001E, 0x001F, 0x0020, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x4024, 0x0026, 0x0025, 0x0025, + 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, + 0x002F, 0x0030, 0x0031, 0x001E, 0x001E, 0x001E, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0034, 0x0035, 0x4034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x8034, 0x0036, 0xC034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0037, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0038, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0039, 0x003A, 0x0001, + 0x0001, 0x003B, 0x003C, 0x0032, 0x0032, 0xC03C, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0001, 0x0001, + 0x0001, 0x0042, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0045, 0x0046, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, + 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x0001, 0x006C, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, +}; + +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, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x21, 0xDE, 0x00, 0x7F, + 0x0C, 0xF3, 0x19, 0xE0, 0x10, 0xEE, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x3F, 0x80, 0xFF, + 0x00, 0xFF, 0x0E, 0xF7, 0x1F, 0xE1, 0x07, 0xF8, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xBF, + 0x40, 0xBE, 0x80, 0x3F, 0x02, 0xFD, 0x00, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, + 0x7F, 0x81, 0xFE, 0x41, 0xFC, 0x03, 0xFC, 0x07, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFF, + 0x00, 0xFB, 0x04, 0xFB, 0x24, 0xDB, 0x64, 0x9B, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0x78, 0x07, 0xF8, + 0x07, 0xFC, 0x07, 0xF8, 0x03, 0xFC, 0x43, 0xBC, + 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xDE, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x04, 0xFB, 0x04, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, + 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x00, 0xFF, + 0x00, 0x77, 0x00, 0x7F, 0x80, 0x6F, 0x82, 0x7D, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF8, 0x07, + 0xF8, 0x8F, 0xF0, 0x8F, 0x70, 0x9F, 0x60, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0x24, 0xDB, 0x20, 0xDF, 0x20, 0xDF, 0x00, 0xDF, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0x18, 0xE7, 0x38, 0xC7, 0x38, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFC, + 0x7E, 0x81, 0x80, 0x01, 0x80, 0x7F, 0xF8, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0x01, 0xFE, 0x01, 0xFF, + 0x01, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x07, 0xFC, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0E, 0xF1, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x9F, 0x00, 0xFF, + 0x10, 0xEF, 0x90, 0x6F, 0x10, 0xEB, 0x14, 0xEB, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x1F, 0xE0, + 0x0E, 0xF1, 0x0C, 0xF3, 0x0C, 0xF7, 0x18, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0x9F, 0x00, 0xFF, + 0x0C, 0xF3, 0x31, 0xC0, 0x60, 0x9F, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x1E, 0xEF, 0x3F, 0xC0, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x77, 0x04, 0xDB, + 0x00, 0xFB, 0x10, 0xEF, 0x00, 0xFD, 0x80, 0x77, + 0xFF, 0x00, 0xFF, 0x00, 0x78, 0x8F, 0x38, 0xE7, + 0x1C, 0xE7, 0x0C, 0xF3, 0x0E, 0xF3, 0x0E, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF, + 0x40, 0xB7, 0x00, 0xEF, 0x01, 0xDE, 0x02, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x83, 0x78, 0x87, + 0x38, 0xCF, 0x30, 0xDF, 0x21, 0xFE, 0x03, 0xFD, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x60, 0x9F, + 0xC0, 0x3F, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x3F, 0xC0, + 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x01, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFE, 0x03, + 0x00, 0xFF, 0x40, 0x3F, 0x30, 0x8F, 0x00, 0xF7, + 0x80, 0x7F, 0x30, 0xCF, 0x01, 0xFE, 0x87, 0x78, + 0x01, 0xFE, 0x80, 0xFF, 0xE0, 0x5F, 0xF8, 0x0F, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFC, + 0x00, 0xFF, 0x08, 0xF7, 0x80, 0x6F, 0x80, 0x7F, + 0x80, 0x5F, 0x87, 0x78, 0x04, 0x7B, 0x08, 0x73, + 0xF8, 0x07, 0xF0, 0x0F, 0x70, 0x9F, 0x60, 0x9F, + 0x60, 0xBF, 0xC3, 0x3C, 0x87, 0xF8, 0x87, 0xFC, + 0xA0, 0x1F, 0x80, 0x7D, 0xE2, 0x1D, 0x02, 0xFD, + 0x02, 0xFD, 0xF0, 0x0F, 0x10, 0xEE, 0x11, 0xEE, + 0x43, 0xFC, 0xE3, 0x1E, 0x03, 0xFC, 0x01, 0xFE, + 0x01, 0xFE, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1E, + 0x44, 0xBB, 0x48, 0xB3, 0x48, 0xB7, 0x08, 0xF7, + 0x0A, 0xF5, 0x02, 0xF5, 0x80, 0x77, 0x90, 0x67, + 0x84, 0x7B, 0x84, 0x7F, 0x84, 0x7B, 0x84, 0x7B, + 0x8C, 0x73, 0x8C, 0x7B, 0x0E, 0xF9, 0x0E, 0xF9, + 0x86, 0x59, 0x06, 0xF9, 0x48, 0xB3, 0x08, 0xF7, + 0x10, 0xE7, 0x14, 0xEB, 0x24, 0xCB, 0x20, 0xDF, + 0x60, 0xBF, 0x44, 0xBB, 0x04, 0xFF, 0x0C, 0xF3, + 0x0C, 0xFB, 0x18, 0xE7, 0x18, 0xF7, 0x38, 0xC7, + 0x08, 0xD7, 0x48, 0x97, 0x48, 0xB7, 0x41, 0xBE, + 0x41, 0xBE, 0x01, 0xBE, 0x10, 0xAF, 0x90, 0x2F, + 0x30, 0xEF, 0x30, 0xEF, 0x30, 0xCF, 0x30, 0xCF, + 0x70, 0x8F, 0x70, 0xCF, 0x60, 0xDF, 0x60, 0xDF, + 0x04, 0xFB, 0x04, 0xFB, 0xFC, 0x03, 0x00, 0xFF, + 0x00, 0xFF, 0xF8, 0x02, 0x05, 0xFA, 0x05, 0xFA, + 0x03, 0xFC, 0x03, 0xFC, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x07, 0xFD, 0x02, 0xFD, 0x06, 0xF9, + 0x80, 0x7F, 0x80, 0x7F, 0x0F, 0xF0, 0x10, 0xE7, + 0x10, 0xEE, 0x1E, 0xE1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0E, 0xF1, 0x0F, 0xF8, + 0x0F, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x60, 0x8F, 0x00, 0xDF, 0x00, 0xFF, 0x00, 0xEF, + 0x04, 0xEB, 0x20, 0xCF, 0x22, 0xDD, 0xC1, 0x1E, + 0x38, 0xD7, 0x38, 0xE7, 0x18, 0xE7, 0x18, 0xF7, + 0x18, 0xF7, 0x1C, 0xF3, 0x3E, 0xC1, 0x7F, 0xA0, + 0x80, 0x3F, 0x80, 0x7F, 0x80, 0x7F, 0x01, 0xFE, + 0x00, 0xBD, 0x18, 0xE7, 0x00, 0xFF, 0x83, 0x7C, + 0x7F, 0xC0, 0x7F, 0x80, 0x7F, 0x80, 0x7E, 0x81, + 0x7E, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x81, 0x76, 0x80, 0x77, 0x10, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x21, 0xCE, 0x41, 0x9E, 0x81, 0x3E, + 0x0E, 0xF9, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF0, + 0x1F, 0xE0, 0x3E, 0xD1, 0x7E, 0xA1, 0xFE, 0x41, + 0x04, 0xF9, 0x08, 0xF3, 0x18, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x10, 0xEF, 0x00, 0xEF, 0x20, 0xCF, + 0x07, 0xFA, 0x07, 0xFC, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x1F, 0xE0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x7C, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x70, 0x8E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC3, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x24, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x70, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF8, 0x03, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3E, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE0, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, +}; diff --git a/thirdparty/SameBoy-old/Core/graphics/mgb_border.inc b/thirdparty/SameBoy-old/Core/graphics/mgb_border.inc new file mode 100644 index 000000000..f19ed8a1b --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/Core/graphics/sgb_animation_logo.inc b/thirdparty/SameBoy-old/Core/graphics/sgb_animation_logo.inc new file mode 100644 index 000000000..75075f492 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/graphics/sgb_animation_logo.inc @@ -0,0 +1,563 @@ +static uint8_t animation_logo[] = { + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x1, + 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0x7, 0x7, 0x9, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x7, 0x7, 0x7, 0x9, 0x1, 0x1, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0xC, 0xC, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, 0x1, 0x0, 0x1, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, + 0x7, 0x7, 0x9, 0x1, 0x1, 0x0, 0x0, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC, 0xC, + 0xC, 0xC, 0x1, 0x1, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x1, 0x9, 0x9, 0x9, 0x0, 0x0, 0x0, 0x1, 0xC, 0xC, + 0xC, 0x1, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0x1, 0xD, 0x1, 0x1, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0xC, 0xC, 0xC, + 0xC, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, + 0xE, 0xE, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, + 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0xF, + 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x5, 0x5, 0x5, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x1, 0xF, + 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x1, 0xC, 0xC, 0xB, 0xB, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0xF, 0xF, + 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, + 0x0, 0x0, 0x1, 0x6, 0x6, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x1, 0x0, 0xC, 0xC, 0xB, 0xB, 0x1, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0x1, 0xF, 0xF, + 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x4, 0x1, 0x0, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x9, 0x9, 0xA, 0x1, 0x1, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x1, 0x6, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0xA, 0x1, + 0x0, 0x0, 0x0, 0x9, 0x9, 0xA, 0xA, 0x1, 0x0, 0x1, 0xB, 0xB, 0xB, 0xD, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x1, 0x1, 0x0, 0x1, + 0x6, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0xA, 0xA, 0x1, + 0x0, 0x0, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0x0, 0xB, 0xB, 0x1, 0xD, 0xD, 0xD, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x0, 0x6, 0x6, 0x1, 0x0, 0x1, 0x6, + 0x1, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0x1, 0x1, 0xA, 0xA, + 0x1, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0xD, 0xD, 0x1, + 0x0, 0x0, 0x0, 0x1, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x1, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x1, + 0x1, 0x0, 0x1, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x1, 0x0, 0x1, 0xA, + 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x0, 0x0, 0xD, 0xD, 0xD, + 0xD, 0xD, 0xD, 0xD, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, + 0xF, 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x1, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, + 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0xD, + 0xD, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xF, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 +}; +static const unsigned animation_logo_height = sizeof(animation_logo) / 160; diff --git a/thirdparty/SameBoy-old/Core/graphics/sgb_border.inc b/thirdparty/SameBoy-old/Core/graphics/sgb_border.inc new file mode 100644 index 000000000..d7d0a5c98 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/graphics/sgb_border.inc @@ -0,0 +1,658 @@ +static const uint16_t palette[] = { + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4631, 0x2033, 0x20EC, 0x18B7 +}; + +static const uint16_t tilemap[] = { + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1001, 0x1003, 0x1004, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x5004, 0x5003, 0x1001, + 0x1001, 0x1006, 0x1007, 0x1007, 0x1007, 0x1008, 0x1009, 0x100A, + 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, 0x1012, + 0x1013, 0x1014, 0x1015, 0x100E, 0x1016, 0x1017, 0x1018, 0x1019, + 0x101A, 0x101B, 0x101C, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001, + 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021, + 0x1022, 0x1023, 0x1024, 0x1025, 0x5024, 0x1026, 0x1025, 0x1025, + 0x1027, 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, + 0x102F, 0x1030, 0x1031, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1034, 0x1035, 0x5034, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x8034, 0x1036, 0xC034, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1037, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1038, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1039, 0x103A, 0x1001, + 0x1001, 0x103B, 0x103C, 0x1032, 0x1032, 0xC03C, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103E, 0x103F, 0x1040, 0x1041, 0x1001, 0x1001, + 0x1001, 0x1042, 0x1043, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1045, 0x1046, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1047, 0x1048, 0x1049, + 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, 0x1050, 0x1051, + 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, 0x1059, + 0x105A, 0x105B, 0x105C, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x105D, 0x105E, 0x105F, + 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, + 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, + 0x1070, 0x1071, 0x1072, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1073, 0x1074, 0x1075, + 0x1076, 0x1077, 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, + 0x107E, 0x107F, 0x1080, 0x1081, 0x1082, 0x1083, 0x507A, 0x1084, + 0x1001, 0x1085, 0x507A, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, +}; + +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, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x01, 0xFD, 0x03, 0xFF, 0x03, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0xFE, 0x02, 0xFC, 0x00, + 0x0E, 0xEE, 0x3F, 0xFF, 0x75, 0x71, 0xFB, 0xE7, + 0xE3, 0xCB, 0xC7, 0x9F, 0x07, 0x3E, 0x84, 0x7C, + 0xFB, 0x1B, 0xE6, 0x26, 0x8E, 0x82, 0x3E, 0x22, + 0x7C, 0x54, 0x7D, 0x25, 0xF9, 0x40, 0xFB, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0x00, 0x7F, 0x80, 0x4F, 0x31, 0x7F, 0x71, 0xFD, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, + 0xFF, 0x00, 0xFF, 0x30, 0xFF, 0xB1, 0xDE, 0x52, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x7B, 0x87, 0xFF, 0x8E, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x84, 0xFA, 0x82, 0xF9, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xFD, 0xE3, 0x7B, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC3, 0xBC, 0x24, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xFB, 0xF7, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0x7C, 0x64, 0xFC, 0xB4, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x7F, 0x80, 0xFF, 0xA0, 0x2F, 0xF0, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x70, 0x8F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFD, 0x00, 0xF7, + 0x00, 0xFF, 0x11, 0xEE, 0x11, 0xEE, 0x10, 0xEF, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x03, 0xF8, 0x0F, + 0xF0, 0x0F, 0xE0, 0x1F, 0xE1, 0x1E, 0xE0, 0x1F, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xE7, 0x00, 0xFB, + 0xC4, 0x3B, 0x98, 0x03, 0x00, 0xEF, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF8, 0x07, 0xFC, + 0x07, 0xF8, 0xEF, 0x74, 0xFF, 0x10, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xEF, 0x00, 0xFF, 0x22, 0xDD, 0x06, 0xB9, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xF0, 0x0F, + 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, 0xC4, 0x7B, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7D, 0x02, 0xFD, + 0x02, 0xBD, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x83, 0x7C, 0x83, + 0x7C, 0xC3, 0x7C, 0x83, 0x3C, 0xC3, 0x3C, 0xC3, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xEF, 0x00, 0xFF, + 0x00, 0xF7, 0x00, 0xF7, 0x48, 0xB6, 0x48, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x0F, 0xF8, 0x07, 0xF9, 0x06, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x02, 0xFC, + 0x02, 0x7D, 0x02, 0xFD, 0x02, 0xFD, 0x20, 0xDD, + 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x7E, 0x81, 0x7F, + 0x81, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0F, 0xF0, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0xE4, 0x1B, 0x00, 0x1F, 0x00, 0xFF, 0x80, 0x3F, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0xF8, 0xE7, 0xF8, 0x07, 0x78, 0xC7, + 0x00, 0xFF, 0x00, 0xFF, 0x04, 0xF9, 0x00, 0xFF, + 0x71, 0x8E, 0x89, 0x06, 0x81, 0x7E, 0xE1, 0x1E, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFE, 0x01, 0xFE, + 0x00, 0xFF, 0x70, 0xFF, 0x70, 0x8F, 0x01, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xF9, 0x00, 0xFF, + 0x00, 0xFF, 0x03, 0xFC, 0x06, 0xB9, 0x44, 0xBB, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF0, 0x0F, + 0xE0, 0x1F, 0xC1, 0x3E, 0xC3, 0x7C, 0x87, 0x78, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFD, + 0xC0, 0x3F, 0x11, 0x0E, 0x00, 0xFF, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x03, 0xFE, + 0x01, 0xFE, 0xE0, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x77, 0x40, 0xBF, + 0x04, 0xBB, 0x00, 0xFE, 0x00, 0xDD, 0x00, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0xF8, 0x87, 0x78, + 0xC3, 0x7C, 0xC3, 0x3D, 0xE2, 0x3F, 0xE0, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFD, 0x06, 0xF9, + 0x0C, 0x73, 0x08, 0xF7, 0x10, 0xE7, 0x20, 0xCF, + 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x3E, 0x83, 0x7C, + 0x87, 0xF8, 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7, 0x00, 0xFF, + 0x01, 0xDE, 0x06, 0xF8, 0x1C, 0xC3, 0x00, 0xF3, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x0F, 0xE0, 0x1F, + 0xE0, 0x3F, 0xC3, 0x3D, 0xE7, 0x38, 0xFF, 0x0C, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x00, 0xFF, + 0x00, 0xF7, 0x08, 0x77, 0x08, 0xF7, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x87, 0xF8, 0x87, 0x78, 0x07, 0xF8, + 0x03, 0xFF, 0x03, 0xFF, 0x01, 0xFF, 0x00, 0xFE, + 0x18, 0xDF, 0x1C, 0xFD, 0x0F, 0xEF, 0x07, 0xF7, + 0xFC, 0x00, 0xFE, 0x02, 0xFF, 0x01, 0xFF, 0x01, + 0xFF, 0x38, 0xF3, 0x12, 0xF9, 0x19, 0xFC, 0x0C, + 0x02, 0x79, 0x80, 0xFD, 0xC0, 0xDF, 0xF0, 0xFE, + 0x79, 0x3F, 0x19, 0xDB, 0x19, 0xFB, 0xF9, 0xF7, + 0xFF, 0x84, 0xFF, 0x82, 0x7F, 0x60, 0x9F, 0x91, + 0xEF, 0xA9, 0xF6, 0x34, 0xFE, 0x1C, 0x1F, 0x11, + 0x63, 0xEF, 0xF3, 0xEB, 0xC6, 0xCE, 0xEF, 0xDE, + 0x8C, 0x9C, 0xDE, 0xBD, 0x9C, 0x9D, 0xFF, 0xEF, + 0x9E, 0x02, 0xBC, 0xA4, 0x3D, 0x14, 0x7B, 0x4A, + 0x73, 0x21, 0xF7, 0x94, 0xF7, 0xF6, 0xFE, 0xEE, + 0x8D, 0xEC, 0x9E, 0x7D, 0x1C, 0x5B, 0x38, 0xFA, + 0x79, 0xF7, 0x71, 0x75, 0xF3, 0xF3, 0xEF, 0xCF, + 0xF3, 0x90, 0xF7, 0x14, 0xEF, 0xA8, 0xEF, 0x2D, + 0xCF, 0x41, 0x8E, 0x8A, 0x3C, 0x3C, 0x39, 0x19, + 0x67, 0xFF, 0xEF, 0xFE, 0xEC, 0xDC, 0xCF, 0xCF, + 0xDD, 0xDC, 0xDC, 0x9F, 0x2C, 0x2F, 0xD7, 0xC7, + 0xB9, 0x21, 0xBB, 0xAA, 0xB3, 0x81, 0x76, 0x76, + 0x77, 0x76, 0xE7, 0xA4, 0xD7, 0x44, 0xFB, 0xCB, + 0xB3, 0x37, 0x73, 0x72, 0xF4, 0xEC, 0xEF, 0xCD, + 0xCD, 0x09, 0x11, 0xF3, 0x29, 0xA7, 0xF1, 0xCF, + 0xCD, 0x49, 0xDF, 0xDE, 0xBF, 0xA5, 0x7F, 0x5D, + 0xF6, 0x32, 0xFE, 0x14, 0xFE, 0x70, 0xFF, 0xC1, + 0xF0, 0x77, 0xF0, 0x67, 0xE0, 0xCF, 0x80, 0x97, + 0xC8, 0xBB, 0x98, 0xBB, 0x90, 0xD3, 0xE8, 0xE7, + 0xDF, 0x58, 0xBF, 0x28, 0x7F, 0x50, 0x7F, 0x28, + 0xF7, 0x84, 0xFF, 0xDC, 0xEF, 0xA4, 0xDF, 0xC0, + 0x00, 0xFF, 0x04, 0xF3, 0x03, 0xF8, 0x00, 0xFF, + 0x08, 0xF7, 0x03, 0xFC, 0x00, 0xBF, 0x18, 0xC7, + 0xF0, 0x0F, 0xF8, 0x0F, 0xFE, 0x05, 0xFF, 0x00, + 0xE7, 0x18, 0xC0, 0x3F, 0xC0, 0x7F, 0xE0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF6, 0x08, 0x77, + 0x08, 0xF5, 0x08, 0xF7, 0x10, 0xE7, 0x70, 0x87, + 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF9, 0x86, 0xF9, + 0x86, 0x7B, 0x0C, 0xF3, 0x08, 0xFF, 0x38, 0xCF, + 0x0A, 0xF1, 0x88, 0x77, 0x0E, 0xF1, 0x00, 0xFF, + 0x00, 0xFF, 0x7F, 0x80, 0x41, 0xBE, 0x81, 0x3E, + 0x84, 0x7F, 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0x81, 0x7E, 0xC1, + 0x04, 0xFB, 0x04, 0xDB, 0x24, 0xDB, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x08, 0xE7, 0x19, 0xE6, + 0x38, 0xC7, 0x38, 0xE7, 0x38, 0xC7, 0x18, 0xE7, + 0x18, 0xE7, 0x18, 0xE7, 0x10, 0xFF, 0x10, 0xEF, + 0x48, 0xB5, 0x80, 0x3F, 0x84, 0x7B, 0x80, 0x7F, + 0xA1, 0x5E, 0x21, 0x5E, 0x02, 0x7C, 0x02, 0x7D, + 0x46, 0xBB, 0x44, 0xFB, 0x40, 0xBF, 0x40, 0xBF, + 0xC0, 0x3F, 0xC1, 0xBE, 0xE1, 0x9F, 0xE3, 0x9C, + 0x60, 0x9D, 0x64, 0x99, 0x84, 0x3B, 0x84, 0x7B, + 0x04, 0x7B, 0x40, 0xBB, 0x41, 0xBA, 0x09, 0xF2, + 0x03, 0xFE, 0x43, 0xBE, 0x43, 0xFC, 0xC3, 0x3C, + 0xC7, 0xB8, 0x87, 0x7C, 0x86, 0x7D, 0x86, 0x7D, + 0x80, 0x7F, 0x80, 0x7F, 0x8F, 0x70, 0x10, 0xEF, + 0x10, 0xEF, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x48, 0xB7, 0x48, 0xB7, 0xC0, 0x3F, 0x01, 0xFE, + 0x01, 0xFE, 0x81, 0x2E, 0x50, 0xAF, 0x50, 0xAF, + 0x30, 0xCF, 0x30, 0xCF, 0xF0, 0x0F, 0xF0, 0x0F, + 0xF0, 0x0F, 0x70, 0xDF, 0x20, 0xDF, 0x60, 0x9F, + 0x06, 0xF8, 0x00, 0xFD, 0xF0, 0x0F, 0x00, 0x7E, + 0x00, 0xEE, 0xE2, 0x1C, 0x02, 0xFD, 0x0C, 0xF1, + 0x03, 0xFD, 0x03, 0xFE, 0xE1, 0x1E, 0xF1, 0x8F, + 0xF1, 0x1F, 0x01, 0xFF, 0x03, 0xFC, 0x07, 0xFA, + 0x08, 0xF3, 0x08, 0xF7, 0x08, 0xF7, 0x00, 0xFF, + 0x40, 0xBB, 0x01, 0xFE, 0x20, 0xDF, 0x18, 0xE7, + 0x87, 0x7C, 0x87, 0x78, 0x87, 0x78, 0x87, 0x78, + 0x87, 0x7C, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, + 0x08, 0xF7, 0x08, 0xF7, 0x01, 0xFE, 0x11, 0xEE, + 0x01, 0xDE, 0x82, 0x7C, 0x04, 0xF9, 0x38, 0xC3, + 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, 0x1F, + 0xE1, 0x3E, 0x03, 0xFD, 0x07, 0xFA, 0x0F, 0xF4, + 0x10, 0x6F, 0x00, 0x7F, 0x01, 0x7E, 0x01, 0xFE, + 0x01, 0xFE, 0x11, 0xEE, 0x10, 0xEE, 0x12, 0xEC, + 0xE0, 0x9F, 0xF0, 0x8F, 0xF0, 0x8F, 0xF0, 0x0F, + 0xF0, 0x0F, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1F, + 0x40, 0x9F, 0x80, 0x3F, 0x80, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xA0, 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFB, 0x08, 0xF7, 0x00, 0xFF, + 0x01, 0xBE, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x7F, + 0xFE, 0x01, 0xFC, 0x07, 0xF0, 0x0F, 0xE0, 0x1F, + 0xC1, 0x7E, 0x80, 0x7F, 0x80, 0xFF, 0x00, 0xFF, + 0x08, 0xF7, 0x10, 0xE7, 0x60, 0x8F, 0xC0, 0x3F, + 0x80, 0x7F, 0xE0, 0x0F, 0x00, 0xEF, 0x00, 0xEF, + 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, 0x7F, 0x80, + 0xFF, 0x00, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x02, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xD0, 0xC6, 0x00, 0x1F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC9, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE7, 0x86, 0x01, 0x39, 0x01, 0xFF, 0x03, 0xFF, + 0x03, 0xFF, 0x00, 0xFC, 0x00, 0xFE, 0x00, 0xFF, + 0xFF, 0x9E, 0xFF, 0xC7, 0xFE, 0x00, 0xFE, 0x02, + 0xFF, 0x03, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x00, + 0xC3, 0xD3, 0xC0, 0xBC, 0x80, 0xBF, 0x00, 0x7F, + 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x6B, 0x7F, 0x03, 0xFF, 0xC0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x1B, 0x00, 0x7C, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x23, 0xFF, 0x83, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC0, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x50, 0x4F, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x60, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF7, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0C, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x12, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x38, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF0, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x0C, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 +}; diff --git a/thirdparty/SameBoy-old/Core/joypad.c b/thirdparty/SameBoy-old/Core/joypad.c new file mode 100644 index 000000000..0d001ee78 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/joypad.c @@ -0,0 +1,224 @@ +#include "gb.h" +#include + +static inline bool should_bounce(GB_gameboy_t *gb) +{ + // Bouncing is super rare on an AGS, so don't emulate it on GB_MODEL_AGB_B (when addeed) + return !GB_is_sgb(gb) && !gb-> no_bouncing_emulation && !(gb->model & GB_MODEL_GBP_BIT) /*&& gb->model != GB_MODEL_AGB_B*/; +} + +static inline uint16_t bounce_for_key(GB_gameboy_t *gb, GB_key_t key) +{ + if (gb->model > GB_MODEL_CGB_E) { + // AGB are less bouncy + return 0xBFF; + } + if (key == GB_KEY_START || key == GB_KEY_SELECT) { + return 0x1FFF; + } + return 0xFFF; +} + +static inline bool get_input(GB_gameboy_t *gb, uint8_t player, GB_key_t key) +{ + if (player != 0) { + return gb->keys[player][key]; + } + bool ret = gb->keys[player][key]; + + if (likely(gb->key_bounce_timing[key] == 0)) return ret; + if (likely((gb->key_bounce_timing[key] & 0x3FF) > 0x300)) return ret; + uint16_t semi_random = ((((key << 5) + gb->div_counter) * 17) ^ ((gb->apu.apu_cycles + gb->display_cycles) * 13)); + semi_random >>= 3; + if (semi_random < gb->key_bounce_timing[key]) { + ret ^= true; + } + return ret; +} + +void GB_update_joyp(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_NO_SFC_BIT) return; + + uint8_t key_selection = 0; + uint8_t previous_state = 0; + + /* Todo: add delay to key selection */ + previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + uint8_t current_player = gb->sgb? gb->sgb->current_player : 0; + switch (key_selection) { + case 3: + if (gb->sgb && gb->sgb->player_count > 1) { + gb->io_registers[GB_IO_JOYP] |= 0xF - current_player; + } + else { + /* Nothing is wired, all up */ + gb->io_registers[GB_IO_JOYP] |= 0x0F; + } + break; + + case 2: + /* Direction keys */ + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!get_input(gb, current_player, i)) << i; + } + /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ + if (likely(!gb->illegal_inputs_allowed)) { + if (!(gb->io_registers[GB_IO_JOYP] & 1)) { + gb->io_registers[GB_IO_JOYP] |= 2; + } + if (!(gb->io_registers[GB_IO_JOYP] & 4)) { + gb->io_registers[GB_IO_JOYP] |= 8; + } + } + break; + + case 1: + /* Other keys */ + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!get_input(gb, current_player, i + 4)) << i; + } + break; + + case 0: + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!(get_input(gb, current_player, i) || get_input(gb, current_player, i + 4))) << i; + } + break; + + nodefault; + } + + // TODO: Implement the lame anti-debouncing mechanism as seen on the DMG schematics + if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { + if (!(gb->io_registers[GB_IO_IF] & 0x10)) { + gb->joyp_accessed = true; + gb->io_registers[GB_IO_IF] |= 0x10; + } + } + + gb->io_registers[GB_IO_JOYP] |= 0xC0; +} + +void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) +{ + uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + gb->io_registers[GB_IO_JOYP] |= value & 0xF; + + if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { + if (!(gb->io_registers[GB_IO_IF] & 0x10)) { + gb->joyp_accessed = true; + gb->io_registers[GB_IO_IF] |= 0x10; + } + } + gb->io_registers[GB_IO_JOYP] |= 0xC0; +} + +void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) +{ + assert(index >= 0 && index < GB_KEY_MAX); + if (should_bounce(gb) && pressed != gb->keys[0][index]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[index] = bounce_for_key(gb, index); + } + gb->keys[0][index] = pressed; + GB_update_joyp(gb); +} + +void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed) +{ + assert(index >= 0 && index < GB_KEY_MAX); + assert(player < 4); + if (should_bounce(gb) && pressed != gb->keys[player][index]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[index] = bounce_for_key(gb, index); + } + gb->keys[player][index] = pressed; + GB_update_joyp(gb); +} + +void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask) +{ + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + bool pressed = mask & (1 << i); + if (should_bounce(gb) && pressed != gb->keys[0][i]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[i] = bounce_for_key(gb, i); + } + gb->keys[0][i] = pressed; + } + + GB_update_joyp(gb); +} + +void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player) +{ + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + bool pressed = mask & (1 << i); + if (should_bounce(gb) && pressed != gb->keys[player][i]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[i] = bounce_for_key(gb, i); + } + gb->keys[player][i] = pressed; + } + + GB_update_joyp(gb); +} + +void GB_joypad_run(GB_gameboy_t *gb, unsigned cycles) +{ + if (gb->joypad_is_stable) return; + bool should_update_joyp = false; + gb->joypad_is_stable = true; + if (gb->joyp_switching_delay) { + gb->joypad_is_stable = false; + if (gb->joyp_switching_delay > cycles) { + gb->joyp_switching_delay -= cycles; + } + else { + gb->joyp_switching_delay = 0; + gb->io_registers[GB_IO_JOYP] = (gb->joyp_switch_value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + should_update_joyp = true; + } + } + + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + if (gb->key_bounce_timing[i]) { + gb->joypad_is_stable = false; + should_update_joyp = true; + if (gb->key_bounce_timing[i] > cycles) { + gb->key_bounce_timing[i] -= cycles; + } + else { + gb->key_bounce_timing[i] = 0; + } + } + } + + if (should_update_joyp) { + GB_update_joyp(gb); + } +} + +bool GB_get_joyp_accessed(GB_gameboy_t *gb) +{ + return gb->joyp_accessed; +} + +void GB_clear_joyp_accessed(GB_gameboy_t *gb) +{ + gb->joyp_accessed = false; +} + +void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow) +{ + gb->illegal_inputs_allowed = allow; +} + +void GB_set_emulate_joypad_bouncing(GB_gameboy_t *gb, bool emulate) +{ + gb->no_bouncing_emulation = !emulate; +} diff --git a/thirdparty/SameBoy-old/Core/joypad.h b/thirdparty/SameBoy-old/Core/joypad.h new file mode 100644 index 000000000..d39eff38d --- /dev/null +++ b/thirdparty/SameBoy-old/Core/joypad.h @@ -0,0 +1,46 @@ +#ifndef joypad_h +#define joypad_h +#include "defs.h" +#include + +typedef enum { + GB_KEY_RIGHT, + GB_KEY_LEFT, + GB_KEY_UP, + GB_KEY_DOWN, + GB_KEY_A, + GB_KEY_B, + GB_KEY_SELECT, + GB_KEY_START, + 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); +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); +void GB_set_emulate_joypad_bouncing(GB_gameboy_t *gb, bool emulate); + +#ifdef GB_INTERNAL +internal void GB_update_joyp(GB_gameboy_t *gb); +internal void GB_joypad_run(GB_gameboy_t *gb, unsigned cycles); +#endif +#endif /* joypad_h */ diff --git a/thirdparty/SameBoy-old/Core/mbc.c b/thirdparty/SameBoy-old/Core/mbc.c new file mode 100644 index 000000000..5d50db571 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/mbc.c @@ -0,0 +1,275 @@ +#include +#include +#include +#include "gb.h" + +const GB_cartridge_t GB_cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC RAM BAT. RTC RUMB. */ + { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , false, false, false, false}, // 01h MBC1 + { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { GB_MBC2 , true , false, false, false}, // 05h MBC2 + { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + { GB_MMM01 , false, false, false, false}, // 0Bh MMM01 + { GB_MMM01 , true , false, false, false}, // 0Ch MMM01+RAM + { GB_MMM01 , true , true , false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , false, false, false, false}, // 11h MBC3 + { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x19] = + { GB_MBC5 , false, false, false, false}, // 19h MBC5 + { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0x22] = + { GB_MBC7 , true, true, false, false}, // 22h MBC7+ACCEL+EEPROM + [0xFC] = + { GB_CAMERA, true , true , false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) + { GB_HUC3 , true , true , true, false}, // FEh HuC3 + { GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY +}; + +void GB_update_mbc_mappings(GB_gameboy_t *gb) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (gb->mbc1_wiring) { + case GB_STANDARD_MBC1_WIRING: + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_ram_bank = gb->mbc1.bank_high; + gb->mbc_rom0_bank = gb->mbc1.bank_high << 5; + } + if ((gb->mbc_rom_bank & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + case GB_MBC1M_WIRING: + gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_rom0_bank = gb->mbc1.bank_high << 4; + gb->mbc_ram_bank = 0; + } + if ((gb->mbc1.bank_low & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + nodefault; + } + break; + case GB_MBC2: + gb->mbc_rom_bank = gb->mbc2.rom_bank; + if ((gb->mbc_rom_bank & 0xF) == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC3: + gb->mbc_rom_bank = gb->mbc3.rom_bank; + gb->mbc_ram_bank = gb->mbc3.ram_bank; + if (!gb->is_mbc30) { + gb->mbc_rom_bank &= 0x7F; + } + if (gb->mbc_rom_bank == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC5: + case GB_CAMERA: + gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); + gb->mbc_ram_bank = gb->mbc5.ram_bank; + break; + case GB_MBC7: + gb->mbc_rom_bank = gb->mbc7.rom_bank; + break; + case GB_MMM01: + if (gb->mmm01.locked) { + if (gb->mmm01.multiplex_mode) { + gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | + ((gb->mmm01.mbc1_mode? 0 : gb->mmm01.ram_bank_low) << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_rom_bank = gb->mmm01.rom_bank_low | + (gb->mmm01.ram_bank_low << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_ram_bank = gb->mmm01.rom_bank_mid | (gb->mmm01.ram_bank_high << 2); + } + else { + gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | + (gb->mmm01.rom_bank_mid << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_rom_bank = gb->mmm01.rom_bank_low | + (gb->mmm01.rom_bank_mid << 5) | + (gb->mmm01.rom_bank_high << 7); + if (gb->mmm01.mbc1_mode) { + gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2); + } + else { + gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2); + } + } + if (gb->mbc_rom_bank == gb->mbc_rom0_bank) { + gb->mbc_rom_bank++; + } + } + else { + gb->mbc_rom_bank = -1; + gb->mbc_rom0_bank = -2; + } + break; + case GB_HUC1: + gb->mbc_rom_bank = gb->huc1.bank_low; + gb->mbc_ram_bank = gb->huc1.bank_high; + break; + case GB_HUC3: + gb->mbc_rom_bank = gb->huc3.rom_bank; + gb->mbc_ram_bank = gb->huc3.ram_bank; + break; + case GB_TPP1: + gb->mbc_rom_bank = gb->tpp1.rom_bank; + gb->mbc_ram_bank = gb->tpp1.ram_bank; + gb->mbc_ram_enable = (gb->tpp1.mode == 2) || (gb->tpp1.mode == 3); + break; + nodefault; + } +} + +void GB_configure_cart(GB_gameboy_t *gb) +{ + gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; + if (gb->cartridge_type->mbc_type == GB_MMM01) { + uint8_t *temp = malloc(0x8000); + memcpy(temp, gb->rom, 0x8000); + memmove(gb->rom, gb->rom + 0x8000, gb->rom_size - 0x8000); + memcpy(gb->rom + gb->rom_size - 0x8000, temp, 0x8000); + free(temp); + } + else { + const GB_cartridge_t *maybe_mmm01_type = &GB_cart_defs[gb->rom[gb->rom_size - 0x8000 + 0x147]]; + if (memcmp(gb->rom + 0x104, gb->rom + gb->rom_size - 0x8000 + 0x104, 0x30) == 0) { + if (maybe_mmm01_type->mbc_type == GB_MMM01) { + gb->cartridge_type = maybe_mmm01_type; + } + else if(gb->rom[gb->rom_size - 0x8000 + 0x147] == 0x11) { + GB_log(gb, "ROM header reports MBC3, but it appears to be an MMM01 ROM. Assuming cartridge uses MMM01."); + gb->cartridge_type = &GB_cart_defs[0xB]; + } + } + } + + if (gb->rom[0x147] == 0xBC && + gb->rom[0x149] == 0xC1 && + gb->rom[0x14A] == 0x65) { + static const GB_cartridge_t tpp1 = {GB_TPP1, true, true, true, true}; + gb->cartridge_type = &tpp1; + gb->tpp1.rom_bank = 1; + } + + if (gb->cartridge_type->mbc_type != GB_MMM01) { + if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { + GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); + gb->cartridge_type = &GB_cart_defs[0x11]; + } + else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) { + GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); + } + } + + if (gb->mbc_ram) { + free(gb->mbc_ram); + gb->mbc_ram = NULL; + gb->mbc_ram_size = 0; + } + + if (gb->cartridge_type->has_ram) { + if (gb->cartridge_type->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); + } + } + else { + static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; + if (gb->cartridge_type->mbc_type == GB_MMM01) { + gb->mbc_ram_size = ram_sizes[gb->rom[gb->rom_size - 0x8000 + 0x149]]; + } + else { + gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + } + } + + if (gb->mbc_ram_size) { + gb->mbc_ram = malloc(gb->mbc_ram_size); + } + + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridge types? */ + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + } + + /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). + See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ + + /* Attempt to "guess" wiring */ + if (gb->cartridge_type->mbc_type == GB_MBC1) { + if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) { + gb->mbc1_wiring = GB_MBC1M_WIRING; + } + } + + /* Detect MBC30 */ + if (gb->cartridge_type->mbc_type == GB_MBC3) { + if (gb->rom_size > 0x200000 || gb->mbc_ram_size > 0x8000) { + gb->is_mbc30 = true; + } + } + + GB_reset_mbc(gb); +} + +void GB_reset_mbc(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type == GB_MMM01) { + gb->mbc_rom_bank = -1; + gb->mbc_rom0_bank = -2; + gb->mmm01.ram_bank_mask = -1; + } + else if (gb->cartridge_type->mbc_type == GB_MBC5 || + gb->cartridge_type->mbc_type == GB_CAMERA) { + gb->mbc5.rom_bank_low = 1; + gb->mbc_rom_bank = 1; + } + else if (gb->cartridge_type->mbc_type == GB_MBC7) { + gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000; + gb->mbc7.latch_ready = true; + gb->mbc7.read_bits = -1; + gb->mbc7.eeprom_do = true; + } + else { + gb->mbc_rom_bank = 1; + } +} diff --git a/thirdparty/SameBoy-old/Core/mbc.h b/thirdparty/SameBoy-old/Core/mbc.h new file mode 100644 index 000000000..8bdb07991 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/mbc.h @@ -0,0 +1,33 @@ +#ifndef MBC_h +#define MBC_h +#include "defs.h" +#include + +typedef struct { + enum { + GB_NO_MBC, + GB_MBC1, + GB_MBC2, + GB_MBC3, + GB_MBC5, + GB_MBC7, + GB_MMM01, + GB_HUC1, + GB_HUC3, + GB_TPP1, + GB_CAMERA, + } mbc_type; + bool has_ram; + bool has_battery; + bool has_rtc; + bool has_rumble; +} GB_cartridge_t; + +#ifdef GB_INTERNAL +internal extern const GB_cartridge_t GB_cart_defs[256]; +internal void GB_update_mbc_mappings(GB_gameboy_t *gb); +internal void GB_configure_cart(GB_gameboy_t *gb); +internal void GB_reset_mbc(GB_gameboy_t *gb); +#endif + +#endif /* MBC_h */ diff --git a/thirdparty/SameBoy-old/Core/memory.c b/thirdparty/SameBoy-old/Core/memory.c new file mode 100644 index 000000000..bac55e078 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/memory.c @@ -0,0 +1,1871 @@ +#include +#include +#include "gb.h" + +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, +} bus_t; + +static bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x8000) { + return GB_BUS_MAIN; + } + if (addr < 0xA000) { + return GB_BUS_VRAM; + } + if (addr < 0xC000) { + return GB_BUS_MAIN; + } + 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) +{ + return ((a ^ c) & (b ^ c)) ^ c; +} + +static uint16_t bitwise_glitch_read(uint16_t a, uint16_t b, uint16_t c) +{ + return b | (a & c); +} + +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) { + 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], + 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]; + } + } + } +} + +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) { + 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->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: + 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); + } + else if (gb->model == GB_MODEL_MGB && gb->accessed_oam_row == 0x40) { + memcpy(gb->oam, gb->oam + gb->accessed_oam_row, 8); + } + } + } +} + + +static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) +{ + if (!GB_is_dma_active(gb) || addr >= 0xFE00 || gb->hdma_in_progress) return false; + if (gb->dma_current_dest == 0xFF || gb->dma_current_dest == 0x0) return false; // Warm up + if (addr >= 0xFE00) return false; + if (gb->dma_current_src == addr) return false; // Shortcut for DMA access flow + if (gb->dma_current_src >= 0xE000 && (gb->dma_current_src & ~0x2000) == addr) return false; + if (GB_is_cgb(gb)) { + if (addr >= 0xC000) { + 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); +} + +static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x100 && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (addr >= 0x200 && addr < 0x900 && GB_is_cgb(gb) && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (!gb->rom_size) { + return 0xFF; + } + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) +{ + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) +{ + if (likely(!GB_is_dma_active(gb))) { + /* Prevent syncing from a DMA read. Batching doesn't happen during DMA anyway. */ + GB_display_sync(gb); + } + else { + if ((gb->dma_current_dest & 0xE000) == 0x8000) { + // TODO: verify conflict behavior + return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)]; + } + } + + if (unlikely(gb->vram_read_blocked && !gb->in_dma_read)) { + return 0xFF; + } + if (unlikely(gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed)) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done*/ + } + else { + addr = gb->last_tile_data_address; + } + } + return gb->vram[(addr & 0x1FFF) + (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 + if (gb->huc3.access_flags == 0x2) { + return 1; + } + 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); + return 1; // TODO: What happens in this case? + case 0: // TODO: R/O RAM? (or is it disabled?) + case 0xA: // RAM + break; + } + } + + 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->tpp1_mr4; + nodefault; + } + case 2: + case 3: + break; // Read RAM + case 5: + return gb->rtc_latched.data[(addr & 3) ^ 3]; + default: + return 0xFF; + } + } + else if ((!gb->mbc_ram_enable) && + gb->cartridge_type->mbc_type != GB_CAMERA && + gb->cartridge_type->mbc_type != GB_HUC1 && + gb->cartridge_type->mbc_type != GB_HUC3) { + return 0xFF; + } + + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { + return 0xC0 | gb->effective_ir_input; + } + + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && + gb->mbc3.rtc_mapped) { + /* RTC read */ + 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) { + return GB_camera_read_register(gb, addr); + } + + if (!gb->mbc_ram || !gb->mbc_ram_size) { + return 0xFF; + } + + if (gb->cartridge_type->mbc_type == GB_CAMERA) { + /* Forbid reading RAM while the camera is busy. */ + if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { + return 0; + } + + if (gb->mbc_ram_bank == 0 && addr >= 0xA100 && addr < 0xAF00) { + return GB_camera_read_image(gb, addr - 0xA100); + } + } + + uint8_t effective_bank = gb->mbc_ram_bank; + 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)]; + if (gb->cartridge_type->mbc_type == GB_MBC2) { + ret |= 0xF0; + } + return ret; +} + +static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) +{ + return gb->ram[addr & 0x0FFF]; +} + +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; + } +} + +internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr) +{ + if (addr < 0xA0) { + return gb->oam[addr]; + } + + switch (gb->model) { + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + return (addr & 0xF0) | (addr >> 4); + + case GB_MODEL_CGB_D: + if (addr >= 0xC0) { + addr |= 0xF0; + } + return gb->extra_oam[addr - 0xA0]; + + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + addr &= ~0x18; + return gb->extra_oam[addr - 0xA0]; + + case GB_MODEL_DMG_B: + case GB_MODEL_MGB: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + return 0; + } + unreachable(); +} + +static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0xFE00) { + return read_banked_ram(gb, addr); + } + + 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); + } + return 0xFF; + } + + if (GB_is_dma_active(gb)) { + /* Todo: Does reading from OAM during DMA causes the OAM bug? */ + return 0xFF; + } + + if (gb->oam_read_blocked) { + 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) { + 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) { + 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; + + nodefault; + } + + for (unsigned i = 0; i < 8; i++) { + gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i]; + } + } + } + } + return 0xFF; + } + + return GB_read_oam(gb, 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; + case GB_IO_TAC: + return gb->io_registers[GB_IO_TAC] | 0xF8; + case GB_IO_STAT: + return gb->io_registers[GB_IO_STAT] | 0x80; + case GB_IO_OPRI: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->io_registers[GB_IO_OPRI] | 0xFE; + + 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: + gb->joyp_accessed = true; + GB_timing_sync(gb); + if (unlikely(gb->joyp_switching_delay)) { + return (gb->io_registers[addr & 0xFF] & ~0x30) | (gb->joyp_switch_value & 0x30); + } + return gb->io_registers[addr & 0xFF]; + case GB_IO_TMA: + case GB_IO_LCDC: + case GB_IO_SCY: + case GB_IO_SCX: + case GB_IO_LY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_SC: + case GB_IO_SB: + case GB_IO_DMA: + return gb->io_registers[addr & 0xFF]; + case GB_IO_TIMA: + if (gb->tima_reload_state == GB_TIMA_RELOADING) { + return 0; + } + return gb->io_registers[GB_IO_TIMA]; + case GB_IO_DIV: + return gb->div_counter >> 8; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return 0xFF; + return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->cgb_ram_bank | ~0x7; + case GB_IO_VBK: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->cgb_vram_bank | ~0x1; + + /* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */ + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->io_registers[addr & 0xFF] | 0x40; + + case GB_IO_BGPD: + case GB_IO_OBPD: + { + if (!gb->cgb_mode && gb->boot_rom_finished) { + return 0xFF; + } + if (gb->cgb_palettes_blocked) { + return 0xFF; + } + uint8_t index_reg = (addr & 0xFF) - 1; + return ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + gb->object_palettes_data)[gb->io_registers[index_reg] & 0x3F]; + } + + case GB_IO_KEY1: + if (!gb->cgb_mode) { + return 0xFF; + } + return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); + + 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) | 0x2E; + if (gb->model != GB_MODEL_CGB_E) { + ret |= 0x10; + } + if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model <= GB_MODEL_CGB_E) { + ret &= ~2; + } + return ret; + } + case GB_IO_PSWX: + case GB_IO_PSWY: + return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] : 0xFF; + 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; + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + return GB_apu_read(gb, addr & 0xFF); + } + return 0xFF; + } + unreachable(); + } + + if (addr == 0xFFFF) { + /* Interrupt Mask */ + return gb->interrupt_enable; + } + + /* HRAM */ + return gb->hram[addr - 0xFF80]; +} + +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 */ + read_vram, read_vram, /* 8XXX, 9XXX */ + read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */ + read_ram, read_banked_ram, /* CXXX, DXXX */ + read_ram, read_high_memory, /* EXXX FXXX */ +}; + +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback) +{ + gb->read_memory_callback = callback; +} + +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) +{ + if (unlikely(gb->n_watchpoints)) { + GB_debugger_test_read_watchpoint(gb, addr); + } + if (unlikely(is_addr_in_dma_use(gb, addr))) { + if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) { + /* 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 - 1) & 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 - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + } + else { + addr = (gb->dma_current_src - 1); + } + } + uint8_t data = read_map[addr >> 12](gb, addr); + GB_apply_cheat(gb, addr, &data); + if (unlikely(gb->read_memory_callback)) { + data = gb->read_memory_callback(gb, addr, data); + } + return data; +} + +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; + GB_apply_cheat(gb, addr, &data); + if (unlikely(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) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->mbc1.mode = value; break; + } + break; + case GB_MBC2: + switch (addr & 0x4100) { + case 0x0000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0100: gb->mbc2.rom_bank = value; break; + } + break; + case GB_MBC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; + case 0x4000: case 0x5000: + gb->mbc3.ram_bank = value; + gb->mbc3.rtc_mapped = value & 8; + break; + case 0x6000: case 0x7000: + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + break; + } + break; + case GB_MBC5: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = value == 0x0A; break; + case 0x2000: gb->mbc5.rom_bank_low = value; break; + case 0x3000: gb->mbc5.rom_bank_high = value; break; + case 0x4000: case 0x5000: + if (gb->cartridge_type->has_rumble) { + if (!!(value & 8) != !!gb->rumble_strength) { + gb->rumble_strength = gb->rumble_strength? 0 : 3; + } + value &= 7; + } + gb->mbc5.ram_bank = value; + break; + } + break; + case GB_CAMERA: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc5.rom_bank_low = value; break; + case 0x4000: case 0x5000: + gb->mbc5.ram_bank = value; + gb->camera_registers_mapped = (value & 0x10); + break; + } + break; + 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_MMM01: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: + gb->mbc_ram_enable = (value & 0xF) == 0xA; + if (!gb->mmm01.locked) { + gb->mmm01.ram_bank_mask = value >> 4; + gb->mmm01.locked = value & 0x40; + } + break; + case 0x2000: case 0x3000: + if (!gb->mmm01.locked) { + gb->mmm01.rom_bank_mid = value >> 5; + } + gb->mmm01.rom_bank_low &= (gb->mmm01.rom_bank_mask << 1); + gb->mmm01.rom_bank_low |= ~(gb->mmm01.rom_bank_mask << 1) & value; + break; + case 0x4000: case 0x5000: + gb->mmm01.ram_bank_low = value | ~gb->mmm01.ram_bank_mask; + if (!gb->mmm01.locked) { + gb->mmm01.ram_bank_high = value >> 2; + gb->mmm01.rom_bank_high = value >> 4; + gb->mmm01.mbc1_mode_disable = value & 0x40; + } + break; + case 0x6000: case 0x7000: + if (!gb->mmm01.mbc1_mode_disable) { + gb->mmm01.mbc1_mode = (value & 1); + } + if (!gb->mmm01.locked) { + gb->mmm01.rom_bank_mask = value >> 2; + gb->mmm01.multiplex_mode = value & 0x40; + } + break; + } + break; + case GB_HUC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; + case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; + } + break; + case GB_HUC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: + 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; + } + 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: { + memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); + break; + } + case 0x14: + gb->tpp1_mr4 &= ~0x8; + break; + case 0x18: + gb->tpp1_mr4 &= ~0x4; + break; + case 0x19: + gb->tpp1_mr4 |= 0x4; + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + gb->rumble_strength = value & 3; + break; + } + } + break; + nodefault; + } + GB_update_mbc_mappings(gb); +} + +static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + GB_display_sync(gb); + if (unlikely(gb->vram_write_blocked)) { + //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); + return; + } + gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)] = value; +} + +static bool huc3_write(GB_gameboy_t *gb, uint8_t value) +{ + switch (gb->huc3.mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3.access_index < 3) { + gb->huc3.read = (gb->huc3.minutes >> (gb->huc3.access_index * 4)) & 0xF; + } + else if (gb->huc3.access_index < 7) { + gb->huc3.read = (gb->huc3.days >> ((gb->huc3.access_index - 3) * 4)) & 0xF; + } + else { + // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3.access_index); + } + gb->huc3.access_index++; + break; + case 2: + case 3: + if (gb->huc3.access_index < 3) { + gb->huc3.minutes &= ~(0xF << (gb->huc3.access_index * 4)); + gb->huc3.minutes |= ((value & 0xF) << (gb->huc3.access_index * 4)); + } + else if (gb->huc3.access_index < 7) { + gb->huc3.days &= ~(0xF << ((gb->huc3.access_index - 3) * 4)); + gb->huc3.days |= ((value & 0xF) << ((gb->huc3.access_index - 3) * 4)); + } + else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5A) { + gb->huc3.alarm_minutes &= ~(0xF << ((gb->huc3.access_index - 0x58) * 4)); + gb->huc3.alarm_minutes |= ((value & 0xF) << ((gb->huc3.access_index - 0x58) * 4)); + } + else if (gb->huc3.access_index >= 0x5B && gb->huc3.access_index <= 0x5E) { + gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5B) * 4)); + gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5B) * 4)); + } + else if (gb->huc3.access_index == 0x5F) { + gb->huc3.alarm_enabled = value & 1; + } + else { + // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3.access_index); + } + if ((value >> 4) == 3) { + gb->huc3.access_index++; + } + break; + case 4: + gb->huc3.access_index &= 0xF0; + gb->huc3.access_index |= value & 0xF; + break; + case 5: + gb->huc3.access_index &= 0x0F; + gb->huc3.access_index |= (value & 0xF) << 4; + break; + case 6: + gb->huc3.access_flags = (value & 0xF); + break; + + default: + break; + } + + return true; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return true; + case 0xE: { // IR mode + if (gb->cart_ir != (value & 1)) { + gb->cart_ir = value & 1; + if (gb->infrared_callback) { + gb->infrared_callback(gb, value & 1); + } + } + return true; + } + case 0xC: + return true; + default: + return false; + case 0: // Disabled + case 0xA: // RAM + return false; + } +} + +static void write_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.argument_bits_left == 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.argument_bits_left = 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.argument_bits_left = 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.argument_bits_left--; + gb->mbc7.eeprom_do = true; + if (gb->mbc7.eeprom_di) { + uint16_t bit = LE16(1 << gb->mbc7.argument_bits_left); + if (gb->mbc7.eeprom_command & 0x100) { + // WRITE + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit; + } + else { + // WRAL + for (unsigned i = 0; i < 0x7F; i++) { + ((uint16_t *)gb->mbc_ram)[i] |= bit; + } + } + } + if (gb->mbc7.argument_bits_left == 0) { // We're done + gb->mbc7.eeprom_command = 0; + gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle + } + } + } + } + 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; + } + + if (gb->camera_registers_mapped) { + GB_camera_write_register(gb, addr, value); + return; + } + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + switch (gb->tpp1.mode) { + case 3: + break; + case 5: + gb->rtc_latched.data[(addr & 3) ^ 3] = value; + return; + default: + return; + } + } + + if ((!gb->mbc_ram_enable) + && gb->cartridge_type->mbc_type != GB_HUC1) return; + + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { + if (gb->cart_ir != (value & 1)) { + gb->cart_ir = value & 1; + if (gb->infrared_callback) { + gb->infrared_callback(gb, value & 1); + } + } + return; + } + + 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_real.data[gb->mbc_ram_bank] = value; + } + return; + } + + if (!gb->mbc_ram || !gb->mbc_ram_size) { + return; + } + + if (gb->cartridge_type->mbc_type == GB_CAMERA && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + /* Forbid writing to RAM while the camera is busy. */ + return; + } + + 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; + } + + gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; +} + +static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[addr & 0x0FFF] = value; +} + +static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value; +} + +static void write_oam(GB_gameboy_t *gb, uint8_t addr, uint8_t value) +{ + if (addr < 0xA0) { + gb->oam[addr] = value; + return; + } + switch (gb->model) { + case GB_MODEL_CGB_D: + if (addr >= 0xC0) { + addr |= 0xF0; + } + gb->extra_oam[addr - 0xA0] = value; + break; + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + addr &= ~0x18; + gb->extra_oam[addr - 0xA0] = value; + break; + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + case GB_MODEL_DMG_B: + case GB_MODEL_MGB: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + break; + } +} + +static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (addr < 0xFE00) { + GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); + write_banked_ram(gb, addr, value); + return; + } + + if (addr < 0xFF00) { + GB_display_sync(gb); + if (gb->oam_write_blocked) { + GB_trigger_oam_bug(gb, addr); + return; + } + + if (GB_is_dma_active(gb)) { + /* Todo: Does writing to OAM during DMA causes the OAM bug? */ + return; + } + + if (GB_is_cgb(gb)) { + write_oam(gb, addr, value); + return; + } + + if (addr < 0xFEA0) { + if (gb->accessed_oam_row == 0xA0) { + for (unsigned i = 0; i < 8; i++) { + if ((i & 6) != (addr & 6)) { + gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i]; + } + else { + gb->oam[(addr & 0xF8) + i] = bitwise_glitch(gb->oam[(addr & 0xF8) + i], gb->oam[0x9C], gb->oam[0x98 + i]); + } + } + } + + gb->oam[addr & 0xFF] = value; + + if (gb->accessed_oam_row == 0) { + gb->oam[0] = bitwise_glitch(gb->oam[0], + gb->oam[(addr & 0xF8)], + gb->oam[(addr & 0xFE)]); + gb->oam[1] = bitwise_glitch(gb->oam[1], + gb->oam[(addr & 0xF8) + 1], + gb->oam[(addr & 0xFE) | 1]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[i] = gb->oam[(addr & 0xF8) + i]; + } + } + } + else if (gb->accessed_oam_row == 0) { + gb->oam[addr & 0x7] = value; + } + return; + } + + /* 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: + if (value == gb->current_line) { + gb->wy_triggered = true; + } + case GB_IO_WX: + case GB_IO_IF: + case GB_IO_SCX: + case GB_IO_SCY: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_SB: + case GB_IO_PSWX: + case GB_IO_PSWY: + case GB_IO_PSW: + case GB_IO_UNKNOWN5: + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_OPRI: + if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_KEY0] & 8)) && GB_is_cgb(gb)) { + gb->io_registers[addr & 0xFF] = value; + gb->object_priority = (value & 1) ? GB_OBJECT_PRIORITY_X : GB_OBJECT_PRIORITY_INDEX; + } + else if (gb->cgb_mode) { + gb->io_registers[addr & 0xFF] = value; + + } + return; + case GB_IO_LYC: + + /* TODO: Probably completely wrong in double speed mode */ + + /* TODO: This hack is disgusting */ + if (gb->display_state == 29 && GB_is_cgb(gb)) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = 0; + } + + gb->io_registers[addr & 0xFF] = value; + + /* These are the states when LY changes, let the display routine call GB_STAT_update for use + so it correctly handles T-cycle accurate LYC writes */ + if (!GB_is_cgb(gb) || ( + gb->display_state != 35 && + gb->display_state != 26 && + gb->display_state != 15 && + gb->display_state != 16)) { + + /* More hacks to make LYC write conflicts work */ + if (gb->display_state == 14 && GB_is_cgb(gb)) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = -1; + } + else { + GB_STAT_update(gb); + } + } + return; + + case GB_IO_TIMA: + if (gb->tima_reload_state != GB_TIMA_RELOADED) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TMA: + gb->io_registers[GB_IO_TMA] = value; + if (gb->tima_reload_state != GB_TIMA_RUNNING) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TAC: + GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); + gb->io_registers[GB_IO_TAC] = value; + return; + + + case GB_IO_LCDC: + if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { + // LCD turned on + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, true); + } + if (!gb->lcd_disabled_outside_of_vblank && + (gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) { + // Trigger a vblank here so we don't exceed LCDC_PERIOD + GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL); + } + + gb->display_cycles = 0; + gb->display_state = 0; + gb->double_speed_alignment = 0; + gb->cycles_for_line = 0; + if (GB_is_sgb(gb)) { + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; + } + else { + 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 */ + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, false); + } + gb->double_speed_alignment = 0; + GB_timing_sync(gb); + GB_lcd_off(gb); + } + /* Handle disabling objects while already fetching an object */ + if ((gb->io_registers[GB_IO_LCDC] & 2) && !(value & 2)) { + if (gb->during_object_fetch) { + gb->cycles_for_line += gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } + gb->io_registers[GB_IO_LCDC] = value; + if (!(value & 0x20)) { + gb->wx_triggered = false; + gb->wx166_glitch = false; + } + return; + + case GB_IO_STAT: + /* Delete previous R/W bits */ + gb->io_registers[GB_IO_STAT] &= 7; + /* Set them by value */ + gb->io_registers[GB_IO_STAT] |= value & ~7; + /* Set unused bit to 1 */ + gb->io_registers[GB_IO_STAT] |= 0x80; + + GB_STAT_update(gb); + 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; + return; + + case GB_IO_JOYP: + if (gb->joyp_write_callback) { + gb->joyp_write_callback(gb, value); + GB_update_joyp(gb); + } + else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { + if (gb->model < GB_MODEL_SGB) { // DMG only + if (gb->joyp_switching_delay) { + gb->io_registers[GB_IO_JOYP] = (gb->joyp_switch_value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + } + gb->joyp_switch_value = value; + gb->joyp_switching_delay = 24; + value &= gb->io_registers[GB_IO_JOYP]; + gb->joypad_is_stable = false; + } + GB_sgb_write(gb, value); + gb->io_registers[GB_IO_JOYP] = (value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + GB_update_joyp(gb); + } + return; + + case GB_IO_BANK: + gb->boot_rom_finished = true; + return; + + case GB_IO_KEY0: + if (GB_is_cgb(gb) && !gb->boot_rom_finished) { + gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ + gb->io_registers[GB_IO_KEY0] = value; + } + return; + + case GB_IO_DMA: + gb->dma_cycles = 0; + gb->dma_cycles_modulo = 2; + gb->dma_current_dest = 0xFF; + gb->dma_current_src = value << 8; + gb->io_registers[GB_IO_DMA] = value; + GB_STAT_update(gb); + return; + case GB_IO_SVBK: + if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) { + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } + } + return; + case GB_IO_VBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_vram_bank = value & 0x1; + return; + + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!GB_is_cgb(gb)) { + return; + } + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_BGPD: + case GB_IO_OBPD: + if (!gb->cgb_mode && gb->boot_rom_finished) { + /* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM + is required. */ + return; + } + + uint8_t index_reg = (addr & 0xFF) - 1; + if (gb->cgb_palettes_blocked) { + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + } + ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + 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]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + case GB_IO_KEY1: + if (!gb->cgb_mode) { + return; + } + gb->io_registers[GB_IO_KEY1] = value; + return; + case GB_IO_HDMA1: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xF0; + gb->hdma_current_src |= value << 8; + } + /* Range 0xE*** like 0xF*** and can't overflow (with 0x800 bytes) to anything meaningful */ + if (gb->hdma_current_src >= 0xE000) { + gb->hdma_current_src |= 0xF000; + } + return; + case GB_IO_HDMA2: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xFF00; + gb->hdma_current_src |= value & 0xF0; + } + return; + case GB_IO_HDMA3: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0xF0; + gb->hdma_current_dest |= value << 8; + } + return; + case GB_IO_HDMA4: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0xFF00; + gb->hdma_current_dest |= value & 0xF0; + } + return; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return; + if ((value & 0x80) == 0 && gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + return; + } + gb->hdma_on = (value & 0x80) == 0; + gb->hdma_on_hblank = (value & 0x80) != 0; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->display_state != 7) { + gb->hdma_on = true; + } + gb->io_registers[GB_IO_HDMA5] = value; + gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; + return; + + /* TODO: What happens when starting a transfer during external clock? + TODO: When a cable is connected, the clock of the other side affects "zombie" serial clocking */ + case GB_IO_SC: + gb->serial_count = 0; + if (!gb->cgb_mode) { + value |= 2; + } + if (gb->serial_master_clock) { + GB_serial_master_edge(gb); + } + gb->io_registers[GB_IO_SC] = value | (~0x83); + gb->serial_mask = gb->cgb_mode && (value & 2)? 4 : 0x80; + if ((value & 0x80) && (value & 0x1) ) { + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + return; + + case GB_IO_RP: { + if (!GB_is_cgb(gb)) { + return; + } + if ((gb->io_registers[GB_IO_RP] ^ value) & 1) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, value & 1); + } + } + gb->io_registers[GB_IO_RP] = value; + + return; + } + + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + GB_apu_write(gb, addr & 0xFF, value); + return; + } + GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); + return; + } + } + + if (addr == 0xFFFF) { + GB_display_sync(gb); + /* Interrupt mask */ + gb->interrupt_enable = value; + return; + } + + /* HRAM */ + gb->hram[addr - 0xFF80] = value; +} + + + +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 */ + 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 */ +}; + +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 (unlikely(gb->n_watchpoints)) { + GB_debugger_test_write_watchpoint(gb, addr, value); + } + + if (unlikely(gb->write_memory_callback)) { + if (!gb->write_memory_callback(gb, addr, value)) return; + } + + 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; + } + + if (GB_is_cgb(gb) && (gb->dma_current_src < 0xC000 || gb->dma_current_src >= 0xE000) && addr >= 0xC000) { + // TODO: this should probably affect the DMA dest as well + addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + goto write; + } + 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 - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + } + else { + addr = (gb->dma_current_src - 1); + } + if (GB_is_cgb(gb) || addr >= 0xA000) { + if (addr < 0xA000) { + gb->oam[gb->dma_current_dest - 1] = 0; + } + else if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B)) { + gb->oam[gb->dma_current_dest - 1] &= value; + } + else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && !oam_write) { + gb->oam[gb->dma_current_dest - 1] = value; + } + if (gb->model < GB_MODEL_CGB_E || addr >= 0xA000) return; + } + } +write: + write_map[addr >> 12](gb, addr, value); +} + +bool GB_is_dma_active(GB_gameboy_t *gb) +{ + return gb->dma_current_dest != 0xA1; +} + +void GB_dma_run(GB_gameboy_t *gb) +{ + if (gb->dma_current_dest == 0xA1) return; + if (unlikely(gb->halted || gb->stopped)) return; + signed cycles = gb->dma_cycles + gb->dma_cycles_modulo; + gb->in_dma_read = true; + while (unlikely(cycles >= 4)) { + cycles -= 4; + if (gb->dma_current_dest >= 0xA0) { + gb->dma_current_dest++; + if (gb->display_state == 8) { + gb->io_registers[GB_IO_STAT] |= 2; + GB_STAT_update(gb); + } + break; + } + if (unlikely(gb->hdma_in_progress && (gb->hdma_steps_left > 1 || (gb->hdma_current_dest & 0xF) != 0xF))) { + gb->dma_current_dest++; + } + else if (gb->dma_current_src < 0xE000) { + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + } + else { + 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 */ + gb->dma_current_src++; + gb->dma_ppu_vram_conflict = false; + } + gb->in_dma_read = false; + gb->dma_cycles_modulo = cycles; + gb->dma_cycles = 0; +} + +void GB_hdma_run(GB_gameboy_t *gb) +{ + unsigned cycles = gb->cgb_double_speed? 4 : 2; + /* This is a bit cart, revision and unit specific. TODO: what if PC is in cart RAM? */ + if (gb->model < GB_MODEL_CGB_D || gb->pc > 0x8000) { + gb->hdma_open_bus = 0xFF; + } + gb->addr_for_hdma_conflict = 0xFFFF; + uint16_t vram_base = gb->cgb_vram_bank? 0x2000 : 0; + gb->hdma_in_progress = true; + GB_advance_cycles(gb, cycles); + while (gb->hdma_on) { + uint8_t byte = gb->hdma_open_bus; + gb->addr_for_hdma_conflict = 0xFFFF; + + if (gb->hdma_current_src < 0x8000 || + (gb->hdma_current_src & 0xE000) == 0xC000 || + (gb->hdma_current_src & 0xE000) == 0xA000) { + byte = GB_read_memory(gb, gb->hdma_current_src); + } + if (unlikely(GB_is_dma_active(gb)) && (gb->dma_cycles_modulo == 2 || gb->cgb_double_speed)) { + write_oam(gb, gb->hdma_current_src, byte); + } + gb->hdma_current_src++; + GB_advance_cycles(gb, cycles); + if (gb->addr_for_hdma_conflict == 0xFFFF /* || ((gb->model & ~GB_MODEL_GBP_BIT) >= GB_MODEL_AGB_B && gb->cgb_double_speed) */) { + uint16_t addr = (gb->hdma_current_dest++ & 0x1FFF); + gb->vram[vram_base + addr] = byte; + // TODO: vram_write_blocked might not be the correct timing + if (gb->vram_write_blocked /* && (gb->model & ~GB_MODEL_GBP_BIT) < GB_MODEL_AGB_B */) { + gb->vram[(vram_base ^ 0x2000) + addr] = byte; + } + } + else { + if (gb->model == GB_MODEL_CGB_E || gb->cgb_double_speed) { + /* + These corruptions revision (unit?) specific in single speed. They happen only on my CGB-E. + */ + gb->addr_for_hdma_conflict &= 0x1FFF; + // TODO: there are *some* scenarions in single speed mode where this write doesn't happen. What's the logic? + uint16_t addr = (gb->hdma_current_dest & gb->addr_for_hdma_conflict & 0x1FFF); + gb->vram[vram_base + addr] = byte; + // TODO: vram_write_blocked might not be the correct timing + if (gb->vram_write_blocked /* && (gb->model & ~GB_MODEL_GBP_BIT) < GB_MODEL_AGB_B */) { + gb->vram[(vram_base ^ 0x2000) + addr] = byte; + } + } + gb->hdma_current_dest++; + } + gb->hdma_open_bus = 0xFF; + + if ((gb->hdma_current_dest & 0xF) == 0) { + if (--gb->hdma_steps_left == 0 || gb->hdma_current_dest == 0) { + gb->hdma_on = false; + gb->hdma_on_hblank = false; + gb->io_registers[GB_IO_HDMA5] &= 0x7F; + } + else if (gb->hdma_on_hblank) { + gb->hdma_on = false; + } + } + } + gb->hdma_in_progress = false; // TODO: timing? (affects VRAM reads) + if (!gb->cgb_double_speed) { + GB_advance_cycles(gb, 2); + } +} diff --git a/thirdparty/SameBoy-old/Core/memory.h b/thirdparty/SameBoy-old/Core/memory.h new file mode 100644 index 000000000..9a5824d9b --- /dev/null +++ b/thirdparty/SameBoy-old/Core/memory.h @@ -0,0 +1,22 @@ +#ifndef memory_h +#define memory_h +#include "defs.h" +#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 +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +#ifdef GB_INTERNAL +internal void GB_dma_run(GB_gameboy_t *gb); +internal bool GB_is_dma_active(GB_gameboy_t *gb); +internal void GB_hdma_run(GB_gameboy_t *gb); +internal void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr); +#endif + +#endif /* memory_h */ diff --git a/thirdparty/SameBoy-old/Core/printer.c b/thirdparty/SameBoy-old/Core/printer.c new file mode 100644 index 000000000..1394a6a7e --- /dev/null +++ b/thirdparty/SameBoy-old/Core/printer.c @@ -0,0 +1,220 @@ +#include "gb.h" + +/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface. + Incorrect usage is not correctly emulated, as it's not well documented, nor do I + have my own GB Printer to figure it out myself. + + It also does not currently emulate communication timeout, which means that a bug + might prevent the printer operation until the GameBoy is restarted. + + Also, field mask values are assumed. */ + +static void handle_command(GB_gameboy_t *gb) +{ + switch (gb->printer.command_id) { + case GB_PRINTER_INIT_COMMAND: + gb->printer.status = 0; + gb->printer.image_offset = 0; + break; + + case GB_PRINTER_START_COMMAND: + if (gb->printer.command_length == 4) { + gb->printer.status = 6; /* Printing */ + uint32_t image[gb->printer.image_offset]; + uint8_t palette = gb->printer.command_data[2]; + uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF), + gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA), + gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55), + gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)}; + for (unsigned i = 0; i < gb->printer.image_offset; i++) { + image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; + } + + if (gb->printer_callback) { + gb->printer_callback(gb, image, gb->printer.image_offset / 160, + gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7, + gb->printer.command_data[3] & 0x7F); + } + + gb->printer.image_offset = 0; + } + break; + + case GB_PRINTER_DATA_COMMAND: + if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) { + gb->printer.image_offset %= sizeof(gb->printer.image); + gb->printer.status = 8; /* Received 0x280 bytes */ + + uint8_t *byte = gb->printer.command_data; + + for (unsigned row = 2; row--; ) { + for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) { + for (unsigned y = 0; y < 8; y++, byte += 2) { + for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) { + gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] = + ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1); + (*byte) <<= 1; + (*(byte + 1)) <<= 1; + } + } + } + + gb->printer.image_offset += 8 * 160; + } + } + + case GB_PRINTER_NOP_COMMAND: + default: + break; + } +} + + +static void byte_recieve_completed(GB_gameboy_t *gb, uint8_t byte_received) +{ + gb->printer.byte_to_send = 0; + switch (gb->printer.command_state) { + case GB_PRINTER_COMMAND_MAGIC1: + if (byte_received != 0x88) { + return; + } + gb->printer.status &= ~1; + gb->printer.command_length = 0; + gb->printer.checksum = 0; + break; + + case GB_PRINTER_COMMAND_MAGIC2: + if (byte_received != 0x33) { + if (byte_received != 0x88) { + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + } + return; + } + break; + + case GB_PRINTER_COMMAND_ID: + gb->printer.command_id = byte_received & 0xF; + break; + + case GB_PRINTER_COMMAND_COMPRESSION: + gb->printer.compression = byte_received & 1; + break; + + case GB_PRINTER_COMMAND_LENGTH_LOW: + gb->printer.length_left = byte_received; + break; + + case GB_PRINTER_COMMAND_LENGTH_HIGH: + gb->printer.length_left |= (byte_received & 3) << 8; + break; + + case GB_PRINTER_COMMAND_DATA: + if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) { + if (gb->printer.compression) { + if (!gb->printer.compression_run_lenth) { + gb->printer.compression_run_is_compressed = byte_received & 0x80; + gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed; + } + else if (gb->printer.compression_run_is_compressed) { + while (gb->printer.compression_run_lenth) { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) { + gb->printer.compression_run_lenth = 0; + } + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + } + } + gb->printer.length_left--; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_LOW: + gb->printer.checksum ^= byte_received; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_HIGH: + gb->printer.checksum ^= byte_received << 8; + if (gb->printer.checksum) { + gb->printer.status |= 1; /* Checksum error*/ + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + return; + } + gb->printer.byte_to_send = 0x81; + + break; + case GB_PRINTER_COMMAND_ACTIVE: + if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) { + /* Games expect INIT commands to return 0? */ + gb->printer.byte_to_send = 0; + } + else { + gb->printer.byte_to_send = gb->printer.status; + } + break; + case GB_PRINTER_COMMAND_STATUS: + + /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */ + if (gb->printer.status == 6) { + gb->printer.status = 4; /* Done */ + } + + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + handle_command(gb); + return; + } + + if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) { + gb->printer.checksum += byte_received; + } + + if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) { + gb->printer.command_state++; + } + + if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) { + if (gb->printer.length_left == 0) { + gb->printer.command_state++; + } + } +} + +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_recieve_completed(gb, gb->printer.byte_being_received); + gb->printer.bits_received = 0; + gb->printer.byte_being_received = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->printer.bit_to_send; + gb->printer.bit_to_send = gb->printer.byte_to_send & 0x80; + gb->printer.byte_to_send <<= 1; + return ret; +} + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback) +{ + memset(&gb->printer, 0, sizeof(gb->printer)); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); + gb->printer_callback = callback; +} diff --git a/thirdparty/SameBoy-old/Core/printer.h b/thirdparty/SameBoy-old/Core/printer.h new file mode 100644 index 000000000..f4ccfe47f --- /dev/null +++ b/thirdparty/SameBoy-old/Core/printer.h @@ -0,0 +1,63 @@ +#ifndef printer_h +#define printer_h +#include +#include +#include "defs.h" +#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280 +#define GB_PRINTER_DATA_SIZE 0x280 + +typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb, + uint32_t *image, + uint8_t height, + uint8_t top_margin, + uint8_t bottom_margin, + uint8_t exposure); + + +typedef struct +{ + /* Communication state machine */ + + enum { + GB_PRINTER_COMMAND_MAGIC1, + GB_PRINTER_COMMAND_MAGIC2, + GB_PRINTER_COMMAND_ID, + GB_PRINTER_COMMAND_COMPRESSION, + GB_PRINTER_COMMAND_LENGTH_LOW, + GB_PRINTER_COMMAND_LENGTH_HIGH, + GB_PRINTER_COMMAND_DATA, + GB_PRINTER_COMMAND_CHECKSUM_LOW, + GB_PRINTER_COMMAND_CHECKSUM_HIGH, + GB_PRINTER_COMMAND_ACTIVE, + GB_PRINTER_COMMAND_STATUS, + } command_state : 8; + enum { + GB_PRINTER_INIT_COMMAND = 1, + GB_PRINTER_START_COMMAND = 2, + GB_PRINTER_DATA_COMMAND = 4, + GB_PRINTER_NOP_COMMAND = 0xF, + } command_id : 8; + bool compression; + uint16_t length_left; + uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH]; + uint16_t command_length; + uint16_t checksum; + uint8_t status; + uint8_t byte_to_send; + + uint8_t image[160 * 200]; + uint16_t image_offset; + + uint64_t idle_time; + + uint8_t compression_run_lenth; + bool compression_run_is_compressed; + + uint8_t bits_received; + uint8_t byte_being_received; + bool bit_to_send; +} GB_printer_t; + + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback); +#endif diff --git a/thirdparty/SameBoy-old/Core/random.c b/thirdparty/SameBoy-old/Core/random.c new file mode 100644 index 000000000..cc4d4d3a6 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/random.c @@ -0,0 +1,38 @@ +#include "random.h" +#include + +static uint64_t seed; +static bool enabled = true; + +uint8_t GB_random(void) +{ + if (!enabled) return 0; + + seed *= 0x27BB2EE687B0B0FDL; + seed += 0xB504F32D; + return seed >> 56; +} + +uint32_t GB_random32(void) +{ + GB_random(); + return seed >> 32; +} + +void GB_random_seed(uint64_t new_seed) +{ + seed = new_seed; +} + +void GB_random_set_enabled(bool enable) +{ + enabled = enable; +} + +static void __attribute__((constructor)) init_seed(void) +{ + seed = time(NULL); + for (unsigned i = 64; i--;) { + GB_random(); + } +} diff --git a/thirdparty/SameBoy-old/Core/random.h b/thirdparty/SameBoy-old/Core/random.h new file mode 100644 index 000000000..8ab0e502c --- /dev/null +++ b/thirdparty/SameBoy-old/Core/random.h @@ -0,0 +1,12 @@ +#ifndef random_h +#define random_h + +#include +#include + +uint8_t GB_random(void); +uint32_t GB_random32(void); +void GB_random_seed(uint64_t seed); +void GB_random_set_enabled(bool enable); + +#endif /* random_h */ diff --git a/thirdparty/SameBoy-old/Core/rewind.c b/thirdparty/SameBoy-old/Core/rewind.c new file mode 100644 index 000000000..2be73a6b7 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/rewind.c @@ -0,0 +1,208 @@ +#include "gb.h" +#include +#include +#include +#include + +static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size) +{ + size_t malloc_size = 0x1000; + uint8_t *compressed = malloc(malloc_size); + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; + *(uint16_t *)compressed = 0; +#define COUNTER (*(uint16_t *)&compressed[counter_pos]) +#define DATA (compressed[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (*data == *prev && COUNTER != 0xFFFF) { + COUNTER++; + data++; + prev++; + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + else { + if (*data != *prev && COUNTER != 0xFFFF) { + COUNTER++; + DATA = *data; + data_pos++; + data++; + prev++; + uncompressed_size--; + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos = counter_pos + sizeof(uint16_t); + if (counter_pos >= malloc_size - 1) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + } + + return realloc(compressed, data_pos); +#undef DATA +#undef COUNTER +} + + +static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size) +{ + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; +#define COUNTER (*(uint16_t *)&data[counter_pos]) +#define DATA (data[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (COUNTER) { + COUNTER--; + *(dest++) = *(prev++); + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + } + } + else { + if (COUNTER) { + COUNTER--; + *(dest++) = DATA; + data_pos++; + prev++; + uncompressed_size--; + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos += sizeof(uint16_t); + } + } + } +#undef DATA +#undef COUNTER +} + +void GB_rewind_push(GB_gameboy_t *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); + memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); + gb->rewind_pos = 0; + } + else { + return; + } + } + + if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) { + gb->rewind_pos++; + if (gb->rewind_pos == gb->rewind_buffer_length) { + gb->rewind_pos = 0; + } + if (gb->rewind_sequences[gb->rewind_pos].key_state) { + free(gb->rewind_sequences[gb->rewind_pos].key_state); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + } + for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) { + if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) { + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0; + } + } + gb->rewind_sequences[gb->rewind_pos].pos = 0; + } + + 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_no_bess(gb, gb->rewind_sequences[gb->rewind_pos].key_state); + } + else { + uint8_t *save_state = malloc(save_size); + 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); + } + +} + +bool GB_rewind_pop(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) { + return false; + } + + 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); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1; + return true; + } + + uint8_t *save_state = malloc(save_size); + state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state, + gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos], + save_state, + save_size); + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL; + GB_load_state_from_buffer(gb, save_state, save_size); + free(save_state); + return true; +} + +void GB_rewind_free(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences) return; + for (unsigned i = 0; i < gb->rewind_buffer_length; i++) { + if (gb->rewind_sequences[i].key_state) { + free(gb->rewind_sequences[i].key_state); + } + for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) { + if (gb->rewind_sequences[i].compressed_states[j]) { + free(gb->rewind_sequences[i].compressed_states[j]); + } + } + } + free(gb->rewind_sequences); + gb->rewind_sequences = NULL; +} + +void GB_set_rewind_length(GB_gameboy_t *gb, double seconds) +{ + GB_rewind_free(gb); + if (seconds == 0) { + gb->rewind_buffer_length = 0; + } + else { + gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY); + } +} diff --git a/thirdparty/SameBoy-old/Core/rewind.h b/thirdparty/SameBoy-old/Core/rewind.h new file mode 100644 index 000000000..3cc23ed90 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/rewind.h @@ -0,0 +1,14 @@ +#ifndef rewind_h +#define rewind_h + +#include +#include "defs.h" + +#ifdef GB_INTERNAL +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); + +#endif diff --git a/thirdparty/SameBoy-old/Core/rumble.c b/thirdparty/SameBoy-old/Core/rumble.c new file mode 100644 index 000000000..5f83c479f --- /dev/null +++ b/thirdparty/SameBoy-old/Core/rumble.c @@ -0,0 +1,57 @@ +#include "rumble.h" +#include "gb.h" + +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode) +{ + gb->rumble_mode = mode; + if (gb->rumble_callback) { + gb->rumble_callback(gb, 0); + } +} + +void GB_handle_rumble(GB_gameboy_t *gb) +{ + if (gb->rumble_callback) { + if (gb->rumble_mode == GB_RUMBLE_DISABLED) { + return; + } + 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; + } + } + else if (gb->rumble_mode == GB_RUMBLE_ALL_GAMES) { + 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)); + unsigned ch4_divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 1; + if (!ch4_divisor) ch4_divisor = 1; + unsigned ch4_sample_length = (ch4_divisor << (gb->io_registers[GB_IO_NR43] >> 4)) - 1; + + double ch4_rumble = (MIN(ch4_sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0; + + ch4_rumble = MIN(ch4_rumble, 1.0); + ch4_rumble = MAX(ch4_rumble, 0.0); + + double ch1_rumble = 0; + if ((gb->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); + ch1_rumble = MAX(ch1_rumble, 0.0); + } + + if (!gb->apu.is_active[GB_NOISE]) { + ch4_rumble = 0; + } + + if (!gb->apu.is_active[GB_SQUARE_1]) { + ch1_rumble = 0; + } + + gb->rumble_callback(gb, MIN(MAX(ch1_rumble / 2 + ch4_rumble, 0.0), 1.0)); + } + } +} diff --git a/thirdparty/SameBoy-old/Core/rumble.h b/thirdparty/SameBoy-old/Core/rumble.h new file mode 100644 index 000000000..ca3473781 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/rumble.h @@ -0,0 +1,17 @@ +#ifndef rumble_h +#define rumble_h + +#include "defs.h" + +typedef enum { + GB_RUMBLE_DISABLED, + GB_RUMBLE_CARTRIDGE_ONLY, + GB_RUMBLE_ALL_GAMES +} GB_rumble_mode_t; + +#ifdef GB_INTERNAL +internal void GB_handle_rumble(GB_gameboy_t *gb); +#endif +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode); + +#endif /* rumble_h */ diff --git a/thirdparty/SameBoy-old/Core/save_state.c b/thirdparty/SameBoy-old/Core/save_state.c new file mode 100644 index 000000000..8a071b189 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/save_state.c @@ -0,0 +1,1408 @@ +#include "gb.h" +#include +#include +#include + +#ifdef GB_BIG_ENDIAN +//#define BESS_NAME "SameBoy v" GB_VERSION " (Big Endian)" +#define BESS_NAME "SameBoy v0.15.5 (Big Endian)" +#else +//#define BESS_NAME "SameBoy v" GB_VERSION +#define BESS_NAME "SameBoy v0.15.5" +#endif + +_Static_assert((GB_SECTION_OFFSET(core_state) & 7) == 0, "Section core_state is not aligned"); +_Static_assert((GB_SECTION_OFFSET(dma) & 7) == 0, "Section dma is not aligned"); +_Static_assert((GB_SECTION_OFFSET(mbc) & 7) == 0, "Section mbc is not aligned"); +_Static_assert((GB_SECTION_OFFSET(hram) & 7) == 0, "Section hram is not aligned"); +_Static_assert((GB_SECTION_OFFSET(timing) & 7) == 0, "Section timing is not aligned"); +_Static_assert((GB_SECTION_OFFSET(apu) & 7) == 0, "Section apu is not aligned"); +_Static_assert((GB_SECTION_OFFSET(rtc) & 7) == 0, "Section rtc is not aligned"); +_Static_assert((GB_SECTION_OFFSET(video) & 7) == 0, "Section video is not aligned"); + +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 _padding; + + uint8_t io_registers[0x80]; + + 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 object_palettes; +} BESS_CORE_t; + +typedef struct __attribute__((packed)) { + BESS_block_t header; + uint8_t extra_oam[96]; +} BESS_XOAM_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; + +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; + 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; + +/* 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)) { + BESS_block_t header; + + // Flags + bool latch_ready:1; + bool eeprom_do:1; + bool eeprom_di:1; + bool eeprom_clk:1; + bool eeprom_cs:1; + bool eeprom_write_enabled:1; + uint8_t padding:2; + + uint8_t argument_bits_left; + + uint16_t eeprom_command; + uint16_t read_bits; + + uint16_t x_latch; + uint16_t y_latch; + +} BESS_MBC7_t; + +typedef struct __attribute__((packed)){ + BESS_block_t header; + uint64_t last_rtc_second; + 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; +} BESS_MBC_pair_t; + +typedef struct virtual_file_s virtual_file_t; +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 { + 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); +} + +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 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; + length = file->size - file->position; + } + + memcpy(dest, file->buffer + file->position, length); + file->position += length; + + return length; +} + +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; +} + +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) + 3 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0); + case GB_MBC5: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + case GB_CAMERA: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + case GB_MBC7: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_MBC7_t); + case GB_MMM01: + return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t); + case GB_HUC1: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + case GB_HUC3: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); + case GB_TPP1: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_TPP1_t); + } +} + +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) + + GB_SECTION_SIZE(dma ) + sizeof(uint32_t) + + GB_SECTION_SIZE(mbc ) + sizeof(uint32_t) + + GB_SECTION_SIZE(hram ) + sizeof(uint32_t) + + GB_SECTION_SIZE(timing ) + sizeof(uint32_t) + + GB_SECTION_SIZE(apu ) + sizeof(uint32_t) + + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + + GB_SECTION_SIZE(video ) + sizeof(uint32_t) + + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + + gb->mbc_ram_size + + gb->ram_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_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/TPP1/MBC7 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, bool *attempt_bess) +{ + *attempt_bess = false; + + if (gb->version != save->version) { + GB_log(gb, "The save state is for a different version of SameBoy.\n"); + *attempt_bess = true; + 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; + } + + if (gb->vram_size != save->vram_size) { + GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n"); + return false; + } + + if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) { + GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not "); + return false; + } + + if (gb->ram_size != save->ram_size) { + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); + return false; + } + + 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_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; + case GB_MODEL_CGB_E: return true; + case GB_MODEL_AGB_A: return true; + case GB_MODEL_GBP_A: 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) +{ + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->oam_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->last_tile_index_address &= 0x1FFF; + gb->window_tile_x &= 0x1F; + + /* These are kind of DOS-ish if too large */ + if (abs(gb->display_cycles) > 0x80000) { + 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; + } + + 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; + } + GB_update_clock_rate(gb); +} + +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; +} + +#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 = {BE32('MBC '), 0}; + BESS_MBC_pair_t pairs[8]; + switch (gb->cartridge_type->mbc_type) { + default: + case GB_NO_MBC: return 0; + case GB_MBC1: + 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){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){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)}; + 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}; + 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_CAMERA: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc5.rom_bank_low}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + case GB_MBC7: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc7.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc7.secondary_ram_enable? 0x40 : 0}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + case GB_MMM01: + pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2) | (gb->mmm01.rom_bank_high << 4) | (gb->mmm01.mbc1_mode_disable << 6)}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x0000), (gb->mbc_ram_enable? 0xA : 0x0) | (gb->mmm01.ram_bank_mask << 4) | (gb->mmm01.locked << 6)}; + /* For compatibility with emulators that inaccurately emulate MMM01, and also require two writes per register */ + pairs[4] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & ~(gb->mmm01.rom_bank_mask << 1))}; + pairs[5] = pairs[1]; + pairs[6] = pairs[2]; + pairs[7] = pairs[3]; + mbc_block.size = 8 * sizeof(pairs[0]); + break; + case GB_HUC1: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc1.ir_mode? 0xE : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc1.bank_low}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc1.bank_high}; + mbc_block.size = 3 * sizeof(pairs[0]); + case GB_HUC3: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc3.mode}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc3.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc3.ram_bank}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + case GB_TPP1: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->tpp1.rom_bank}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x0001), gb->tpp1.rom_bank >> 8}; + 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); + + if (file->write(file, &mbc_block, sizeof(mbc_block)) != sizeof(mbc_block)) { + return errno; + } + + if (file->write(file, &pairs, LE32(mbc_block.size)) != LE32(mbc_block.size)) { + return errno; + } + + return 0; +} + +static const uint8_t *get_header_bank(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type == GB_MMM01) { + return gb->rom + gb->rom_size - 0x8000; + } + return gb->rom; +} + +static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess) +{ + if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; + 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; + + if (GB_is_hle_sgb(gb)) { + sgb_offset = file->tell(file) + 4; + 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)); + 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 = 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 = 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; + } + + if (!append_bess) return 0; + + BESS_footer_t bess_footer = { + .start_offset = LE32(file->tell(file)), + .magic = BE32('BESS'), + }; + + /* BESS NAME */ + + 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; + } + + if (file->write(file, BESS_NAME, sizeof(BESS_NAME) - 1) != sizeof(BESS_NAME) - 1) { + 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; + } + + const uint8_t *bank = get_header_bank(gb); + + if (file->write(file, bank + 0x134, 0x10) != 0x10) { + goto error; + } + + if (file->write(file, bank + 0x14E, 2) != 2) { + goto error; + } + + /* BESS CORE */ + + 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 = 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: + bess_core.full_model = BE32('SN '); break; + + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB_PAL: + bess_core.full_model = BE32('SP '); break; + + case GB_MODEL_SGB2_NO_SFC: + 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_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; + case GB_MODEL_CGB_E: bess_core.full_model = BE32('CCE '); break; + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + bess_core.full_model = BE32('CAA '); break; + } + + 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; + 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; + 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.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)) { + goto error; + } + + /* BESS XOAM */ + + 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_xoam, sizeof(bess_xoam)) != sizeof(bess_xoam)) { + goto error; + } + + save_bess_mbc_block(gb, file); + if (gb->cartridge_type->has_rtc) { + 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; + 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; + } + } + } + + if (gb->cartridge_type ->mbc_type == GB_MBC7) { + BESS_MBC7_t bess_mbc7 = { + .latch_ready = gb->mbc7.latch_ready, + .eeprom_do = gb->mbc7.eeprom_do, + .eeprom_di = gb->mbc7.eeprom_di, + .eeprom_clk = gb->mbc7.eeprom_clk, + .eeprom_cs = gb->mbc7.eeprom_cs, + .eeprom_write_enabled = gb->mbc7.eeprom_write_enabled, + + .argument_bits_left = gb->mbc7.argument_bits_left, + + .eeprom_command = LE16(gb->mbc7.eeprom_command), + .read_bits = LE16(gb->mbc7.read_bits), + + .x_latch = LE16(gb->mbc7.x_latch), + .y_latch = LE16(gb->mbc7.y_latch), + }; + bess_mbc7.header = (BESS_block_t){BE32('MBC7'), LE32(sizeof(bess_mbc7) - sizeof(bess_mbc7.header))}; + + if (file->write(file, &bess_mbc7, sizeof(bess_mbc7)) != sizeof(bess_mbc7)) { + goto error; + } + } + + bool needs_sgb_padding = false; + if (gb->sgb) { + /* BESS SGB */ + 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; + if (file->write(file, &bess_sgb, sizeof(bess_sgb)) != sizeof(bess_sgb)) { + goto error; + } + } + } + + /* BESS END */ + + 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)) { + 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, + .tell = file_tell, + .file = f, + }; + int ret = save_state_internal(gb, &file, true); + 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, + .tell = buffer_tell, + .buffer = (uint8_t *)buffer, + .position = 0, + }; + + 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) +{ + 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 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, 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) +{ + 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 != 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"); + } + return -1; + } + + GB_gameboy_t save; + GB_init(&save, gb->model); + save.cartridge_type = gb->cartridge_type; + + 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 BE32('CORE'): + if (found_core) goto parse_error; + found_core = true; + 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, LE32(block.size) - (sizeof(core) - sizeof(block)), SEEK_CUR); + } + else { + if (file->read(file, &core.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + } + + if (core.major != LE16(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 = 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; + + 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 + 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]); + + // Interrupts + GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); + + /* Required to be compatible with both SameBoy 0.14.x AND BGB */ + if (GB_is_cgb(&save) && !save.cgb_mode && save.cgb_ram_bank == 7) { + save.cgb_ram_bank = 1; + } + + break; + case BE32('NAME'): + if (LE32(block.size) > sizeof(emulator_name) - 1) { + file->seek(file, LE32(block.size), SEEK_CUR); + } + else { + 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; + const uint8_t *bank = get_header_bank(gb); + if (memcmp(bess_info.title, bank + 0x134, sizeof(bess_info.title))) { + char ascii_title[0x11] = {0,}; + for (unsigned i = 0; i < 0x10; i++) { + if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break; + 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, bank + 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; + file->read(file, save.extra_oam, sizeof(save.extra_oam)); + break; + 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; + /* Inject some default writes, as some emulators omit them */ + if (gb->cartridge_type->mbc_type == GB_MMM01) { + GB_write_memory(&save, 0x6000, 0x30); + GB_write_memory(&save, 0x4000, 0x70); + } + for (unsigned i = LE32(block.size); i > 0; i -= 3) { + BESS_MBC_pair_t pair; + file->read(file, &pair, sizeof(pair)); + 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; + case BE32('RTC '): + if (!found_core) goto parse_error; + 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_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) 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('MBC7'): + if (!found_core) goto parse_error; + BESS_MBC7_t bess_mbc7; + if (LE32(block.size) != sizeof(bess_mbc7) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_mbc7.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type != GB_MBC7) break; + + save.mbc7.latch_ready = bess_mbc7.latch_ready; + save.mbc7.eeprom_do = bess_mbc7.eeprom_do; + save.mbc7.eeprom_di = bess_mbc7.eeprom_di; + save.mbc7.eeprom_clk = bess_mbc7.eeprom_clk; + save.mbc7.eeprom_cs = bess_mbc7.eeprom_cs; + save.mbc7.eeprom_write_enabled = bess_mbc7.eeprom_write_enabled; + + save.mbc7.argument_bits_left = bess_mbc7.argument_bits_left; + + save.mbc7.eeprom_command = LE16(bess_mbc7.eeprom_command); + save.mbc7.read_bits = LE16(bess_mbc7.read_bits); + + save.mbc7.x_latch = LE16(bess_mbc7.x_latch); + save.mbc7.y_latch = LE16(bess_mbc7.y_latch); + + break; + case BE32('SGB '): + if (!found_core) goto parse_error; + if (!gb->sgb) goto parse_error; + 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 '): + if (!found_core) goto parse_error; + if (LE32(block.size) != 0) goto parse_error; + goto done; + default: + file->seek(file, LE32(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); + 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.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); + 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->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) { + 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); + } + 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); + sanitize_state(gb); + return errno; +} + +static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) +{ + GB_gameboy_t save; + + /* Every unread value should be kept the same. */ + memcpy(&save, gb, sizeof(save)); + /* ...Except ram size, we use it to detect old saves with incorrect ram sizes */ + save.ram_size = 0; + + bool fix_broken_windows_saves = false; + + 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*/ + + 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) { + 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; + 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 + + + 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; + } + + if (GB_is_hle_sgb(gb)) { + 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 ?: EIO; + } + + if (file->read(file, gb->ram, gb->ram_size) != gb->ram_size) { + 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 ?: EIO; + } + + size_t orig_ram_size = gb->ram_size; + memcpy(gb, &save, sizeof(save)); + gb->ram_size = orig_ram_size; + + sanitize_state(gb); + + return 0; +} + +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, + .tell = file_tell, + .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, + .tell = buffer_tell, + .buffer = (uint8_t *)buffer, + .position = 0, + .size = length, + }; + + return load_state_internal(gb, &file); +} + + +bool GB_is_save_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/thirdparty/SameBoy-old/Core/save_state.h b/thirdparty/SameBoy-old/Core/save_state.h new file mode 100644 index 000000000..acb6b89bd --- /dev/null +++ b/thirdparty/SameBoy-old/Core/save_state.h @@ -0,0 +1,48 @@ +/* Macros to make the GB_gameboy_t struct more future compatible when state saving */ +#ifndef save_state_h +#define save_state_h +#include + +#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 */ +#if __clang__ +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__ +#else +// GCC's handling of attributes is so awfully bad, that it is alone a good enough reason to never use that compiler +#define GB_SECTION(name, ...) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wpedantic\"") alignas(8) char _align_##name[0]; __VA_ARGS__ _Pragma("GCC diagnostic pop") +#endif +#else +#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]; +#define GB_SECTION_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 + + +/* Public calls related to save states */ +int GB_save_state(GB_gameboy_t *gb, const char *path); +size_t GB_get_save_state_size(GB_gameboy_t *gb); +/* Assumes buffer is big enough to contain the save state. Use with GB_get_save_state_size(). */ +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_save_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 */ +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/thirdparty/SameBoy-old/Core/sgb.c b/thirdparty/SameBoy-old/Core/sgb.c new file mode 100644 index 000000000..891a27dd9 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/sgb.c @@ -0,0 +1,910 @@ +#include "gb.h" +#include "random.h" +#include +#include + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +enum { + PAL01 = 0x00, + PAL23 = 0x01, + PAL03 = 0x02, + PAL12 = 0x03, + ATTR_BLK = 0x04, + ATTR_LIN = 0x05, + ATTR_DIV = 0x06, + ATTR_CHR = 0x07, + PAL_SET = 0x0A, + PAL_TRN = 0x0B, + DATA_SND = 0x0F, + MLT_REQ = 0x11, + CHR_TRN = 0x13, + PCT_TRN = 0x14, + ATTR_TRN = 0x15, + ATTR_SET = 0x16, + MASK_EN = 0x17, +}; + +typedef enum { + MASK_DISABLED, + MASK_FREEZE, + MASK_BLACK, + MASK_COLOR_0, +} mask_mode_t; + +typedef enum { + TRANSFER_LOW_TILES, + TRANSFER_HIGH_TILES, + TRANSFER_BORDER_DATA, + TRANSFER_PALETTES, + TRANSFER_ATTRIBUTES, +} transfer_dest_t; + +#define SGB_PACKET_SIZE 16 +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] = + *(uint16_t *)&gb->sgb->command[1]; + + for (unsigned i = 0; i < 3; i++) { + 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] = *(uint16_t *)&gb->sgb->command[9 + i * 2]; + } +} + +static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index) +{ + if (file_index > 0x2C) return; + uint8_t *output = gb->sgb->attribute_map; + for (unsigned i = 0; i < 90; i++) { + uint8_t byte = gb->sgb->attribute_files[file_index * 90 + i]; + for (unsigned j = 4; j--;) { + *(output++) = byte >> 6; + byte <<= 2; + } + } +} + +static const uint16_t built_in_palettes[] = +{ + 0x67BF, 0x265B, 0x10B5, 0x2866, + 0x637B, 0x3AD9, 0x0956, 0x0000, + 0x7F1F, 0x2A7D, 0x30F3, 0x4CE7, + 0x57FF, 0x2618, 0x001F, 0x006A, + 0x5B7F, 0x3F0F, 0x222D, 0x10EB, + 0x7FBB, 0x2A3C, 0x0015, 0x0900, + 0x2800, 0x7680, 0x01EF, 0x2FFF, + 0x73BF, 0x46FF, 0x0110, 0x0066, + 0x533E, 0x2638, 0x01E5, 0x0000, + 0x7FFF, 0x2BBF, 0x00DF, 0x2C0A, + 0x7F1F, 0x463D, 0x74CF, 0x4CA5, + 0x53FF, 0x03E0, 0x00DF, 0x2800, + 0x433F, 0x72D2, 0x3045, 0x0822, + 0x7FFA, 0x2A5F, 0x0014, 0x0003, + 0x1EED, 0x215C, 0x42FC, 0x0060, + 0x7FFF, 0x5EF7, 0x39CE, 0x0000, + 0x4F5F, 0x630E, 0x159F, 0x3126, + 0x637B, 0x121C, 0x0140, 0x0840, + 0x66BC, 0x3FFF, 0x7EE0, 0x2C84, + 0x5FFE, 0x3EBC, 0x0321, 0x0000, + 0x63FF, 0x36DC, 0x11F6, 0x392A, + 0x65EF, 0x7DBF, 0x035F, 0x2108, + 0x2B6C, 0x7FFF, 0x1CD9, 0x0007, + 0x53FC, 0x1F2F, 0x0E29, 0x0061, + 0x36BE, 0x7EAF, 0x681A, 0x3C00, + 0x7BBE, 0x329D, 0x1DE8, 0x0423, + 0x739F, 0x6A9B, 0x7293, 0x0001, + 0x5FFF, 0x6732, 0x3DA9, 0x2481, + 0x577F, 0x3EBC, 0x456F, 0x1880, + 0x6B57, 0x6E1B, 0x5010, 0x0007, + 0x0F96, 0x2C97, 0x0045, 0x3200, + 0x67FF, 0x2F17, 0x2230, 0x1548, +}; + +static const struct { + char name[16]; + unsigned palette_index; +} palette_assignments[] = +{ + {"ZELDA", 5}, + {"SUPER MARIOLAND", 6}, + {"MARIOLAND2", 0x14}, + {"SUPERMARIOLAND3", 2}, + {"KIRBY DREAM LAND", 0xB}, + {"HOSHINOKA-BI", 0xB}, + {"KIRBY'S PINBALL", 3}, + {"YOSSY NO TAMAGO", 0xC}, + {"MARIO & YOSHI", 0xC}, + {"YOSSY NO COOKIE", 4}, + {"YOSHI'S COOKIE", 4}, + {"DR.MARIO", 0x12}, + {"TETRIS", 0x11}, + {"YAKUMAN", 0x13}, + {"METROID2", 0x1F}, + {"KAERUNOTAMENI", 9}, + {"GOLF", 0x18}, + {"ALLEY WAY", 0x16}, + {"BASEBALL", 0xF}, + {"TENNIS", 0x17}, + {"F1RACE", 0x1E}, + {"KID ICARUS", 0xE}, + {"QIX", 0x19}, + {"SOLARSTRIKER", 7}, + {"X", 0x1C}, + {"GBWARS", 0x15}, +}; + +static void command_ready(GB_gameboy_t *gb) +{ + /* SGB header commands are used to send the contents of the header to the SNES CPU. + A header command looks like this: + Command ID: 0b1111xxx1, where xxx is the packet index. (e.g. F1 for [0x104, 0x112), F3 for [0x112, 0x120)) + Checksum: Simple one byte sum for the following content bytes + 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ + + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { + if (gb->boot_rom_finished) return; + + uint8_t checksum = 0; + for (unsigned i = 2; i < 0x10; i++) { + checksum += gb->sgb->command[i]; + } + if (checksum != gb->sgb->command[1]) { + GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n"); + gb->sgb->disable_commands = true; + return; + } + unsigned index = (gb->sgb->command[0] >> 1) & 7; + if (index > 5) { + return; + } + memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14); + if (gb->sgb->command[0] == 0xFB) { + if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) { + gb->sgb->disable_commands = true; + for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) { + if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) { + 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; + } + } + } + } + return; + } + + /* Ignore malformed commands (0 length)*/ + if ((gb->sgb->command[0] & 7) == 0) return; + + switch (gb->sgb->command[0] >> 3) { + case PAL01: + pal_command(gb, 0, 1); + break; + case PAL23: + pal_command(gb, 2, 3); + break; + case PAL03: + pal_command(gb, 0, 3); + break; + case PAL12: + pal_command(gb, 1, 2); + break; + case ATTR_BLK: { + struct { + uint8_t count; + struct { + uint8_t control; + uint8_t palettes; + uint8_t left, top, right, bottom; + } data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > 0x12) return; + + for (unsigned i = 0; i < command->count; i++) { + bool inside = command->data[i].control & 1; + bool middle = command->data[i].control & 2; + bool outside = command->data[i].control & 4; + uint8_t inside_palette = command->data[i].palettes & 0x3; + uint8_t middle_palette = (command->data[i].palettes >> 2) & 0x3; + uint8_t outside_palette = (command->data[i].palettes >> 4) & 0x3; + + if (inside && !middle && !outside) { + middle = true; + middle_palette = inside_palette; + } + else if (outside && !middle && !inside) { + middle = true; + middle_palette = outside_palette; + } + + command->data[i].left &= 0x1F; + command->data[i].top &= 0x1F; + command->data[i].right &= 0x1F; + command->data[i].bottom &= 0x1F; + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if (x < command->data[i].left || x > command->data[i].right || + y < command->data[i].top || y > command->data[i].bottom) { + if (outside) { + gb->sgb->attribute_map[x + 20 * y] = outside_palette; + } + } + else if (x > command->data[i].left && x < command->data[i].right && + y > command->data[i].top && y < command->data[i].bottom) { + if (inside) { + gb->sgb->attribute_map[x + 20 * y] = inside_palette; + } + } + else if (middle) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + } + } + } + break; + } + case ATTR_CHR: { + struct __attribute__((packed)) { + uint8_t x, y; + uint16_t length; + uint8_t direction; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + + uint16_t count = command->length; + count = LE16(count); + uint8_t x = command->x; + uint8_t y = command->y; + if (x >= 20 || y >= 18) { + /* TODO: Verify with the SFC BIOS */ + break; + } + + for (unsigned i = 0; i < count; i++) { + uint8_t palette = (command->data[i / 4] >> (((~i) & 3) << 1)) & 3; + gb->sgb->attribute_map[x + 20 * y] = palette; + if (command->direction) { + y++; + if (y == 18) { + x++; + y = 0; + if (x == 20) { + break; + } + } + } + else { + x++; + if (x == 20) { + y++; + x = 0; + if (y == 18) { + break; + } + } + } + } + + break; + } + case ATTR_LIN: { + struct { + uint8_t count; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > sizeof(gb->sgb->command) - 2) return; + + for (unsigned i = 0; i < command->count; i++) { + bool horizontal = command->data[i] & 0x80; + uint8_t palette = (command->data[i] >> 5) & 0x3; + uint8_t line = (command->data[i]) & 0x1F; + + if (horizontal) { + if (line > 18) continue; + for (unsigned x = 0; x < 20; x++) { + gb->sgb->attribute_map[x + 20 * line] = palette; + } + } + else { + if (line > 20) continue; + for (unsigned y = 0; y < 18; y++) { + gb->sgb->attribute_map[line + 20 * y] = palette; + } + } + } + break; + } + case ATTR_DIV: { + uint8_t high_palette = gb->sgb->command[1] & 3; + uint8_t low_palette = (gb->sgb->command[1] >> 2) & 3; + uint8_t middle_palette = (gb->sgb->command[1] >> 4) & 3; + bool horizontal = gb->sgb->command[1] & 0x40; + uint8_t line = gb->sgb->command[2] & 0x1F; + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if ((horizontal? y : x) < line) { + gb->sgb->attribute_map[x + 20 * y] = low_palette; + } + else if ((horizontal? y : x) == line) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + else { + gb->sgb->attribute_map[x + 20 * y] = high_palette; + } + } + } + + break; + } + case PAL_SET: + memcpy(&gb->sgb->effective_palettes[0], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[4], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[3] + (gb->sgb->command[4] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[8], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[5] + (gb->sgb->command[6] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[12], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)], + 8); + + gb->sgb->effective_palettes[12] = gb->sgb->effective_palettes[8] = + gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[0]; + + if (gb->sgb->command[9] & 0x80) { + load_attribute_file(gb, gb->sgb->command[9] & 0x3F); + } + + if (gb->sgb->command[9] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case PAL_TRN: + gb->sgb->vram_transfer_countdown = 3; + gb->sgb->transfer_dest = TRANSFER_PALETTES; + break; + case DATA_SND: + // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this + break; + case MLT_REQ: + gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility, + fix this to be 0 based. */ + if (gb->sgb->player_count == 3) { + gb->sgb->player_count++; + } + gb->sgb->current_player &= (gb->sgb->player_count - 1); + break; + case CHR_TRN: + gb->sgb->vram_transfer_countdown = 3; + gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES; + break; + case PCT_TRN: + gb->sgb->vram_transfer_countdown = 3; + gb->sgb->transfer_dest = TRANSFER_BORDER_DATA; + break; + case ATTR_TRN: + gb->sgb->vram_transfer_countdown = 3; + gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES; + break; + case ATTR_SET: + load_attribute_file(gb, gb->sgb->command[1] & 0x3F); + + if (gb->sgb->command[1] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case MASK_EN: + gb->sgb->mask_mode = gb->sgb->command[1] & 3; + break; + default: + if ((gb->sgb->command[0] >> 3) == 8 && + (gb->sgb->command[1] & ~0x80) == 0 && + (gb->sgb->command[2] & ~0x80) == 0) { + /* Mute/dummy sound commands, ignore this command as it's used by many games at startup */ + break; + } + GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); + for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { + GB_log(gb, "%02x ", gb->sgb->command[i]); + } + GB_log(gb, "\n"); + } +} + +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) +{ + if (!GB_is_sgb(gb)) return; + if (!GB_is_hle_sgb(gb)) { + /* Notify via callback */ + return; + } + if (gb->sgb->disable_commands) return; + + uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { + command_size = SGB_PACKET_SIZE * 8; + } + + if ((value & 0x20) != 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) == 0) { + if ((gb->sgb->player_count & 1) == 0) { + gb->sgb->current_player++; + gb->sgb->current_player &= (gb->sgb->player_count - 1); + } + } + + switch ((value >> 4) & 3) { + case 3: + gb->sgb->ready_for_pulse = true; + break; + + case 2: // Zero + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + if (gb->sgb->command_write_index == command_size) { + command_ready(gb); + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + } + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->ready_for_stop = false; + } + else { + if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) { + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } + } + } + break; + case 1: // One + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + GB_log(gb, "Corrupt SGB command.\n"); + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + } + else { + if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) { + gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } + } + } + break; + + case 0: + if (!gb->sgb->ready_for_pulse) return; + gb->sgb->ready_for_write = true; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) != 0 || + gb->sgb->command_write_index == 0 || + gb->sgb->ready_for_stop) { + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + gb->sgb->ready_for_stop = false; + } + break; + + default: + break; + } +} + +static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) +{ + return GB_convert_rgb15(gb, color, false); +} + +static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade) +{ + uint8_t r = ((color) & 0x1F) - fade; + uint8_t g = ((color >> 5) & 0x1F) - fade; + uint8_t b = ((color >> 10) & 0x1F) - fade; + + if (r >= 0x20) r = 0; + if (g >= 0x20) g = 0; + if (b >= 0x20) b = 0; + + color = r | (g << 5) | (b << 10); + + return GB_convert_rgb15(gb, color, false); +} + +static void render_boot_animation (GB_gameboy_t *gb) +{ +#include "graphics/sgb_animation_logo.inc" + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } + uint8_t *input = animation_logo; + unsigned fade_blue = 0; + unsigned fade_red = 0; + if (gb->sgb->intro_animation < 80 - 32) { + fade_blue = 32; + } + else if (gb->sgb->intro_animation < 80) { + fade_blue = 80 - gb->sgb->intro_animation; + } + 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), + convert_rgb15_with_fade(gb, 0x14A5, fade_blue), + convert_rgb15_with_fade(gb, 0x54E0, fade_blue), + convert_rgb15_with_fade(gb, 0x0019, fade_red), + convert_rgb15(gb, 0x0011), + convert_rgb15(gb, 0x0009), + }; + unsigned y_min = (144 - animation_logo_height) / 2; + unsigned y_max = y_min + animation_logo_height; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + if (y < y_min || y >= y_max) { + *(output++) = colors[0]; + } + else { + uint8_t color = *input; + if (color >= 3) { + if (color == gb->sgb->intro_animation / 2 - 3) { + color = 5; + } + else if (color == gb->sgb->intro_animation / 2 - 4) { + color = 4; + } + else if (color < gb->sgb->intro_animation / 2 - 4) { + color = 3; + } + else { + color = 0; + } + } + *(output++) = colors[color]; + input++; + } + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } +} + +static void render_jingle(GB_gameboy_t *gb, size_t count); +void GB_sgb_render(GB_gameboy_t *gb) +{ + if (gb->apu_output.sample_rate) { + render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb)); + } + + 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) { + 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? + } + + 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; + } + *data = LE16(*data); + data++; + } + } + if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { + gb->sgb->border_animation = 105; // Measured on an SGB2, but might be off by ±2 + } + } + } + + 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++) { + colors[i] = convert_rgb15(gb, LE16(gb->sgb->effective_palettes[i])); + } + + if (gb->sgb->mask_mode != MASK_FREEZE) { + memcpy(gb->sgb->effective_screen_buffer, + gb->sgb->screen_buffer, + sizeof(gb->sgb->effective_screen_buffer)); + } + + if (gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { + render_boot_animation(gb); + } + else { + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } + uint8_t *input = gb->sgb->effective_screen_buffer; + switch ((mask_mode_t) gb->sgb->mask_mode) { + case MASK_DISABLED: + case MASK_FREEZE: { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; + *(output++) = colors[(*(input++) & 3) + palette * 4]; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + case MASK_BLACK: + { + uint32_t black = convert_rgb15(gb, 0); + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = black; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + case MASK_COLOR_0: + { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = colors[0]; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + } + } + + uint32_t border_colors[16 * 4]; + if (gb->sgb->border_animation == 0 || gb->sgb->border_animation > 64 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { + if (gb->sgb->border_animation != 0) { + gb->sgb->border_animation--; + } + for (unsigned i = 0; i < 16 * 4; 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, 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, LE16(gb->sgb->border.palette[i]), gb->sgb->border_animation); + } + } + + + if (gb->sgb->border_animation == 32) { + memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border)); + } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + bool gb_area = false; + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + gb_area = true; + } + else if (gb->border_mode == GB_BORDER_NEVER) { + continue; + } + uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]); + if (tile & 0x300) continue; // Unused tile + 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 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; + } + else { + output += tile_x * 8 + x + (tile_y * 8 + y) * 256; + } + if (color == 0) { + if (gb_area) continue; + *output = colors[0]; + } + else { + *output = border_colors[color + palette * 16]; + } + } + } + } + } +} + +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 + memcpy(gb->sgb->border.tiles, tiles, sizeof(tiles)); + + if (gb->model != GB_MODEL_SGB2) { + /* Delete the "2" */ + gb->sgb->border.map[25 * 32 + 25] = gb->sgb->border.map[25 * 32 + 26] = + gb->sgb->border.map[26 * 32 + 25] = gb->sgb->border.map[26 * 32 + 26] = + gb->sgb->border.map[27 * 32 + 25] = gb->sgb->border.map[27 * 32 + 26] = + gb->sgb->border.map[0]; + + /* 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] = 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) +{ + return (sin(phase * M_PI * 2) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 2)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 3)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 4))) / 4; +} + +static double fm_sweep(double phase) +{ + double ret = 0; + for (unsigned i = 0; i < 8; i++) { + ret += sin((phase * M_PI * 2 + sin(phase * M_PI * 8) / 4) * pow(1.25, i)) * (8 - i) / 36; + } + return ret; +} +static double random_double(void) +{ + return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000; +} + +static void render_jingle(GB_gameboy_t *gb, size_t count) +{ + const double frequencies[7] = { + 466.16, // Bb4 + 587.33, // D5 + 698.46, // F5 + 830.61, // Ab5 + 1046.50, // C6 + 1244.51, // Eb6 + 1567.98, // G6 + }; + + assert(gb->apu_output.sample_callback); + + if (gb->sgb->intro_animation < 0) { + GB_sample_t sample = {0, 0}; + for (unsigned i = 0; i < count; i++) { + gb->apu_output.sample_callback(gb, &sample); + } + 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; + double sweep_phase_shift = 1000.0 * pow(2, gb->sgb->intro_animation / 40.0) / gb->apu_output.sample_rate; + if (sweep_cutoff_ratio > 1) { + sweep_cutoff_ratio = 1; + } + + GB_sample_t stereo; + for (unsigned i = 0; i < count; i++) { + double sample = 0; + for (signed f = 0; f < 7 && f < jingle_stage; f++) { + sample += fm_synth(gb->sgb_intro_jingle_phases[f]) * + (0.75 * pow(0.5, jingle_stage - f) + 0.25) / 5.0; + gb->sgb_intro_jingle_phases[f] += frequencies[f] / gb->apu_output.sample_rate; + } + if (gb->sgb->intro_animation > 100) { + 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) { + double next = fm_sweep(gb->sgb_intro_sweep_phase) * 0.3 + random_double() * 0.7; + gb->sgb_intro_sweep_phase += sweep_phase_shift; + + gb->sgb_intro_sweep_previous_sample = next * (sweep_cutoff_ratio) + + gb->sgb_intro_sweep_previous_sample * (1 - sweep_cutoff_ratio); + sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8; + } + + stereo.left = stereo.right = sample * 0x7000; + gb->apu_output.sample_callback(gb, &stereo); + } + + return; +} + diff --git a/thirdparty/SameBoy-old/Core/sgb.h b/thirdparty/SameBoy-old/Core/sgb.h new file mode 100644 index 000000000..3069c368e --- /dev/null +++ b/thirdparty/SameBoy-old/Core/sgb.h @@ -0,0 +1,71 @@ +#ifndef sgb_h +#define sgb_h +#include "defs.h" +#include +#include + +typedef struct GB_sgb_s GB_sgb_t; +typedef struct { + uint8_t tiles[0x100 * 8 * 4]; +#ifdef GB_INTERNAL + union { + struct { + uint16_t map[32 * 32]; + uint16_t palette[16 * 4]; + }; + uint16_t raw_data[0x440]; + }; +#else + uint16_t raw_data[0x440]; +#endif +} 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; + bool ready_for_pulse; + bool ready_for_write; + bool ready_for_stop; + bool disable_commands; + + /* Screen buffer */ + uint8_t screen_buffer[160 * 144]; // Live image from the Game Boy + uint8_t effective_screen_buffer[160 * 144]; // Image actually rendered to the screen + + /* Multiplayer Input */ + uint8_t player_count, current_player; + + /* Mask */ + uint8_t mask_mode; + + /* Data Transfer */ + uint8_t vram_transfer_countdown, transfer_dest; + + /* Border */ + GB_sgb_border_t border, pending_border; + uint8_t border_animation; + + /* Colorization */ + uint16_t effective_palettes[4 * 4]; + uint16_t ram_palettes[4 * 512]; + uint8_t attribute_map[20 * 18]; + uint8_t attribute_files[0xFD2]; + uint8_t attribute_files_padding[0xFE0 - 0xFD2]; + + /* Intro */ + int16_t intro_animation; + + /* GB Header */ + uint8_t received_header[0x54]; +}; + +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 + +#endif diff --git a/thirdparty/SameBoy-old/Core/sm83_cpu.c b/thirdparty/SameBoy-old/Core/sm83_cpu.c new file mode 100644 index 000000000..a56fbdeda --- /dev/null +++ b/thirdparty/SameBoy-old/Core/sm83_cpu.c @@ -0,0 +1,1717 @@ +#include +#include +#include +#include "gb.h" + + +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 */ + GB_CONFLICT_READ_OLD, + /* If the CPU writes while another component reads, it reads the new value */ + GB_CONFLICT_READ_NEW, + /* If the CPU and another component write at the same time, the CPU's value "wins" */ + GB_CONFLICT_WRITE_CPU, + /* Register specific values */ + GB_CONFLICT_STAT_CGB, + GB_CONFLICT_STAT_DMG, + GB_CONFLICT_PALETTE_DMG, + GB_CONFLICT_PALETTE_CGB, + GB_CONFLICT_DMG_LCDC, + GB_CONFLICT_SGB_LCDC, + GB_CONFLICT_WX, + GB_CONFLICT_CGB_LCDC, + GB_CONFLICT_NR10, + GB_CONFLICT_CGB_SCX, +} conflict_t; + +/* Todo: How does double speed mode affect these? */ +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, + [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, + [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, + [GB_IO_SCX] = GB_CONFLICT_CGB_SCX, +}; + +/* Todo: verify on an MGB */ +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, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, + + [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, + [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, +}; + +/* Todo: Verify on an SGB1 */ +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, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, + + [GB_IO_BGP] = GB_CONFLICT_READ_NEW, + [GB_IO_OBP0] = GB_CONFLICT_READ_NEW, + [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, +}; + +static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->address_bus = addr; + 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. + If this timing proves incorrect, the ISR emulation must be updated so IF reads are + timed correctly. */ +/* TODO: Does this affect the address bus? Verify. */ +static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) +{ + assert(gb->pending_cycles); + GB_advance_cycles(gb, gb->pending_cycles); + gb->address_bus = 0xFF00 + GB_IO_IF; + uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F; + GB_write_memory(gb, 0xFF00 + GB_IO_IF, value); + gb->pending_cycles = 4; + return old; +} + +static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + assert(gb->pending_cycles); + conflict_t conflict = GB_CONFLICT_READ_OLD; + if ((addr & 0xFF80) == 0xFF00) { + const conflict_t *map = NULL; + if (GB_is_cgb(gb)) { + map = cgb_conflict_map; + } + else if (GB_is_sgb(gb)) { + map = sgb_conflict_map; + } + else { + map = dmg_conflict_map; + } + conflict = map[addr & 0x7F]; + } + switch (conflict) { + case GB_CONFLICT_READ_OLD: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + break; + + case GB_CONFLICT_READ_NEW: + GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + break; + + case GB_CONFLICT_WRITE_CPU: + GB_advance_cycles(gb, gb->pending_cycles + 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + break; + + /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ + case GB_CONFLICT_STAT_DMG: + GB_advance_cycles(gb, gb->pending_cycles); + GB_display_sync(gb); + /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. + The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite + the timing not making much sense for that. + This is a hack to simulate this effect */ + if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) { + GB_write_memory(gb, addr, ~0x20); + } + else { + GB_write_memory(gb, addr, 0xFF); + } + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + break; + + case GB_CONFLICT_STAT_CGB: { + /* Todo: Verify this with SCX adjustments */ + /* The LYC bit behaves differently */ + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, (old_value & 0x40) | (value & ~0x40)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + break; + } + + /* There is some "time travel" going on with these two values, as it appears + that there's some off-by-1-T-cycle timing issue in the PPU implementation. + + This is should be accurate for every measureable scenario, though. */ + + case GB_CONFLICT_PALETTE_DMG: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + uint8_t old_value = GB_read_memory(gb, addr); + GB_write_memory(gb, addr, value | old_value); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + break; + } + + case GB_CONFLICT_PALETTE_CGB: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 6; + break; + } + + case GB_CONFLICT_DMG_LCDC: { + /* 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 object-fetching state machine, and both behave differently when it comes to access conflicts. + Hacks ahead. + */ + + + + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_display_sync(gb); + if (gb->model != GB_MODEL_MGB && gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { + old_value &= ~2; + } + + GB_write_memory(gb, addr, old_value | (value & 1)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + break; + } + + case GB_CONFLICT_SGB_LCDC: { + /* Simplified version of the above */ + + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + /* Hack to force aborting object fetch */ + GB_write_memory(gb, addr, value); + GB_write_memory(gb, addr, old_value); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + break; + } + + case GB_CONFLICT_WX: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->wx_just_changed = true; + GB_advance_cycles(gb, 1); + gb->wx_just_changed = false; + gb->pending_cycles = 3; + break; + + case GB_CONFLICT_CGB_LCDC: + if ((~value & gb->io_registers[GB_IO_LCDC]) & 0x10) { + // Todo: This is difference is because my timing is off in one of the models + if (gb->model > GB_MODEL_CGB_C) { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first + gb->tile_sel_glitch = true; + GB_advance_cycles(gb, 1); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + } + else { + GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first + gb->tile_sel_glitch = true; + GB_advance_cycles(gb, 1); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + } + else { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + break; + + 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 + GB_apu_run(gb, true); + if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) { + gb->apu.square_sweep_calculate_countdown -= 2; + } + gb->apu.enable_zombie_calculate_stepping = true; + /* TODO: this causes audio regressions in the Donkey Kong Land series. + The exact behavior of this quirk should be further investigated, as it seems + more complicated than a single FF pseudo-write. */ + // GB_write_memory(gb, addr, 0xFF); + } + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + break; + + case GB_CONFLICT_CGB_SCX: + if (gb->cgb_double_speed) { + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 6; + } + else { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + break; + } + gb->address_bus = addr; +} + +static void cycle_no_access(GB_gameboy_t *gb) +{ + gb->pending_cycles += 4; +} + +static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->address_bus = gb->registers[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->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->address_bus = gb->pc; + 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) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->pending_cycles = 0; +} + +/* Todo: test if multi-byte opcodes trigger the OAM bug correctly */ + +static void ill(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_log(gb, "Illegal Opcode. Halting.\n"); + gb->interrupt_enable = 0; + gb->halted = true; +} + +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->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); + gb->oam_ppu_blocked = !gb->oam_read_blocked; + gb->vram_ppu_blocked = !gb->vram_read_blocked; + gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked; +} + +static void leave_stop_mode(GB_gameboy_t *gb) +{ + gb->stopped = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + // TODO: verify this + gb->dma_cycles = 4; + GB_dma_run(gb); + gb->oam_ppu_blocked = false; + gb->vram_ppu_blocked = false; + gb->cgb_palettes_ppu_blocked = false; +} + +/* 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); + 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; + bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F); + // When entering with IF&IE, the 2nd byte of STOP is actually executed + if (!exit_by_joyp) { + if (!immediate_exit) { + GB_dma_run(gb); + } + enter_stop_mode(gb); + } + + if (!interrupt_pending) { + 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) */ + if (speed_switch) { + flush_pending_cycles(gb); + + 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 = 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->cartridge_type->mbc_type == GB_CAMERA && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && !gb->cgb_double_speed) { + GB_log(gb, "ROM entered double speed mode with a camera cartridge, this could damage a real cartridge's camera.\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->io_registers[GB_IO_KEY1] = 0; + } + + if (immediate_exit) { + leave_stop_mode(gb); + if (!interrupt_pending) { + GB_dma_run(gb); + gb->halted = true; + gb->just_halted = true; + gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); + } + else { + gb->speed_switch_halt_countdown = 0; + } + } +} + +/* Operand naming conventions for functions: + r = 8-bit register + lr = low 8-bit register + hr = high 8-bit register + rr = 16-bit register + d8 = 8-bit imm + d16 = 16-bit imm + d.. = [..] + cc = condition code (z, nz, c, nc) + */ + +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(gb, gb->pc++); + value |= cycle_read(gb, gb->pc++) << 8; + gb->registers[register_id] = value; +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + cycle_write(gb, gb->registers[register_id], gb->af >> 8); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id = (opcode >> 4) + 1; + cycle_oam_bug(gb, register_id); + gb->registers[register_id]++; +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] += 0x100; + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F00) == 0) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] -= 0x100; + gb->af &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F00) == 0xF00) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +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(gb, gb->pc++) << 8; +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->af & 0x8000) != 0; + + gb->af = (gb->af & 0xFF00) << 1; + if (carry) { + gb->af |= GB_CARRY_FLAG | 0x0100; + } +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit7 = (gb->af & 0x8000) != 0; + bool carry = (gb->af & GB_CARRY_FLAG) != 0; + + gb->af = (gb->af & 0xFF00) << 1; + if (carry) { + gb->af |= 0x0100; + } + if (bit7) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify order is correct */ + uint16_t addr; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->sp & 0xFF); + cycle_write(gb, addr + 1, gb->sp >> 8); +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t hl = gb->hl; + uint16_t rr; + uint8_t register_id; + cycle_no_access(gb); + register_id = (opcode >> 4) + 1; + rr = gb->registers[register_id]; + gb->hl = hl + rr; + gb->af &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + + /* The meaning of the Half Carry flag is really hard to track -_- */ + if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ( ((unsigned) hl + (unsigned) rr) & 0x10000) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + gb->af &= 0xFF; + gb->af |= cycle_read(gb, gb->registers[register_id]) << 8; +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id = (opcode >> 4) + 1; + cycle_oam_bug(gb, register_id); + gb->registers[register_id]--; +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) + 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F) == 0) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) - 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->af &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F) == 0xF) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +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(gb, gb->pc++); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->af & 0x100) != 0; + + gb->af = (gb->af >> 1) & 0xFF00; + if (carry) { + gb->af |= GB_CARRY_FLAG | 0x8000; + } +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit1 = (gb->af & 0x0100) != 0; + bool carry = (gb->af & GB_CARRY_FLAG) != 0; + + gb->af = (gb->af >> 1) & 0xFF00; + if (carry) { + gb->af |= 0x8000; + } + if (bit1) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify timing */ + gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; + cycle_no_access(gb); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->af & GB_ZERO_FLAG); + case 1: + return (gb->af & GB_ZERO_FLAG); + case 2: + return !(gb->af & GB_CARRY_FLAG); + case 3: + return (gb->af & GB_CARRY_FLAG); + nodefault; + } + + return false; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int8_t offset = cycle_read(gb, gb->pc++); + if (condition_code(gb, opcode)) { + gb->pc += offset; + cycle_no_access(gb); + } +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t result = gb->af >> 8; + + gb->af &= ~(0xFF00 | GB_ZERO_FLAG); + + if (gb->af & GB_SUBTRACT_FLAG) { + if (gb->af & GB_HALF_CARRY_FLAG) { + result = (result - 0x06) & 0xFF; + } + + if (gb->af & GB_CARRY_FLAG) { + result -= 0x60; + } + } + else { + if ((gb->af & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) { + result += 0x06; + } + + if ((gb->af & GB_CARRY_FLAG) || result > 0x9F) { + result += 0x60; + } + } + + if ((result & 0xFF) == 0) { + gb->af |= GB_ZERO_FLAG; + } + + if ((result & 0x100) == 0x100) { + gb->af |= GB_CARRY_FLAG; + } + + gb->af &= ~GB_HALF_CARRY_FLAG; + gb->af |= result << 8; +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af ^= 0xFF00; + gb->af |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af |= GB_CARRY_FLAG; + gb->af &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af ^= GB_CARRY_FLAG; + gb->af &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, gb->hl++, gb->af >> 8); +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, gb->hl--, gb->af >> 8); +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af &= 0xFF; + gb->af |= cycle_read(gb, gb->hl++) << 8; +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af &= 0xFF; + gb->af |= cycle_read(gb, gb->hl--) << 8; +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = cycle_read(gb, gb->hl) + 1; + cycle_write(gb, gb->hl, value); + + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + if ((value & 0x0F) == 0) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = cycle_read(gb, gb->hl) - 1; + cycle_write(gb, gb->hl, value); + + gb->af &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; + if ((value & 0x0F) == 0x0F) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t data = cycle_read(gb, gb->pc++); + cycle_write(gb, gb->hl, data); +} + +static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + return gb->af >> 8; + } + return cycle_read(gb, gb->hl); + } + if (src_low) { + return gb->registers[src_register_id] & 0xFF; + } + return gb->registers[src_register_id] >> 8; +} + +static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + gb->af &= 0xFF; + gb->af |= value << 8; + } + else { + cycle_write(gb, gb->hl, value); + } + } + else { + if (src_low) { + gb->registers[src_register_id] &= 0xFF00; + gb->registers[src_register_id] |= value; + } + else { + gb->registers[src_register_id] &= 0xFF; + gb->registers[src_register_id] |= value << 8; + } + } +} + +/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent + performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */ + +/* Todo: It's probably wise to do the same to all opcodes. */ + +#define LD_X_Y(x, y) \ +static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ + gb->x = gb->y;\ +} + +#define LD_X_DHL(x) \ +static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +gb->x = cycle_read(gb, gb->hl); \ +} + +#define LD_DHL_Y(y) \ +static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +cycle_write(gb, gb->hl, gb->y); \ +} + + LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) +LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) +LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) +LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) +LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a) +LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a) +LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) +LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) + +// fire the debugger if software breakpoints are enabled +static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) +{ + if (gb->has_software_breakpoints) { + gb->debug_stopped = true; + } +} + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af = (a + value) << 8; + if ((uint8_t)(a + value) == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = (a + value + carry) << 8; + + if ((uint8_t)(a + value + carry) == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af = ((a - value) << 8) | GB_SUBTRACT_FLAG; + if (a == value) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af = (a | value) << 8; + if ((a | value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->af >> 8; + gb->af &= 0xFF00; + gb->af |= GB_SUBTRACT_FLAG; + if (a == value) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode) +{ + assert(gb->pending_cycles == 4); + gb->pending_cycles = 0; + GB_advance_cycles(gb, 4); + + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) { + if (gb->ime) { + gb->halted = false; + gb->pc--; + } + else { + gb->halted = false; + gb->halt_bug = true; + } + } + else { + gb->halted = true; + gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); + } + gb->just_halted = true; +} + +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(gb, gb->sp++); + gb->registers[register_id] |= cycle_read(gb, gb->sp++) << 8; + gb->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(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); + if (condition_code(gb, opcode)) { + cycle_no_access(gb); + gb->pc = addr; + } +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr = cycle_read(gb, gb->pc); + addr |= (cycle_read(gb, gb->pc + 1) << 8); + cycle_no_access(gb); + gb->pc = addr; + +} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + 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->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); + gb->pc = addr; + + GB_debugger_call_hook(gb, call_addr); + } +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + cycle_oam_bug(gb, GB_REGISTER_SP); + register_id = ((opcode >> 4) + 1) & 3; + cycle_write(gb, --gb->sp, (gb->registers[register_id]) >> 8); + cycle_write(gb, --gb->sp, (gb->registers[register_id]) & 0xFF); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a + value) << 8; + if ((uint8_t) (a + value) == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = (a + value + carry) << 8; + + if (gb->af == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = ((a - value) << 8) | GB_SUBTRACT_FLAG; + if (a == value) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a | value) << 8; + if ((a | value) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af &= 0xFF00; + gb->af |= GB_SUBTRACT_FLAG; + if (a == value) { + gb->af |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->af |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + cycle_oam_bug(gb, GB_REGISTER_SP); + cycle_write(gb, --gb->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); + gb->pc = opcode ^ 0xC7; + GB_debugger_call_hook(gb, call_addr); +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_debugger_ret_hook(gb); + gb->pc = cycle_read(gb, gb->sp++); + gb->pc |= cycle_read(gb, gb->sp++) << 8; + cycle_no_access(gb); +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode) +{ + ret(gb, opcode); + gb->ime = true; +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + cycle_no_access(gb); + ret(gb, opcode); + } + else { + cycle_no_access(gb); + } +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + 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->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); + gb->pc = addr; + GB_debugger_call_hook(gb, call_addr); +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t temp = cycle_read(gb, gb->pc++); + cycle_write(gb, 0xFF00 + temp, gb->af >> 8); +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af &= 0xFF; + uint8_t temp = cycle_read(gb, gb->pc++); + gb->af |= cycle_read(gb, 0xFF00 + temp) << 8; +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, 0xFF00 + (gb->bc & 0xFF), gb->af >> 8); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->af &= 0xFF; + gb->af |= cycle_read(gb, 0xFF00 + (gb->bc & 0xFF)) << 8; +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + uint16_t sp = gb->sp; + offset = (int8_t) cycle_read(gb, gb->pc++); + cycle_no_access(gb); + cycle_no_access(gb); + gb->sp += offset; + + gb->af &= 0xFF00; + + /* A new instruction, a new meaning for Half Carry! */ + if ((sp & 0xF) + (offset & 0xF) > 0xF) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->pc = gb->hl; +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->af >> 8); +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + gb->af &= 0xFF; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + gb->af |= cycle_read(gb, addr) << 8; +} + +static void di(GB_gameboy_t *gb, uint8_t opcode) +{ + /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB + for different reasons. */ + gb->ime = false; +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode) +{ + /* ei is actually "disable interrupts for one instruction, then enable them". */ + if (!gb->ime && !gb->ime_toggle) { + gb->ime_toggle = true; + } +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + gb->af &= 0xFF00; + offset = (int8_t) cycle_read(gb, gb->pc++); + cycle_no_access(gb); + gb->hl = gb->sp + offset; + + if ((gb->sp & 0xF) + (offset & 0xF) > 0xF) { + gb->af |= GB_HALF_CARRY_FLAG; + } + + if ((gb->sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->af |= GB_CARRY_FLAG; + } +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->sp = gb->hl; + cycle_oam_bug(gb, GB_REGISTER_HL); +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->af &= 0xFF00; + set_src_value(gb, opcode, (value << 1) | carry); + if (carry) { + gb->af |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + value = get_src_value(gb, opcode); + carry = (value & 0x01) != 0; + gb->af &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (carry) { + gb->af |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit7; + value = get_src_value(gb, opcode); + carry = (gb->af & GB_CARRY_FLAG) != 0; + bit7 = (value & 0x80) != 0; + + gb->af &= 0xFF00; + value = (value << 1) | carry; + set_src_value(gb, opcode, value); + if (bit7) { + gb->af |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit1; + + value = get_src_value(gb, opcode); + carry = (gb->af & GB_CARRY_FLAG) != 0; + bit1 = (value & 0x1) != 0; + + gb->af &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (bit1) { + gb->af |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + bool carry; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->af &= 0xFF00; + set_src_value(gb, opcode, (value << 1)); + if (carry) { + gb->af |= GB_CARRY_FLAG; + } + if ((value & 0x7F) == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t bit7; + uint8_t value; + value = get_src_value(gb, opcode); + bit7 = value & 0x80; + gb->af &= 0xFF00; + if (value & 1) { + gb->af |= GB_CARRY_FLAG; + } + value = (value >> 1) | bit7; + set_src_value(gb, opcode, value); + if (value == 0) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = get_src_value(gb, opcode); + gb->af &= 0xFF00; + set_src_value(gb, opcode, (value >> 1)); + if (value & 1) { + gb->af |= GB_CARRY_FLAG; + } + if (!(value >> 1)) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = get_src_value(gb, opcode); + gb->af &= 0xFF00; + set_src_value(gb, opcode, (value >> 4) | (value << 4)); + if (!value) { + gb->af |= GB_ZERO_FLAG; + } +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + uint8_t bit; + value = get_src_value(gb, opcode); + bit = 1 << ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb->af &= 0xFF00 | GB_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; + if (!(bit & value)) { + gb->af |= GB_ZERO_FLAG; + } + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + set_src_value(gb, opcode, value & ~bit); + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + set_src_value(gb, opcode, value | bit); + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) +{ + opcode = cycle_read(gb, gb->pc++); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode); + break; + case 1: + rrc_r(gb, opcode); + break; + case 2: + rl_r(gb, opcode); + break; + case 3: + rr_r(gb, opcode); + break; + case 4: + sla_r(gb, opcode); + break; + case 5: + sra_r(gb, opcode); + break; + case 6: + swap_r(gb, opcode); + break; + case 7: + srl_r(gb, opcode); + break; + default: + bit_r(gb, opcode); + break; + } +} + +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 */ + 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 */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, + ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ + ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, + ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */ + ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a, + ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */ + ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; +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); + } + return; + } + + if ((gb->interrupt_enable & 0x10) && (gb->ime || gb->halted)) { + GB_timing_sync(gb); + } + + if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) { + GB_advance_cycles(gb, 2); + } + + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + + if (gb->halted) { + GB_advance_cycles(gb, (GB_is_cgb(gb) || gb->just_halted) ? 4 : 2); + } + gb->just_halted = false; + + bool effective_ime = gb->ime; + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } + + /* Wake up from HALT mode without calling interrupt code. */ + if (gb->halted && !effective_ime && interrupt_queue) { + gb->halted = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + gb->dma_cycles = 4; + GB_dma_run(gb); + gb->speed_switch_halt_countdown = 0; + } + + /* Call interrupt */ + else if (effective_ime && interrupt_queue) { + gb->halted = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + // TODO: verify the timing! + gb->dma_cycles = 4; + GB_dma_run(gb); + gb->speed_switch_halt_countdown = 0; + uint16_t call_addr = gb->pc; + + cycle_read(gb, gb->pc++); + cycle_oam_bug_pc(gb); + gb->pc--; + GB_trigger_oam_bug(gb, gb->sp); /* Todo: test T-cycle timing */ + cycle_no_access(gb); + + cycle_write(gb, --gb->sp, (gb->pc) >> 8); + interrupt_queue = gb->interrupt_enable; + + if (gb->sp == GB_IO_IF + 0xFF00 + 1) { + gb->sp--; + interrupt_queue &= cycle_write_if(gb, (gb->pc) & 0xFF); + } + else { + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); + interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; + } + + if (interrupt_queue) { + uint8_t interrupt_bit = 0; + while (!(interrupt_queue & 1)) { + 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; + } + else { + gb->pc = 0; + } + gb->ime = false; + GB_debugger_call_hook(gb, call_addr); + } + /* Run mode */ + else if (!gb->halted) { + uint8_t opcode = gb->hdma_open_bus = cycle_read(gb, gb->pc++); + if (unlikely(gb->hdma_on)) { + GB_hdma_run(gb); + } + if (unlikely(gb->execution_callback)) { + gb->execution_callback(gb, gb->pc - 1, opcode); + } + if (unlikely(gb->halt_bug)) { + gb->pc--; + gb->halt_bug = false; + } + opcodes[opcode](gb, opcode); + } + + flush_pending_cycles(gb); +} diff --git a/thirdparty/SameBoy-old/Core/sm83_cpu.h b/thirdparty/SameBoy-old/Core/sm83_cpu.h new file mode 100644 index 000000000..1221fd763 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/sm83_cpu.h @@ -0,0 +1,11 @@ +#ifndef sm83_cpu_h +#define sm83_cpu_h +#include "defs.h" +#include + +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); +#ifdef GB_INTERNAL +internal void GB_cpu_run(GB_gameboy_t *gb); +#endif + +#endif /* sm83_cpu_h */ diff --git a/thirdparty/SameBoy-old/Core/sm83_disassembler.c b/thirdparty/SameBoy-old/Core/sm83_disassembler.c new file mode 100644 index 000000000..b3d583630 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/sm83_disassembler.c @@ -0,0 +1,790 @@ +#include +#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); + +static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, ".BYTE $%02x\n", opcode); + (*pc)++; +} + +static void nop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "NOP\n"); + (*pc)++; +} + +static void stop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t next = GB_read_memory(gb, (*pc)++); + if (next) { + GB_log(gb, "CORRUPTED STOP (%02x)\n", next); + } + else { + GB_log(gb, "STOP\n"); + } +} + +static char *register_names[] = {"af", "bc", "de", "hl", "sp"}; + +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + uint16_t value; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + value = GB_read_memory(gb, (*pc)++); + value |= GB_read_memory(gb, (*pc)++) << 8; + const char *symbol = GB_debugger_name_for_address(gb, value); + if (symbol) { + GB_log(gb, "LD %s, %s ; =$%04x\n", register_names[register_id], symbol, value); + } + else { + GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value); + } +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "LD [%s], a\n", register_names[register_id]); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "INC %s\n", register_names[register_id]); +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "INC %c\n", register_names[register_id][0]); + +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "DEC %c\n", register_names[register_id][0]); +} + +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++)); +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLCA\n"); +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLA\n"); +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint16_t addr; + (*pc)++; + addr = GB_read_memory(gb, (*pc)++); + addr |= GB_read_memory(gb, (*pc)++) << 8; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], sp ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], sp\n", addr); + } +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = (opcode >> 4) + 1; + GB_log(gb, "ADD hl, %s\n", register_names[register_id]); +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "LD a, [%s]\n", register_names[register_id]); +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "DEC %s\n", register_names[register_id]); +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "INC %c\n", register_names[register_id][1]); +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "DEC %c\n", register_names[register_id][1]); +} + +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++)); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "RRCA\n"); + (*pc)++; +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "RRA\n"); + (*pc)++; +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %s ; =$%04x\n", symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", addr); + } + (*pc)++; +} + +static const char *condition_code(uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return "nz"; + case 1: + return "z"; + case 2: + return "nc"; + case 3: + return "c"; + } + + return NULL; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), addr); + } + (*pc)++; +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "DAA\n"); + (*pc)++; +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "CPL\n"); + (*pc)++; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "SCF\n"); + (*pc)++; +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "CCF\n"); + (*pc)++; +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD [hli], a\n"); + (*pc)++; +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD [hld], a\n"); + (*pc)++; +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD a, [hli]\n"); + (*pc)++; +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD a, [hld]\n"); + (*pc)++; +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "INC [hl]\n"); + (*pc)++; +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "DEC [hl]\n"); + (*pc)++; +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD [hl], $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static const char *get_src_name(uint8_t opcode) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = (opcode & 1); + if (src_register_id == GB_REGISTER_AF) { + return src_low? "a": "[hl]"; + } + if (src_low) { + return register_names[src_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[src_register_id]; +} + +static const char *get_dst_name(uint8_t opcode) +{ + uint8_t dst_register_id; + uint8_t dst_low; + dst_register_id = ((opcode >> 4) + 1) & 3; + dst_low = opcode & 8; + if (dst_register_id == GB_REGISTER_AF) { + return dst_low? "a": "[hl]"; + } + if (dst_low) { + return register_names[dst_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[dst_register_id]; +} + +static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode)); +} + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADD %s\n", get_src_name(opcode)); +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADC %s\n", get_src_name(opcode)); +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SUB %s\n", get_src_name(opcode)); +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SBC %s\n", get_src_name(opcode)); +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "AND %s\n", get_src_name(opcode)); +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "XOR %s\n", get_src_name(opcode)); +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "OR %s\n", get_src_name(opcode)); +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "CP %s\n", get_src_name(opcode)); +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "HALT\n"); +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode)); +} + +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3; + GB_log(gb, "POP %s\n", register_names[register_id]); +} + +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), addr); + } + (*pc) += 2; +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "JP %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "JP $%04x\n", addr); + } + (*pc) += 2; +} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), addr); + } + (*pc) += 2; +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3; + GB_log(gb, "PUSH %s\n", register_names[register_id]); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADD $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADC $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SUB $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SBC $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "AND $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "XOR $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "OR $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "CP $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RST $%02x\n", opcode ^ 0xC7); + +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n"); +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n"); +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "CALL $%04x\n", addr); + } + (*pc) += 2; +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t addr = GB_read_memory(gb, (*pc)++); + const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr); + if (symbol) { + GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr); + } + else { + GB_log(gb, "LDH [$%02x], a\n", addr); + } +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t addr = GB_read_memory(gb, (*pc)++); + const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr); + if (symbol) { + GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr); + } + else { + GB_log(gb, "LDH a, [$%02x]\n", addr); + } +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LDH [c], a\n"); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LDH a, [c]\n"); +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + int8_t temp = GB_read_memory(gb, (*pc)++); + GB_log(gb, "ADD SP, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "JP hl\n"); +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], a ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], a\n", addr); + } + (*pc) += 2; +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD a, [%s] ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD a, [$%04x]\n", addr); + } + (*pc) += 2; +} + +static void di(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "DI\n"); +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "EI\n"); +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + int8_t temp = GB_read_memory(gb, (*pc)++); + GB_log(gb, "LD hl, sp, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD sp, hl\n"); +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLC %s\n", get_src_name(opcode)); +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RRC %s\n", get_src_name(opcode)); +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RL %s\n", get_src_name(opcode)); +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RR %s\n", get_src_name(opcode)); +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SLA %s\n", get_src_name(opcode)); +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SRA %s\n", get_src_name(opcode)); +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SRL %s\n", get_src_name(opcode)); +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SWAP %s\n", get_src_name(opcode)); +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t bit; + (*pc)++; + bit = ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + GB_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + GB_log(gb, "RES %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + GB_log(gb, "SET %s, %d\n", get_src_name(opcode), bit); + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + opcode = GB_read_memory(gb, ++*pc); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode, pc); + break; + case 1: + rrc_r(gb, opcode, pc); + break; + case 2: + rl_r(gb, opcode, pc); + break; + case 3: + rr_r(gb, opcode, pc); + break; + case 4: + sla_r(gb, opcode, pc); + break; + case 5: + sra_r(gb, opcode, pc); + break; + case 6: + swap_r(gb, opcode, pc); + break; + case 7: + srl_r(gb, opcode, pc); + break; + default: + bit_r(gb, opcode, pc); + break; + } +} + +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 */ + 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 */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; + + + +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count) +{ + const GB_bank_symbol_t *function_symbol = GB_debugger_find_symbol(gb, pc); + + if (function_symbol && pc - function_symbol->addr > 0x1000) { + function_symbol = NULL; + } + + if (function_symbol && pc != function_symbol->addr) { + GB_log(gb, "%s:\n", function_symbol->name); + } + + uint16_t current_function = function_symbol? function_symbol->addr : 0; + + while (count--) { + function_symbol = GB_debugger_find_symbol(gb, pc); + if (function_symbol && function_symbol->addr == pc) { + if (current_function != function_symbol->addr) { + GB_log(gb, "\n"); + } + GB_log(gb, "%s:\n", function_symbol->name); + } + if (function_symbol) { + GB_log(gb, "%s%04x <+%03x>: ", pc == gb->pc? " ->": " ", pc, pc - function_symbol->addr); + } + else { + GB_log(gb, "%s%04x: ", pc == gb->pc? " ->": " ", pc); + } + uint8_t opcode = GB_read_memory(gb, pc); + opcodes[opcode](gb, opcode, &pc); + } +} diff --git a/thirdparty/SameBoy-old/Core/symbol_hash.c b/thirdparty/SameBoy-old/Core/symbol_hash.c new file mode 100644 index 000000000..62ec52a6d --- /dev/null +++ b/thirdparty/SameBoy-old/Core/symbol_hash.c @@ -0,0 +1,111 @@ +#include "gb.h" +#include +#include +#include +#include + +static size_t map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) +{ + if (!map->symbols) { + return 0; + } + ssize_t min = 0; + ssize_t max = map->n_symbols; + while (min < max) { + size_t pivot = (min + max) / 2; + if (map->symbols[pivot].addr == addr) return pivot; + if (map->symbols[pivot].addr > addr) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (size_t) min; +} + +GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) +{ + 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])); + map->symbols[index].addr = addr; + map->symbols[index].name = strdup(name); + map->n_symbols++; + return &map->symbols[index]; +} + +const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) +{ + if (!map) return NULL; + size_t index = map_find_symbol_index(map, addr); + if (index >= map->n_symbols || map->symbols[index].addr != addr) { + index--; + } + if (index < map->n_symbols) { + while (index && map->symbols[index].addr == map->symbols[index - 1].addr) { + index--; + } + return &map->symbols[index]; + } + return NULL; +} + +GB_symbol_map_t *GB_map_alloc(void) +{ + GB_symbol_map_t *map = malloc(sizeof(*map)); + memset(map, 0, sizeof(*map)); + return map; +} + +void GB_map_free(GB_symbol_map_t *map) +{ + for (unsigned i = 0; i < map->n_symbols; i++) { + free(map->symbols[i].name); + } + + if (map->symbols) { + free(map->symbols); + } + + free(map); +} + +static unsigned hash_name(const char *name) +{ + unsigned r = 0; + while (*name) { + r <<= 1; + if (r & 0x400) { + r ^= 0x401; + } + r += (unsigned char)*(name++); + } + + return r & 0x3FF; +} + +void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol) +{ + unsigned hash = hash_name(bank_symbol->name); + GB_symbol_t *symbol = malloc(sizeof(*symbol)); + symbol->name = bank_symbol->name; + symbol->addr = bank_symbol->addr; + symbol->bank = bank; + symbol->next = map->buckets[hash]; + map->buckets[hash] = symbol; +} + +const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name) +{ + unsigned hash = hash_name(name); + GB_symbol_t *symbol = map->buckets[hash]; + + while (symbol) { + if (strcmp(symbol->name, name) == 0) return symbol; + symbol = symbol->next; + } + + return NULL; +} diff --git a/thirdparty/SameBoy-old/Core/symbol_hash.h b/thirdparty/SameBoy-old/Core/symbol_hash.h new file mode 100644 index 000000000..d0633128d --- /dev/null +++ b/thirdparty/SameBoy-old/Core/symbol_hash.h @@ -0,0 +1,38 @@ +#ifndef symbol_hash_h +#define symbol_hash_h + +#include +#include +#include + +typedef struct { + char *name; + uint16_t addr; +} GB_bank_symbol_t; + +typedef struct GB_symbol_s { + struct GB_symbol_s *next; + const char *name; + uint16_t bank; + uint16_t addr; +} GB_symbol_t; + +typedef struct { + GB_bank_symbol_t *symbols; + size_t n_symbols; +} GB_symbol_map_t; + +typedef struct { + GB_symbol_t *buckets[0x400]; +} GB_reversed_symbol_map_t; + +#ifdef GB_INTERNAL +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/thirdparty/SameBoy-old/Core/timing.c b/thirdparty/SameBoy-old/Core/timing.c new file mode 100644 index 000000000..67cd996fe --- /dev/null +++ b/thirdparty/SameBoy-old/Core/timing.c @@ -0,0 +1,483 @@ +#include "gb.h" +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif +#include +#else +#include +#endif + +static const unsigned TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; + +#ifndef GB_DISABLE_TIMEKEEPING +static int64_t get_nanoseconds(void) +{ +#ifndef _WIN32 + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; +#else + FILETIME time; + GetSystemTimeAsFileTime(&time); + return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L; +#endif +} + +static void nsleep(uint64_t nanoseconds) +{ +#ifndef _WIN32 + struct timespec sleep = {0, nanoseconds}; + nanosleep(&sleep, NULL); +#else + HANDLE timer; + LARGE_INTEGER time; + timer = CreateWaitableTimer(NULL, true, NULL); + time.QuadPart = -(nanoseconds / 100L); + SetWaitableTimer(timer, &time, 0, NULL, NULL, false); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif +} + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + if (!gb->turbo_dont_skip) { + int64_t nanoseconds = get_nanoseconds(); + if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { + return true; + } + gb->last_sync = nanoseconds; + } + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ + /* 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; + 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 < 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; + } + gb->last_sync = nanoseconds; + } + + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } +} +#else + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ + if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + gb->cycles_since_last_sync = 0; + + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } + return; +} + +#endif + +#define IR_DECAY 31500 +#define IR_WARMUP 19900 +#define IR_THRESHOLD 240 +#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + 268 + +static void ir_run(GB_gameboy_t *gb, uint32_t cycles) +{ + /* TODO: the way this thing works makes the CGB IR port behave inaccurately when used together with HUC1/3 IR ports*/ + if ((gb->model > GB_MODEL_CGB_E || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return; + bool is_sensing = (gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 || + (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) || + (gb->cartridge_type->mbc_type == GB_HUC3 && gb->huc3.mode == 0xE); + if (is_sensing && (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1))) { + gb->ir_sensor += cycles; + if (gb->ir_sensor > IR_MAX) { + gb->ir_sensor = IR_MAX; + } + + gb->effective_ir_input = gb->ir_sensor >= IR_WARMUP + IR_THRESHOLD && gb->ir_sensor <= IR_WARMUP + IR_THRESHOLD + IR_DECAY; + } + else { + unsigned target = is_sensing? IR_WARMUP : 0; + if (gb->ir_sensor < target) { + gb->ir_sensor += cycles; + } + else if (gb->ir_sensor <= target + cycles) { + gb->ir_sensor = target; + } + else { + gb->ir_sensor -= cycles; + } + gb->effective_ir_input = false; + } + +} + +static void advance_tima_state_machine(GB_gameboy_t *gb) +{ + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->tima_reload_state = GB_TIMA_RUNNING; + } + else if (gb->tima_reload_state == GB_TIMA_RELOADING) { + gb->io_registers[GB_IO_IF] |= 4; + gb->tima_reload_state = GB_TIMA_RELOADED; + } +} + +static void increase_tima(GB_gameboy_t *gb) +{ + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->tima_reload_state = GB_TIMA_RELOADING; + } +} + +void GB_serial_master_edge(GB_gameboy_t *gb) +{ + if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) { + gb->printer.idle_time += 1 << gb->serial_mask; + } + + gb->serial_master_clock ^= true; + + if (!gb->serial_master_clock && (gb->io_registers[GB_IO_SC] & 0x81) == 0x81) { + gb->serial_count++; + if (gb->serial_count == 8) { + gb->serial_count = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_IF] |= 8; + } + + gb->io_registers[GB_IO_SB] <<= 1; + + if (gb->serial_transfer_bit_end_callback) { + gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] |= 1; + } + + if (gb->serial_count) { + /* Still more bits to send */ + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + } +} + + +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + uint16_t triggers = gb->div_counter & ~value; + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + + if (triggers & gb->serial_mask) { + GB_serial_master_edge(gb); + } + + /* TODO: Can switching to double speed mode trigger an event? */ + uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000; + if (triggers & apu_bit) { + GB_apu_div_event(gb); + } + else { + uint16_t secondary_triggers = ~gb->div_counter & value; + if (secondary_triggers & apu_bit) { + GB_apu_div_secondary_event(gb); + } + } + gb->div_counter = value; +} + +static void timers_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->stopped) { + if (GB_is_cgb(gb)) { + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + } + return; + } + + GB_STATE_MACHINE(gb, div, cycles, 1) { + GB_STATE(gb, div, 1); + GB_STATE(gb, div, 2); + } + + GB_SLEEP(gb, div, 1, 3); + while (true) { + advance_tima_state_machine(gb); + GB_set_internal_div_counter(gb, gb->div_counter + 4); + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + GB_SLEEP(gb, div, 2, 4); + } +} + +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 (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; + + 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->cartridge_type->mbc_type != GB_HUC3 && (gb->rtc_real.high & 0x40)) { + gb->rtc_cycles -= cycles; + return; + } + if (gb->rtc_cycles < rtc_second_length) return; + gb->rtc_cycles -= rtc_second_length; + 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; + } + 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 (running) { /* is timer running? */ + while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { + gb->last_rtc_second += 60 * 60 * 24; + 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.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->cartridge_type->mbc_type == GB_TPP1) { + 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 */ + } + + gb->rtc_real.high ^= 1; + } + } + } +} + +static void camera_run(GB_gameboy_t *gb, uint8_t cycles) +{ + /* Do we have a camera? */ + if (likely(gb->cartridge_type->mbc_type != GB_CAMERA)) return; + + /* The camera mapper uses the PHI pin to clock itself */ + + /* PHI does not run in halt nor stop mode */ + if (unlikely(gb->halted || gb->stopped)) return; + + /* Only every other PHI is used (as the camera wants a 512KiHz clock) */ + gb->camera_alignment += cycles; + + /* Is the camera processing an image? */ + if (likely(gb->camera_countdown == 0)) return; + + gb->camera_countdown -= cycles; + if (gb->camera_countdown <= 0) { + gb->camera_countdown = 0; + GB_camera_updated(gb); + } +} + + +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) +{ + if (unlikely(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; + + timers_run(gb, cycles); + camera_run(gb, cycles); + + 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; + 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 (unlikely(!gb->cgb_double_speed)) { + cycles <<= 1; + } + + gb->absolute_debugger_ticks += cycles; + + // Not affected by speed boost + if (likely(gb->io_registers[GB_IO_LCDC] & 0x80)) { + gb->double_speed_alignment += cycles; + } + gb->apu_output.sample_cycles += cycles * gb->apu_output.sample_rate; + gb->cycles_since_last_sync += cycles; + gb->cycles_since_run += cycles; + + gb->rumble_on_cycles += gb->rumble_strength & 3; + gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3; + + GB_joypad_run(gb, cycles); + GB_apu_run(gb, false); + GB_display_run(gb, cycles, false); + if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode + GB_dma_run(gb); + } + ir_run(gb, cycles); + rtc_run(gb, cycles); +} + +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +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 = 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) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || !(gb->div_counter & new_clocks)) { + increase_tima(gb); + } + } +} diff --git a/thirdparty/SameBoy-old/Core/timing.h b/thirdparty/SameBoy-old/Core/timing.h new file mode 100644 index 000000000..8cc82e322 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/timing.h @@ -0,0 +1,66 @@ +#ifndef timing_h +#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); +internal void GB_serial_master_edge(GB_gameboy_t *gb); +enum { + GB_TIMA_RUNNING = 0, + GB_TIMA_RELOADING = 1, + GB_TIMA_RELOADED = 2 +}; + + +#define GB_SLEEP(gb, unit, state, cycles) do {\ + (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ + 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; \ +if ((gb)->unit##_cycles <= 0) {\ + return;\ +}\ +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 + +#endif /* timing_h */ diff --git a/thirdparty/SameBoy-old/Core/workboy.c b/thirdparty/SameBoy-old/Core/workboy.c new file mode 100644 index 000000000..3b103796f --- /dev/null +++ b/thirdparty/SameBoy-old/Core/workboy.c @@ -0,0 +1,169 @@ +#include "gb.h" +#include + +static inline uint8_t int_to_bcd(uint8_t i) +{ + return (i % 10) + ((i / 10) << 4); +} + +static inline uint8_t bcd_to_int(uint8_t i) +{ + return (i & 0xF) + (i >> 4) * 10; +} + +/* + Note: This peripheral was never released. This is a hacky software reimplementation of it that allows + reaccessing all of the features present in Workboy's ROM. Some of the implementation details are + obviously wrong, but without access to the actual hardware, this is the best I can do. +*/ + +static void serial_start(GB_gameboy_t *gb, bool bit_received) +{ + gb->workboy.byte_being_received <<= 1; + gb->workboy.byte_being_received |= bit_received; + gb->workboy.bits_received++; + if (gb->workboy.bits_received == 8) { + gb->workboy.byte_to_send = 0; + if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'R') { + gb->workboy.byte_to_send = 'D'; + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 1; + + time_t time = gb->workboy_get_time_callback(gb); + struct tm tm; + tm = *localtime(&time); + memset(gb->workboy.buffer, 0, sizeof(gb->workboy.buffer)); + + gb->workboy.buffer[0] = 4; // Unknown, unused, but appears to be expected to be 4 + gb->workboy.buffer[2] = int_to_bcd(tm.tm_sec); // Seconds, BCD + gb->workboy.buffer[3] = int_to_bcd(tm.tm_min); // Minutes, BCD + gb->workboy.buffer[4] = int_to_bcd(tm.tm_hour); // Hours, BCD + gb->workboy.buffer[5] = int_to_bcd(tm.tm_mday); // Days, BCD. Upper most 2 bits are added to Year for some reason + gb->workboy.buffer[6] = int_to_bcd(tm.tm_mon + 1); // Months, BCD + gb->workboy.buffer[0xF] = tm.tm_year; // Years, plain number, since 1900 + + } + else if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'W') { + gb->workboy.byte_to_send = 'D'; // It is actually unknown what this value should be + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 0; + } + else if (gb->workboy.mode != 'W' && (gb->workboy.byte_being_received == 'O' || gb->workboy.mode == 'O')) { + gb->workboy.mode = 'O'; + gb->workboy.byte_to_send = gb->workboy.key; + if (gb->workboy.key != GB_WORKBOY_NONE) { + if (gb->workboy.key & GB_WORKBOY_REQUIRE_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_REQUIRE_SHIFT; + if (gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_DOWN; + gb->workboy.shift_down = true; + } + } + else if (gb->workboy.key & GB_WORKBOY_FORBID_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_FORBID_SHIFT; + if (!gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_UP; + gb->workboy.shift_down = false; + } + } + else { + if (gb->workboy.key == GB_WORKBOY_SHIFT_DOWN) { + gb->workboy.shift_down = true; + gb->workboy.user_shift_down = true; + } + else if (gb->workboy.key == GB_WORKBOY_SHIFT_UP) { + gb->workboy.shift_down = false; + gb->workboy.user_shift_down = false; + } + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + } + } + else if (gb->workboy.mode == 'R') { + if (gb->workboy.buffer_index / 2 >= sizeof(gb->workboy.buffer)) { + gb->workboy.byte_to_send = 0; + } + else { + if (gb->workboy.buffer_index & 1) { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] & 0xF]; + } + else { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] >> 4]; + } + gb->workboy.buffer_index++; + } + } + else if (gb->workboy.mode == 'W') { + gb->workboy.byte_to_send = 'D'; + if (gb->workboy.buffer_index < 2) { + gb->workboy.buffer_index++; + } + else if ((gb->workboy.buffer_index - 2) < sizeof(gb->workboy.buffer)) { + gb->workboy.buffer[gb->workboy.buffer_index - 2] = gb->workboy.byte_being_received; + gb->workboy.buffer_index++; + if (gb->workboy.buffer_index - 2 == sizeof(gb->workboy.buffer)) { + struct tm tm = {0,}; + tm.tm_sec = bcd_to_int(gb->workboy.buffer[7]); + tm.tm_min = bcd_to_int(gb->workboy.buffer[8]); + tm.tm_hour = bcd_to_int(gb->workboy.buffer[9]); + tm.tm_mday = bcd_to_int(gb->workboy.buffer[0xA]); + tm.tm_mon = bcd_to_int(gb->workboy.buffer[0xB] & 0x3F) - 1; + tm.tm_year = (uint8_t)(gb->workboy.buffer[0x14] + (gb->workboy.buffer[0xA] >> 6)); // What were they thinking? + gb->workboy_set_time_callback(gb, mktime(&tm)); + gb->workboy.mode = 'O'; + } + } + } + gb->workboy.bits_received = 0; + gb->workboy.byte_being_received = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->workboy.bit_to_send; + gb->workboy.bit_to_send = gb->workboy.byte_to_send & 0x80; + gb->workboy.byte_to_send <<= 1; + return ret; +} + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback) +{ + memset(&gb->workboy, 0, sizeof(gb->workboy)); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); + gb->workboy_set_time_callback = set_time_callback; + gb->workboy_get_time_callback = get_time_callback; +} + +bool GB_workboy_is_enabled(GB_gameboy_t *gb) +{ + return gb->workboy.mode; +} + +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key) +{ + if (gb->workboy.user_shift_down != gb->workboy.shift_down && + (key & (GB_WORKBOY_REQUIRE_SHIFT | GB_WORKBOY_FORBID_SHIFT)) == 0) { + if (gb->workboy.user_shift_down) { + key |= GB_WORKBOY_REQUIRE_SHIFT; + } + else { + key |= GB_WORKBOY_FORBID_SHIFT; + } + } + gb->workboy.key = key; +} diff --git a/thirdparty/SameBoy-old/Core/workboy.h b/thirdparty/SameBoy-old/Core/workboy.h new file mode 100644 index 000000000..c99c27242 --- /dev/null +++ b/thirdparty/SameBoy-old/Core/workboy.h @@ -0,0 +1,118 @@ +#ifndef workboy_h +#define workboy_h +#include +#include +#include +#include "defs.h" + + +typedef struct { + uint8_t byte_to_send; + bool bit_to_send; + uint8_t byte_being_received; + uint8_t bits_received; + uint8_t mode; + uint8_t key; + bool shift_down; + bool user_shift_down; + uint8_t buffer[0x15]; + uint8_t buffer_index; // In nibbles during read, in bytes during write +} GB_workboy_t; + +typedef void (*GB_workboy_set_time_callback)(GB_gameboy_t *gb, time_t time); +typedef time_t (*GB_workboy_get_time_callback)(GB_gameboy_t *gb); + +enum { + GB_WORKBOY_NONE = 0xFF, + GB_WORKBOY_REQUIRE_SHIFT = 0x40, + GB_WORKBOY_FORBID_SHIFT = 0x80, + + GB_WORKBOY_CLOCK = 1, + GB_WORKBOY_TEMPERATURE = 2, + GB_WORKBOY_MONEY = 3, + GB_WORKBOY_CALCULATOR = 4, + GB_WORKBOY_DATE = 5, + GB_WORKBOY_CONVERSION = 6, + GB_WORKBOY_RECORD = 7, + GB_WORKBOY_WORLD = 8, + GB_WORKBOY_PHONE = 9, + GB_WORKBOY_ESCAPE = 10, + GB_WORKBOY_BACKSPACE = 11, + GB_WORKBOY_UNKNOWN = 12, + GB_WORKBOY_LEFT = 13, + GB_WORKBOY_Q = 17, + GB_WORKBOY_1 = 17 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_W = 18, + GB_WORKBOY_2 = 18 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_E = 19, + GB_WORKBOY_3 = 19 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_R = 20, + GB_WORKBOY_T = 21, + GB_WORKBOY_Y = 22 , + GB_WORKBOY_U = 23 , + GB_WORKBOY_I = 24, + GB_WORKBOY_EXCLAMATION_MARK = 24 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_O = 25, + GB_WORKBOY_TILDE = 25 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_P = 26, + GB_WORKBOY_ASTERISK = 26 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOLLAR = 27 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_HASH = 27 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_A = 28, + GB_WORKBOY_4 = 28 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_S = 29, + GB_WORKBOY_5 = 29 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_D = 30, + GB_WORKBOY_6 = 30 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_F = 31, + GB_WORKBOY_PLUS = 31 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_G = 32, + GB_WORKBOY_MINUS = 32 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_H = 33, + GB_WORKBOY_J = 34, + GB_WORKBOY_K = 35, + GB_WORKBOY_LEFT_PARENTHESIS = 35 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_L = 36, + GB_WORKBOY_RIGHT_PARENTHESIS = 36 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SEMICOLON = 37 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_COLON = 37, + GB_WORKBOY_ENTER = 38, + GB_WORKBOY_SHIFT_DOWN = 39, + GB_WORKBOY_Z = 40, + GB_WORKBOY_7 = 40 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_X = 41, + GB_WORKBOY_8 = 41 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_C = 42, + GB_WORKBOY_9 = 42 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_V = 43, + GB_WORKBOY_DECIMAL_POINT = 43 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_B = 44, + GB_WORKBOY_PERCENT = 44 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_N = 45, + GB_WORKBOY_EQUAL = 45 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_M = 46, + GB_WORKBOY_COMMA = 47 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_LT = 47 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOT = 48 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_GT = 48 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SLASH = 49 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_QUESTION_MARK = 49 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SHIFT_UP = 50, + GB_WORKBOY_0 = 51 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UMLAUT = 51, + GB_WORKBOY_SPACE = 52, + GB_WORKBOY_QUOTE = 53 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_AT = 53 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UP = 54, + GB_WORKBOY_DOWN = 55, + GB_WORKBOY_RIGHT = 56, +}; + + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback); +bool GB_workboy_is_enabled(GB_gameboy_t *gb); +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key); + +#endif diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/128x128.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/128x128.png new file mode 100644 index 000000000..b3259ed2a Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/128x128.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/16x16.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/16x16.png new file mode 100644 index 000000000..335107ec7 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/16x16.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/256x256.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/256x256.png new file mode 100644 index 000000000..56ea01664 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/256x256.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/32x32.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/32x32.png new file mode 100644 index 000000000..ea65e5a6f Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/32x32.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/512x512.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/512x512.png new file mode 100644 index 000000000..9004db209 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/512x512.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/AppIcon/64x64.png b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/64x64.png new file mode 100644 index 000000000..a5675ca2c Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/AppIcon/64x64.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/128x128.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/128x128.png new file mode 100644 index 000000000..e64a3bb77 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/128x128.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/16x16.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/16x16.png new file mode 100644 index 000000000..e3780396f Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/16x16.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/256x256.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/256x256.png new file mode 100644 index 000000000..b052b313f Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/256x256.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/32x32.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/32x32.png new file mode 100644 index 000000000..270de894c Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/32x32.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/512x512.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/512x512.png new file mode 100644 index 000000000..e8257c19e Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/512x512.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/Cartridge/64x64.png b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/64x64.png new file mode 100644 index 000000000..34d006d7a Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/Cartridge/64x64.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/128x128.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/128x128.png new file mode 100644 index 000000000..978b27b32 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/128x128.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/16x16.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/16x16.png new file mode 100644 index 000000000..899886ef1 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/16x16.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/256x256.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/256x256.png new file mode 100644 index 000000000..10e655837 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/256x256.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/32x32.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/32x32.png new file mode 100644 index 000000000..83f7f4b19 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/32x32.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/512x512.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/512x512.png new file mode 100644 index 000000000..417b2e53b Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/512x512.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/64x64.png b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/64x64.png new file mode 100644 index 000000000..01435cee0 Binary files /dev/null and b/thirdparty/SameBoy-old/FreeDesktop/ColorCartridge/64x64.png differ diff --git a/thirdparty/SameBoy-old/FreeDesktop/sameboy.desktop b/thirdparty/SameBoy-old/FreeDesktop/sameboy.desktop new file mode 100644 index 000000000..80d0902f5 --- /dev/null +++ b/thirdparty/SameBoy-old/FreeDesktop/sameboy.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Icon=sameboy +Exec=sameboy +Name=SameBoy +Comment=Game Boy and Game Boy Color emulator +Keywords=game;boy;gameboy;color;emulator +Terminal=false +StartupNotify=false +Categories=Game;Emulator; +MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx diff --git a/thirdparty/SameBoy-old/FreeDesktop/sameboy.xml b/thirdparty/SameBoy-old/FreeDesktop/sameboy.xml new file mode 100644 index 000000000..18123edcf --- /dev/null +++ b/thirdparty/SameBoy-old/FreeDesktop/sameboy.xml @@ -0,0 +1,23 @@ + + + + Game Boy ROM + + + + + + + Game Boy Color ROM + + + + + + + Game Boy ISX binary + + + + + diff --git a/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.h b/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.h new file mode 100644 index 000000000..32122a189 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.h @@ -0,0 +1,57 @@ +// +// HFAnnotatedTree.h +// HexFiend_2 +// +// Copyright 2010 ridiculous_fish. All rights reserved. +// + +#import + +typedef unsigned long long (*HFAnnotatedTreeAnnotaterFunction_t)(id left, id right); + + +@interface HFAnnotatedTreeNode : NSObject { + HFAnnotatedTreeNode *left; + HFAnnotatedTreeNode *right; + HFAnnotatedTreeNode *parent; + uint32_t level; +@public + unsigned long long annotation; +} + +/* Pure virtual method, which must be overridden. */ +- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node; + +/* Returns the next in-order node. */ +- (id)nextNode; + +- (id)leftNode; +- (id)rightNode; +- (id)parentNode; + +#if ! NDEBUG +- (void)verifyIntegrity; +- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater; +#endif + + +@end + + +@interface HFAnnotatedTree : NSObject { + HFAnnotatedTreeAnnotaterFunction_t annotater; + HFAnnotatedTreeNode *root; +} + +- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annotater; +- (void)insertNode:(HFAnnotatedTreeNode *)node; +- (void)removeNode:(HFAnnotatedTreeNode *)node; +- (id)rootNode; +- (id)firstNode; +- (BOOL)isEmpty; + +#if ! NDEBUG +- (void)verifyIntegrity; +#endif + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.m b/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.m new file mode 100644 index 000000000..9e64b9ae1 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFAnnotatedTree.m @@ -0,0 +1,432 @@ +// +// HFAnnotatedTree.m +// HexFiend_2 +// +// Copyright 2010 ridiculous_fish. All rights reserved. +// + +#import "HFAnnotatedTree.h" + +#if NDEBUG +#define VERIFY_INTEGRITY() do { } while (0) +#else +#define VERIFY_INTEGRITY() [self verifyIntegrity] +#endif + +/* HFAnnotatedTree is an AA tree. */ + +static unsigned long long null_annotater(id left, id right) { USE(left); USE(right); return 0; } +static void skew(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); +static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree); +static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree); +static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree); +static void verify_integrity(HFAnnotatedTreeNode *n); + +static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node); + +static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); + +static inline HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node); +static inline HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree); +static inline HFAnnotatedTreeNode *create_root(void); +static inline HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree); + +static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree); + +static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node); + +static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node); +static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node); + +@implementation HFAnnotatedTree + +- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annot { + self = [super init]; + annotater = annot ? annot : null_annotater; + /* root is always an HFAnnotatedTreeNode with a left child but no right child */ + root = create_root(); + return self; +} + +- (void)dealloc { + [root release]; + [super dealloc]; +} + +- (id)rootNode { + return root; +} + +- (id)firstNode { + return first_node(root); +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + HFAnnotatedTree *copied = [[[self class] alloc] init]; + copied->annotater = annotater; + [copied->root release]; + copied->root = [root mutableCopyWithZone:zone]; + return copied; +} + +- (BOOL)isEmpty { + /* We're empty if our root has no children. */ + return left_child(root) == nil && right_child(root) == nil; +} + +- (void)insertNode:(HFAnnotatedTreeNode *)node { + HFASSERT(node != nil); + HFASSERT(get_parent(node) == nil); + /* Insert into the root */ + insert(root, [node retain], self); + VERIFY_INTEGRITY(); +} + +- (void)removeNode:(HFAnnotatedTreeNode *)node { + HFASSERT(node != nil); + HFASSERT(get_parent(node) != nil); + delete(node, self); + [node release]; + VERIFY_INTEGRITY(); +} + +#if ! NDEBUG +- (void)verifyIntegrity { + [root verifyIntegrity]; + [root verifyAnnotation:annotater]; +} +#endif + +static HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree) { + return tree->root; +} + +static HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree) { + return tree->annotater; +} + +@end + +@implementation HFAnnotatedTreeNode + +- (void)dealloc { + [left release]; + [right release]; + [super dealloc]; +} + +- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node { + USE(node); + UNIMPLEMENTED(); +} + +- (id)nextNode { + return next_node(self); +} + +- (id)leftNode { return left; } +- (id)rightNode { return right; } +- (id)parentNode { return parent; } + +- (id)mutableCopyWithZone:(NSZone *)zone { + HFAnnotatedTreeNode *copied = [[[self class] alloc] init]; + if (left) { + copied->left = [left mutableCopyWithZone:zone]; + copied->left->parent = copied; + } + if (right) { + copied->right = [right mutableCopyWithZone:zone]; + copied->right->parent = copied; + } + copied->level = level; + copied->annotation = annotation; + return copied; +} + +static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node) { + return node->left; +} + +static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node) { + return node->right; +} + + +static HFAnnotatedTreeNode *create_root(void) { + HFAnnotatedTreeNode *result = [[HFAnnotatedTreeNode alloc] init]; + result->level = UINT_MAX; //the root has a huge level + return result; +} + +static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { + HFASSERT(node != nil); + HFASSERT(tree != nil); + const HFAnnotatedTreeAnnotaterFunction_t annotater = get_annotater(tree); + node->annotation = annotater(node->left, node->right); +} + +static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { + /* Insert node at the proper place in the tree. root is the root node, and we always insert to the left of root */ + BOOL left = YES; + HFAnnotatedTreeNode *parentNode = root, *currentChild; + /* Descend the tree until we find where to insert */ + while ((currentChild = (left ? parentNode->left : parentNode->right)) != nil) { + parentNode = currentChild; + left = ([parentNode compare:node] >= 0); //if parentNode is larger than the child, then the child goes to the left of node + } + + /* Now insert, potentially unbalancing the tree */ + if (left) { + parentNode->left = node; + } + else { + parentNode->right = node; + } + + /* Tell our node about its new parent */ + node->parent = parentNode; + + /* Rebalance and update annotations */ + rebalanceAfterLeafAdd(node, tree); +} + +static void skew(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) { + HFAnnotatedTreeNode *newp = oldparent->left; + + if (oldparent->parent->left == oldparent) { + /* oldparent is the left child of its parent. Substitute in our left child. */ + oldparent->parent->left = newp; + } + else { + /* oldparent is the right child of its parent. Substitute in our left child. */ + oldparent->parent->right = newp; + } + + /* Tell the child about its new parent */ + newp->parent = oldparent->parent; + + /* Adopt its right child as our left child, and tell it about its new parent */ + oldparent->left = newp->right; + if (oldparent->left) oldparent->left->parent = oldparent; + + /* We are now the right child of the new parent */ + newp->right = oldparent; + oldparent->parent = newp; + + /* If we're now a leaf, our level is 1. Otherwise, it's one more than the level of our child. */ + oldparent->level = oldparent->left ? oldparent->left->level + 1 : 1; + + /* oldparent and newp both had their children changed, so need to be reannotated */ + reannotate(oldparent, tree); + reannotate(newp, tree); +} + +static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) { + HFAnnotatedTreeNode *newp = oldparent->right; + if (newp && newp->right && newp->right->level == oldparent->level) { + if (oldparent->parent->left == oldparent) oldparent->parent->left = newp; + else oldparent->parent->right = newp; + newp->parent = oldparent->parent; + oldparent->parent = newp; + + oldparent->right = newp->left; + if (oldparent->right) oldparent->right->parent = oldparent; + newp->left = oldparent; + newp->level = oldparent->level + 1; + + /* oldparent and newp both had their children changed, so need to be reannotated */ + reannotate(oldparent, tree); + reannotate(newp, tree); + + return YES; + } + return NO; +} + +static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { // n is a node that has just been inserted and is now a leaf node. + node->level = 1; + node->left = nil; + node->right = nil; + reannotate(node, tree); + HFAnnotatedTreeNode * const root = get_root(tree); + HFAnnotatedTreeNode *probe; + for (probe = node->parent; probe != root; probe = probe->parent) { + reannotate(probe, tree); + // At this point probe->parent->level == probe->level + if (probe->level != (probe->left ? probe->left->level + 1 : 1)) { + // At this point the tree is correct, except (AA2) for n->parent + skew(probe, tree); + // We handle it (a left add) by changing it into a right add using Skew + // If the original add was to the left side of a node that is on the + // right side of a horisontal link, probe now points to the rights side + // of the second horisontal link, which is correct. + + // However if the original add was to the left of node with a horizontal + // link, we must get to the right side of the second link. + if (!probe->right || probe->level != probe->right->level) probe = probe->parent; + } + if (! split(probe->parent, tree)) break; + } + while (probe) { + reannotate(probe, tree); + probe = probe->parent; + } +} + +static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree) { // If n is not a leaf, we first swap it out with the leaf node that just + // precedes it. + HFAnnotatedTreeNode *leaf = n, *tmp; + + if (n->left) { + /* Descend the right subtree of our left child, to get the closest predecessor */ + for (leaf = n->left; leaf->right; leaf = leaf->right) {} + // When we stop, leaf has no 'right' child so it cannot have a left one + } + else if (n->right) { + /* We have no children that precede us, but we have a child after us, so use our closest successor */ + leaf = n->right; + } + + /* tmp is either the parent who loses the child, or tmp is our right subtree. Either way, we will have to reduce its level. */ + tmp = leaf->parent == n ? leaf : leaf->parent; + + /* Tell leaf's parent to forget about leaf */ + if (leaf->parent->left == leaf) { + leaf->parent->left = NULL; + } + else { + leaf->parent->right = NULL; + } + reannotate(leaf->parent, tree); + + if (n != leaf) { + /* Replace ourself as our parent's child with leaf */ + if (n->parent->left == n) n->parent->left = leaf; + else n->parent->right = leaf; + + /* Leaf's parent is our parent */ + leaf->parent = n->parent; + + /* Our left and right children are now leaf's left and right children */ + if (n->left) n->left->parent = leaf; + leaf->left = n->left; + if (n->right) n->right->parent = leaf; + leaf->right = n->right; + + /* Leaf's level is our level */ + leaf->level = n->level; + } + /* Since we adopted n's children, transferring the retain, tell n to forget about them so it doesn't release them */ + n->left = nil; + n->right = nil; + + // free (n); + + HFAnnotatedTreeNode * const root = get_root(tree); + while (tmp != root) { + reannotate(tmp, tree); + // One of tmp's childern had its level reduced + if (tmp->level > (tmp->left ? tmp->left->level + 1 : 1)) { // AA2 failed + tmp->level--; + if (split(tmp, tree)) { + if (split(tmp, tree)) skew(tmp->parent->parent, tree); + break; + } + tmp = tmp->parent; + } + else if (tmp->level <= (tmp->right ? tmp->right->level + 1 : 1)){ + break; + } + else { // AA3 failed + skew(tmp, tree); + //if (tmp->right) tmp->right->level = tmp->right->left ? tmp->right->left->level + 1 : 1; + if (tmp->level > tmp->parent->level) { + skew(tmp, tree); + split(tmp->parent->parent, tree); + break; + } + tmp = tmp->parent->parent; + } + } + while (tmp) { + reannotate(tmp, tree); + tmp = tmp->parent; + } +} + +static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node) { + /* Return the next in-order node */ + HFAnnotatedTreeNode *result; + if (node->right) { + /* We have a right child, which is after us. Descend its left subtree. */ + result = node->right; + while (result->left) { + result = result->left; + } + } + else { + /* We have no right child. If we are our parent's left child, then our parent is after us. Otherwise, we're our parent's right child and it was before us, so ascend while we're the parent's right child. */ + result = node; + while (result->parent && result->parent->right == result) { + result = result->parent; + } + /* Now result is the left child of the parent (or has NULL parents), so its parent is the next node */ + result = result->parent; + } + /* Don't return the root */ + if (result != nil && result->parent == nil) { + result = next_node(result); + } + return result; +} + +static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node) { + /* Return the first node */ + HFAnnotatedTreeNode *result = nil, *cursor = node->left; + while (cursor) { + /* Descend the left subtree */ + result = cursor; + cursor = cursor->left; + } + return result; +} + +static HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node) { + HFASSERT(node != nil); + return node->parent; +} + +static void __attribute__((unused))verify_integrity(HFAnnotatedTreeNode *n) { + HFASSERT(!n->left || n->left->parent == n); + HFASSERT(!n->right || n->right->parent == n); + HFASSERT(!next_node(n) || [n compare:next_node(n)] <= 0); + HFASSERT(!n->parent || n->parent->level >= n->level); + if (n->parent == nil) { + /* root node */ + HFASSERT(n->level == UINT_MAX); + } + else { + /* non-root node */ + HFASSERT(n->level == (n->left == NULL ? 1 : n->left->level + 1)); + HFASSERT((n->level <= 1) || (n->right && n->level - n->right->level <= 1)); + } + HFASSERT(!n->parent || !n->parent->parent || + n->parent->parent->level > n->level); +} + +#if ! NDEBUG +- (void)verifyIntegrity { + [left verifyIntegrity]; + [right verifyIntegrity]; + verify_integrity(self); +} + +- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater { + [left verifyAnnotation:annotater]; + [right verifyAnnotation:annotater]; + unsigned long long expectedAnnotation = annotater(left, right); + HFASSERT(annotation == expectedAnnotation); +} +#endif + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFBTree.h b/thirdparty/SameBoy-old/HexFiend/HFBTree.h new file mode 100644 index 000000000..3bb8bd902 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFBTree.h @@ -0,0 +1,40 @@ +// +// HFBTree.h +// HexFiend +// +// + +#import + +typedef unsigned long long HFBTreeIndex; + +@class HFBTreeNode; + +@protocol HFBTreeEntry +- (unsigned long long)length; +@end + +@interface HFBTree : NSObject { + unsigned int depth; + HFBTreeNode *root; +} + +- (void)insertEntry:(id)entry atOffset:(HFBTreeIndex)offset; +- (id)entryContainingOffset:(HFBTreeIndex)offset beginningOffset:(HFBTreeIndex *)outBeginningOffset; +- (void)removeEntryAtOffset:(HFBTreeIndex)offset; +- (void)removeAllEntries; + +#if HFUNIT_TESTS +- (void)checkIntegrityOfCachedLengths; +- (void)checkIntegrityOfBTreeStructure; +#endif + +- (NSEnumerator *)entryEnumerator; +- (NSArray *)allEntries; + +- (HFBTreeIndex)length; + +/* Applies the given function to the entry at the given offset, continuing with subsequent entries until the function returns NO. Do not modify the tree from within this function. */ +- (void)applyFunction:(BOOL (*)(id entry, HFBTreeIndex offset, void *userInfo))func toEntriesStartingAtOffset:(HFBTreeIndex)offset withUserInfo:(void *)userInfo; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFBTree.m b/thirdparty/SameBoy-old/HexFiend/HFBTree.m new file mode 100644 index 000000000..5bb7806a9 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFBTree.m @@ -0,0 +1,1099 @@ +// +// HFBTree.m +// BTree +// +// Created by peter on 2/6/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import "HFBTree.h" +#include + +#define FIXUP_LENGTHS 0 + +#define BTREE_BRANCH_ORDER 10 +#define BTREE_LEAF_ORDER 10 + +#define BTREE_ORDER 10 +#define BTREE_NODE_MINIMUM_VALUE_COUNT (BTREE_ORDER / 2) + +#define BTREE_LEAF_MINIMUM_VALUE_COUNT (BTREE_LEAF_ORDER / 2) + +#define BAD_INDEX ((ChildIndex_t)(-1)) +typedef unsigned int ChildIndex_t; + +/* How deep can our tree get? 128 is huge. */ +#define MAX_DEPTH 128 +#define BAD_DEPTH ((TreeDepth_t)(-1)) +typedef unsigned int TreeDepth_t; + +#define TreeEntry NSObject +#define HFBTreeLength(x) [(TreeEntry *)(x) length] + + +@class HFBTreeNode, HFBTreeBranch, HFBTreeLeaf; + +static TreeEntry *btree_search(HFBTree *tree, HFBTreeIndex offset, HFBTreeIndex *outBeginningOffset); +static id btree_insert_returning_retained_value_for_parent(HFBTree *tree, TreeEntry *entry, HFBTreeIndex offset); +static BOOL btree_remove(HFBTree *tree, HFBTreeIndex offset); +static void __attribute__((unused)) btree_recursive_check_integrity(HFBTree *tree, HFBTreeNode *branchOrLeaf, TreeDepth_t depth, HFBTreeNode **linkHelper); +#if FIXUP_LENGTHS +static HFBTreeIndex btree_recursive_fixup_cached_lengths(HFBTree *tree, HFBTreeNode *branchOrLeaf); +#endif +static HFBTreeIndex __attribute__((unused)) btree_recursive_check_integrity_of_cached_lengths(HFBTreeNode *branchOrLeaf); +static BOOL btree_are_cached_lengths_correct(HFBTreeNode *branchOrLeaf, HFBTreeIndex *outLength); +#if FIXUP_LENGTHS +static NSUInteger btree_entry_count(HFBTreeNode *branchOrLeaf); +#endif +static ChildIndex_t count_node_values(HFBTreeNode *node); +static HFBTreeIndex sum_child_lengths(const id *children, const BOOL isLeaf); +static HFBTreeNode *mutable_copy_node(HFBTreeNode *node, TreeDepth_t depth, HFBTreeNode **linkingHelper); + +#if NDEBUG +#define VERIFY_LENGTH(a) +#else +#define VERIFY_LENGTH(a) btree_recursive_check_integrity_of_cached_lengths((a)) +#endif + +#define IS_BRANCH(a) [(a) isKindOfClass:[HFBTreeBranch class]] +#define IS_LEAF(a) [(a) isKindOfClass:[HFBTreeLeaf class]] + +#define ASSERT_IS_BRANCH(a) HFASSERT(IS_BRANCH(a)) +#define ASSERT_IS_LEAF(a) HFASSERT(IS_LEAF(a)) + +#define GET_LENGTH(node, parentIsLeaf) ((parentIsLeaf) ? HFBTreeLength(node) : CHECK_CAST((node), HFBTreeNode)->subtreeLength) + +#define CHECK_CAST(a, b) ({HFASSERT([(a) isKindOfClass:[b class]]); (b *)(a);}) +#define CHECK_CAST_OR_NULL(a, b) ({HFASSERT((a == nil) || [(a) isKindOfClass:[b class]]); (b *)(a);}) + +#define DEFEAT_INLINE 1 + +#if DEFEAT_INLINE +#define FORCE_STATIC_INLINE static +#else +#define FORCE_STATIC_INLINE static __inline__ __attribute__((always_inline)) +#endif + +@interface HFBTreeEnumerator : NSEnumerator { + HFBTreeLeaf *currentLeaf; + ChildIndex_t childIndex; +} + +- (instancetype)initWithLeaf:(HFBTreeLeaf *)leaf; + +@end + +@interface HFBTreeNode : NSObject { + @public + NSUInteger rc; + HFBTreeIndex subtreeLength; + HFBTreeNode *left, *right; + id children[BTREE_ORDER]; +} + +@end + +@implementation HFBTreeNode + +- (id)retain { + HFAtomicIncrement(&rc, NO); + return self; +} + +- (oneway void)release { + NSUInteger result = HFAtomicDecrement(&rc, NO); + if (result == (NSUInteger)(-1)) { + [self dealloc]; + } +} + +- (NSUInteger)retainCount { + return 1 + rc; +} + +- (void)dealloc { + for (ChildIndex_t i=0; i < BTREE_BRANCH_ORDER; i++) { + if (! children[i]) break; + [children[i] release]; + } + [super dealloc]; +} + +- (NSString *)shortDescription { + return [NSString stringWithFormat:@"<%@: %p (%llu)>", [self class], self, subtreeLength]; +} + +@end + +@interface HFBTreeBranch : HFBTreeNode +@end + +@implementation HFBTreeBranch + +- (NSString *)description { + const char *lengthsMatchString = (subtreeLength == sum_child_lengths(children, NO) ? "" : " INCONSISTENT "); + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@: %p (length: %llu%s) (children: %u) (", [self class], self, subtreeLength, lengthsMatchString, count_node_values(self)]; + NSUInteger i; + for (i=0; i < BTREE_ORDER; i++) { + if (children[i] == nil) break; + [s appendFormat:@"%s%@", (i == 0 ? "" : ", "), [children[i] shortDescription]]; + } + [s appendString:@")>"]; + return s; +} + +@end + +@interface HFBTreeLeaf : HFBTreeNode +@end + +@implementation HFBTreeLeaf + +- (NSString *)description { + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@: %p (%u) (", [self class], self, count_node_values(self)]; + NSUInteger i; + for (i=0; i < BTREE_ORDER; i++) { + if (children[i] == nil) break; + [s appendFormat:@"%s%@", (i == 0 ? "" : ", "), children[i]]; + } + [s appendString:@")>"]; + return s; +} + +@end + +@implementation HFBTree + +- (instancetype)init { + self = [super init]; + depth = BAD_DEPTH; + root = nil; + return self; +} + +- (void)dealloc { + [root release]; + [super dealloc]; +} + +#if HFUNIT_TESTS +- (void)checkIntegrityOfCachedLengths { + if (root == nil) { + /* nothing */ + } + else { + btree_recursive_check_integrity_of_cached_lengths(root); + } +} + +- (void)checkIntegrityOfBTreeStructure { + if (depth == BAD_DEPTH) { + HFASSERT(root == nil); + } + else { + HFBTreeNode *linkHelper[MAX_DEPTH + 1] = {}; + btree_recursive_check_integrity(self, root, depth, linkHelper); + } +} +#endif + +- (HFBTreeIndex)length { + if (root == nil) return 0; + return ((HFBTreeNode *)root)->subtreeLength; +} + +- (void)insertEntry:(id)entryObj atOffset:(HFBTreeIndex)offset { + TreeEntry *entry = (TreeEntry *)entryObj; //avoid a conflicting types warning + HFASSERT(entry); + HFASSERT(offset <= [self length]); + if (! root) { + HFASSERT([self length] == 0); + HFASSERT(depth == BAD_DEPTH); + HFBTreeLeaf *leaf = [[HFBTreeLeaf alloc] init]; + leaf->children[0] = [entry retain]; + leaf->subtreeLength = HFBTreeLength(entry); + root = leaf; + depth = 0; + } + else { + HFBTreeNode *newParentValue = btree_insert_returning_retained_value_for_parent(self, entry, offset); + if (newParentValue) { + HFBTreeBranch *newRoot = [[HFBTreeBranch alloc] init]; + newRoot->children[0] = root; //transfer our retain + newRoot->children[1] = newParentValue; //transfer the retain we got from the function + newRoot->subtreeLength = HFSum(root->subtreeLength, newParentValue->subtreeLength); + root = newRoot; + depth++; + HFASSERT(depth <= MAX_DEPTH); + } +#if FIXUP_LENGTHS + HFBTreeIndex outLength = -1; + if (! btree_are_cached_lengths_correct(root, &outLength)) { + puts("Fixed up length after insertion"); + btree_recursive_fixup_cached_lengths(self, root); + } +#endif + } +} + +- (TreeEntry *)entryContainingOffset:(HFBTreeIndex)offset beginningOffset:(HFBTreeIndex *)outBeginningOffset { + HFASSERT(root != nil); + return btree_search(self, offset, outBeginningOffset); +} + +- (void)removeAllEntries { + [root release]; + root = nil; + depth = BAD_DEPTH; +} + +- (void)removeEntryAtOffset:(HFBTreeIndex)offset { + HFASSERT(root != nil); +#if FIXUP_LENGTHS + const NSUInteger beforeCount = btree_entry_count(root); +#endif + BOOL deleteRoot = btree_remove(self, offset); + if (deleteRoot) { + HFASSERT(count_node_values(root) <= 1); + id newRoot = [root->children[0] retain]; //may be nil! + [root release]; + root = newRoot; + depth--; + } +#if FIXUP_LENGTHS + const NSUInteger afterCount = btree_entry_count(root); + if (beforeCount != afterCount + 1) { + NSLog(@"Bad counts: before %lu, after %lu", beforeCount, afterCount); + } + HFBTreeIndex outLength = -1; + static NSUInteger fixupCount; + if (! btree_are_cached_lengths_correct(root, &outLength)) { + fixupCount++; + printf("Fixed up length after deletion (%lu)\n", (unsigned long)fixupCount); + btree_recursive_fixup_cached_lengths(self, root); + } + else { + //printf("Length post-deletion was OK! (%lu)\n", fixupCount); + } +#endif +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + HFBTree *result = [[[self class] alloc] init]; + result->depth = depth; + HFBTreeNode *linkingHelper[MAX_DEPTH + 1]; + bzero(linkingHelper, (1 + depth) * sizeof *linkingHelper); + result->root = mutable_copy_node(root, depth, linkingHelper); + return result; +} + +FORCE_STATIC_INLINE ChildIndex_t count_node_values(HFBTreeNode *node) { + ChildIndex_t count; + for (count=0; count < BTREE_LEAF_ORDER; count++) { + if (node->children[count] == nil) break; + } + return count; +} + +FORCE_STATIC_INLINE HFBTreeIndex sum_child_lengths(const id *children, const BOOL isLeaf) { + HFBTreeIndex result = 0; + for (ChildIndex_t childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + id child = children[childIndex]; + if (! child) break; + HFBTreeIndex childLength = GET_LENGTH(child, isLeaf); + result = HFSum(result, childLength); + } + return result; +} + +FORCE_STATIC_INLINE HFBTreeIndex sum_N_child_lengths(const id *children, ChildIndex_t numChildren, const BOOL isLeaf) { + HFBTreeIndex result = 0; + for (ChildIndex_t childIndex = 0; childIndex < numChildren; childIndex++) { + id child = children[childIndex]; + HFASSERT(child != NULL); + HFBTreeIndex childLength = GET_LENGTH(child, isLeaf); + result = HFSum(result, childLength); + } + return result; +} + +FORCE_STATIC_INLINE ChildIndex_t index_containing_offset(HFBTreeNode *node, HFBTreeIndex offset, HFBTreeIndex * restrict outOffset, const BOOL isLeaf) { + ChildIndex_t childIndex; + HFBTreeIndex previousSum = 0; + const id *children = node->children; + for (childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + HFASSERT(children[childIndex] != nil); + HFBTreeIndex childLength = GET_LENGTH(children[childIndex], isLeaf); + HFBTreeIndex newSum = HFSum(childLength, previousSum); + if (newSum > offset) { + break; + } + previousSum = newSum; + } + *outOffset = previousSum; + return childIndex; +} + +FORCE_STATIC_INLINE id child_containing_offset(HFBTreeNode *node, HFBTreeIndex offset, HFBTreeIndex * restrict outOffset, const BOOL isLeaf) { + return node->children[index_containing_offset(node, offset, outOffset, isLeaf)]; +} + +FORCE_STATIC_INLINE ChildIndex_t index_for_child_at_offset(HFBTreeNode *node, HFBTreeIndex offset, const BOOL isLeaf) { + ChildIndex_t childIndex; + HFBTreeIndex previousSum = 0; + id *const children = node->children; + for (childIndex = 0; childIndex < BTREE_ORDER; childIndex++) { + if (previousSum == offset) break; + HFASSERT(children[childIndex] != nil); + HFBTreeIndex childLength = GET_LENGTH(children[childIndex], isLeaf); + previousSum = HFSum(childLength, previousSum); + HFASSERT(previousSum <= offset); + } + HFASSERT(childIndex <= BTREE_ORDER); //note we allow the child index to be one past the end (in which case we are sure to split the node) + HFASSERT(previousSum == offset); //but we still require the offset to be the sum of all the lengths of this node + return childIndex; +} + +FORCE_STATIC_INLINE ChildIndex_t child_index_for_insertion_at_offset(HFBTreeBranch *branch, HFBTreeIndex insertionOffset, HFBTreeIndex *outPriorCombinedOffset) { + ChildIndex_t indexForInsertion; + HFBTreeIndex priorCombinedOffset = 0; + id *const children = branch->children; + for (indexForInsertion = 0; indexForInsertion < BTREE_BRANCH_ORDER; indexForInsertion++) { + if (! children[indexForInsertion]) break; + HFBTreeNode *childNode = CHECK_CAST(children[indexForInsertion], HFBTreeNode); + HFBTreeIndex subtreeLength = childNode->subtreeLength; + HFASSERT(subtreeLength > 0); + HFBTreeIndex newOffset = HFSum(priorCombinedOffset, subtreeLength); + if (newOffset >= insertionOffset) { + break; + } + priorCombinedOffset = newOffset; + } + *outPriorCombinedOffset = priorCombinedOffset; + return indexForInsertion; +} + +FORCE_STATIC_INLINE ChildIndex_t child_index_for_deletion_at_offset(HFBTreeBranch *branch, HFBTreeIndex deletionOffset, HFBTreeIndex *outPriorCombinedOffset) { + ChildIndex_t indexForDeletion; + HFBTreeIndex priorCombinedOffset = 0; + for (indexForDeletion = 0; indexForDeletion < BTREE_BRANCH_ORDER; indexForDeletion++) { + HFASSERT(branch->children[indexForDeletion] != nil); + HFBTreeNode *childNode = CHECK_CAST(branch->children[indexForDeletion], HFBTreeNode); + HFBTreeIndex subtreeLength = childNode->subtreeLength; + HFASSERT(subtreeLength > 0); + HFBTreeIndex newOffset = HFSum(priorCombinedOffset, subtreeLength); + if (newOffset > deletionOffset) { + /* Key difference between insertion and deletion: insertion uses >=, while deletion uses > */ + break; + } + priorCombinedOffset = newOffset; + } + *outPriorCombinedOffset = priorCombinedOffset; + return indexForDeletion; +} + +FORCE_STATIC_INLINE void insert_value_into_array(id value, NSUInteger insertionIndex, id *array, NSUInteger arrayCount) { + HFASSERT(insertionIndex <= arrayCount); + HFASSERT(arrayCount > 0); + NSUInteger pushingIndex = arrayCount - 1; + while (pushingIndex > insertionIndex) { + array[pushingIndex] = array[pushingIndex - 1]; + pushingIndex--; + } + array[insertionIndex] = [value retain]; +} + + +FORCE_STATIC_INLINE void remove_value_from_array(NSUInteger removalIndex, id *array, NSUInteger arrayCount) { + HFASSERT(removalIndex < arrayCount); + HFASSERT(arrayCount > 0); + HFASSERT(array[removalIndex] != nil); + [array[removalIndex] release]; + for (NSUInteger pullingIndex = removalIndex + 1; pullingIndex < arrayCount; pullingIndex++) { + array[pullingIndex - 1] = array[pullingIndex]; + } + array[arrayCount - 1] = nil; +} + +static void split_array(const restrict id *values, ChildIndex_t valueCount, restrict id *left, restrict id *right, ChildIndex_t leftArraySizeForClearing) { + const ChildIndex_t midPoint = valueCount/2; + ChildIndex_t inputIndex = 0, outputIndex = 0; + while (inputIndex < midPoint) { + left[outputIndex++] = values[inputIndex++]; + } + + /* Clear the remainder of our left array. Right array does not have to be cleared. */ + HFASSERT(outputIndex <= leftArraySizeForClearing); + while (outputIndex < leftArraySizeForClearing) { + left[outputIndex++] = nil; + } + + /* Move the second half of our values into the right array */ + outputIndex = 0; + while (inputIndex < valueCount) { + right[outputIndex++] = values[inputIndex++]; + } +} + +FORCE_STATIC_INLINE HFBTreeNode *add_child_to_node_possibly_creating_split(HFBTreeNode *node, id value, ChildIndex_t insertionLocation, BOOL isLeaf) { + ChildIndex_t childCount = count_node_values(node); + HFASSERT(insertionLocation <= childCount); + if (childCount < BTREE_ORDER) { + /* No need to make a split */ + insert_value_into_array(value, insertionLocation, node->children, childCount + 1); + node->subtreeLength = HFSum(node->subtreeLength, GET_LENGTH(value, isLeaf)); + return nil; + } + + HFASSERT(node->children[BTREE_ORDER - 1] != nil); /* we require that it be full */ + id allEntries[BTREE_ORDER + 1]; + memcpy(allEntries, node->children, BTREE_ORDER * sizeof *node->children); + allEntries[BTREE_ORDER] = nil; + + /* insert_value_into_array applies a retain, so allEntries owns a retain on its values */ + insert_value_into_array(value, insertionLocation, allEntries, BTREE_ORDER + 1); + HFBTreeNode *newNode = [[[node class] alloc] init]; + + /* figure out our total length */ + HFBTreeIndex totalLength = HFSum(node->subtreeLength, GET_LENGTH(value, isLeaf)); + + /* Distribute half our values to the new leaf */ + split_array(allEntries, sizeof allEntries / sizeof *allEntries, node->children, newNode->children, BTREE_ORDER); + + /* figure out how much is in the new array */ + HFBTreeIndex newNodeLength = sum_child_lengths(newNode->children, isLeaf); + + /* update our lengths */ + HFASSERT(newNodeLength < totalLength); + newNode->subtreeLength = newNodeLength; + node->subtreeLength = totalLength - newNodeLength; + + /* Link it in */ + HFBTreeNode *rightNode = node->right; + newNode->right = rightNode; + if (rightNode) rightNode->left = newNode; + newNode->left = node; + node->right = newNode; + return newNode; +} + +FORCE_STATIC_INLINE void add_values_to_array(const id * restrict srcValues, NSUInteger amountToCopy, id * restrict targetValues, NSUInteger amountToPush) { + // a pushed value at index X goes to index X + amountToCopy + NSUInteger pushIndex = amountToPush; + while (pushIndex--) { + targetValues[amountToCopy + pushIndex] = targetValues[pushIndex]; + } + for (NSUInteger i = 0; i < amountToCopy; i++) { + targetValues[i] = [srcValues[i] retain]; + } +} + +FORCE_STATIC_INLINE void remove_values_from_array(id * restrict array, NSUInteger amountToRemove, NSUInteger totalArrayLength) { + HFASSERT(totalArrayLength >= amountToRemove); + /* Release existing values */ + NSUInteger i; + for (i=0; i < amountToRemove; i++) { + [array[i] release]; + } + /* Move remaining values */ + for (i=amountToRemove; i < totalArrayLength; i++) { + array[i - amountToRemove] = array[i]; + } + /* Clear the end */ + for (i=totalArrayLength - amountToRemove; i < totalArrayLength; i++) { + array[i] = nil; + } +} + +FORCE_STATIC_INLINE BOOL rebalance_node_by_distributing_to_neighbors(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_NODE_MINIMUM_VALUE_COUNT); + BOOL result = NO; + HFBTreeNode *leftNeighbor = node->left, *rightNeighbor = node->right; + const ChildIndex_t leftSpaceAvailable = (leftNeighbor ? BTREE_ORDER - count_node_values(leftNeighbor) : 0); + const ChildIndex_t rightSpaceAvailable = (rightNeighbor ? BTREE_ORDER - count_node_values(rightNeighbor) : 0); + if (leftSpaceAvailable + rightSpaceAvailable >= childCount) { + /* We have enough space to redistribute. Try to do it in such a way that both neighbors end up with the same number of items. */ + ChildIndex_t itemCountForLeft = 0, itemCountForRight = 0, itemCountRemaining = childCount; + if (leftSpaceAvailable > rightSpaceAvailable) { + ChildIndex_t amountForLeft = MIN(leftSpaceAvailable - rightSpaceAvailable, itemCountRemaining); + itemCountForLeft += amountForLeft; + itemCountRemaining -= amountForLeft; + } + else if (rightSpaceAvailable > leftSpaceAvailable) { + ChildIndex_t amountForRight = MIN(rightSpaceAvailable - leftSpaceAvailable, itemCountRemaining); + itemCountForRight += amountForRight; + itemCountRemaining -= amountForRight; + } + /* Now distribute the remainder (if any) evenly, preferring the remainder to go left, because it is slightly cheaper to append to the left than prepend to the right */ + itemCountForRight += itemCountRemaining / 2; + itemCountForLeft += itemCountRemaining - (itemCountRemaining / 2); + HFASSERT(itemCountForLeft <= leftSpaceAvailable); + HFASSERT(itemCountForRight <= rightSpaceAvailable); + HFASSERT(itemCountForLeft + itemCountForRight == childCount); + + if (itemCountForLeft > 0) { + /* append to the end */ + HFBTreeIndex additionalLengthForLeft = sum_N_child_lengths(node->children, itemCountForLeft, isLeaf); + leftNeighbor->subtreeLength = HFSum(leftNeighbor->subtreeLength, additionalLengthForLeft); + add_values_to_array(node->children, itemCountForLeft, leftNeighbor->children + BTREE_ORDER - leftSpaceAvailable, 0); + HFASSERT(leftNeighbor->subtreeLength == sum_child_lengths(leftNeighbor->children, isLeaf)); + *modifiedLeftNeighbor = YES; + } + if (itemCountForRight > 0) { + /* append to the beginning */ + HFBTreeIndex additionalLengthForRight = sum_N_child_lengths(node->children + itemCountForLeft, itemCountForRight, isLeaf); + rightNeighbor->subtreeLength = HFSum(rightNeighbor->subtreeLength, additionalLengthForRight); + add_values_to_array(node->children + itemCountForLeft, itemCountForRight, rightNeighbor->children, BTREE_ORDER - rightSpaceAvailable); + HFASSERT(rightNeighbor->subtreeLength == sum_child_lengths(rightNeighbor->children, isLeaf)); + *modifiedRightNeighbor = YES; + } + /* Remove ourself from the linked list */ + if (leftNeighbor) { + leftNeighbor->right = rightNeighbor; + } + if (rightNeighbor) { + rightNeighbor->left = leftNeighbor; + } + /* Even though we've essentially orphaned ourself, we need to force ourselves consistent (by making ourselves empty) because our parent still references us, and we don't want to make our parent inconsistent. */ + for (ChildIndex_t childIndex = 0; node->children[childIndex] != nil; childIndex++) { + [node->children[childIndex] release]; + node->children[childIndex] = nil; + } + node->subtreeLength = 0; + + result = YES; + } + return result; +} + + +FORCE_STATIC_INLINE BOOL share_children(HFBTreeNode *node, ChildIndex_t childCount, HFBTreeNode *neighbor, BOOL isRightNeighbor, BOOL isLeaf) { + ChildIndex_t neighborCount = count_node_values(neighbor); + ChildIndex_t totalChildren = (childCount + neighborCount); + BOOL result = NO; + if (totalChildren <= 2 * BTREE_LEAF_ORDER && totalChildren >= 2 * BTREE_LEAF_MINIMUM_VALUE_COUNT) { + ChildIndex_t finalMyCount = totalChildren / 2; + ChildIndex_t finalNeighborCount = totalChildren - finalMyCount; + HFASSERT(finalNeighborCount < neighborCount); + HFASSERT(finalMyCount > childCount); + ChildIndex_t amountToTransfer = finalMyCount - childCount; + HFBTreeIndex lengthChange; + if (isRightNeighbor) { + /* Transfer from left end of right neighbor to this right end of this leaf. This retains the values. */ + add_values_to_array(neighbor->children, amountToTransfer, node->children + childCount, 0); + /* Remove from beginning of right neighbor. This releases them. */ + remove_values_from_array(neighbor->children, amountToTransfer, neighborCount); + lengthChange = sum_N_child_lengths(node->children + childCount, amountToTransfer, isLeaf); + } + else { + /* Transfer from right end of left neighbor to left end of this leaf */ + add_values_to_array(neighbor->children + neighborCount - amountToTransfer, amountToTransfer, node->children, childCount); + /* Remove from end of left neighbor */ + remove_values_from_array(neighbor->children + neighborCount - amountToTransfer, amountToTransfer, amountToTransfer); + lengthChange = sum_N_child_lengths(node->children, amountToTransfer, isLeaf); + } + HFASSERT(lengthChange <= neighbor->subtreeLength); + neighbor->subtreeLength -= lengthChange; + node->subtreeLength = HFSum(node->subtreeLength, lengthChange); + HFASSERT(count_node_values(node) == finalMyCount); + HFASSERT(count_node_values(neighbor) == finalNeighborCount); + result = YES; + } + return result; +} + +static BOOL rebalance_node_by_sharing_with_neighbors(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT); + BOOL result = NO; + HFBTreeNode *leftNeighbor = node->left, *rightNeighbor = node->right; + if (leftNeighbor) { + result = share_children(node, childCount, leftNeighbor, NO, isLeaf); + if (result) *modifiedLeftNeighbor = YES; + } + if (! result && rightNeighbor) { + result = share_children(node, childCount, rightNeighbor, YES, isLeaf); + if (result) *modifiedRightNeighbor = YES; + } + return result; +} + +/* Return YES if this leaf should be removed after rebalancing. Other nodes are never removed. */ +FORCE_STATIC_INLINE BOOL rebalance_node_after_deletion(HFBTreeNode *node, ChildIndex_t childCount, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT); + /* We may only delete this leaf, and not adjacent leaves. Thus our rebalancing strategy is: + If the items to the left or right have sufficient space to hold us, then push our values left or right, and delete this node. + Otherwise, steal items from the left until we have the same number of items. */ + BOOL deleteNode = NO; + if (rebalance_node_by_distributing_to_neighbors(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor)) { + deleteNode = YES; + //puts("rebalance_node_by_distributing_to_neighbors"); + } + else if (rebalance_node_by_sharing_with_neighbors(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor)) { + deleteNode = NO; + //puts("rebalance_node_by_sharing_with_neighbors"); + } + else { + [NSException raise:NSInternalInconsistencyException format:@"Unable to rebalance after deleting node %@", node]; + } + return deleteNode; +} + + +FORCE_STATIC_INLINE BOOL remove_value_from_node_with_possible_rebalance(HFBTreeNode *node, ChildIndex_t childIndex, BOOL isRootNode, BOOL isLeaf, BOOL * restrict modifiedLeftNeighbor, BOOL *restrict modifiedRightNeighbor) { + HFASSERT(childIndex < BTREE_ORDER); + HFASSERT(node != nil); + HFASSERT(node->children[childIndex] != nil); + HFBTreeIndex entryLength = GET_LENGTH(node->children[childIndex], isLeaf); + HFASSERT(entryLength <= node->subtreeLength); + node->subtreeLength -= entryLength; + BOOL deleteInputNode = NO; + +#if ! NDEBUG + const id savedChild = node->children[childIndex]; + NSUInteger childMultiplicity = 0; + NSUInteger v; + for (v = 0; v < BTREE_ORDER; v++) { + if (node->children[v] == savedChild) childMultiplicity++; + if (node->children[v] == nil) break; + } + +#endif + + /* Figure out how many children we have; start at one more than childIndex since we know that childIndex is a valid index */ + ChildIndex_t childCount; + for (childCount = childIndex + 1; childCount < BTREE_ORDER; childCount++) { + if (! node->children[childCount]) break; + } + + /* Remove our value at childIndex; this sends it a release message */ + remove_value_from_array(childIndex, node->children, childCount); + HFASSERT(childCount > 0); + childCount--; + +#if ! NDEBUG + for (v = 0; v < childCount; v++) { + if (node->children[v] == savedChild) childMultiplicity--; + } + HFASSERT(childMultiplicity == 1); +#endif + + if (childCount < BTREE_LEAF_MINIMUM_VALUE_COUNT && ! isRootNode) { + /* We have too few items; try to rebalance (this will always be possible except from the root node) */ + deleteInputNode = rebalance_node_after_deletion(node, childCount, isLeaf, modifiedLeftNeighbor, modifiedRightNeighbor); + } + else { + //NSLog(@"Deletion from %@ with %u remaining, %s root node, so no need to rebalance\n", node, childCount, isRootNode ? "is" : "is not"); + } + + return deleteInputNode; +} + +FORCE_STATIC_INLINE void update_node_having_changed_size_of_child(HFBTreeNode *node, BOOL isLeaf) { + HFBTreeIndex newLength = sum_child_lengths(node->children, isLeaf); + /* This should only be called if the length actually changes - so assert as such */ + /* I no longer think the above line is true. It's possible that we can delete a node, and then after a rebalance, we can become the same size we were before. */ + //HFASSERT(node->subtreeLength != newLength); + node->subtreeLength = newLength; +} + +struct SubtreeInfo_t { + HFBTreeBranch *branch; + ChildIndex_t childIndex; //childIndex is the index of the child of branch, not branch's index in its parent +}; + +static HFBTreeLeaf *btree_descend(HFBTree *tree, struct SubtreeInfo_t *outDescentInfo, HFBTreeIndex *insertionOffset, BOOL isForDelete) { + TreeDepth_t maxDepth = tree->depth; + HFASSERT(maxDepth != BAD_DEPTH && maxDepth <= MAX_DEPTH); + id currentBranchOrLeaf = tree->root; + HFBTreeIndex offsetForSubtree = *insertionOffset; + for (TreeDepth_t currentDepth = 0; currentDepth < maxDepth; currentDepth++) { + ASSERT_IS_BRANCH(currentBranchOrLeaf); + HFBTreeBranch *currentBranch = currentBranchOrLeaf; + HFBTreeIndex priorCombinedOffset = (HFBTreeIndex)-1; + ChildIndex_t nextChildIndex = (isForDelete ? child_index_for_deletion_at_offset : child_index_for_insertion_at_offset)(currentBranch, offsetForSubtree, &priorCombinedOffset); + outDescentInfo[currentDepth].branch = currentBranch; + outDescentInfo[currentDepth].childIndex = nextChildIndex; + offsetForSubtree -= priorCombinedOffset; + currentBranchOrLeaf = currentBranch->children[nextChildIndex]; + if (isForDelete) { + HFBTreeNode *node = currentBranchOrLeaf; + HFASSERT(node->subtreeLength > offsetForSubtree); + } + } + ASSERT_IS_LEAF(currentBranchOrLeaf); + *insertionOffset = offsetForSubtree; + return currentBranchOrLeaf; +} + +struct LeafInfo_t { + HFBTreeLeaf *leaf; + ChildIndex_t entryIndex; + HFBTreeIndex offsetOfEntryInTree; +}; + +static struct LeafInfo_t btree_find_leaf(HFBTree *tree, HFBTreeIndex offset) { + TreeDepth_t depth = tree->depth; + HFBTreeNode *currentNode = tree->root; + HFBTreeIndex remainingOffset = offset; + while (depth--) { + HFBTreeIndex beginningOffsetOfNode; + currentNode = child_containing_offset(currentNode, remainingOffset, &beginningOffsetOfNode, NO); + HFASSERT(beginningOffsetOfNode <= remainingOffset); + remainingOffset = remainingOffset - beginningOffsetOfNode; + } + ASSERT_IS_LEAF(currentNode); + HFBTreeIndex startOffsetOfEntry; + ChildIndex_t entryIndex = index_containing_offset(currentNode, remainingOffset, &startOffsetOfEntry, YES); + /* The offset of this entry is the requested offset minus the difference between its starting offset within the leaf and the requested offset within the leaf */ + HFASSERT(remainingOffset >= startOffsetOfEntry); + HFBTreeIndex offsetIntoEntry = remainingOffset - startOffsetOfEntry; + HFASSERT(offset >= offsetIntoEntry); + HFBTreeIndex beginningOffset = offset - offsetIntoEntry; + return (struct LeafInfo_t){.leaf = CHECK_CAST(currentNode, HFBTreeLeaf), .entryIndex = entryIndex, .offsetOfEntryInTree = beginningOffset}; +} + +static TreeEntry *btree_search(HFBTree *tree, HFBTreeIndex offset, HFBTreeIndex *outBeginningOffset) { + struct LeafInfo_t leafInfo = btree_find_leaf(tree, offset); + *outBeginningOffset = leafInfo.offsetOfEntryInTree; + return leafInfo.leaf->children[leafInfo.entryIndex]; +} + +static id btree_insert_returning_retained_value_for_parent(HFBTree *tree, TreeEntry *entry, HFBTreeIndex insertionOffset) { + struct SubtreeInfo_t descentInfo[MAX_DEPTH]; +#if ! NDEBUG + memset(descentInfo, -1, sizeof descentInfo); +#endif + HFBTreeIndex subtreeOffset = insertionOffset; + HFBTreeLeaf *leaf = btree_descend(tree, descentInfo, &subtreeOffset, NO); + ASSERT_IS_LEAF(leaf); + + ChildIndex_t insertionLocation = index_for_child_at_offset(leaf, subtreeOffset, YES); + HFBTreeNode *retainedValueToInsertIntoParentBranch = add_child_to_node_possibly_creating_split(leaf, entry, insertionLocation, YES); + + /* Walk up */ + TreeDepth_t depth = tree->depth; + HFASSERT(depth != BAD_DEPTH); + HFBTreeIndex entryLength = HFBTreeLength(entry); + while (depth--) { + HFBTreeBranch *branch = descentInfo[depth].branch; + branch->subtreeLength = HFSum(branch->subtreeLength, entryLength); + ChildIndex_t childIndex = descentInfo[depth].childIndex; + if (retainedValueToInsertIntoParentBranch) { + HFASSERT(branch->subtreeLength > retainedValueToInsertIntoParentBranch->subtreeLength); + /* Since we copied some stuff out from under ourselves, subtract its length */ + branch->subtreeLength -= retainedValueToInsertIntoParentBranch->subtreeLength; + HFBTreeNode *newRetainedValueToInsertIntoParentBranch = add_child_to_node_possibly_creating_split(branch, retainedValueToInsertIntoParentBranch, childIndex + 1, NO); + [retainedValueToInsertIntoParentBranch release]; + retainedValueToInsertIntoParentBranch = newRetainedValueToInsertIntoParentBranch; + } + } + return retainedValueToInsertIntoParentBranch; +} + +static BOOL btree_remove(HFBTree *tree, HFBTreeIndex deletionOffset) { + struct SubtreeInfo_t descentInfo[MAX_DEPTH]; +#if ! NDEBUG + memset(descentInfo, -1, sizeof descentInfo); +#endif + HFBTreeIndex subtreeOffset = deletionOffset; + HFBTreeLeaf *leaf = btree_descend(tree, descentInfo, &subtreeOffset, YES); + ASSERT_IS_LEAF(leaf); + + HFBTreeIndex previousOffsetSum = 0; + ChildIndex_t childIndex; + for (childIndex = 0; childIndex < BTREE_LEAF_ORDER; childIndex++) { + if (previousOffsetSum == subtreeOffset) break; + TreeEntry *entry = leaf->children[childIndex]; + HFASSERT(entry != nil); //if it were nil, then the offset is too large + HFBTreeIndex childLength = HFBTreeLength(entry); + previousOffsetSum = HFSum(childLength, previousOffsetSum); + } + HFASSERT(childIndex < BTREE_LEAF_ORDER); + HFASSERT(previousOffsetSum == subtreeOffset); + + TreeDepth_t depth = tree->depth; + HFASSERT(depth != BAD_DEPTH); + BOOL modifiedLeft = NO, modifiedRight = NO; + BOOL deleteNode = remove_value_from_node_with_possible_rebalance(leaf, childIndex, depth==0/*isRootNode*/, YES, &modifiedLeft, &modifiedRight); + HFASSERT(btree_are_cached_lengths_correct(leaf, NULL)); + while (depth--) { + HFBTreeBranch *branch = descentInfo[depth].branch; + ChildIndex_t branchChildIndex = descentInfo[depth].childIndex; + BOOL leftNeighborNeedsUpdating = modifiedLeft && branchChildIndex == 0; //if our child tweaked its left neighbor, and its left neighbor is not also a child of us, we need to inform its parent (which is our left neighbor) + BOOL rightNeighborNeedsUpdating = modifiedRight && (branchChildIndex + 1 == BTREE_BRANCH_ORDER || branch->children[branchChildIndex + 1] == NULL); //same goes for right + if (leftNeighborNeedsUpdating) { + HFASSERT(branch->left != NULL); +// NSLog(@"Updating lefty %p", branch->left); + update_node_having_changed_size_of_child(branch->left, NO); + } +#if ! NDEBUG + if (branch->left) HFASSERT(btree_are_cached_lengths_correct(branch->left, NULL)); +#endif + if (rightNeighborNeedsUpdating) { + HFASSERT(branch->right != NULL); +// NSLog(@"Updating righty %p", branch->right); + update_node_having_changed_size_of_child(branch->right, NO); + } +#if ! NDEBUG + if (branch->right) HFASSERT(btree_are_cached_lengths_correct(branch->right, NULL)); +#endif + update_node_having_changed_size_of_child(branch, NO); + modifiedLeft = NO; + modifiedRight = NO; + if (deleteNode) { + deleteNode = remove_value_from_node_with_possible_rebalance(branch, branchChildIndex, depth==0/*isRootNode*/, NO, &modifiedLeft, &modifiedRight); + } + else { + // update_node_having_changed_size_of_child(branch, NO); + // no need to delete parent nodes, so leave deleteNode as NO + } + /* Our parent may have to modify its left or right neighbor if we had to modify our left or right neighbor or if one of our children modified a neighbor that is not also a child of us. */ + modifiedLeft = modifiedLeft || leftNeighborNeedsUpdating; + modifiedRight = modifiedRight || rightNeighborNeedsUpdating; + } + + if (! deleteNode) { + /* Delete the root if it has one node and a depth of at least 1, or zero nodes and a depth of 0 */ + deleteNode = (tree->depth >= 1 && tree->root->children[1] == nil) || (tree->depth == 0 && tree->root->children[0] == nil); + } + return deleteNode; +} + +/* linkingHelper stores the last seen node for each depth. */ +static HFBTreeNode *mutable_copy_node(HFBTreeNode *node, TreeDepth_t depth, HFBTreeNode **linkingHelper) { + if (node == nil) return nil; + HFASSERT(depth != BAD_DEPTH); + Class class = (depth == 0 ? [HFBTreeLeaf class] : [HFBTreeBranch class]); + HFBTreeNode *result = [[class alloc] init]; + result->subtreeLength = node->subtreeLength; + + /* Link us in */ + HFBTreeNode *leftNeighbor = linkingHelper[0]; + if (leftNeighbor != nil) { + leftNeighbor->right = result; + result->left = leftNeighbor; + } + + /* Leave us for our future right neighbor to find */ + linkingHelper[0] = (void *)result; + + HFBTreeIndex index; + for (index = 0; index < BTREE_ORDER; index++) { + id child = node->children[index]; + if (! node->children[index]) break; + if (depth > 0) { + result->children[index] = mutable_copy_node(child, depth - 1, linkingHelper + 1); + } + else { + result->children[index] = [(TreeEntry *)child retain]; + } + } + return result; +} + +__attribute__((unused)) +static BOOL non_nulls_are_grouped_at_start(const id *ptr, NSUInteger count) { + BOOL hasSeenNull = NO; + for (NSUInteger i=0; i < count; i++) { + BOOL ptrIsNull = (ptr[i] == nil); + hasSeenNull = hasSeenNull || ptrIsNull; + if (hasSeenNull && ! ptrIsNull) { + return NO; + } + } + return YES; +} + + +static void btree_recursive_check_integrity(HFBTree *tree, HFBTreeNode *branchOrLeaf, TreeDepth_t depth, HFBTreeNode **linkHelper) { + HFASSERT(linkHelper[0] == branchOrLeaf->left); + if (linkHelper[0]) HFASSERT(linkHelper[0]->right == branchOrLeaf); + linkHelper[0] = branchOrLeaf; + + if (depth == 0) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + HFASSERT(non_nulls_are_grouped_at_start(leaf->children, BTREE_LEAF_ORDER)); + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + HFASSERT(non_nulls_are_grouped_at_start(branch->children, BTREE_BRANCH_ORDER)); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + btree_recursive_check_integrity(tree, branch->children[i], depth - 1, linkHelper + 1); + } + } + ChildIndex_t childCount = count_node_values(branchOrLeaf); + if (depth < tree->depth) { // only the root may have fewer than BTREE_NODE_MINIMUM_VALUE_COUNT + HFASSERT(childCount >= BTREE_NODE_MINIMUM_VALUE_COUNT); + } + HFASSERT(childCount <= BTREE_ORDER); +} + +static HFBTreeIndex btree_recursive_check_integrity_of_cached_lengths(HFBTreeNode *branchOrLeaf) { + HFBTreeIndex result = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i = 0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result = HFSum(result, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (branch->children[i]) { + HFBTreeIndex subtreeLength = btree_recursive_check_integrity_of_cached_lengths(branch->children[i]); + result = HFSum(result, subtreeLength); + } + } + } + HFASSERT(result == branchOrLeaf->subtreeLength); + return result; +} + +static BOOL btree_are_cached_lengths_correct(HFBTreeNode *branchOrLeaf, HFBTreeIndex *outLength) { + if (! branchOrLeaf) { + if (outLength) *outLength = 0; + return YES; + } + HFBTreeIndex length = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + length = HFSum(length, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i=0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + HFBTreeIndex subLength = (HFBTreeIndex)-1; + if (! btree_are_cached_lengths_correct(branch->children[i], &subLength)) { + return NO; + } + length = HFSum(length, subLength); + } + } + if (outLength) *outLength = length; + return length == branchOrLeaf->subtreeLength; +} + +#if FIXUP_LENGTHS +static NSUInteger btree_entry_count(HFBTreeNode *branchOrLeaf) { + NSUInteger result = 0; + if (branchOrLeaf == nil) { + // do nothing + } + else if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result++; + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i=0; i < BTREE_LEAF_ORDER; i++) { + if (! branch->children[i]) break; + result += btree_entry_count(branch->children[i]); + } + } + return result; +} + +static HFBTreeIndex btree_recursive_fixup_cached_lengths(HFBTree *tree, HFBTreeNode *branchOrLeaf) { + HFBTreeIndex result = 0; + if (IS_LEAF(branchOrLeaf)) { + HFBTreeLeaf *leaf = CHECK_CAST(branchOrLeaf, HFBTreeLeaf); + for (ChildIndex_t i = 0; i < BTREE_LEAF_ORDER; i++) { + if (! leaf->children[i]) break; + result = HFSum(result, HFBTreeLength(leaf->children[i])); + } + } + else { + HFBTreeBranch *branch = CHECK_CAST(branchOrLeaf, HFBTreeBranch); + for (ChildIndex_t i = 0; i < BTREE_BRANCH_ORDER; i++) { + if (! branch->children[i]) break; + btree_recursive_fixup_cached_lengths(tree, branch->children[i]); + result = HFSum(result, CHECK_CAST(branch->children[i], HFBTreeNode)->subtreeLength); + } + } + branchOrLeaf->subtreeLength = result; + return result; +} +#endif + +FORCE_STATIC_INLINE void btree_apply_function_to_entries(HFBTree *tree, HFBTreeIndex offset, BOOL (*func)(id, HFBTreeIndex, void *), void *userInfo) { + struct LeafInfo_t leafInfo = btree_find_leaf(tree, offset); + HFBTreeLeaf *leaf = leafInfo.leaf; + ChildIndex_t entryIndex = leafInfo.entryIndex; + HFBTreeIndex leafOffset = leafInfo.offsetOfEntryInTree; + BOOL continueApplying = YES; + while (leaf != NULL) { + for (; entryIndex < BTREE_LEAF_ORDER; entryIndex++) { + TreeEntry *entry = leaf->children[entryIndex]; + if (! entry) break; + continueApplying = func(entry, leafOffset, userInfo); + if (! continueApplying) break; + leafOffset = HFSum(leafOffset, HFBTreeLength(entry)); + } + if (! continueApplying) break; + leaf = CHECK_CAST_OR_NULL(leaf->right, HFBTreeLeaf); + entryIndex = 0; + } +} + +- (NSEnumerator *)entryEnumerator { + if (! root) return [@[] objectEnumerator]; + HFBTreeLeaf *leaf = btree_find_leaf(self, 0).leaf; + return [[[HFBTreeEnumerator alloc] initWithLeaf:leaf] autorelease]; +} + + +static BOOL add_to_array(id entry, HFBTreeIndex offset __attribute__((unused)), void *array) { + [(id)array addObject:entry]; + return YES; +} + +- (NSArray *)allEntries { + if (! root) return @[]; + NSUInteger treeCapacity = 1; + unsigned int depthIndex = depth; + while (depthIndex--) treeCapacity *= BTREE_ORDER; + NSMutableArray *result = [NSMutableArray arrayWithCapacity: treeCapacity/2]; //assume we're half full + btree_apply_function_to_entries(self, 0, add_to_array, result); + return result; +} + +- (void)applyFunction:(BOOL (*)(id entry, HFBTreeIndex offset, void *userInfo))func toEntriesStartingAtOffset:(HFBTreeIndex)offset withUserInfo:(void *)userInfo { + NSParameterAssert(func != NULL); + if (! root) return; + btree_apply_function_to_entries(self, offset, func, userInfo); +} + +@end + + +@implementation HFBTreeEnumerator + +- (instancetype)initWithLeaf:(HFBTreeLeaf *)leaf { + NSParameterAssert(leaf != nil); + ASSERT_IS_LEAF(leaf); + currentLeaf = leaf; + return self; +} + +- (id)nextObject { + if (! currentLeaf) return nil; + if (childIndex >= BTREE_LEAF_ORDER || currentLeaf->children[childIndex] == nil) { + childIndex = 0; + currentLeaf = CHECK_CAST_OR_NULL(currentLeaf->right, HFBTreeLeaf); + } + if (currentLeaf == nil) return nil; + HFASSERT(currentLeaf->children[childIndex] != nil); + return currentLeaf->children[childIndex++]; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.h b/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.h new file mode 100644 index 000000000..3c1aac075 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.h @@ -0,0 +1,30 @@ +// +// HFBTreeByteArray.h +// HexFiend_2 +// +// Created by peter on 4/28/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import + +@class HFBTree; + +/*! @class HFBTreeByteArray +@brief The principal efficient implementation of HFByteArray. + +HFBTreeByteArray is an efficient subclass of HFByteArray that stores @link HFByteSlice HFByteSlices@endlink, using a 10-way B+ tree. This allows for insertion, deletion, and searching in approximately log-base-10 time. + +Create an HFBTreeByteArray via \c -init. It has no methods other than those on HFByteArray. +*/ + +@interface HFBTreeByteArray : HFByteArray { +@private + HFBTree *btree; +} + +/*! Designated initializer for HFBTreeByteArray. +*/ +- (instancetype)init; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.m b/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.m new file mode 100644 index 000000000..33ba96739 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFBTreeByteArray.m @@ -0,0 +1,279 @@ +// +// HFBTreeByteArray.m +// HexFiend_2 +// +// Created by peter on 4/28/09. +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +@implementation HFBTreeByteArray + +- (instancetype)init { + if ((self = [super init])) { + btree = [[HFBTree alloc] init]; + } + return self; +} + +- (void)dealloc { + [btree release]; + [super dealloc]; +} + +- (unsigned long long)length { + return [btree length]; +} + +- (NSArray *)byteSlices { + return [btree allEntries]; +} + +- (NSEnumerator *)byteSliceEnumerator { + return [btree entryEnumerator]; +} + +- (NSString*)description { + NSMutableArray* result = [NSMutableArray array]; + NSEnumerator *enumer = [self byteSliceEnumerator]; + HFByteSlice *slice; + unsigned long long offset = 0; + while ((slice = [enumer nextObject])) { + unsigned long long length = [slice length]; + [result addObject:[NSString stringWithFormat:@"{%llu - %llu}", offset, length]]; + offset = HFSum(offset, length); + } + if (! [result count]) return @"(empty tree)"; + return [NSString stringWithFormat:@"<%@: %p>: %@", [self class], self, [result componentsJoinedByString:@" "]]; + +} + +struct HFBTreeByteArrayCopyInfo_t { + unsigned char *dst; + unsigned long long startingOffset; + NSUInteger remainingLength; +}; + +static BOOL copy_bytes(id entry, HFBTreeIndex offset, void *userInfo) { + struct HFBTreeByteArrayCopyInfo_t *info = userInfo; + HFByteSlice *slice = entry; + HFASSERT(slice != nil); + HFASSERT(info != NULL); + HFASSERT(offset <= info->startingOffset); + + unsigned long long sliceLength = [slice length]; + HFASSERT(sliceLength > 0); + unsigned long long offsetIntoSlice = info->startingOffset - offset; + HFASSERT(offsetIntoSlice < sliceLength); + NSUInteger amountToCopy = ll2l(MIN(info->remainingLength, sliceLength - offsetIntoSlice)); + HFRange srcRange = HFRangeMake(info->startingOffset - offset, amountToCopy); + [slice copyBytes:info->dst range:srcRange]; + info->dst += amountToCopy; + info->startingOffset = HFSum(info->startingOffset, amountToCopy); + info->remainingLength -= amountToCopy; + return info->remainingLength > 0; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + HFASSERT(HFMaxRange(range) <= [self length]); + if (range.length > 0) { + struct HFBTreeByteArrayCopyInfo_t copyInfo = {.dst = dst, .remainingLength = ll2l(range.length), .startingOffset = range.location}; + [btree applyFunction:copy_bytes toEntriesStartingAtOffset:range.location withUserInfo:©Info]; + } +} + +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset { + return [btree entryContainingOffset:offset beginningOffset:actualOffset]; +} + +/* Given a HFByteArray and a range contained within it, return the first byte slice containing that range, and the range within that slice. Modifies the given range to reflect what you get when the returned slice is removed. */ +static inline HFByteSlice *findInitialSlice(HFBTree *btree, HFRange *inoutArrayRange, HFRange *outRangeWithinSlice) { + const HFRange arrayRange = *inoutArrayRange; + const unsigned long long arrayRangeEnd = HFMaxRange(arrayRange); + + unsigned long long offsetIntoSlice, lengthFromOffsetIntoSlice; + + unsigned long long beginningOffset; + HFByteSlice *slice = [btree entryContainingOffset:arrayRange.location beginningOffset:&beginningOffset]; + const unsigned long long sliceLength = [slice length]; + HFASSERT(beginningOffset <= arrayRange.location); + offsetIntoSlice = arrayRange.location - beginningOffset; + HFASSERT(offsetIntoSlice < sliceLength); + + unsigned long long sliceEndInArray = HFSum(sliceLength, beginningOffset); + if (sliceEndInArray <= arrayRangeEnd) { + /* Our slice ends before or at the requested range end */ + lengthFromOffsetIntoSlice = sliceLength - offsetIntoSlice; + } + else { + /* Our slice ends after the requested range end */ + unsigned long long overflow = sliceEndInArray - arrayRangeEnd; + HFASSERT(HFSum(overflow, offsetIntoSlice) < sliceLength); + lengthFromOffsetIntoSlice = sliceLength - HFSum(overflow, offsetIntoSlice); + } + + /* Set the out range to the input range minus the range consumed by the slice */ + inoutArrayRange->location = MIN(sliceEndInArray, arrayRangeEnd); + inoutArrayRange->length = arrayRangeEnd - inoutArrayRange->location; + + /* Set the out range within the slice to what we computed */ + *outRangeWithinSlice = HFRangeMake(offsetIntoSlice, lengthFromOffsetIntoSlice); + + return slice; +} + +- (BOOL)fastPathInsertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset { + HFASSERT(offset > 0); + unsigned long long priorSliceOffset; + HFByteSlice *priorSlice = [btree entryContainingOffset:offset - 1 beginningOffset:&priorSliceOffset]; + HFByteSlice *appendedSlice = [priorSlice byteSliceByAppendingSlice:slice]; + if (appendedSlice) { + [btree removeEntryAtOffset:priorSliceOffset]; + [btree insertEntry:appendedSlice atOffset:priorSliceOffset]; + return YES; + } + else { + return NO; + } +} + +- (void)insertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + + if (offset == 0) { + [btree insertEntry:slice atOffset:0]; + } + else if (offset == [btree length]) { + if (! [self fastPathInsertByteSlice:slice atOffset:offset]) { + [btree insertEntry:slice atOffset:offset]; + } + } + else { + unsigned long long beginningOffset; + HFByteSlice *overlappingSlice = [btree entryContainingOffset:offset beginningOffset:&beginningOffset]; + if (beginningOffset == offset) { + if (! [self fastPathInsertByteSlice:slice atOffset:offset]) { + [btree insertEntry:slice atOffset:offset]; + } + } + else { + HFASSERT(offset > beginningOffset); + unsigned long long offsetIntoSlice = offset - beginningOffset; + unsigned long long sliceLength = [overlappingSlice length]; + HFASSERT(sliceLength > offsetIntoSlice); + HFByteSlice *left = [overlappingSlice subsliceWithRange:HFRangeMake(0, offsetIntoSlice)]; + HFByteSlice *right = [overlappingSlice subsliceWithRange:HFRangeMake(offsetIntoSlice, sliceLength - offsetIntoSlice)]; + [btree removeEntryAtOffset:beginningOffset]; + + [btree insertEntry:right atOffset:beginningOffset]; + + /* Try the fast appending path */ + HFByteSlice *joinedSlice = [left byteSliceByAppendingSlice:slice]; + if (joinedSlice) { + [btree insertEntry:joinedSlice atOffset:beginningOffset]; + } + else { + [btree insertEntry:slice atOffset:beginningOffset]; + [btree insertEntry:left atOffset:beginningOffset]; + } + } + } +} + +- (void)deleteBytesInRange:(HFRange)range { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFRange remainingRange = range; + + HFASSERT(HFMaxRange(range) <= [self length]); + if (range.length == 0) return; //nothing to delete + + //fast path for deleting everything + if (range.location == 0 && range.length == [self length]) { + [btree removeAllEntries]; + return; + } + + unsigned long long beforeLength = [self length]; + + unsigned long long rangeStartLocation = range.location; + HFByteSlice *beforeSlice = nil, *afterSlice = nil; + while (remainingRange.length > 0) { + HFRange rangeWithinSlice; + HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice); + const unsigned long long sliceLength = [slice length]; + const unsigned long long rangeWithinSliceEnd = HFMaxRange(rangeWithinSlice); + HFRange lefty = HFRangeMake(0, rangeWithinSlice.location); + HFRange righty = HFRangeMake(rangeWithinSliceEnd, sliceLength - rangeWithinSliceEnd); + HFASSERT(lefty.length == 0 || beforeSlice == nil); + HFASSERT(righty.length == 0 || afterSlice == nil); + + unsigned long long beginningOffset = remainingRange.location - HFMaxRange(rangeWithinSlice); + + if (lefty.length > 0){ + beforeSlice = [slice subsliceWithRange:lefty]; + rangeStartLocation = beginningOffset; + } + if (righty.length > 0) afterSlice = [slice subsliceWithRange:righty]; + + [btree removeEntryAtOffset:beginningOffset]; + remainingRange.location = beginningOffset; + } + if (afterSlice) { + [self insertByteSlice:afterSlice atOffset:rangeStartLocation]; + } + if (beforeSlice) { + [self insertByteSlice:beforeSlice atOffset:rangeStartLocation]; + } + + unsigned long long afterLength = [self length]; + HFASSERT(beforeLength - afterLength == range.length); +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + + if (lrange.length > 0) { + [self deleteBytesInRange:lrange]; + } + if ([slice length] > 0) { + [self insertByteSlice:slice atOffset:lrange.location]; + } +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + HFBTreeByteArray *result = [[[self class] alloc] init]; + [result->btree release]; + result->btree = [btree mutableCopy]; + return result; +} + +- (id)subarrayWithRange:(HFRange)range { + if (range.location == 0 && range.length == [self length]) { + return [[self mutableCopy] autorelease]; + } + HFBTreeByteArray *result = [[[[self class] alloc] init] autorelease]; + HFRange remainingRange = range; + unsigned long long offsetInResult = 0; + while (remainingRange.length > 0) { + HFRange rangeWithinSlice; + HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice); + HFByteSlice *subslice; + if (rangeWithinSlice.location == 0 && rangeWithinSlice.length == [slice length]) { + subslice = slice; + } + else { + subslice = [slice subsliceWithRange:rangeWithinSlice]; + } + [result insertByteSlice:subslice atOffset:offsetInResult]; + offsetInResult = HFSum(offsetInResult, rangeWithinSlice.length); + } + return result; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteArray.h b/thirdparty/SameBoy-old/HexFiend/HFByteArray.h new file mode 100644 index 000000000..dd452d5b1 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteArray.h @@ -0,0 +1,180 @@ +// +// HFByteArray.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@class HFByteSlice, HFFileReference, HFByteRangeAttributeArray; + +typedef NS_ENUM(NSUInteger, HFByteArrayDataStringType) { + HFHexDataStringType, + HFASCIIDataStringType +}; + + +/*! @class HFByteArray +@brief The principal Model class for HexFiend's MVC architecture. + +HFByteArray implements the Model portion of HexFiend.framework. It is logically a mutable, resizable array of bytes, with a 64 bit length. It is somewhat analagous to a 64 bit version of NSMutableData, except that it is designed to enable efficient (faster than O(n)) implementations of insertion and deletion. + +HFByteArray, being an abstract class, will raise an exception if you attempt to instantiate it directly. For most uses, instantiate HFBTreeByteArray instead, with the usual [[class alloc] init]. + +HFByteArray also exposes itself as an array of @link HFByteSlice HFByteSlices@endlink, which are logically immutable arrays of bytes. which is useful for operations such as file saving that need to access the underlying byte slices. + +HFByteArray contains a generation count, which is incremented whenever the HFByteArray changes (to allow caches to be implemented on top of it). It also includes the notion of locking: a locked HFByteArray will raise an exception if written to, but it may still be read. + +ByteArrays have the usual threading restrictions for non-concurrent data structures. It is safe to read an HFByteArray concurrently from multiple threads. It is not safe to read an HFByteArray while it is being modified from another thread, nor is it safe to modify one simultaneously from two threads. + +HFByteArray is an abstract class. It will raise an exception if you attempt to instantiate it directly. The principal concrete subclass is HFBTreeByteArray. +*/ + +@class HFByteRangeAttributeArray; + +@interface HFByteArray : NSObject { +@private + NSUInteger changeLockCounter; + NSUInteger changeGenerationCount; +} + +/*! @name Initialization + */ +//@{ +/*! Initialize to a byte array containing only the given slice. */ +- (instancetype)initWithByteSlice:(HFByteSlice *)slice; + +/*! Initialize to a byte array containing the slices of the given array. */ +- (instancetype)initWithByteArray:(HFByteArray *)array; +//@} + + +/*! @name Accessing raw data +*/ +//@{ + +/*! Returns the length of the HFByteArray as a 64 bit unsigned long long. This is an abstract method that concrete subclasses must override. */ +- (unsigned long long)length; + +/*! Copies a range of bytes into a buffer. This is an abstract method that concrete subclasses must override. */ +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range; +//@} + +/*! @name Accessing byte slices + Methods to access the byte slices underlying the HFByteArray. +*/ +//@{ +/*! Returns the contents of the receiver as an array of byte slices. This is an abstract method that concrete subclasses must override. */ +- (NSArray *)byteSlices; + +/*! Returns an NSEnumerator representing the byte slices of the receiver. This is implemented as enumerating over the result of -byteSlices, but subclasses can override this to be more efficient. */ +- (NSEnumerator *)byteSliceEnumerator; + +/*! Returns the byte slice containing the byte at the given index, and the actual offset of this slice. */ +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset; +//@} + +/*! @name Modifying the byte array + Methods to modify the given byte array. +*/ +//@{ +/*! Insert an HFByteSlice in the given range. The maximum value of the range must not exceed the length of the subarray. The length of the given slice is not required to be equal to length of the range - in other words, this method may change the length of the receiver. This is an abstract method that concrete subclasses must override. */ +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange; + +/*! Insert an HFByteArray in the given range. This is implemented via calling insertByteSlice:inRange: with the byte slices from the given byte array. */ +- (void)insertByteArray:(HFByteArray *)array inRange:(HFRange)lrange; + +/*! Delete bytes in the given range. This is implemented on the base class by creating an empty byte array and inserting it in the range to be deleted, via insertByteSlice:inRange:. */ +- (void)deleteBytesInRange:(HFRange)range; + +/*! Returns a new HFByteArray containing the given range. This is an abstract method that concrete subclasses must override. */ +- (HFByteArray *)subarrayWithRange:(HFRange)range; +//@} + +/*! @name Write locking and generation count + Methods to lock and query the lock that prevents writes. +*/ +//@{ + +/*! Increment the change lock. Until the change lock reaches 0, all modifications to the receiver will raise an exception. */ +- (void)incrementChangeLockCounter; + +/*! Decrement the change lock. If the change lock reaches 0, modifications will be allowed again. */ +- (void)decrementChangeLockCounter; + +/*! Query if the changes are locked. This method is KVO compliant. */ +- (BOOL)changesAreLocked; +//@} + +/* @name Generation count + Manipulate the generation count */ +// @{ +/*! Increments the generation count, unless the receiver is locked, in which case it raises an exception. All subclasses of HFByteArray should call this method at the beginning of any overridden method that may modify the receiver. + @param sel The selector that would modify the receiver (e.g. deleteBytesInRange:). This is usually _cmd. */ +- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel; + +/*! Return the change generation count. Every change to the ByteArray increments this by one or more. This can be used for caching layers on top of HFByteArray, to known when to expire their cache. */ +- (NSUInteger)changeGenerationCount; + +//@} + + + +/*! @name Searching +*/ +//@{ +/*! Searches the receiver for a byte array matching findBytes within the given range, and returns the index that it was found. This is a concrete method on HFByteArray. + @param findBytes The HFByteArray containing the data to be found (the needle to the receiver's haystack). + @param range The range of the receiver in which to search. The end of the range must not exceed the receiver's length. + @param forwards If this is YES, then the first match within the range is returned. Otherwise the last is returned. + @param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the search operation. + @return The index in the receiver of bytes equal to findBytes, or ULLONG_MAX if the byte array was not found (or the operation was cancelled) +*/ +- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker; +//@} + +@end + + +/*! @category HFByteArray(HFFileWriting) + @brief HFByteArray methods for writing to files, and preparing other HFByteArrays for potentially destructive file writes. +*/ +@interface HFByteArray (HFFileWriting) +/*! Attempts to write the receiver to a file. This is a concrete method on HFByteArray. + @param targetURL A URL to the file to be written to. It is OK for the receiver to contain one or more instances of HFByteSlice that are sourced from the file. + @param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the write operation. + @param error An out NSError parameter. + @return YES if the write succeeded, NO if it failed. +*/ +- (BOOL)writeToFile:(NSURL *)targetURL trackingProgress:(id)progressTracker error:(NSError **)error; + +/*! Returns the ranges of the file that would be modified, if the receiver were written to it. This is useful (for example) in determining if the clipboard can be preserved after a save operation. This is a concrete method on HFByteArray. + @param reference An HFFileReference to the file to be modified + @return An array of @link HFRangeWrapper HFRangeWrappers@endlink, representing the ranges of the file that would be affected. If no range would be affected, the result is an empty array. +*/ +- (NSArray *)rangesOfFileModifiedIfSavedToFile:(HFFileReference *)reference; + +/*! Attempts to modify the receiver so that it no longer depends on any of the HFRanges in the array within the given file. It is not necessary to perform this operation on the byte array that is being written to the file. + @param ranges An array of HFRangeWrappers, representing ranges in the given file that the receiver should no longer depend on. + @param reference The HFFileReference that the receiver should no longer depend on. + @param hint A dictionary that can be used to improve the efficiency of the operation, by allowing multiple byte arrays to share the same state. If you plan to call this method on multiple byte arrays, pass the first one an empty NSMutableDictionary, and pass the same dictionary to subsequent calls. + @return A YES return indicates the operation was successful, and the receiver no longer contains byte slices that source data from any of the ranges of the given file (or never did). A NO return indicates that breaking the dependencies would require too much memory, and so the receiver still depends on some of those ranges. +*/ +- (BOOL)clearDependenciesOnRanges:(NSArray *)ranges inFile:(HFFileReference *)reference hint:(NSMutableDictionary *)hint; + +@end + + +/*! @category HFByteArray(HFAttributes) + @brief HFByteArray methods for attributes of byte arrays. +*/ +@interface HFByteArray (HFAttributes) + +/*! Returns a byte range attribute array for the bytes in the given range. */ +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range; + +/*! Returns the HFByteArray level byte range attribute array. Default is to return nil. */ +- (HFByteRangeAttributeArray *)byteRangeAttributeArray; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteArray.m b/thirdparty/SameBoy-old/HexFiend/HFByteArray.m new file mode 100644 index 000000000..55f25cc81 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteArray.m @@ -0,0 +1,218 @@ +// +// HFByteArray.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + + +@implementation HFByteArray + +- (instancetype)init { + if ([self class] == [HFByteArray class]) { + [NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead, like HFBTreeByteArray."]; + } + return [super init]; +} + +- (instancetype)initWithByteSlice:(HFByteSlice *)slice { + if(!(self = [self init])) return nil; + self = [self init]; + [self insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + return self; +} + +- (instancetype)initWithByteArray:(HFByteArray *)array { + if(!(self = [self init])) return nil; + NSEnumerator *e = [array byteSliceEnumerator]; + HFByteSlice *slice; + while((slice = [e nextObject])) { + [self insertByteSlice:slice inRange:HFRangeMake([self length], 0)]; + } + return self; +} + +- (NSArray *)byteSlices { UNIMPLEMENTED(); } +- (unsigned long long)length { UNIMPLEMENTED(); } +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); } +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { USE(slice); USE(lrange); UNIMPLEMENTED_VOID(); } + +- (NSEnumerator *)byteSliceEnumerator { + return [[self byteSlices] objectEnumerator]; +} + +- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset { + HFByteSlice *slice; + unsigned long long current = 0; + NSEnumerator *enumer = [self byteSliceEnumerator]; + while ((slice = [enumer nextObject])) { + unsigned long long sum = HFSum([slice length], current); + if (sum > offset) break; + current = sum; + } + if (actualOffset) *actualOffset = current; + return slice; +} + +- (void)insertByteArray:(HFByteArray*)array inRange:(HFRange)lrange { + REQUIRE_NOT_NULL(array); + HFASSERT(HFRangeIsSubrangeOfRange(lrange, HFRangeMake(0, [self length]))); +#ifndef NDEBUG + unsigned long long expectedLength = [self length] - lrange.length + [array length]; +#endif + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + NSEnumerator *sliceEnumerator; + HFByteSlice *byteSlice; + if (array == self) { + /* Guard against self insertion */ + sliceEnumerator = [[array byteSlices] objectEnumerator]; + } + else { + sliceEnumerator = [array byteSliceEnumerator]; + } + while ((byteSlice = [sliceEnumerator nextObject])) { + [self insertByteSlice:byteSlice inRange:lrange]; + lrange.location += [byteSlice length]; + lrange.length = 0; + } + /* If there were no slices, delete the lrange */ + if (lrange.length > 0) { + [self deleteBytesInRange:lrange]; + } +#ifndef NDEBUG + HFASSERT(expectedLength == [self length]); +#endif +} + +- (HFByteArray *)subarrayWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); } + +- (id)mutableCopyWithZone:(NSZone *)zone { + USE(zone); + return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain]; +} + +- (id)copyWithZone:(NSZone *)zone { + USE(zone); + return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain]; +} + +- (void)deleteBytesInRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFByteSlice* slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData data]]; + [self insertByteSlice:slice inRange:lrange]; + [slice release]; +} + +- (BOOL)isEqual:v { + REQUIRE_NOT_NULL(v); + if (self == v) return YES; + else if (! [v isKindOfClass:[HFByteArray class]]) return NO; + else { + HFByteArray* obj = v; + unsigned long long length = [self length]; + if (length != [obj length]) return NO; + unsigned long long offset; + unsigned char buffer1[1024]; + unsigned char buffer2[sizeof buffer1 / sizeof *buffer1]; + for (offset = 0; offset < length; offset += sizeof buffer1) { + size_t amountToGrab = sizeof buffer1; + if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset); + [self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)]; + [obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)]; + if (memcmp(buffer1, buffer2, amountToGrab)) return NO; + } + } + return YES; +} + +- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker { + UNIMPLEMENTED(); +} + +- (BOOL)_debugIsEqual:(HFByteArray *)v { + REQUIRE_NOT_NULL(v); + if (! [v isKindOfClass:[HFByteArray class]]) return NO; + HFByteArray* obj = v; + unsigned long long length = [self length]; + if (length != [obj length]) { + printf("Lengths differ: %llu versus %llu\n", length, [obj length]); + abort(); + return NO; + } + + unsigned long long offset; + unsigned char buffer1[1024]; + unsigned char buffer2[sizeof buffer1 / sizeof *buffer1]; + for (offset = 0; offset < length; offset += sizeof buffer1) { + memset(buffer1, 0, sizeof buffer1); + memset(buffer2, 0, sizeof buffer2); + size_t amountToGrab = sizeof buffer1; + if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset); + [self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)]; + [obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)]; + size_t i; + for (i=0; i < amountToGrab; i++) { + if (buffer1[i] != buffer2[i]) { + printf("Inconsistency found at %llu (%02x versus %02x)\n", i + offset, buffer1[i], buffer2[i]); + abort(); + return NO; + } + } + } + return YES; +} + +- (NSData *)_debugData { + NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)[self length]]; + [self copyBytes:[data mutableBytes] range:HFRangeMake(0, [self length])]; + return data; +} + +- (BOOL)_debugIsEqualToData:(NSData *)val { + REQUIRE_NOT_NULL(val); + HFByteArray *byteArray = [[NSClassFromString(@"HFFullMemoryByteArray") alloc] init]; + HFByteSlice *byteSlice = [[HFFullMemoryByteSlice alloc] initWithData:val]; + [byteArray insertByteSlice:byteSlice inRange:HFRangeMake(0, 0)]; + [byteSlice release]; + BOOL result = [self _debugIsEqual:byteArray]; + [byteArray release]; + return result; +} + +- (void)incrementChangeLockCounter { + [self willChangeValueForKey:@"changesAreLocked"]; + if (HFAtomicIncrement(&changeLockCounter, NO) == 0) { + [NSException raise:NSInvalidArgumentException format:@"change lock counter overflow for %@", self]; + } + [self didChangeValueForKey:@"changesAreLocked"]; +} + +- (void)decrementChangeLockCounter { + [self willChangeValueForKey:@"changesAreLocked"]; + if (HFAtomicDecrement(&changeLockCounter, NO) == NSUIntegerMax) { + [NSException raise:NSInvalidArgumentException format:@"change lock counter underflow for %@", self]; + } + [self didChangeValueForKey:@"changesAreLocked"]; +} + +- (BOOL)changesAreLocked { + return !! changeLockCounter; +} + +- (NSUInteger)changeGenerationCount { + return changeGenerationCount; +} + +- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel { + if (changeLockCounter) { + [NSException raise:NSInvalidArgumentException format:@"Selector %@ sent to a locked byte array %@", NSStringFromSelector(sel), self]; + } + else { + HFAtomicIncrement(&changeGenerationCount, YES); + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteArray_Internal.h b/thirdparty/SameBoy-old/HexFiend/HFByteArray_Internal.h new file mode 100644 index 000000000..648dd92c9 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteArray_Internal.h @@ -0,0 +1,8 @@ +#import + +@interface HFByteArray (HFInternal) + +- (BOOL)_debugIsEqual:(HFByteArray *)val; +- (BOOL)_debugIsEqualToData:(NSData *)val; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteSlice.h b/thirdparty/SameBoy-old/HexFiend/HFByteSlice.h new file mode 100644 index 000000000..b17da08d0 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteSlice.h @@ -0,0 +1,53 @@ +// +// HFByteSlice.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@class HFFileReference, HFByteRangeAttributeArray; + +/*! @class HFByteSlice +@brief A class representing a source of data for an HFByteArray. + +HFByteSlice is an abstract class encapsulating primitive data sources (files, memory buffers, etc.). Each source must support random access reads, and have a well defined length. All HFByteSlices are \b immutable. + +The two principal subclasses of HFByteSlice are HFSharedMemoryByteSlice and HFFileByteSlice, which respectively encapsulate data from memory and from a file. +*/ +@interface HFByteSlice : NSObject { + NSUInteger retainCount; +} + +/*! Return the length of the byte slice as a 64 bit value. This is an abstract method that concrete subclasses must override. */ +- (unsigned long long)length; + +/*! Copies a range of data from the byte slice into an in-memory buffer. This is an abstract method that concrete subclasses must override. */ +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range; + +/*! Returns a new slice containing a subrange of the given slice. This is an abstract method that concrete subclasses must override. */ +- (HFByteSlice *)subsliceWithRange:(HFRange)range; + +/*! Attempts to create a new byte slice by appending one byte slice to another. This does not modify the receiver or the slice argument (after all, both are immutable). This is provided as an optimization, and is allowed to return nil if the appending cannot be done efficiently. The default implementation returns nil. +*/ +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice; + +/*! Returns YES if the receiver is sourced from a file. The default implementation returns NO. This is used to estimate cost when writing to a file. +*/ +- (BOOL)isSourcedFromFile; + +/*! For a given file reference, returns the range within the file that the receiver is sourced from. If the receiver is not sourced from this file, returns {ULLONG_MAX, ULLONG_MAX}. The default implementation returns {ULLONG_MAX, ULLONG_MAX}. This is used during file saving to to determine how to properly overwrite a given file. +*/ +- (HFRange)sourceRangeForFile:(HFFileReference *)reference; + +@end + +/*! @category HFByteSlice(HFAttributes) + @brief Methods for querying attributes of individual byte slices. */ +@interface HFByteSlice (HFAttributes) + +/*! Returns the attributes for the bytes in the given range. */ +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteSlice.m b/thirdparty/SameBoy-old/HexFiend/HFByteSlice.m new file mode 100644 index 000000000..fadb442a8 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteSlice.m @@ -0,0 +1,85 @@ +// +// HFByteSlice.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + + +@implementation HFByteSlice + +- (instancetype)init { + if ([self class] == [HFByteSlice class]) { + [NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead."]; + } + return [super init]; +} + +- (unsigned long long)length { UNIMPLEMENTED(); } + +- (void)copyBytes:(unsigned char*)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); } + +- (HFByteSlice *)subsliceWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); } + +- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second { + const unsigned long long length = [self length]; + + //clip the range to our extent + range.location = llmin(range.location, length); + range.length = llmin(range.length, length - range.location); + + HFRange firstRange = {0, range.location}; + HFRange secondRange = {range.location + range.length, [self length] - (range.location + range.length)}; + + if (first) { + if (firstRange.length > 0) + *first = [self subsliceWithRange:firstRange]; + else + *first = nil; + } + + if (second) { + if (secondRange.length > 0) + *second = [self subsliceWithRange:secondRange]; + else + *second = nil; + } +} + +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice { + USE(slice); + return nil; +} + +- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range { + USE(range); + return nil; +} + +- (BOOL)isSourcedFromFile { + return NO; +} + +- (HFRange)sourceRangeForFile:(HFFileReference *)reference { + USE(reference); + return HFRangeMake(ULLONG_MAX, ULLONG_MAX); +} + +- (id)retain { + HFAtomicIncrement(&retainCount, NO); + return self; +} + +- (oneway void)release { + if (HFAtomicDecrement(&retainCount, NO) == (NSUInteger)(-1)) { + [self dealloc]; + } +} + +- (NSUInteger)retainCount { + return 1 + retainCount; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFByteSlice_Private.h b/thirdparty/SameBoy-old/HexFiend/HFByteSlice_Private.h new file mode 100644 index 000000000..2827bfd33 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFByteSlice_Private.h @@ -0,0 +1,7 @@ +#import + +@interface HFByteSlice (HFByteSlice_Private) + +- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFController.h b/thirdparty/SameBoy-old/HexFiend/HFController.h new file mode 100644 index 000000000..4a59a4d6a --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFController.h @@ -0,0 +1,395 @@ +// +// HFController.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +#import + +/*! @header HFController + @abstract The HFController.h header contains the HFController class, which is a central class in Hex Fiend. +*/ + +@class HFRepresenter, HFByteArray, HFFileReference, HFControllerCoalescedUndo, HFByteRangeAttributeArray; + +/*! @enum HFControllerPropertyBits + The HFControllerPropertyBits bitmask is used to inform the HFRepresenters of a change in the current state that they may need to react to. A bitmask of the changed properties is passed to representerChangedProperties:. It is common for multiple properties to be included in such a bitmask. +*/ +typedef NS_OPTIONS(NSUInteger, HFControllerPropertyBits) { + HFControllerContentValue = 1 << 0, /*!< Indicates that the contents of the ByteArray has changed within the document. There is no indication as to what the change is. If redisplaying everything is expensive, Representers should cache their displayed data and compute any changes manually. */ + HFControllerContentLength = 1 << 1, /*!< Indicates that the length of the ByteArray has changed. */ + HFControllerDisplayedLineRange = 1 << 2, /*!< Indicates that the displayedLineRange property of the document has changed (e.g. the user scrolled). */ + HFControllerSelectedRanges = 1 << 3, /*!< Indicates that the selectedContentsRanges property of the document has changed (e.g. the user selected some other range). */ + HFControllerSelectionPulseAmount = 1 << 4, /*!< Indicates that the amount of "pulse" to show in the Find pulse indicator has changed. */ + HFControllerBytesPerLine = 1 << 5, /*!< Indicates that the number of bytes to show per line has changed. */ + HFControllerBytesPerColumn = 1 << 6, /*!< Indicates that the number of bytes per column (byte grouping) has changed. */ + HFControllerEditable = 1 << 7, /*!< Indicates that the document has become (or is no longer) editable. */ + HFControllerFont = 1 << 8, /*!< Indicates that the font property has changed. */ + HFControllerAntialias = 1 << 9, /*!< Indicates that the shouldAntialias property has changed. */ + HFControllerLineHeight = 1 << 10, /*!< Indicates that the lineHeight property has changed. */ + HFControllerViewSizeRatios = 1 << 11, /*!< Indicates that the optimum size for each view may have changed; used by HFLayoutController after font changes. */ + HFControllerByteRangeAttributes = 1 << 12, /*!< Indicates that some attributes of the ByteArray has changed within the document. There is no indication as to what the change is. */ + HFControllerByteGranularity = 1 << 13, /*!< Indicates that the byte granularity has changed. For example, when moving from ASCII to UTF-16, the byte granularity increases from 1 to 2. */ + HFControllerBookmarks = 1 << 14, /*!< Indicates that a bookmark has been added or removed. */ + HFControllerColorBytes = 1 << 15, /*!< Indicates that the shouldColorBytes property has changed. */ + HFControllerShowCallouts = 1 << 16, /*!< Indicates that the shouldShowCallouts property has changed. */ + HFControllerHideNullBytes = 1 << 17, /*!< Indicates that the shouldHideNullBytes property has changed. */ +}; + +/*! @enum HFControllerMovementDirection + +The HFControllerMovementDirection enum is used to specify a direction (either left or right) in various text editing APIs. HexFiend does not support left-to-right languages. +*/ +typedef NS_ENUM(NSInteger, HFControllerMovementDirection) { + HFControllerDirectionLeft, + HFControllerDirectionRight +}; + +/*! @enum HFControllerSelectionTransformation + +The HFControllerSelectionTransformation enum is used to specify what happens to the selection in various APIs. This is mainly interesting for text-editing style Representers. +*/ +typedef NS_ENUM(NSInteger, HFControllerSelectionTransformation) { + HFControllerDiscardSelection, /*!< The selection should be discarded. */ + HFControllerShiftSelection, /*!< The selection should be moved, without changing its length. */ + HFControllerExtendSelection /*!< The selection should be extended, changing its length. */ +}; + +/*! @enum HFControllerMovementGranularity + +The HFControllerMovementGranularity enum is used to specify the granularity of text movement in various APIs. This is mainly interesting for text-editing style Representers. +*/ +typedef NS_ENUM(NSInteger, HFControllerMovementGranularity) { + HFControllerMovementByte, /*!< Move by individual bytes */ + HFControllerMovementColumn, /*!< Move by a column */ + HFControllerMovementLine, /*!< Move by lines */ + HFControllerMovementPage, /*!< Move by pages */ + HFControllerMovementDocument /*!< Move by the whole document */ +}; + +/*! @enum HFEditMode + +HFEditMode enumerates the different edit modes that a document might be in. + */ +typedef NS_ENUM(NSInteger, HFEditMode) { + HFInsertMode, + HFOverwriteMode, + HFReadOnlyMode, +} ; + +/*! @class HFController +@brief A central class that acts as the controller layer for HexFiend.framework + +HFController acts as the controller layer in the MVC architecture of HexFiend. The HFController plays several significant central roles, including: + - Mediating between the data itself (in the HFByteArray) and the views of the data (the @link HFRepresenter HFRepresenters@endlink). + - Propagating changes to the views. + - Storing properties common to all Representers, such as the currently diplayed range, the currently selected range(s), the font, etc. + - Handling text editing actions, such as selection changes or insertions/deletions. + +An HFController is the top point of ownership for a HexFiend object graph. It retains both its ByteArray (model) and its array of Representers (views). + +You create an HFController via [[HFController alloc] init]. After that, give it an HFByteArray via setByteArray:, and some Representers via addRepresenter:. Then insert the Representers' views in a window, and you're done. + +*/ +@interface HFController : NSObject { +@private + NSMutableArray *representers; + HFByteArray *byteArray; + NSMutableArray *selectedContentsRanges; + HFRange displayedContentsRange; + HFFPRange displayedLineRange; + NSUInteger bytesPerLine; + NSUInteger bytesPerColumn; + CGFloat lineHeight; + + NSUInteger currentPropertyChangeToken; + NSMutableArray *additionalPendingTransactions; + HFControllerPropertyBits propertiesToUpdateInCurrentTransaction; + + NSUndoManager *undoManager; + NSMutableSet *undoOperations; + HFControllerCoalescedUndo *undoCoalescer; + + unsigned long long selectionAnchor; + HFRange selectionAnchorRange; + + CFAbsoluteTime pulseSelectionStartTime, pulseSelectionCurrentTime; + NSTimer *pulseSelectionTimer; + + /* Basic cache support */ + HFRange cachedRange; + NSData *cachedData; + NSUInteger cachedGenerationIndex; + + struct { + unsigned antialias:1; + unsigned colorbytes:1; + unsigned showcallouts:1; + unsigned hideNullBytes:1; + HFEditMode editMode:2; + unsigned editable:1; + unsigned selectable:1; + unsigned selectionInProgress:1; + unsigned shiftExtendSelection:1; + unsigned commandExtendSelection:1; + unsigned livereload:1; + } _hfflags; +} + +/*! @name Representer handling. + Methods for modifying the list of HFRepresenters attached to a controller. Attached representers receive the controllerDidChange: message when various properties of the controller change. A representer may only be attached to one controller at a time. Representers are retained by the controller. +*/ +//@{ +/// Gets the current array of representers attached to this controller. +@property (readonly, copy) NSArray *representers; + +/// Adds a new representer to this controller. +- (void)addRepresenter:(HFRepresenter *)representer; + +/// Removes an existing representer from this controller. The representer must be present in the array of representers. +- (void)removeRepresenter:(HFRepresenter *)representer; + +//@} + +/*! @name Property transactions + Methods for temporarily delaying notifying representers of property changes. There is a property transaction stack, and all property changes are collected until the last token is popped off the stack, at which point all representers are notified of all collected changes via representerChangedProperties:. To use this, call beginPropertyChangeTransaction, and record the token that is returned. Pass it to endPropertyChangeTransaction: to notify representers of all changed properties in bulk. + + Tokens cannot be popped out of order - they are used only as a correctness check. +*/ +//@{ +/*! Begins delaying property change transactions. Returns a token that should be passed to endPropertyChangeTransactions:. */ +- (NSUInteger)beginPropertyChangeTransaction; + +/*! Pass a token returned from beginPropertyChangeTransaction to this method to pop the transaction off the stack and, if the stack is empty, to notify Representers of all collected changes. Tokens cannot be popped out of order - they are used strictly as a correctness check. */ +- (void)endPropertyChangeTransaction:(NSUInteger)token; +//@} + +/*! @name Byte array + Set and get the byte array. */ +//@{ + +/*! The byte array must be non-nil. In general, HFRepresenters should not use this to determine what bytes to display. Instead they should use copyBytes:range: or dataForRange: below. */ +@property (nonatomic, strong) HFByteArray *byteArray; + +/*! Replaces the entire byte array with a new one, preserving as much of the selection as possible. Unlike setByteArray:, this method is undoable, and intended to be used from representers that make a global change (such as Replace All). */ +- (void)replaceByteArray:(HFByteArray *)newArray; +//@} + +/*! @name Properties shared between all representers + The following properties are considered global among all HFRepresenters attached to the receiver. +*/ +//@{ +/*! Returns the number of lines on which the cursor may be placed. This is always at least 1, and is equivalent to (unsigned long long)(HFRoundUpToNextMultiple(contentsLength, bytesPerLine) / bytesPerLine) */ +- (unsigned long long)totalLineCount; + +/*! Indicates the number of bytes per line, which is a global property among all the line-oriented representers. */ +- (NSUInteger)bytesPerLine; + +/*! Returns the height of a line, in points. This is generally determined by the font. Representers that wish to align things to lines should use this. */ +- (CGFloat)lineHeight; + +//@} + +/*! @name Selection pulsing + Used to show the current selection after a change, similar to Find in Safari +*/ +//{@ + +/*! Begins selection pulsing (e.g. following a successful Find operation). Representers will receive callbacks indicating that HFControllerSelectionPulseAmount has changed. */ +- (void)pulseSelection; + +/*! Return the amount that the "Find pulse indicator" should show. 0 means no pulse, 1 means maximum pulse. This is useful for Representers that support find and replace. */ +- (double)selectionPulseAmount; +//@} + +/*! @name Selection handling + Methods for manipulating the current selected ranges. Hex Fiend supports discontiguous selection. +*/ +//{@ + +/*! An array of HFRangeWrappers, representing the selected ranges. It satisfies the following: + The array is non-nil. + There always is at least one selected range. + If any range has length 0, that range is the only range. + No range extends beyond the contentsLength, with the exception of a single zero-length range at the end. + + When setting, the setter MUST obey the above criteria. A zero length range when setting or getting represents the cursor position. */ +@property (nonatomic, copy) NSArray *selectedContentsRanges; + +/*! Selects the entire contents. */ +- (IBAction)selectAll:(id)sender; + +/*! Returns the smallest value in the selected contents ranges, or the insertion location if the selection is empty. */ +- (unsigned long long)minimumSelectionLocation; + +/*! Returns the largest HFMaxRange of the selected contents ranges, or the insertion location if the selection is empty. */ +- (unsigned long long)maximumSelectionLocation; + +/*! Convenience method for creating a byte array containing all of the selected bytes. If the selection has length 0, this returns an empty byte array. */ +- (HFByteArray *)byteArrayForSelectedContentsRanges; +//@} + +/* Number of bytes used in each column for a text-style representer. */ +@property (nonatomic) NSUInteger bytesPerColumn; + +/*! @name Edit Mode + Determines what mode we're in, read-only, overwrite or insert. */ +@property (nonatomic) HFEditMode editMode; + +/*! @name Displayed line range + Methods for setting and getting the current range of displayed lines. +*/ +//{@ +/*! Get the current displayed line range. The displayed line range is an HFFPRange (range of long doubles) containing the lines that are currently displayed. + + The values may be fractional. That is, if only the bottom half of line 4 through the top two thirds of line 8 is shown, then the displayedLineRange.location will be 4.5 and the displayedLineRange.length will be 3.17 ( = 7.67 - 4.5). Representers are expected to be able to handle such fractional values. + + When setting the displayed line range, the given range must be nonnegative, and the maximum of the range must be no larger than the total line count. + +*/ +@property (nonatomic) HFFPRange displayedLineRange; + +/*! Modify the displayedLineRange so that as much of the given range as can fit is visible. If possible, moves by as little as possible so that the visible ranges before and afterward intersect with each other. */ +- (void)maximizeVisibilityOfContentsRange:(HFRange)range; + +/*! Modify the displayedLineRange as to center the given contents range. If the range is near the bottom or top, this will center as close as possible. If contents range is too large to fit, it centers the top of the range. contentsRange may be empty. */ +- (void)centerContentsRange:(HFRange)range; + +//@} + +/*! The current font. */ +@property (nonatomic, copy) NSFont *font; + +/*! The undo manager. If no undo manager is set, then undo is not supported. By default the undo manager is nil. +*/ +@property (nonatomic, strong) NSUndoManager *undoManager; + +/*! Whether the user can edit the document. */ +@property (nonatomic) BOOL editable; + +/*! Whether the text should be antialiased. Note that Mac OS X settings may prevent antialiasing text below a certain point size. */ +@property (nonatomic) BOOL shouldAntialias; + +/*! When enabled, characters have a background color that correlates to their byte values. */ +@property (nonatomic) BOOL shouldColorBytes; + +/*! When enabled, byte bookmarks display callout-style labels attached to them. */ +@property (nonatomic) BOOL shouldShowCallouts; + +/*! When enabled, null bytes are hidden in the hex view. */ +@property (nonatomic) BOOL shouldHideNullBytes; + +/*! When enabled, unmodified documents are auto refreshed to their latest on disk state. */ +@property (nonatomic) BOOL shouldLiveReload; + +/*! Representer initiated property changes + Called from a representer to indicate when some internal property of the representer has changed which requires that some properties be recalculated. +*/ +//@{ +/*! Callback for a representer-initiated change to some property. For example, if some property of a view changes that would cause the number of bytes per line to change, then the representer should call this method which will trigger the HFController to recompute the relevant properties. */ + +- (void)representer:(HFRepresenter *)rep changedProperties:(HFControllerPropertyBits)properties; +//@} + +/*! @name Mouse selection + Methods to handle mouse selection. Representers that allow text selection should call beginSelectionWithEvent:forByteIndex: upon receiving a mouseDown event, and then continueSelectionWithEvent:forByteIndex: for mouseDragged events, terminating with endSelectionWithEvent:forByteIndex: upon receiving the mouse up. HFController will compute the correct selected ranges and propagate any changes via the HFControllerPropertyBits mechanism. */ +//@{ +/*! Begin a selection session, with a mouse down at the given byte index. */ +- (void)beginSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! Continue a selection session, whe the user drags over the given byte index. */ +- (void)continueSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! End a selection session, with a mouse up at the given byte index. */ +- (void)endSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex; + +/*! @name Scrollling + Support for the mouse wheel and scroll bars. */ +//@{ +/*! Trigger scrolling appropriate for the given scroll event. */ +- (void)scrollWithScrollEvent:(NSEvent *)scrollEvent; + +/*! Trigger scrolling by the given number of lines. If lines is positive, then the document is scrolled down; otherwise it is scrolled up. */ +- (void)scrollByLines:(long double)lines; + +//@} + +/*! @name Keyboard navigation + Support for chaging the selection via the keyboard +*/ + +/*! General purpose navigation function. Modify the selection in the given direction by the given number of bytes. The selection is modifed according to the given transformation. If useAnchor is set, then anchored selection is used; otherwise any anchor is discarded. + + This has a few limitations: + - Only HFControllerDirectionLeft and HFControllerDirectionRight movement directions are supported. + - Anchored selection is not supported for HFControllerShiftSelection (useAnchor must be NO) +*/ +- (void)moveInDirection:(HFControllerMovementDirection)direction byByteCount:(unsigned long long)amountToMove withSelectionTransformation:(HFControllerSelectionTransformation)transformation usingAnchor:(BOOL)useAnchor; + +/*! Navigation designed for key events. */ +- (void)moveInDirection:(HFControllerMovementDirection)direction withGranularity:(HFControllerMovementGranularity)granularity andModifySelection:(BOOL)extendSelection; +- (void)moveToLineBoundaryInDirection:(HFControllerMovementDirection)direction andModifySelection:(BOOL)extendSelection; + +/*! @name Text editing + Methods to support common text editing operations */ +//@{ + +/*! Replaces the selection with the given data. For something like a hex view representer, it takes two keypresses to create a whole byte; the way this is implemented, the first keypress goes into the data as a complete byte, and the second one (if any) replaces it. If previousByteCount > 0, then that many prior bytes are replaced, without breaking undo coalescing. For previousByteCount to be > 0, the following must be true: There is only one selected range, and it is of length 0, and its location >= previousByteCount + + These functions return YES if they succeed, and NO if they fail. Currently they may fail only in overwrite mode, if you attempt to insert data that would require lengthening the byte array. + + These methods are undoable. + */ +- (BOOL)insertByteArray:(HFByteArray *)byteArray replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing; +- (BOOL)insertData:(NSData *)data replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing; + +/*! Deletes the selection. This operation is undoable. */ +- (void)deleteSelection; + +/*! If the selection is empty, deletes one byte in a given direction, which must be HFControllerDirectionLeft or HFControllerDirectionRight; if the selection is not empty, deletes the selection. Undoable. */ +- (void)deleteDirection:(HFControllerMovementDirection)direction; + +//@} + +/*! @name Reading data + Methods for reading data */ + +/*! Returns an NSData representing the given HFRange. The length of the HFRange must be of a size that can reasonably be fit in memory. This method may cache the result. */ +- (NSData *)dataForRange:(HFRange)range; + +/*! Copies data within the given HFRange into an in-memory buffer. This is equivalent to [[controller byteArray] copyBytes:bytes range:range]. */ +- (void)copyBytes:(unsigned char *)bytes range:(HFRange)range; + +/*! Returns total number of bytes. This is equivalent to [[controller byteArray] length]. */ +- (unsigned long long)contentsLength; + +- (void) reloadData; +- (void)_ensureVisibilityOfLocation:(unsigned long long)location; +@end + +/*! A notification posted whenever any of the HFController's properties change. The object is the HFController. The userInfo contains one key, HFControllerChangedPropertiesKey, which contains an NSNumber with the changed properties as a HFControllerPropertyBits bitmask. This is useful for external objects to be notified of changes. HFRepresenters added to the HFController are notified via the controllerDidChange: message. +*/ +extern NSString * const HFControllerDidChangePropertiesNotification; + +/*! @name HFControllerDidChangePropertiesNotification keys +*/ +//@{ +extern NSString * const HFControllerChangedPropertiesKey; //!< A key in the HFControllerDidChangeProperties containing a bitmask of the changed properties, as a HFControllerPropertyBits +//@} + +/*! A notification posted from prepareForChangeInFile:fromWritingByteArray: because we are about to write a ByteArray to a file. The object is the FileReference. + Currently, HFControllers do not listen for this notification. This is because under GC there is no way of knowing whether the controller is live or not. However, pasteboard owners do listen for it, because as long as we own a pasteboard we are guaranteed to be live. +*/ +extern NSString * const HFPrepareForChangeInFileNotification; + +/*! @name HFPrepareForChangeInFileNotification keys +*/ +//@{ +extern NSString * const HFChangeInFileByteArrayKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the byte array that will be written +extern NSString * const HFChangeInFileModifiedRangesKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the array of HFRangeWrappers indicating which parts of the file will be modified +extern NSString * const HFChangeInFileShouldCancelKey; //!< A key in the HFPrepareForChangeInFileNotification specifying an NSValue containing a pointer to a BOOL. If set to YES, then someone was unable to prepare and the file should not be saved. It's a good idea to check if this value points to YES; if so your notification handler does not have to do anything. +extern NSString * const HFChangeInFileHintKey; //!< The hint parameter that you may pass to clearDependenciesOnRanges:inFile:hint: +//@} diff --git a/thirdparty/SameBoy-old/HexFiend/HFController.m b/thirdparty/SameBoy-old/HexFiend/HFController.m new file mode 100644 index 000000000..9d4b1aec0 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFController.m @@ -0,0 +1,1905 @@ +// +// HFController.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +/* Used for the anchor range and location */ +#define NO_SELECTION ULLONG_MAX + +#define VALIDATE_SELECTION() [self _ensureSelectionIsValid] + +#define BENCHMARK_BYTEARRAYS 0 + +#define BEGIN_TRANSACTION() NSUInteger token = [self beginPropertyChangeTransaction] +#define END_TRANSACTION() [self endPropertyChangeTransaction:token] + +static const CGFloat kScrollMultiplier = (CGFloat)1.5; + +static const CFTimeInterval kPulseDuration = .2; + +static void *KVOContextChangesAreLocked = &KVOContextChangesAreLocked; + +NSString * const HFPrepareForChangeInFileNotification = @"HFPrepareForChangeInFileNotification"; +NSString * const HFChangeInFileByteArrayKey = @"HFChangeInFileByteArrayKey"; +NSString * const HFChangeInFileModifiedRangesKey = @"HFChangeInFileModifiedRangesKey"; +NSString * const HFChangeInFileShouldCancelKey = @"HFChangeInFileShouldCancelKey"; +NSString * const HFChangeInFileHintKey = @"HFChangeInFileHintKey"; + +NSString * const HFControllerDidChangePropertiesNotification = @"HFControllerDidChangePropertiesNotification"; +NSString * const HFControllerChangedPropertiesKey = @"HFControllerChangedPropertiesKey"; + + +typedef NS_ENUM(NSInteger, HFControllerSelectAction) { + eSelectResult, + eSelectAfterResult, + ePreserveSelection, + NUM_SELECTION_ACTIONS +}; + +@interface HFController (ForwardDeclarations) +- (void)_commandInsertByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction; +- (void)_removeUndoManagerNotifications; +- (void)_removeAllUndoOperations; +- (void)_registerUndoOperationForInsertingByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction; + +- (void)_updateBytesPerLine; +- (void)_updateDisplayedRange; +@end + +@interface NSEvent (HFLionStuff) +- (CGFloat)scrollingDeltaY; +- (BOOL)hasPreciseScrollingDeltas; +- (CGFloat)deviceDeltaY; +@end + +static inline Class preferredByteArrayClass(void) { + return [HFBTreeByteArray class]; +} + +@implementation HFController + +- (void)_sharedInit { + selectedContentsRanges = [[NSMutableArray alloc] initWithObjects:[HFRangeWrapper withRange:HFRangeMake(0, 0)], nil]; + byteArray = [[preferredByteArrayClass() alloc] init]; + [byteArray addObserver:self forKeyPath:@"changesAreLocked" options:0 context:KVOContextChangesAreLocked]; + selectionAnchor = NO_SELECTION; + undoOperations = [[NSMutableSet alloc] init]; +} + +- (instancetype)init { + self = [super init]; + [self _sharedInit]; + bytesPerLine = 16; + bytesPerColumn = 1; + _hfflags.editable = YES; + _hfflags.antialias = YES; + _hfflags.showcallouts = YES; + _hfflags.hideNullBytes = NO; + _hfflags.selectable = YES; + representers = [[NSMutableArray alloc] init]; + [self setFont:[NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]]; + return self; +} + +- (void)dealloc { + [representers makeObjectsPerformSelector:@selector(_setController:) withObject:nil]; + [representers release]; + [selectedContentsRanges release]; + [self _removeUndoManagerNotifications]; + [self _removeAllUndoOperations]; + [undoOperations release]; + [undoManager release]; + [undoCoalescer release]; + [_font release]; + [byteArray removeObserver:self forKeyPath:@"changesAreLocked"]; + [byteArray release]; + [cachedData release]; + [additionalPendingTransactions release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [coder encodeObject:representers forKey:@"HFRepresenters"]; + [coder encodeInt64:bytesPerLine forKey:@"HFBytesPerLine"]; + [coder encodeInt64:bytesPerColumn forKey:@"HFBytesPerColumn"]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeDouble:lineHeight forKey:@"HFLineHeight"]; + [coder encodeBool:_hfflags.antialias forKey:@"HFAntialias"]; + [coder encodeBool:_hfflags.colorbytes forKey:@"HFColorBytes"]; + [coder encodeBool:_hfflags.showcallouts forKey:@"HFShowCallouts"]; + [coder encodeBool:_hfflags.hideNullBytes forKey:@"HFHidesNullBytes"]; + [coder encodeBool:_hfflags.livereload forKey:@"HFLiveReload"]; + [coder encodeInt:_hfflags.editMode forKey:@"HFEditMode"]; + [coder encodeBool:_hfflags.editable forKey:@"HFEditable"]; + [coder encodeBool:_hfflags.selectable forKey:@"HFSelectable"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super init]; + [self _sharedInit]; + bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"]; + bytesPerColumn = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerColumn"]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + _hfflags.antialias = [coder decodeBoolForKey:@"HFAntialias"]; + _hfflags.colorbytes = [coder decodeBoolForKey:@"HFColorBytes"]; + _hfflags.livereload = [coder decodeBoolForKey:@"HFLiveReload"]; + + if ([coder containsValueForKey:@"HFEditMode"]) + _hfflags.editMode = [coder decodeIntForKey:@"HFEditMode"]; + else { + _hfflags.editMode = ([coder decodeBoolForKey:@"HFOverwriteMode"] + ? HFOverwriteMode : HFInsertMode); + } + + _hfflags.editable = [coder decodeBoolForKey:@"HFEditable"]; + _hfflags.selectable = [coder decodeBoolForKey:@"HFSelectable"]; + _hfflags.hideNullBytes = [coder decodeBoolForKey:@"HFHidesNullBytes"]; + representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain]; + return self; +} + +- (NSArray *)representers { + return [[representers copy] autorelease]; +} + +- (void)notifyRepresentersOfChanges:(HFControllerPropertyBits)bits { + FOREACH(HFRepresenter*, rep, representers) { + [rep controllerDidChange:bits]; + } + + /* Post the HFControllerDidChangePropertiesNotification */ + NSNumber *number = [[NSNumber alloc] initWithUnsignedInteger:bits]; + NSDictionary *userInfo = [[NSDictionary alloc] initWithObjects:&number forKeys:(id *)&HFControllerChangedPropertiesKey count:1]; + [number release]; + [[NSNotificationCenter defaultCenter] postNotificationName:HFControllerDidChangePropertiesNotification object:self userInfo:userInfo]; + [userInfo release]; +} + +- (void)_firePropertyChanges { + NSMutableArray *pendingTransactions = additionalPendingTransactions; + NSUInteger pendingTransactionCount = [pendingTransactions count]; + additionalPendingTransactions = nil; + HFControllerPropertyBits propertiesToUpdate = propertiesToUpdateInCurrentTransaction; + propertiesToUpdateInCurrentTransaction = 0; + if (pendingTransactionCount > 0 || propertiesToUpdate != 0) { + BEGIN_TRANSACTION(); + while (pendingTransactionCount--) { + HFControllerPropertyBits propertiesInThisTransaction = [pendingTransactions[0] unsignedIntegerValue]; + [pendingTransactions removeObjectAtIndex:0]; + HFASSERT(propertiesInThisTransaction != 0); + [self notifyRepresentersOfChanges:propertiesInThisTransaction]; + } + [pendingTransactions release]; + if (propertiesToUpdate) { + [self notifyRepresentersOfChanges:propertiesToUpdate]; + } + END_TRANSACTION(); + } +} + +/* Inserts a "fence" so that all prior property change bits will be complete before any new ones */ +- (void)_insertPropertyChangeFence { + if (currentPropertyChangeToken == 0) { + HFASSERT(additionalPendingTransactions == nil); + /* There can be no prior property changes */ + HFASSERT(propertiesToUpdateInCurrentTransaction == 0); + return; + } + if (propertiesToUpdateInCurrentTransaction == 0) { + /* Nothing to fence */ + return; + } + if (additionalPendingTransactions == nil) additionalPendingTransactions = [[NSMutableArray alloc] init]; + [additionalPendingTransactions addObject:@(propertiesToUpdateInCurrentTransaction)]; + propertiesToUpdateInCurrentTransaction = 0; +} + +- (void)_addPropertyChangeBits:(HFControllerPropertyBits)bits { + propertiesToUpdateInCurrentTransaction |= bits; + if (currentPropertyChangeToken == 0) { + [self _firePropertyChanges]; + } +} + +- (NSUInteger)beginPropertyChangeTransaction { + HFASSERT(currentPropertyChangeToken < NSUIntegerMax); + return ++currentPropertyChangeToken; +} + +- (void)endPropertyChangeTransaction:(NSUInteger)token { + if (currentPropertyChangeToken != token) { + [NSException raise:NSInvalidArgumentException format:@"endPropertyChangeTransaction passed token %lu, but expected token %lu", (unsigned long)token, (unsigned long)currentPropertyChangeToken]; + } + HFASSERT(currentPropertyChangeToken > 0); + if (--currentPropertyChangeToken == 0) [self _firePropertyChanges]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == KVOContextChangesAreLocked) { + HFASSERT([keyPath isEqual:@"changesAreLocked"]); + [self _addPropertyChangeBits:HFControllerEditable]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)addRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound); + HFASSERT([representer controller] == nil); + [representer _setController:self]; + [representers addObject:representer]; + [representer controllerDidChange: -1]; +} + +- (void)removeRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound); + [representers removeObjectIdenticalTo:representer]; + [representer _setController:nil]; +} + +- (HFRange)_maximumDisplayedRangeSet { + unsigned long long contentsLength = [self contentsLength]; + HFRange maximumDisplayedRangeSet = HFRangeMake(0, HFRoundUpToNextMultipleSaturate(contentsLength, bytesPerLine)); + return maximumDisplayedRangeSet; +} + +- (unsigned long long)totalLineCount { + return HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate([self contentsLength] - 1, bytesPerLine), bytesPerLine); +} + +- (HFFPRange)displayedLineRange { +#if ! NDEBUG + HFASSERT(displayedLineRange.location >= 0); + HFASSERT(displayedLineRange.length >= 0); + HFASSERT(displayedLineRange.location + displayedLineRange.length <= HFULToFP([self totalLineCount])); +#endif + return displayedLineRange; +} + +- (void)setDisplayedLineRange:(HFFPRange)range { +#if ! NDEBUG + HFASSERT(range.location >= 0); + HFASSERT(range.length >= 0); +#endif + if (range.location + range.length > HFULToFP([self totalLineCount])) { + range.location = [self totalLineCount] - range.length; + if (range.location < 0) { + return; + } + } + if (! HFFPRangeEqualsRange(range, displayedLineRange)) { + displayedLineRange = range; + [self _addPropertyChangeBits:HFControllerDisplayedLineRange]; + } +} + +- (CGFloat)lineHeight { + return lineHeight; +} + +- (void)setFont:(NSFont *)val { + if (val != _font) { + CGFloat priorLineHeight = [self lineHeight]; + + [_font release]; + _font = [val copy]; + + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + lineHeight = [manager defaultLineHeightForFont:_font]; + [manager release]; + + HFControllerPropertyBits bits = HFControllerFont; + if (lineHeight != priorLineHeight) bits |= HFControllerLineHeight; + [self _addPropertyChangeBits:bits]; + [self _insertPropertyChangeFence]; + [self _addPropertyChangeBits:HFControllerViewSizeRatios]; + [self _updateDisplayedRange]; + } +} + +- (BOOL)shouldAntialias { + return _hfflags.antialias; +} + +- (void)setShouldAntialias:(BOOL)antialias { + antialias = !! antialias; + if (antialias != _hfflags.antialias) { + _hfflags.antialias = antialias; + [self _addPropertyChangeBits:HFControllerAntialias]; + } +} + +- (BOOL)shouldColorBytes { + return _hfflags.colorbytes; +} + +- (void)setShouldColorBytes:(BOOL)colorbytes { + colorbytes = !! colorbytes; + if (colorbytes != _hfflags.colorbytes) { + _hfflags.colorbytes = colorbytes; + [self _addPropertyChangeBits:HFControllerColorBytes]; + } +} + +- (BOOL)shouldShowCallouts { + return _hfflags.showcallouts; +} + +- (void)setShouldShowCallouts:(BOOL)showcallouts { + showcallouts = !! showcallouts; + if (showcallouts != _hfflags.showcallouts) { + _hfflags.showcallouts = showcallouts; + [self _addPropertyChangeBits:HFControllerShowCallouts]; + } +} + +- (BOOL)shouldHideNullBytes { + return _hfflags.hideNullBytes; +} + +- (void)setShouldHideNullBytes:(BOOL)hideNullBytes +{ + hideNullBytes = !! hideNullBytes; + if (hideNullBytes != _hfflags.hideNullBytes) { + _hfflags.hideNullBytes = hideNullBytes; + [self _addPropertyChangeBits:HFControllerHideNullBytes]; + } +} + +- (BOOL)shouldLiveReload { + return _hfflags.livereload; +} + +- (void)setShouldLiveReload:(BOOL)livereload { + _hfflags.livereload = !!livereload; + +} + +- (void)setBytesPerColumn:(NSUInteger)val { + if (val != bytesPerColumn) { + bytesPerColumn = val; + [self _addPropertyChangeBits:HFControllerBytesPerColumn]; + } +} + +- (NSUInteger)bytesPerColumn { + return bytesPerColumn; +} + +- (BOOL)_shouldInvertSelectedRangesByAnchorRange { + return _hfflags.selectionInProgress && _hfflags.commandExtendSelection; +} + +- (NSArray *)_invertedSelectedContentsRanges { + HFASSERT([selectedContentsRanges count] > 0); + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + if (selectionAnchorRange.length == 0) return [NSArray arrayWithArray:selectedContentsRanges]; + + NSArray *cleanedRanges = [HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]; + NSMutableArray *result = [NSMutableArray array]; + + /* Our algorithm works as follows - add any ranges outside of the selectionAnchorRange, clipped by the selectionAnchorRange. Then extract every "index" in our cleaned selected arrays that are within the selectionAnchorArray. An index is the location where a range starts or stops. Then use those indexes to create the inverted arrays. A range parity of 1 means that we are adding the range. */ + + /* Add all the ranges that are outside of selectionAnchorRange, clipping them if necessary */ + HFASSERT(HFSumDoesNotOverflow(selectionAnchorRange.location, selectionAnchorRange.length)); + FOREACH(HFRangeWrapper*, outsideWrapper, cleanedRanges) { + HFRange range = [outsideWrapper HFRange]; + if (range.location < selectionAnchorRange.location) { + HFRange clippedRange; + clippedRange.location = range.location; + HFASSERT(MIN(HFMaxRange(range), selectionAnchorRange.location) >= clippedRange.location); + clippedRange.length = MIN(HFMaxRange(range), selectionAnchorRange.location) - clippedRange.location; + [result addObject:[HFRangeWrapper withRange:clippedRange]]; + } + if (HFMaxRange(range) > HFMaxRange(selectionAnchorRange)) { + HFRange clippedRange; + clippedRange.location = MAX(range.location, HFMaxRange(selectionAnchorRange)); + HFASSERT(HFMaxRange(range) >= clippedRange.location); + clippedRange.length = HFMaxRange(range) - clippedRange.location; + [result addObject:[HFRangeWrapper withRange:clippedRange]]; + } + } + + HFASSERT(HFSumDoesNotOverflow(selectionAnchorRange.location, selectionAnchorRange.length)); + + NEW_ARRAY(unsigned long long, partitions, 2*[cleanedRanges count] + 2); + NSUInteger partitionCount, partitionIndex = 0; + + partitions[partitionIndex++] = selectionAnchorRange.location; + FOREACH(HFRangeWrapper*, wrapper, cleanedRanges) { + HFRange range = [wrapper HFRange]; + if (! HFIntersectsRange(range, selectionAnchorRange)) continue; + + partitions[partitionIndex++] = MAX(selectionAnchorRange.location, range.location); + partitions[partitionIndex++] = MIN(HFMaxRange(selectionAnchorRange), HFMaxRange(range)); + } + + // For some reason, using HFMaxRange confuses the static analyzer + partitions[partitionIndex++] = HFSum(selectionAnchorRange.location, selectionAnchorRange.length); + + partitionCount = partitionIndex; + HFASSERT((partitionCount % 2) == 0); + + partitionIndex = 0; + while (partitionIndex < partitionCount) { + HFASSERT(partitionIndex + 1 < partitionCount); + HFASSERT(partitions[partitionIndex] <= partitions[partitionIndex + 1]); + if (partitions[partitionIndex] < partitions[partitionIndex + 1]) { + HFRange range = HFRangeMake(partitions[partitionIndex], partitions[partitionIndex + 1] - partitions[partitionIndex]); + [result addObject:[HFRangeWrapper withRange:range]]; + } + partitionIndex += 2; + } + + FREE_ARRAY(partitions); + + if ([result count] == 0) [result addObject:[HFRangeWrapper withRange:HFRangeMake(selectionAnchor, 0)]]; + + return [HFRangeWrapper organizeAndMergeRanges:result]; +} + +- (void)_ensureSelectionIsValid { + HFASSERT(selectedContentsRanges != nil); + HFASSERT([selectedContentsRanges count] > 0); + BOOL onlyOneWrapper = ([selectedContentsRanges count] == 1); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + EXPECT_CLASS(wrapper, HFRangeWrapper); + HFRange range = [wrapper HFRange]; + if (!HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))){ + [self setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(0, 0)]]]; + return; + } + if (onlyOneWrapper == NO) HFASSERT(range.length > 0); /* If we have more than one wrapper, then none of them should be zero length */ + } +} + +- (void)_setSingleSelectedContentsRange:(HFRange)newSelection { + HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))); + BOOL selectionChanged; + if ([selectedContentsRanges count] == 1) { + selectionChanged = ! HFRangeEqualsRange([selectedContentsRanges[0] HFRange], newSelection); + } + else { + selectionChanged = YES; + } + + if (selectionChanged) { + [selectedContentsRanges removeAllObjects]; + [selectedContentsRanges addObject:[HFRangeWrapper withRange:newSelection]]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + VALIDATE_SELECTION(); +} + +- (NSArray *)selectedContentsRanges { + VALIDATE_SELECTION(); + if ([self _shouldInvertSelectedRangesByAnchorRange]) return [self _invertedSelectedContentsRanges]; + else return [NSArray arrayWithArray:selectedContentsRanges]; +} + +- (unsigned long long)contentsLength { + if (! byteArray) return 0; + else return [byteArray length]; +} + +- (NSData *)dataForRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); // it doesn't make sense to ask for a buffer larger than can be stored in memory + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + + if(range.length == 0) { + // Don't throw out cache for an empty request! Also makes the analyzer happier. + return [NSData data]; + } + + NSUInteger newGenerationIndex = [byteArray changeGenerationCount]; + if (cachedData == nil || newGenerationIndex != cachedGenerationIndex || ! HFRangeIsSubrangeOfRange(range, cachedRange)) { + [cachedData release]; + cachedGenerationIndex = newGenerationIndex; + cachedRange = range; + NSUInteger length = ll2l(range.length); + unsigned char *data = check_malloc(length); + [byteArray copyBytes:data range:range]; + cachedData = [[NSData alloc] initWithBytesNoCopy:data length:length freeWhenDone:YES]; + } + + if (HFRangeEqualsRange(range, cachedRange)) { + return cachedData; + } + else { + HFASSERT(cachedRange.location <= range.location); + NSRange cachedDataSubrange; + cachedDataSubrange.location = ll2l(range.location - cachedRange.location); + cachedDataSubrange.length = ll2l(range.length); + return [cachedData subdataWithRange:cachedDataSubrange]; + } +} + +- (void)copyBytes:(unsigned char *)bytes range:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); // it doesn't make sense to ask for a buffer larger than can be stored in memory + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + [byteArray copyBytes:bytes range:range]; +} + +- (void)_updateDisplayedRange { + HFRange proposedNewDisplayRange; + HFFPRange proposedNewLineRange; + HFRange maxRangeSet = [self _maximumDisplayedRangeSet]; + NSUInteger maxBytesForViewSize = NSUIntegerMax; + double maxLines = DBL_MAX; + FOREACH(HFRepresenter*, rep, representers) { + NSView *view = [rep view]; + double repMaxLines = [rep maximumAvailableLinesForViewHeight:NSHeight([view frame])]; + if (repMaxLines != DBL_MAX) { + /* bytesPerLine may be ULONG_MAX. We want to compute the smaller of maxBytesForViewSize and ceil(repMaxLines) * bytesPerLine. If the latter expression overflows, the smaller is the former. */ + NSUInteger repMaxLinesUInt = (NSUInteger)ceil(repMaxLines); + NSUInteger maxLinesTimesBytesPerLine = repMaxLinesUInt * bytesPerLine; + /* Check if we overflowed */ + BOOL overflowed = (repMaxLinesUInt != 0 && (maxLinesTimesBytesPerLine / repMaxLinesUInt != bytesPerLine)); + if (! overflowed) { + maxBytesForViewSize = MIN(maxLinesTimesBytesPerLine, maxBytesForViewSize); + } + } + maxLines = MIN(repMaxLines, maxLines); + } + if (maxLines == DBL_MAX) { + proposedNewDisplayRange = HFRangeMake(0, 0); + proposedNewLineRange = (HFFPRange){0, 0}; + } + else { + unsigned long long maximumDisplayedBytes = MIN(maxRangeSet.length, maxBytesForViewSize); + HFASSERT(HFMaxRange(maxRangeSet) >= maximumDisplayedBytes); + + proposedNewDisplayRange.location = MIN(HFMaxRange(maxRangeSet) - maximumDisplayedBytes, displayedContentsRange.location); + proposedNewDisplayRange.location -= proposedNewDisplayRange.location % bytesPerLine; + proposedNewDisplayRange.length = MIN(HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location, maxBytesForViewSize); + if (maxBytesForViewSize % bytesPerLine != 0) { + NSLog(@"Bad max bytes: %lu (%lu)", (unsigned long)maxBytesForViewSize, (unsigned long)bytesPerLine); + } + if (HFMaxRange(maxRangeSet) != ULLONG_MAX && (HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location) % bytesPerLine != 0) { + NSLog(@"Bad max range minus: %llu (%lu)", HFMaxRange(maxRangeSet) - proposedNewDisplayRange.location, (unsigned long)bytesPerLine); + } + + long double lastLine = HFULToFP([self totalLineCount]); + proposedNewLineRange.length = MIN(maxLines, lastLine); + proposedNewLineRange.location = MIN(displayedLineRange.location, lastLine - proposedNewLineRange.length); + } + HFASSERT(HFRangeIsSubrangeOfRange(proposedNewDisplayRange, maxRangeSet)); + HFASSERT(proposedNewDisplayRange.location % bytesPerLine == 0); + if (! HFRangeEqualsRange(proposedNewDisplayRange, displayedContentsRange) || ! HFFPRangeEqualsRange(proposedNewLineRange, displayedLineRange)) { + displayedContentsRange = proposedNewDisplayRange; + displayedLineRange = proposedNewLineRange; + [self _addPropertyChangeBits:HFControllerDisplayedLineRange]; + } +} + +- (void)_ensureVisibilityOfLocation:(unsigned long long)location { + HFASSERT(location <= [self contentsLength]); + unsigned long long lineInt = location / bytesPerLine; + long double line = HFULToFP(lineInt); + HFASSERT(line >= 0); + line = MIN(line, HFULToFP([self totalLineCount]) - 1); + HFFPRange lineRange = [self displayedLineRange]; + HFFPRange newLineRange = lineRange; + if (line < lineRange.location) { + newLineRange.location = line; + } + else if (line >= lineRange.location + lineRange.length) { + HFASSERT(lineRange.location + lineRange.length >= 1); + newLineRange.location = lineRange.location + (line - (lineRange.location + lineRange.length - 1)); + } + [self setDisplayedLineRange:newLineRange]; +} + +- (void)maximizeVisibilityOfContentsRange:(HFRange)range { + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + + // Find the minimum move necessary to make range visible + HFFPRange displayRange = [self displayedLineRange]; + HFFPRange newDisplayRange = displayRange; + unsigned long long startLine = range.location / bytesPerLine; + unsigned long long endLine = HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate(HFMaxRange(range), bytesPerLine), bytesPerLine); + HFASSERT(endLine > startLine || endLine == ULLONG_MAX); + long double linesInRange = HFULToFP(endLine - startLine); + long double linesToDisplay = MIN(displayRange.length, linesInRange); + HFASSERT(linesToDisplay <= linesInRange); + long double linesToMoveDownToMakeLastLineVisible = HFULToFP(endLine) - (displayRange.location + displayRange.length); + long double linesToMoveUpToMakeFirstLineVisible = displayRange.location - HFULToFP(startLine); + //HFASSERT(linesToMoveUpToMakeFirstLineVisible <= 0 || linesToMoveDownToMakeLastLineVisible <= 0); + // in general, we expect either linesToMoveUpToMakeFirstLineVisible to be <= zero, or linesToMoveDownToMakeLastLineVisible to be <= zero. However, if the available space is smaller than one line, then that won't be true. + if (linesToMoveDownToMakeLastLineVisible > 0) { + newDisplayRange.location += linesToMoveDownToMakeLastLineVisible; + } + else if (linesToMoveUpToMakeFirstLineVisible > 0 && linesToDisplay >= 1) { + // the >= 1 check prevents some wacky behavior when we have less than one line's worth of space, that caused bouncing between the top and bottom of the line + newDisplayRange.location -= linesToMoveUpToMakeFirstLineVisible; + } + + // Use the minimum movement if it would be visually helpful; otherwise just center. + if (HFFPIntersectsRange(displayRange, newDisplayRange)) { + [self setDisplayedLineRange:newDisplayRange]; + } else { + [self centerContentsRange:range]; + } +} + +- (void)centerContentsRange:(HFRange)range { + HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + HFFPRange displayRange = [self displayedLineRange]; + const long double numDisplayedLines = displayRange.length; + HFFPRange newDisplayRange; + unsigned long long startLine = range.location / bytesPerLine; + unsigned long long endLine = HFDivideULLRoundingUp(HFRoundUpToNextMultipleSaturate(HFMaxRange(range), bytesPerLine), bytesPerLine); + HFASSERT(endLine > startLine || endLine == ULLONG_MAX); + long double linesInRange = HFULToFP(endLine - startLine); + + /* Handle the case of a line range bigger than we can display by choosing the top lines. */ + if (numDisplayedLines <= linesInRange) { + newDisplayRange = (HFFPRange){startLine, numDisplayedLines}; + } + else { + /* Construct a newDisplayRange that centers {startLine, endLine} */ + long double center = startLine + (endLine - startLine) / 2.; + newDisplayRange = (HFFPRange){center - numDisplayedLines / 2., numDisplayedLines}; + } + + /* Move the newDisplayRange up or down as necessary */ + newDisplayRange.location = fmaxl(newDisplayRange.location, (long double)0.); + newDisplayRange.location = fminl(newDisplayRange.location, HFULToFP([self totalLineCount]) - numDisplayedLines); + [self setDisplayedLineRange:newDisplayRange]; +} + +/* Clips the selection to a given length. If this would clip the entire selection, returns a zero length selection at the end. Indicates HFControllerSelectedRanges if the selection changes. */ +- (void)_clipSelectedContentsRangesToLength:(unsigned long long)newLength { + NSMutableArray *newTempSelection = [selectedContentsRanges mutableCopy]; + NSUInteger i, max = [newTempSelection count]; + for (i=0; i < max; i++) { + HFRange range = [newTempSelection[i] HFRange]; + if (HFMaxRange(range) > newLength) { + if (range.location > newLength) { + /* The range starts past our new max. Just remove this range entirely */ + [newTempSelection removeObjectAtIndex:i]; + i--; + max--; + } + else { + /* Need to clip this range */ + range.length = newLength - range.location; + newTempSelection[i] = [HFRangeWrapper withRange:range]; + } + } + } + [newTempSelection setArray:[HFRangeWrapper organizeAndMergeRanges:newTempSelection]]; + + /* If there are multiple empty ranges, remove all but the first */ + BOOL foundEmptyRange = NO; + max = [newTempSelection count]; + for (i=0; i < max; i++) { + HFRange range = [newTempSelection[i] HFRange]; + HFASSERT(HFMaxRange(range) <= newLength); + if (range.length == 0) { + if (foundEmptyRange) { + [newTempSelection removeObjectAtIndex:i]; + i--; + max--; + } + foundEmptyRange = YES; + } + } + if (max == 0) { + /* Removed all ranges - insert one at the end */ + [newTempSelection addObject:[HFRangeWrapper withRange:HFRangeMake(newLength, 0)]]; + } + + /* If something changed, set the new selection and post the change bit */ + if (! [selectedContentsRanges isEqualToArray:newTempSelection]) { + [selectedContentsRanges setArray:newTempSelection]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + + [newTempSelection release]; +} + +- (void)setByteArray:(HFByteArray *)val { + REQUIRE_NOT_NULL(val); + BEGIN_TRANSACTION(); + [byteArray removeObserver:self forKeyPath:@"changesAreLocked"]; + [val retain]; + [byteArray release]; + byteArray = val; + [cachedData release]; + cachedData = nil; + [byteArray addObserver:self forKeyPath:@"changesAreLocked" options:0 context:KVOContextChangesAreLocked]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits: HFControllerContentValue | HFControllerContentLength]; + [self _clipSelectedContentsRangesToLength:[byteArray length]]; + END_TRANSACTION(); +} + +- (HFByteArray *)byteArray { + return byteArray; +} + +- (void)_undoNotification:note { + USE(note); +} + +- (void)_removeUndoManagerNotifications { + if (undoManager) { + NSNotificationCenter *noter = [NSNotificationCenter defaultCenter]; + [noter removeObserver:self name:NSUndoManagerWillUndoChangeNotification object:undoManager]; + } +} + +- (void)_addUndoManagerNotifications { + if (undoManager) { + NSNotificationCenter *noter = [NSNotificationCenter defaultCenter]; + [noter addObserver:self selector:@selector(_undoNotification:) name:NSUndoManagerWillUndoChangeNotification object:undoManager]; + } +} + +- (void)_removeAllUndoOperations { + /* Remove all the undo operations, because some undo operation is unsupported. Note that if we were smarter we would keep a stack of undo operations and only remove ones "up to" a certain point. */ + [undoManager removeAllActionsWithTarget:self]; + [undoOperations makeObjectsPerformSelector:@selector(invalidate)]; + [undoOperations removeAllObjects]; +} + +- (void)setUndoManager:(NSUndoManager *)manager { + [self _removeUndoManagerNotifications]; + [self _removeAllUndoOperations]; + [manager retain]; + [undoManager release]; + undoManager = manager; + [self _addUndoManagerNotifications]; +} + +- (NSUndoManager *)undoManager { + return undoManager; +} + +- (NSUInteger)bytesPerLine { + return bytesPerLine; +} + +- (BOOL)editable { + return _hfflags.editable && ! [byteArray changesAreLocked] && _hfflags.editMode != HFReadOnlyMode; +} + +- (void)setEditable:(BOOL)flag { + if (flag != _hfflags.editable) { + _hfflags.editable = flag; + [self _addPropertyChangeBits:HFControllerEditable]; + } +} + +- (void)_updateBytesPerLine { + NSUInteger newBytesPerLine = NSUIntegerMax; + FOREACH(HFRepresenter*, rep, representers) { + NSView *view = [rep view]; + CGFloat width = [view frame].size.width; + NSUInteger repMaxBytesPerLine = [rep maximumBytesPerLineForViewWidth:width]; + HFASSERT(repMaxBytesPerLine > 0); + newBytesPerLine = MIN(repMaxBytesPerLine, newBytesPerLine); + } + if (newBytesPerLine != bytesPerLine) { + HFASSERT(newBytesPerLine > 0); + bytesPerLine = newBytesPerLine; + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerBytesPerLine]; + END_TRANSACTION(); + } +} + +- (void)representer:(HFRepresenter *)rep changedProperties:(HFControllerPropertyBits)properties { + USE(rep); + HFControllerPropertyBits remainingProperties = properties; + BEGIN_TRANSACTION(); + if (remainingProperties & HFControllerBytesPerLine) { + [self _updateBytesPerLine]; + remainingProperties &= ~HFControllerBytesPerLine; + } + if (remainingProperties & HFControllerDisplayedLineRange) { + [self _updateDisplayedRange]; + remainingProperties &= ~HFControllerDisplayedLineRange; + } + if (remainingProperties & HFControllerByteRangeAttributes) { + [self _addPropertyChangeBits:HFControllerByteRangeAttributes]; + remainingProperties &= ~HFControllerByteRangeAttributes; + } + if (remainingProperties & HFControllerViewSizeRatios) { + [self _addPropertyChangeBits:HFControllerViewSizeRatios]; + remainingProperties &= ~HFControllerViewSizeRatios; + } + if (remainingProperties) { + NSLog(@"Unknown properties: %lx", (long)remainingProperties); + } + END_TRANSACTION(); +} + +- (HFByteArray *)byteArrayForSelectedContentsRanges { + HFByteArray *result = nil; + HFByteArray *bytes = [self byteArray]; + VALIDATE_SELECTION(); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + HFByteArray *additionalBytes = [bytes subarrayWithRange:range]; + if (! result) { + result = additionalBytes; + } + else { + [result insertByteArray:additionalBytes inRange:HFRangeMake([result length], 0)]; + } + } + return result; +} + +/* Flattens the selected range to a single range (the selected range becomes any character within or between the selected ranges). Modifies the selectedContentsRanges and returns the new single HFRange. Does not call notifyRepresentersOfChanges: */ +- (HFRange)_flattenSelectionRange { + HFASSERT([selectedContentsRanges count] >= 1); + + HFRange resultRange = [selectedContentsRanges[0] HFRange]; + if ([selectedContentsRanges count] == 1) return resultRange; //already flat + + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange selectedRange = [wrapper HFRange]; + if (selectedRange.location < resultRange.location) { + /* Extend our result range backwards */ + resultRange.length += resultRange.location - selectedRange.location; + resultRange.location = selectedRange.location; + } + if (HFRangeExtendsPastRange(selectedRange, resultRange)) { + HFASSERT(selectedRange.location >= resultRange.location); //must be true by if statement above + resultRange.length = HFSum(selectedRange.location - resultRange.location, selectedRange.length); + } + } + [self _setSingleSelectedContentsRange:resultRange]; + return resultRange; +} + +- (unsigned long long)_minimumSelectionLocation { + HFASSERT([selectedContentsRanges count] >= 1); + unsigned long long minSelection = ULLONG_MAX; + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + minSelection = MIN(minSelection, range.location); + } + return minSelection; +} + +- (unsigned long long)_maximumSelectionLocation { + HFASSERT([selectedContentsRanges count] >= 1); + unsigned long long maxSelection = 0; + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + maxSelection = MAX(maxSelection, HFMaxRange(range)); + } + return maxSelection; +} + +- (unsigned long long)minimumSelectionLocation { + return [self _minimumSelectionLocation]; +} + +- (unsigned long long)maximumSelectionLocation { + return [self _maximumSelectionLocation]; +} + +/* Put the selection at the left or right end of the current selection, with zero length. Modifies the selectedContentsRanges and returns the new single HFRange. Does not call notifyRepresentersOfChanges: */ +- (HFRange)_telescopeSelectionRangeInDirection:(HFControllerMovementDirection)direction { + HFRange resultRange; + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + resultRange.location = (direction == HFControllerDirectionLeft ? [self _minimumSelectionLocation] : [self _maximumSelectionLocation]); + resultRange.length = 0; + [self _setSingleSelectedContentsRange:resultRange]; + return resultRange; +} + +- (void)beginSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)characterIndex { + USE(event); + HFASSERT(characterIndex <= [self contentsLength]); + + /* Determine how to perform the selection - normally, with command key, or with shift key. Command + shift is the same as command. The shift key closes the selection - the selected range becomes the single range containing the first and last selected character. */ + _hfflags.shiftExtendSelection = NO; + _hfflags.commandExtendSelection = NO; + NSUInteger flags = [event modifierFlags]; + if (flags & NSCommandKeyMask) _hfflags.commandExtendSelection = YES; + else if (flags & NSShiftKeyMask) _hfflags.shiftExtendSelection = YES; + + selectionAnchor = NO_SELECTION; + selectionAnchorRange = HFRangeMake(NO_SELECTION, 0); + + _hfflags.selectionInProgress = YES; + if (_hfflags.commandExtendSelection) { + /* The selection anchor is used to track the "invert" range. All characters within this range have their selection inverted. This is tracked by the _shouldInvertSelectedRangesByAnchorRange method. */ + selectionAnchor = characterIndex; + selectionAnchorRange = HFRangeMake(characterIndex, 0); + } + else if (_hfflags.shiftExtendSelection) { + /* The selection anchor is used to track the single (flattened) selected range. */ + HFRange selectedRange = [self _flattenSelectionRange]; + unsigned long long distanceFromRangeStart = HFAbsoluteDifference(selectedRange.location, characterIndex); + unsigned long long distanceFromRangeEnd = HFAbsoluteDifference(HFMaxRange(selectedRange), characterIndex); + if (selectedRange.length == 0) { + HFASSERT(distanceFromRangeStart == distanceFromRangeEnd); + selectionAnchor = selectedRange.location; + selectedRange.location = MIN(characterIndex, selectedRange.location); + selectedRange.length = distanceFromRangeStart; + } + else if (distanceFromRangeStart >= distanceFromRangeEnd) { + /* Push the "end forwards" */ + selectedRange.length = distanceFromRangeStart; + selectionAnchor = selectedRange.location; + } + else { + /* Push the "start back" */ + selectedRange.location = selectedRange.location + selectedRange.length - distanceFromRangeEnd; + selectedRange.length = distanceFromRangeEnd; + selectionAnchor = HFSum(selectedRange.length, selectedRange.location); + } + HFASSERT(HFRangeIsSubrangeOfRange(selectedRange, HFRangeMake(0, [self contentsLength]))); + selectionAnchorRange = selectedRange; + [self _setSingleSelectedContentsRange:selectedRange]; + } + else { + /* No modifier key selection. The selection anchor is not used. */ + [self _setSingleSelectedContentsRange:HFRangeMake(characterIndex, 0)]; + selectionAnchor = characterIndex; + } +} + +- (void)continueSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex { + USE(event); + HFASSERT(_hfflags.selectionInProgress); + HFASSERT(byteIndex <= [self contentsLength]); + BEGIN_TRANSACTION(); + if (_hfflags.commandExtendSelection) { + /* Clear any zero-length ranges, unless there's only one */ + NSUInteger rangeCount = [selectedContentsRanges count]; + NSUInteger rangeIndex = rangeCount; + while (rangeIndex-- > 0) { + if (rangeCount > 1 && [selectedContentsRanges[rangeIndex] HFRange].length == 0) { + [selectedContentsRanges removeObjectAtIndex:rangeIndex]; + rangeCount--; + } + } + selectionAnchorRange.location = MIN(byteIndex, selectionAnchor); + selectionAnchorRange.length = MAX(byteIndex, selectionAnchor) - selectionAnchorRange.location; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + else if (_hfflags.shiftExtendSelection) { + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + HFASSERT(selectionAnchor != NO_SELECTION); + HFRange range; + if (! HFLocationInRange(byteIndex, selectionAnchorRange)) { + /* The character index is outside of the selection anchor range. The new range is just the selected anchor range combined with the character index. */ + range.location = MIN(byteIndex, selectionAnchorRange.location); + unsigned long long rangeEnd = MAX(byteIndex, HFSum(selectionAnchorRange.location, selectionAnchorRange.length)); + HFASSERT(rangeEnd >= range.location); + range.length = rangeEnd - range.location; + } + else { + /* The character is within the selection anchor range. We use the selection anchor index to determine which "side" of the range is selected. */ + range.location = MIN(selectionAnchor, byteIndex); + range.length = HFAbsoluteDifference(selectionAnchor, byteIndex); + } + [self _setSingleSelectedContentsRange:range]; + } + else { + /* No modifier key selection */ + HFRange range; + range.location = MIN(byteIndex, selectionAnchor); + range.length = MAX(byteIndex, selectionAnchor) - range.location; + [self _setSingleSelectedContentsRange:range]; + } + END_TRANSACTION(); + VALIDATE_SELECTION(); +} + +- (void)endSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)characterIndex { + USE(event); + HFASSERT(_hfflags.selectionInProgress); + HFASSERT(characterIndex <= [self contentsLength]); + if (_hfflags.commandExtendSelection) { + selectionAnchorRange.location = MIN(characterIndex, selectionAnchor); + selectionAnchorRange.length = MAX(characterIndex, selectionAnchor) - selectionAnchorRange.location; + + /* "Commit" our selectionAnchorRange */ + NSArray *newSelection = [self _invertedSelectedContentsRanges]; + [selectedContentsRanges setArray:newSelection]; + } + else if (_hfflags.shiftExtendSelection) { + HFASSERT(selectionAnchorRange.location != NO_SELECTION); + HFASSERT(selectionAnchor != NO_SELECTION); + HFRange range; + if (! HFLocationInRange(characterIndex, selectionAnchorRange)) { + /* The character index is outside of the selection anchor range. The new range is just the selected anchor range combined with the character index. */ + range.location = MIN(characterIndex, selectionAnchorRange.location); + unsigned long long rangeEnd = MAX(characterIndex, HFSum(selectionAnchorRange.location, selectionAnchorRange.length)); + HFASSERT(rangeEnd >= range.location); + range.length = rangeEnd - range.location; + } + else { + /* The character is within the selection anchor range. We use the selection anchor index to determine which "side" of the range is selected. */ + range.location = MIN(selectionAnchor, characterIndex); + range.length = HFAbsoluteDifference(selectionAnchor, characterIndex); + } + [self _setSingleSelectedContentsRange:range]; + } + else { + /* No modifier key selection */ + HFRange range; + range.location = MIN(characterIndex, selectionAnchor); + range.length = MAX(characterIndex, selectionAnchor) - range.location; + [self _setSingleSelectedContentsRange:range]; + } + + _hfflags.selectionInProgress = NO; + _hfflags.shiftExtendSelection = NO; + _hfflags.commandExtendSelection = NO; + selectionAnchor = NO_SELECTION; +} + +- (double)selectionPulseAmount { + double result = 0; + if (pulseSelectionStartTime > 0) { + CFTimeInterval diff = pulseSelectionCurrentTime - pulseSelectionStartTime; + if (diff > 0 && diff < kPulseDuration) { + result = 1. - fabs(diff * 2 - kPulseDuration) / kPulseDuration; + } + } + return result; +} + +- (void)firePulseTimer:(NSTimer *)timer { + USE(timer); + HFASSERT(pulseSelectionStartTime != 0); + pulseSelectionCurrentTime = CFAbsoluteTimeGetCurrent(); + [self _addPropertyChangeBits:HFControllerSelectionPulseAmount]; + if (pulseSelectionCurrentTime - pulseSelectionStartTime > kPulseDuration) { + [pulseSelectionTimer invalidate]; + [pulseSelectionTimer release]; + pulseSelectionTimer = nil; + } +} + +- (void)pulseSelection { + pulseSelectionStartTime = CFAbsoluteTimeGetCurrent(); + if (pulseSelectionTimer == nil) { + pulseSelectionTimer = [[NSTimer scheduledTimerWithTimeInterval:(1. / 30.) target:self selector:@selector(firePulseTimer:) userInfo:nil repeats:YES] retain]; + } +} + +- (void)scrollByLines:(long double)lines { + HFFPRange lineRange = [self displayedLineRange]; + HFASSERT(HFULToFP([self totalLineCount]) >= lineRange.length); + long double maxScroll = HFULToFP([self totalLineCount]) - lineRange.length; + if (lines < 0) { + lineRange.location -= MIN(lineRange.location, -lines); + } + else { + lineRange.location = MIN(maxScroll, lineRange.location + lines); + } + [self setDisplayedLineRange:lineRange]; +} + +- (void)scrollWithScrollEvent:(NSEvent *)scrollEvent { + HFASSERT(scrollEvent != NULL); + HFASSERT([scrollEvent type] == NSScrollWheel); + CGFloat preciseScroll = 0; + BOOL hasPreciseScroll; + + /* Prefer precise deltas */ + if ([scrollEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) { + hasPreciseScroll = [scrollEvent hasPreciseScrollingDeltas]; + if (hasPreciseScroll) { + /* In this case, we're going to scroll by a certain number of points */ + preciseScroll = [scrollEvent scrollingDeltaY]; + } + } else if ([scrollEvent respondsToSelector:@selector(deviceDeltaY)]) { + /* Legacy (SnowLeopard) support */ + hasPreciseScroll = ([scrollEvent subtype] == 1); + if (hasPreciseScroll) { + preciseScroll = [scrollEvent deviceDeltaY]; + } + } else { + hasPreciseScroll = NO; + } + + long double scrollY = 0; + if (! hasPreciseScroll) { + scrollY = -kScrollMultiplier * [scrollEvent deltaY]; + } else { + scrollY = -preciseScroll / [self lineHeight]; + } + [self scrollByLines:scrollY]; +} + +- (void)setSelectedContentsRanges:(NSArray *)selectedRanges { + REQUIRE_NOT_NULL(selectedRanges); + [selectedContentsRanges setArray:selectedRanges]; + VALIDATE_SELECTION(); + selectionAnchor = NO_SELECTION; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; +} + +- (IBAction)selectAll:sender { + USE(sender); + if (_hfflags.selectable) { + [self _setSingleSelectedContentsRange:HFRangeMake(0, [self contentsLength])]; + } +} + +- (void)_addRangeToSelection:(HFRange)range { + [selectedContentsRanges addObject:[HFRangeWrapper withRange:range]]; + [selectedContentsRanges setArray:[HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]]; + VALIDATE_SELECTION(); +} + +- (void)_removeRangeFromSelection:(HFRange)inputRange withCursorLocationIfAllSelectionRemoved:(unsigned long long)cursorLocation { + NSUInteger selectionCount = [selectedContentsRanges count]; + HFASSERT(selectionCount > 0 && selectionCount <= NSUIntegerMax / 2); + NSUInteger rangeIndex = 0; + NSArray *wrappers; + NEW_ARRAY(HFRange, tempRanges, selectionCount * 2); + FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { + HFRange range = [wrapper HFRange]; + if (! HFIntersectsRange(range, inputRange)) { + tempRanges[rangeIndex++] = range; + } + else { + if (range.location < inputRange.location) { + tempRanges[rangeIndex++] = HFRangeMake(range.location, inputRange.location - range.location); + } + if (HFMaxRange(range) > HFMaxRange(inputRange)) { + tempRanges[rangeIndex++] = HFRangeMake(HFMaxRange(inputRange), HFMaxRange(range) - HFMaxRange(inputRange)); + } + } + } + if (rangeIndex == 0 || (rangeIndex == 1 && tempRanges[0].length == 0)) { + /* We removed all of our ranges. Telescope us. */ + HFASSERT(cursorLocation <= [self contentsLength]); + [self _setSingleSelectedContentsRange:HFRangeMake(cursorLocation, 0)]; + } + else { + wrappers = [HFRangeWrapper withRanges:tempRanges count:rangeIndex]; + [selectedContentsRanges setArray:[HFRangeWrapper organizeAndMergeRanges:wrappers]]; + } + FREE_ARRAY(tempRanges); + VALIDATE_SELECTION(); +} + +- (void)_moveDirectionDiscardingSelection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + BEGIN_TRANSACTION(); + BOOL selectionWasEmpty = ([selectedContentsRanges count] == 1 && [selectedContentsRanges[0] HFRange].length == 0); + BOOL directionIsForward = (direction == HFControllerDirectionRight); + HFRange selectedRange = [self _telescopeSelectionRangeInDirection: (directionIsForward ? HFControllerDirectionRight : HFControllerDirectionLeft)]; + HFASSERT(selectedRange.length == 0); + HFASSERT([self contentsLength] >= selectedRange.location); + /* A movement of just 1 with a selection only clears the selection; it does not move the cursor */ + if (selectionWasEmpty || amountToMove > 1) { + if (direction == HFControllerDirectionLeft) { + selectedRange.location -= MIN(amountToMove, selectedRange.location); + } + else { + selectedRange.location += MIN(amountToMove, [self contentsLength] - selectedRange.location); + } + } + selectionAnchor = NO_SELECTION; + [self _setSingleSelectedContentsRange:selectedRange]; + [self _ensureVisibilityOfLocation:selectedRange.location]; + END_TRANSACTION(); +} + +/* In _extendSelectionInDirection:byAmount:, we only allow left/right movement. up/down is not allowed. */ +- (void)_extendSelectionInDirection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + unsigned long long minSelection = [self _minimumSelectionLocation]; + unsigned long long maxSelection = [self _maximumSelectionLocation]; + BOOL selectionChanged = NO; + unsigned long long locationToMakeVisible = NO_SELECTION; + unsigned long long contentsLength = [self contentsLength]; + if (selectionAnchor == NO_SELECTION) { + /* Pick the anchor opposite the choice of direction */ + if (direction == HFControllerDirectionLeft) selectionAnchor = maxSelection; + else selectionAnchor = minSelection; + } + if (direction == HFControllerDirectionLeft) { + if (minSelection >= selectionAnchor && maxSelection > minSelection) { + unsigned long long amountToRemove = llmin(maxSelection - selectionAnchor, amountToMove); + unsigned long long amountToAdd = llmin(amountToMove - amountToRemove, selectionAnchor); + if (amountToRemove > 0) [self _removeRangeFromSelection:HFRangeMake(maxSelection - amountToRemove, amountToRemove) withCursorLocationIfAllSelectionRemoved:minSelection]; + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(selectionAnchor - amountToAdd, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = (amountToAdd > 0 ? selectionAnchor - amountToAdd : maxSelection - amountToRemove); + } + else { + if (minSelection > 0) { + NSUInteger amountToAdd = ll2l(llmin(minSelection, amountToMove)); + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(minSelection - amountToAdd, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = minSelection - amountToAdd; + } + } + } + else if (direction == HFControllerDirectionRight) { + if (maxSelection <= selectionAnchor && maxSelection > minSelection) { + HFASSERT(contentsLength >= maxSelection); + unsigned long long amountToRemove = ll2l(llmin(maxSelection - minSelection, amountToMove)); + unsigned long long amountToAdd = amountToMove - amountToRemove; + if (amountToRemove > 0) [self _removeRangeFromSelection:HFRangeMake(minSelection, amountToRemove) withCursorLocationIfAllSelectionRemoved:maxSelection]; + if (amountToAdd > 0) [self _addRangeToSelection:HFRangeMake(maxSelection, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = llmin(contentsLength, (amountToAdd > 0 ? maxSelection + amountToAdd : minSelection + amountToRemove)); + } + else { + if (maxSelection < contentsLength) { + NSUInteger amountToAdd = ll2l(llmin(contentsLength - maxSelection, amountToMove)); + [self _addRangeToSelection:HFRangeMake(maxSelection, amountToAdd)]; + selectionChanged = YES; + locationToMakeVisible = maxSelection + amountToAdd; + } + } + } + if (selectionChanged) { + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + if (locationToMakeVisible != NO_SELECTION) [self _ensureVisibilityOfLocation:locationToMakeVisible]; + END_TRANSACTION(); + } +} + +/* Returns the distance to the next "word" (at least 1, unless we are empty). Here a word is identified as a column. If there are no columns, a word is a line. This is used for word movement (e.g. option + right arrow) */ +- (unsigned long long)_distanceToWordBoundaryForDirection:(HFControllerMovementDirection)direction { + unsigned long long result = 0, locationToConsider; + + /* Figure out how big a word is. By default, it's the column width, unless we have no columns, in which case it's the bytes per line. */ + NSUInteger wordGranularity = [self bytesPerColumn]; + if (wordGranularity == 0) wordGranularity = MAX(1u, [self bytesPerLine]); + if (selectionAnchor == NO_SELECTION) { + /* Pick the anchor inline with the choice of direction */ + if (direction == HFControllerDirectionLeft) locationToConsider = [self _minimumSelectionLocation]; + else locationToConsider = [self _maximumSelectionLocation]; + } else { + /* Just use the anchor */ + locationToConsider = selectionAnchor; + } + if (direction == HFControllerDirectionRight) { + result = HFRoundUpToNextMultipleSaturate(locationToConsider, wordGranularity) - locationToConsider; + } else { + result = locationToConsider % wordGranularity; + if (result == 0) result = wordGranularity; + } + return result; + +} + +/* Anchored selection is not allowed; neither is up/down movement */ +- (void)_shiftSelectionInDirection:(HFControllerMovementDirection)direction byAmount:(unsigned long long)amountToMove { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + HFASSERT(selectionAnchor == NO_SELECTION); + NSUInteger i, max = [selectedContentsRanges count]; + const unsigned long long maxLength = [self contentsLength]; + NSMutableArray *newRanges = [NSMutableArray arrayWithCapacity:max]; + BOOL hasAddedNonemptyRange = NO; + for (i=0; i < max; i++) { + HFRange range = [selectedContentsRanges[i] HFRange]; + HFASSERT(range.location <= maxLength && HFMaxRange(range) <= maxLength); + if (direction == HFControllerDirectionRight) { + unsigned long long offset = MIN(maxLength - range.location, amountToMove); + unsigned long long lengthToSubtract = MIN(range.length, amountToMove - offset); + range.location += offset; + range.length -= lengthToSubtract; + } + else { /* direction == HFControllerDirectionLeft */ + unsigned long long negOffset = MIN(amountToMove, range.location); + unsigned long long lengthToSubtract = MIN(range.length, amountToMove - negOffset); + range.location -= negOffset; + range.length -= lengthToSubtract; + } + [newRanges addObject:[HFRangeWrapper withRange:range]]; + hasAddedNonemptyRange = hasAddedNonemptyRange || (range.length > 0); + } + + newRanges = [[[HFRangeWrapper organizeAndMergeRanges:newRanges] mutableCopy] autorelease]; + + BOOL hasFoundEmptyRange = NO; + max = [newRanges count]; + for (i=0; i < max; i++) { + HFRange range = [newRanges[i] HFRange]; + if (range.length == 0) { + if (hasFoundEmptyRange || hasAddedNonemptyRange) { + [newRanges removeObjectAtIndex:i]; + i--; + max--; + } + hasFoundEmptyRange = YES; + } + } + [selectedContentsRanges setArray:newRanges]; + VALIDATE_SELECTION(); + [self _addPropertyChangeBits:HFControllerSelectedRanges]; +} + +__attribute__((unused)) +static BOOL rangesAreInAscendingOrder(NSEnumerator *rangeEnumerator) { + unsigned long long index = 0; + HFRangeWrapper *rangeWrapper; + while ((rangeWrapper = [rangeEnumerator nextObject])) { + HFRange range = [rangeWrapper HFRange]; + if (range.location < index) return NO; + index = HFSum(range.location, range.length); + } + return YES; +} + +- (BOOL)_registerCondemnedRangesForUndo:(NSArray *)ranges selectingRangesAfterUndo:(BOOL)selectAfterUndo { + HFASSERT(ranges != NULL); + HFASSERT(ranges != selectedContentsRanges); //selectedContentsRanges is mutable - we really don't want to stash it away with undo + BOOL result = NO; + NSUndoManager *manager = [self undoManager]; + NSUInteger rangeCount = [ranges count]; + if (! manager || ! rangeCount) return NO; + + HFASSERT(rangesAreInAscendingOrder([ranges objectEnumerator])); + + NSMutableArray *rangesToRestore = [NSMutableArray arrayWithCapacity:rangeCount]; + NSMutableArray *correspondingByteArrays = [NSMutableArray arrayWithCapacity:rangeCount]; + HFByteArray *bytes = [self byteArray]; + + /* Enumerate the ranges in forward order so when we insert them, we insert later ranges before earlier ones, so we don't have to worry about shifting indexes */ + FOREACH(HFRangeWrapper *, rangeWrapper, ranges) { + HFRange range = [rangeWrapper HFRange]; + if (range.length > 0) { + [rangesToRestore addObject:[HFRangeWrapper withRange:HFRangeMake(range.location, 0)]]; + [correspondingByteArrays addObject:[bytes subarrayWithRange:range]]; + result = YES; + } + } + + if (result) [self _registerUndoOperationForInsertingByteArrays:correspondingByteArrays inRanges:rangesToRestore withSelectionAction:(selectAfterUndo ? eSelectResult : eSelectAfterResult)]; + return result; +} + +- (void)_commandDeleteRanges:(NSArray *)rangesToDelete { + HFASSERT(rangesToDelete != selectedContentsRanges); //selectedContentsRanges is mutable - we really don't want to stash it away with undo + HFASSERT(rangesAreInAscendingOrder([rangesToDelete objectEnumerator])); + + /* Delete all the selection - in reverse order */ + unsigned long long minSelection = ULLONG_MAX; + BOOL somethingWasDeleted = NO; + [self _registerCondemnedRangesForUndo:rangesToDelete selectingRangesAfterUndo:YES]; + NSUInteger rangeIndex = [rangesToDelete count]; + HFASSERT(rangeIndex > 0); + while (rangeIndex--) { + HFRange range = [rangesToDelete[rangeIndex] HFRange]; + minSelection = llmin(range.location, minSelection); + if (range.length > 0) { + [byteArray deleteBytesInRange:range]; + somethingWasDeleted = YES; + } + } + + HFASSERT(minSelection != ULLONG_MAX); + if (somethingWasDeleted) { + BEGIN_TRANSACTION(); + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength]; + [self _setSingleSelectedContentsRange:HFRangeMake(minSelection, 0)]; + [self _updateDisplayedRange]; + END_TRANSACTION(); + } + else { + NSBeep(); + } +} + +- (void)_commandInsertByteArrays:(NSArray *)byteArrays inRanges:(NSArray *)ranges withSelectionAction:(HFControllerSelectAction)selectionAction { + HFASSERT(selectionAction < NUM_SELECTION_ACTIONS); + REQUIRE_NOT_NULL(byteArrays); + REQUIRE_NOT_NULL(ranges); + HFASSERT([ranges count] == [byteArrays count]); + NSUInteger index, max = [ranges count]; + HFByteArray *bytes = [self byteArray]; + HFASSERT(rangesAreInAscendingOrder([ranges objectEnumerator])); + + NSMutableArray *byteArraysToInsertOnUndo = [NSMutableArray arrayWithCapacity:max]; + NSMutableArray *rangesToInsertOnUndo = [NSMutableArray arrayWithCapacity:max]; + + BEGIN_TRANSACTION(); + if (selectionAction == eSelectResult || selectionAction == eSelectAfterResult) { + [selectedContentsRanges removeAllObjects]; + } + unsigned long long endOfInsertedRanges = ULLONG_MAX; + for (index = 0; index < max; index++) { + HFRange range = [ranges[index] HFRange]; + HFByteArray *oldBytes = [bytes subarrayWithRange:range]; + [byteArraysToInsertOnUndo addObject:oldBytes]; + HFByteArray *newBytes = byteArrays[index]; + EXPECT_CLASS(newBytes, [HFByteArray class]); + [bytes insertByteArray:newBytes inRange:range]; + HFRange insertedRange = HFRangeMake(range.location, [newBytes length]); + HFRangeWrapper *insertedRangeWrapper = [HFRangeWrapper withRange:insertedRange]; + [rangesToInsertOnUndo addObject:insertedRangeWrapper]; + if (selectionAction == eSelectResult) { + [selectedContentsRanges addObject:insertedRangeWrapper]; + } + else { + endOfInsertedRanges = HFMaxRange(insertedRange); + } + } + if (selectionAction == eSelectAfterResult) { + HFASSERT([ranges count] > 0); + [selectedContentsRanges addObject:[HFRangeWrapper withRange:HFRangeMake(endOfInsertedRanges, 0)]]; + } + + if (selectionAction == ePreserveSelection) { + HFASSERT([selectedContentsRanges count] > 0); + [self _clipSelectedContentsRangesToLength:[self contentsLength]]; + } + + VALIDATE_SELECTION(); + HFASSERT([byteArraysToInsertOnUndo count] == [rangesToInsertOnUndo count]); + [self _registerUndoOperationForInsertingByteArrays:byteArraysToInsertOnUndo inRanges:rangesToInsertOnUndo withSelectionAction:(selectionAction == ePreserveSelection ? ePreserveSelection : eSelectAfterResult)]; + [self _updateDisplayedRange]; + [self maximizeVisibilityOfContentsRange:[selectedContentsRanges[0] HFRange]]; + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges]; + END_TRANSACTION(); +} + +/* The user has hit undo after typing a string. */ +- (void)_commandReplaceBytesAfterBytesFromBeginning:(unsigned long long)leftOffset upToBytesFromEnd:(unsigned long long)rightOffset withByteArray:(HFByteArray *)bytesToReinsert { + HFASSERT(bytesToReinsert != NULL); + + BEGIN_TRANSACTION(); + HFByteArray *bytes = [self byteArray]; + unsigned long long contentsLength = [self contentsLength]; + HFASSERT(leftOffset <= contentsLength); + HFASSERT(rightOffset <= contentsLength); + HFASSERT(contentsLength - rightOffset >= leftOffset); + HFRange rangeToReplace = HFRangeMake(leftOffset, contentsLength - rightOffset - leftOffset); + [self _registerCondemnedRangesForUndo:[HFRangeWrapper withRanges:&rangeToReplace count:1] selectingRangesAfterUndo:NO]; + [bytes insertByteArray:bytesToReinsert inRange:rangeToReplace]; + [self _updateDisplayedRange]; + [self _setSingleSelectedContentsRange:HFRangeMake(rangeToReplace.location, [bytesToReinsert length])]; + [self _addPropertyChangeBits:HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges]; + END_TRANSACTION(); +} + +/* We use NSNumbers instead of long longs here because Tiger/PPC NSInvocation had trouble with long longs */ +- (void)_commandValueObjectsReplaceBytesAfterBytesFromBeginning:(NSNumber *)leftOffset upToBytesFromEnd:(NSNumber *)rightOffset withByteArray:(HFByteArray *)bytesToReinsert { + HFASSERT(leftOffset != NULL); + HFASSERT(rightOffset != NULL); + EXPECT_CLASS(leftOffset, NSNumber); + EXPECT_CLASS(rightOffset, NSNumber); + [self _commandReplaceBytesAfterBytesFromBeginning:[leftOffset unsignedLongLongValue] upToBytesFromEnd:[rightOffset unsignedLongLongValue] withByteArray:bytesToReinsert]; +} + +- (void)moveInDirection:(HFControllerMovementDirection)direction byByteCount:(unsigned long long)amountToMove withSelectionTransformation:(HFControllerSelectionTransformation)transformation usingAnchor:(BOOL)useAnchor { + if (! useAnchor) selectionAnchor = NO_SELECTION; + switch (transformation) { + case HFControllerDiscardSelection: + [self _moveDirectionDiscardingSelection:direction byAmount:amountToMove]; + break; + + case HFControllerShiftSelection: + [self _shiftSelectionInDirection:direction byAmount:amountToMove]; + break; + + case HFControllerExtendSelection: + [self _extendSelectionInDirection:direction byAmount:amountToMove]; + break; + + default: + [NSException raise:NSInvalidArgumentException format:@"Invalid transformation %ld", (long)transformation]; + break; + } + if (! useAnchor) selectionAnchor = NO_SELECTION; +} + +- (void)moveInDirection:(HFControllerMovementDirection)direction withGranularity:(HFControllerMovementGranularity)granularity andModifySelection:(BOOL)extendSelection { + HFASSERT(granularity == HFControllerMovementByte || granularity == HFControllerMovementColumn || granularity == HFControllerMovementLine || granularity == HFControllerMovementPage || granularity == HFControllerMovementDocument); + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + unsigned long long bytesToMove = 0; + switch (granularity) { + case HFControllerMovementByte: + bytesToMove = 1; + break; + case HFControllerMovementColumn: + /* This is a tricky case because the amount we have to move depends on our position in the column. */ + bytesToMove = [self _distanceToWordBoundaryForDirection:direction]; + break; + case HFControllerMovementLine: + bytesToMove = [self bytesPerLine]; + break; + case HFControllerMovementPage: + bytesToMove = HFProductULL([self bytesPerLine], HFFPToUL(MIN(floorl([self displayedLineRange].length), 1.))); + break; + case HFControllerMovementDocument: + bytesToMove = [self contentsLength]; + break; + } + HFControllerSelectionTransformation transformation = (extendSelection ? HFControllerExtendSelection : HFControllerDiscardSelection); + [self moveInDirection:direction byByteCount:bytesToMove withSelectionTransformation:transformation usingAnchor:YES]; +} + +- (void)moveToLineBoundaryInDirection:(HFControllerMovementDirection)direction andModifySelection:(BOOL)modifySelection { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + BEGIN_TRANSACTION(); + unsigned long long locationToMakeVisible; + HFRange additionalSelection; + + if (direction == HFControllerDirectionLeft) { + /* If we are at the beginning of a line, this should be a no-op */ + unsigned long long minLocation = [self _minimumSelectionLocation]; + unsigned long long newMinLocation = (minLocation / bytesPerLine) * bytesPerLine; + locationToMakeVisible = newMinLocation; + additionalSelection = HFRangeMake(newMinLocation, minLocation - newMinLocation); + } + else { + /* This always advances to the next line */ + unsigned long long maxLocation = [self _maximumSelectionLocation]; + unsigned long long proposedNewMaxLocation = HFRoundUpToNextMultipleSaturate(maxLocation, bytesPerLine); + unsigned long long newMaxLocation = MIN([self contentsLength], proposedNewMaxLocation); + HFASSERT(newMaxLocation >= maxLocation); + locationToMakeVisible = newMaxLocation; + additionalSelection = HFRangeMake(maxLocation, newMaxLocation - maxLocation); + } + + if (modifySelection) { + if (additionalSelection.length > 0) { + [self _addRangeToSelection:additionalSelection]; + [self _addPropertyChangeBits:HFControllerSelectedRanges]; + } + } + else { + [self _setSingleSelectedContentsRange:HFRangeMake(locationToMakeVisible, 0)]; + } + [self _ensureVisibilityOfLocation:locationToMakeVisible]; + END_TRANSACTION(); +} + +- (void)deleteSelection { + if ([self editMode] == HFOverwriteMode || ! [self editable]) { + NSBeep(); + } + else { + [self _commandDeleteRanges:[HFRangeWrapper organizeAndMergeRanges:selectedContentsRanges]]; + } +} + +// Called after Replace All is finished. +- (void)replaceByteArray:(HFByteArray *)newArray { + REQUIRE_NOT_NULL(newArray); + EXPECT_CLASS(newArray, HFByteArray); + HFRange entireRange = HFRangeMake(0, [self contentsLength]); + if ([self editMode] == HFOverwriteMode && [newArray length] != entireRange.length) { + NSBeep(); + } + else { + [self _commandInsertByteArrays:@[newArray] inRanges:[HFRangeWrapper withRanges:&entireRange count:1] withSelectionAction:ePreserveSelection]; + } +} + +- (BOOL)insertData:(NSData *)data replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing { + REQUIRE_NOT_NULL(data); + BOOL result; +#if ! NDEBUG + const unsigned long long startLength = [byteArray length]; + unsigned long long expectedNewLength; + if ([self editMode] == HFOverwriteMode) { + expectedNewLength = startLength; + } + else { + expectedNewLength = startLength + [data length] - previousBytes; + FOREACH(HFRangeWrapper*, wrapper, [self selectedContentsRanges]) expectedNewLength -= [wrapper HFRange].length; + } +#endif + HFByteSlice *slice = [[HFSharedMemoryByteSlice alloc] initWithUnsharedData:data]; + HFASSERT([slice length] == [data length]); + HFByteArray *array = [[preferredByteArrayClass() alloc] init]; + [array insertByteSlice:slice inRange:HFRangeMake(0, 0)]; + HFASSERT([array length] == [data length]); + result = [self insertByteArray:array replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing]; + [slice release]; + [array release]; +#if ! NDEBUG + HFASSERT((result && [byteArray length] == expectedNewLength) || (! result && [byteArray length] == startLength)); +#endif + return result; +} + +- (BOOL)_insertionModeCoreInsertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing outNewSingleSelectedRange:(HFRange *)outSelectedRange { + HFASSERT([self editMode] == HFInsertMode); + REQUIRE_NOT_NULL(bytesToInsert); + + /* Guard against overflow. If [bytesToInsert length] + [self contentsLength] - previousBytes overflows, then we can't do it */ + HFASSERT([self contentsLength] >= previousBytes); + if (! HFSumDoesNotOverflow([bytesToInsert length], [self contentsLength] - previousBytes)) { + return NO; //don't do anything + } + + + unsigned long long amountDeleted = 0, amountAdded = [bytesToInsert length]; + HFByteArray *bytes = [self byteArray]; + + /* Delete all the selection - in reverse order - except the last (really first) one, which we will overwrite. */ + NSArray *allRangesToRemove = [HFRangeWrapper organizeAndMergeRanges:[self selectedContentsRanges]]; + HFRange rangeToReplace = [allRangesToRemove[0] HFRange]; + HFASSERT(rangeToReplace.location == [self _minimumSelectionLocation]); + NSUInteger rangeIndex, rangeCount = [allRangesToRemove count]; + HFASSERT(rangeCount > 0); + NSMutableArray *rangesToDelete = [NSMutableArray arrayWithCapacity:rangeCount - 1]; + for (rangeIndex = rangeCount - 1; rangeIndex > 0; rangeIndex--) { + HFRangeWrapper *rangeWrapper = allRangesToRemove[rangeIndex]; + HFRange range = [rangeWrapper HFRange]; + if (range.length > 0) { + amountDeleted = HFSum(amountDeleted, range.length); + [rangesToDelete insertObject:rangeWrapper atIndex:0]; + } + } + + if ([rangesToDelete count] > 0) { + HFASSERT(rangesAreInAscendingOrder([rangesToDelete objectEnumerator])); + /* TODO: This is problematic because it overwrites the selection that gets set by _activateTypingUndoCoalescingForReplacingRange:, so we lose the first selection in a multiple selection scenario. */ + [self _registerCondemnedRangesForUndo:rangesToDelete selectingRangesAfterUndo:YES]; + NSEnumerator *enumer = [rangesToDelete reverseObjectEnumerator]; + HFRangeWrapper *rangeWrapper; + while ((rangeWrapper = [enumer nextObject])) { + [bytes deleteBytesInRange:[rangeWrapper HFRange]]; + } + } + + rangeToReplace.length = HFSum(rangeToReplace.length, previousBytes); + + /* Insert data */ +#if ! NDEBUG + unsigned long long expectedLength = [byteArray length] + [bytesToInsert length] - rangeToReplace.length; +#endif + [byteArray insertByteArray:bytesToInsert inRange:rangeToReplace]; +#if ! NDEBUG + HFASSERT(expectedLength == [byteArray length]); +#endif + + /* return the new selected range */ + *outSelectedRange = HFRangeMake(HFSum(rangeToReplace.location, amountAdded), 0); + return YES; +} + + +- (BOOL)_overwriteModeCoreInsertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing outRangeToRemoveFromSelection:(HFRange *)outRangeToRemove { + REQUIRE_NOT_NULL(bytesToInsert); + const unsigned long long byteArrayLength = [byteArray length]; + const unsigned long long bytesToInsertLength = [bytesToInsert length]; + HFRange firstSelectedRange = [selectedContentsRanges[0] HFRange]; + HFRange proposedRangeToOverwrite = HFRangeMake(firstSelectedRange.location, bytesToInsertLength); + HFASSERT(proposedRangeToOverwrite.location >= previousBytes); + proposedRangeToOverwrite.location -= previousBytes; + if (! HFRangeIsSubrangeOfRange(proposedRangeToOverwrite, HFRangeMake(0, byteArrayLength))) { + /* The user tried to overwrite past the end */ + NSBeep(); + return NO; + } + + [byteArray insertByteArray:bytesToInsert inRange:proposedRangeToOverwrite]; + + *outRangeToRemove = proposedRangeToOverwrite; + return YES; +} + +- (BOOL)insertByteArray:(HFByteArray *)bytesToInsert replacingPreviousBytes:(unsigned long long)previousBytes allowUndoCoalescing:(BOOL)allowUndoCoalescing { +#if ! NDEBUG + if (previousBytes > 0) { + NSArray *selectedRanges = [self selectedContentsRanges]; + HFASSERT([selectedRanges count] == 1); + HFRange selectedRange = [selectedRanges[0] HFRange]; + HFASSERT(selectedRange.location >= previousBytes); //don't try to delete more trailing bytes than we actually have! + } +#endif + REQUIRE_NOT_NULL(bytesToInsert); + + + BEGIN_TRANSACTION(); + unsigned long long beforeLength = [byteArray length]; + BOOL inOverwriteMode = [self editMode] == HFOverwriteMode; + HFRange modificationRange; //either range to remove from selection if in overwrite mode, or range to select if not + BOOL success; + if (inOverwriteMode) { + success = [self _overwriteModeCoreInsertByteArray:bytesToInsert replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing outRangeToRemoveFromSelection:&modificationRange]; + } + else { + success = [self _insertionModeCoreInsertByteArray:bytesToInsert replacingPreviousBytes:previousBytes allowUndoCoalescing:allowUndoCoalescing outNewSingleSelectedRange:&modificationRange]; + } + + if (success) { + /* Update our selection */ + [self _addPropertyChangeBits:HFControllerContentValue]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits:HFControllerContentValue]; + if (inOverwriteMode) { + [self _removeRangeFromSelection:modificationRange withCursorLocationIfAllSelectionRemoved:HFMaxRange(modificationRange)]; + [self maximizeVisibilityOfContentsRange:[selectedContentsRanges[0] HFRange]]; + } + else { + [self _setSingleSelectedContentsRange:modificationRange]; + [self maximizeVisibilityOfContentsRange:modificationRange]; + } + if (beforeLength != [byteArray length]) [self _addPropertyChangeBits:HFControllerContentLength]; + } + END_TRANSACTION(); + return success; +} + +- (void)deleteDirection:(HFControllerMovementDirection)direction { + HFASSERT(direction == HFControllerDirectionLeft || direction == HFControllerDirectionRight); + if ([self editMode] != HFInsertMode || ! [self editable]) { + NSBeep(); + return; + } + unsigned long long minSelection = [self _minimumSelectionLocation]; + unsigned long long maxSelection = [self _maximumSelectionLocation]; + if (maxSelection != minSelection) { + [self deleteSelection]; + } + else { + HFRange rangeToDelete = HFRangeMake(minSelection, 1); + BOOL rangeIsValid; + if (direction == HFControllerDirectionLeft) { + rangeIsValid = (rangeToDelete.location > 0); + rangeToDelete.location--; + } + else { + rangeIsValid = (rangeToDelete.location < [self contentsLength]); + } + if (rangeIsValid) { + BEGIN_TRANSACTION(); + [byteArray deleteBytesInRange:rangeToDelete]; + [self _setSingleSelectedContentsRange:HFRangeMake(rangeToDelete.location, 0)]; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits:HFControllerSelectedRanges | HFControllerContentValue | HFControllerContentLength]; + END_TRANSACTION(); + } + } +} + +- (HFEditMode)editMode { + return _hfflags.editMode; +} + +- (void)setEditMode:(HFEditMode)val +{ + if (val != _hfflags.editMode) { + _hfflags.editMode = val; + // don't allow undo coalescing when switching modes + [self _addPropertyChangeBits:HFControllerEditable]; + } +} + +- (void)reloadData { + BEGIN_TRANSACTION(); + [cachedData release]; + cachedData = nil; + [self _updateDisplayedRange]; + [self _addPropertyChangeBits: HFControllerContentValue | HFControllerContentLength]; + END_TRANSACTION(); +} + +#if BENCHMARK_BYTEARRAYS + ++ (void)_testByteArray { + HFByteArray* first = [[[HFFullMemoryByteArray alloc] init] autorelease]; + HFBTreeByteArray* second = [[[HFBTreeByteArray alloc] init] autorelease]; + first = nil; + // second = nil; + + //srandom(time(NULL)); + + unsigned opCount = 4096 * 512; + unsigned long long expectedLength = 0; + unsigned i; + for (i=1; i <= opCount; i++) { + @autoreleasepool { + NSUInteger op; + const unsigned long long length = [first length]; + unsigned long long offset; + unsigned long long number; + switch ((op = (random()%2))) { + case 0: { //insert + offset = random() % (1 + length); + HFByteSlice* slice = [[HFRandomDataByteSlice alloc] initWithRandomDataLength: 1 + random() % 1000]; + [first insertByteSlice:slice inRange:HFRangeMake(offset, 0)]; + [second insertByteSlice:slice inRange:HFRangeMake(offset, 0)]; + expectedLength += [slice length]; + [slice release]; + break; + } + case 1: { //delete + if (length > 0) { + offset = random() % length; + number = 1 + random() % (length - offset); + [first deleteBytesInRange:HFRangeMake(offset, number)]; + [second deleteBytesInRange:HFRangeMake(offset, number)]; + expectedLength -= number; + } + break; + } + } + } // @autoreleasepool + } +} + ++ (void)_testAttributeArrays { + HFByteRangeAttributeArray *naiveTree = [[HFNaiveByteRangeAttributeArray alloc] init]; + HFAnnotatedTreeByteRangeAttributeArray *smartTree = [[HFAnnotatedTreeByteRangeAttributeArray alloc] init]; + naiveTree = nil; + // smartTree = nil; + + NSString * const attributes[3] = {@"Alpha", @"Beta", @"Gamma"}; + + const NSUInteger supportedIndexEnd = NSNotFound; + NSUInteger round; + for (round = 0; round < 4096 * 256; round++) { + NSString *attribute = attributes[random() % (sizeof attributes / sizeof *attributes)]; + BOOL insert = ([smartTree isEmpty] || [naiveTree isEmpty] || (random() % 2)); + + unsigned long long end = random(); + unsigned long long start = random(); + if (end < start) { + unsigned long long temp = end; + end = start; + start = temp; + } + HFRange range = HFRangeMake(start, end - start); + + if (insert) { + [naiveTree addAttribute:attribute range:range]; + [smartTree addAttribute:attribute range:range]; + } + else { + [naiveTree removeAttribute:attribute range:range]; + [smartTree removeAttribute:attribute range:range]; + } + } + + [naiveTree release]; + [smartTree release]; +} + + ++ (void)initialize { + CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); + srandom(0); + [self _testByteArray]; + CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); + printf("Byte array time: %f\n", end - start); + + srandom(0); + start = CFAbsoluteTimeGetCurrent(); + [self _testAttributeArrays]; + end = CFAbsoluteTimeGetCurrent(); + printf("Attribute array time: %f\n", end - start); + + exit(0); +} + +#endif + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.h b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.h new file mode 100644 index 000000000..9debeb2ec --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.h @@ -0,0 +1,21 @@ +// +// HFFullMemoryByteArray.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! + @class HFFullMemoryByteArray + @brief A naive subclass of HFByteArray suitable mainly for testing. Use HFBTreeByteArray instead. + + HFFullMemoryByteArray is a simple subclass of HFByteArray that does not store any byte slices. Because it stores all data in an NSMutableData, it is not efficient. It is mainly useful as a naive implementation for testing. Use HFBTreeByteArray instead. +*/ +@interface HFFullMemoryByteArray : HFByteArray { + NSMutableData *data; +} + + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.m b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.m new file mode 100644 index 000000000..f5a582b96 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteArray.m @@ -0,0 +1,70 @@ +// +// HFFullMemoryByteArray.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +@implementation HFFullMemoryByteArray + +- (instancetype)init { + self = [super init]; + data = [[NSMutableData alloc] init]; + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { + return [data length]; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { + HFASSERT(range.length == 0 || dst != NULL); + HFASSERT(HFSumDoesNotOverflow(range.location, range.length)); + HFASSERT(range.location + range.length <= [self length]); + unsigned char* bytes = [data mutableBytes]; + memmove(dst, bytes + ll2l(range.location), ll2l(range.length)); +} + +- (HFByteArray *)subarrayWithRange:(HFRange)lrange { + HFRange entireRange = HFRangeMake(0, [self length]); + HFASSERT(HFRangeIsSubrangeOfRange(lrange, entireRange)); + NSRange range; + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + HFFullMemoryByteArray* result = [[[self class] alloc] init]; + [result->data setData:[data subdataWithRange:range]]; + return [result autorelease]; +} + +- (NSArray *)byteSlices { + return @[[[[HFFullMemoryByteSlice alloc] initWithData:data] autorelease]]; +} + +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { + [self incrementGenerationOrRaiseIfLockedForSelector:_cmd]; + HFASSERT([slice length] <= NSUIntegerMax); + NSUInteger length = ll2l([slice length]); + NSRange range; + HFASSERT(lrange.location <= NSUIntegerMax); + HFASSERT(lrange.length <= NSUIntegerMax); + HFASSERT(HFSumDoesNotOverflow(lrange.location, lrange.length)); + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + + void* buff = check_malloc(length); + [slice copyBytes:buff range:HFRangeMake(0, length)]; + [data replaceBytesInRange:range withBytes:buff length:length]; + free(buff); +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.h b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.h new file mode 100644 index 000000000..ec195cadd --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.h @@ -0,0 +1,21 @@ +// +// HFFullMemoryByteSlice.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFFullMemoryByteSlice + + @brief A simple subclass of HFByteSlice that wraps an NSData. For most uses, prefer HFSharedMemoryByteSlice. +*/ +@interface HFFullMemoryByteSlice : HFByteSlice { + NSData *data; +} + +/*! Init with a given NSData, which is copied via the \c -copy message. */ +- (instancetype)initWithData:(NSData *)val; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.m b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.m new file mode 100644 index 000000000..2a38ccdf7 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFullMemoryByteSlice.m @@ -0,0 +1,46 @@ +// +// HFFullMemoryByteSlice.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import "HFFullMemoryByteSlice.h" + + +@implementation HFFullMemoryByteSlice + +- (instancetype)initWithData:(NSData *)val { + REQUIRE_NOT_NULL(val); + self = [super init]; + data = [val copy]; + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { return [data length]; } + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange { + NSRange range; + HFASSERT(lrange.location <= NSUIntegerMax); + HFASSERT(lrange.length <= NSUIntegerMax); + HFASSERT(lrange.location + lrange.length >= lrange.location); + range.location = ll2l(lrange.location); + range.length = ll2l(lrange.length); + [data getBytes:dst range:range]; +} + +- (HFByteSlice *)subsliceWithRange:(HFRange)range { + HFASSERT(range.length > 0); + HFASSERT(range.location < [self length]); + HFASSERT([self length] - range.location >= range.length); + HFASSERT(range.location <= NSUIntegerMax); + HFASSERT(range.length <= NSUIntegerMax); + return [[[[self class] alloc] initWithData:[data subdataWithRange:NSMakeRange(ll2l(range.location), ll2l(range.length))]] autorelease]; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFFunctions.h b/thirdparty/SameBoy-old/HexFiend/HFFunctions.h new file mode 100644 index 000000000..aaab1076d --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFunctions.h @@ -0,0 +1,533 @@ +/* Functions and convenience methods for working with HFTypes */ + +#import +#import + +#define HFDEFAULT_FONT (@"Monaco") +#define HFDEFAULT_FONTSIZE ((CGFloat)11.) + +#define HFZeroRange (HFRange){0, 0} + +/*! + Makes an HFRange. An HFRange is like an NSRange except it uses unsigned long longs. +*/ +static inline HFRange HFRangeMake(unsigned long long loc, unsigned long long len) { + return (HFRange){loc, len}; +} + +/*! + Returns true if a given location is within a given HFRange. If the location is at the end of the range (range.location + range.length) this returns NO. +*/ +static inline BOOL HFLocationInRange(unsigned long long location, HFRange range) { + return location >= range.location && location - range.location < range.length; +} + +/*! + Like NSRangeToString but for HFRanges +*/ +static inline NSString* HFRangeToString(HFRange range) { + return [NSString stringWithFormat:@"{%llu, %llu}", range.location, range.length]; +} + +/*! + Converts a given HFFPRange to a string. +*/ +static inline NSString* HFFPRangeToString(HFFPRange range) { + return [NSString stringWithFormat:@"{%Lf, %Lf}", range.location, range.length]; +} + +/*! + Returns true if two HFRanges are equal. +*/ +static inline BOOL HFRangeEqualsRange(HFRange a, HFRange b) { + return a.location == b.location && a.length == b.length; +} + +/*! + Returns true if a + b does not overflow an unsigned long long. +*/ +static inline BOOL HFSumDoesNotOverflow(unsigned long long a, unsigned long long b) { + return a + b >= a; +} + +/*! + Returns true if a * b does not overflow an unsigned long long. +*/ +static inline BOOL HFProductDoesNotOverflow(unsigned long long a, unsigned long long b) { + if (b == 0) return YES; + unsigned long long result = a * b; + return result / b == a; +} + +/*! + Returns a * b as an NSUInteger. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline NSUInteger HFProductInt(NSUInteger a, NSUInteger b) { + NSUInteger result = a * b; + assert(a == 0 || result / a == b); //detect overflow + return result; +} + +/*! + Returns a + b as an NSUInteger. This asserts on overflow unless NDEBUG is defined. +*/ +static inline NSUInteger HFSumInt(NSUInteger a, NSUInteger b) { + assert(a + b >= a); + return a + b; +} + +/*! + Returns a + b as an NSUInteger, saturating at NSUIntegerMax + */ +static inline NSUInteger HFSumIntSaturate(NSUInteger a, NSUInteger b) { + NSUInteger result = a + b; + return (result < a) ? NSUIntegerMax : result; +} + +/*! + Returns a + b as an unsigned long long, saturating at ULLONG_MAX + */ +static inline unsigned long long HFSumULLSaturate(unsigned long long a, unsigned long long b) { + unsigned long long result = a + b; + return (result < a) ? ULLONG_MAX : result; +} + +/*! + Returns a * b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline unsigned long long HFProductULL(unsigned long long a, unsigned long long b) { + unsigned long long result = a * b; + assert(HFProductDoesNotOverflow(a, b)); //detect overflow + return result; +} + +/*! + Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. +*/ +static inline unsigned long long HFSum(unsigned long long a, unsigned long long b) { + assert(HFSumDoesNotOverflow(a, b)); + return a + b; +} + +/*! + Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined. + */ +static inline unsigned long long HFMaxULL(unsigned long long a, unsigned long long b) { + return a < b ? b : a; +} + +/*! + Returns a - b as an unsigned long long. This asserts on underflow (if b > a), unless NDEBUG is defined. +*/ +static inline unsigned long long HFSubtract(unsigned long long a, unsigned long long b) { + assert(a >= b); + return a - b; +} + +/*! + Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow. +*/ +static inline unsigned long long HFRoundUpToMultiple(unsigned long long a, unsigned long long b) { + // The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly + unsigned long long remainder = a % b; + if (remainder == 0) return a; + else return HFSum(a, b - remainder); +} + +/*! + Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow. + */ +static inline NSUInteger HFRoundUpToMultipleInt(NSUInteger a, NSUInteger b) { + // The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly + NSUInteger remainder = a % b; + if (remainder == 0) return a; + else return (NSUInteger)HFSum(a, b - remainder); +} + +/*! + Returns the least common multiple of A and B, and asserts on overflow or if A or B is zero. + */ +static inline NSUInteger HFLeastCommonMultiple(NSUInteger a, NSUInteger b) { + assert(a > 0); + assert(b > 0); + + /* Compute GCD. It ends up in U. */ + NSUInteger t, u = a, v = b; + while (v > 0) { + t = v; + v = u % v; + u = t; + } + + /* Return the product divided by the GCD, in an overflow safe manner */ + return HFProductInt(a/u, b); +} + + +/*! + Returns the smallest multiple of B strictly larger than A, or ULLONG_MAX if it would overflow +*/ +static inline unsigned long long HFRoundUpToNextMultipleSaturate(unsigned long long a, unsigned long long b) { + assert(b > 0); + unsigned long long result = a + (b - a % b); + if (result < a) result = ULLONG_MAX; //the saturation...on overflow go to the max + return result; +} + +/*! Like NSMaxRange, but for an HFRange. */ +static inline unsigned long long HFMaxRange(HFRange a) { + assert(HFSumDoesNotOverflow(a.location, a.length)); + return a.location + a.length; +} + +/*! Returns YES if needle is fully contained within haystack. Equal ranges are always considered to be subranges of each other (even if they are empty). Furthermore, a zero length needle at the end of haystack is considered a subrange - for example, {6, 0} is a subrange of {3, 3}. */ +static inline BOOL HFRangeIsSubrangeOfRange(HFRange needle, HFRange haystack) { + // If needle starts before haystack, or if needle is longer than haystack, it is not a subrange of haystack + if (needle.location < haystack.location || needle.length > haystack.length) return NO; + + // Their difference in lengths determines the maximum difference in their start locations. We know that these expressions cannot overflow because of the above checks. + return haystack.length - needle.length >= needle.location - haystack.location; +} + +/*! Splits a range about a subrange, returning by reference the prefix and suffix (which may have length zero). */ +static inline void HFRangeSplitAboutSubrange(HFRange range, HFRange subrange, HFRange *outPrefix, HFRange *outSuffix) { + // Requires it to be a subrange + assert(HFRangeIsSubrangeOfRange(subrange, range)); + outPrefix->location = range.location; + outPrefix->length = HFSubtract(subrange.location, range.location); + outSuffix->location = HFMaxRange(subrange); + outSuffix->length = HFMaxRange(range) - outSuffix->location; +} + +/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if they share at least one index in common. Thus, zero-length ranges do not intersect anything. */ +static inline BOOL HFIntersectsRange(HFRange a, HFRange b) { + // Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything. + if (a.length == 0 || b.length == 0) return NO; + + // rearrange (a.location < b.location + b.length && b.location < a.location + a.length) to not overflow + // = ! (a.location >= b.location + b.length || b.location >= a.location + a.length) + BOOL clause1 = (a.location >= b.location && a.location - b.location >= b.length); + BOOL clause2 = (b.location >= a.location && b.location - a.location >= a.length); + return ! (clause1 || clause2); +} + +/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if any fraction overlaps; zero-length ranges do not intersect anything. */ +static inline BOOL HFFPIntersectsRange(HFFPRange a, HFFPRange b) { + // Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything. + if (a.length == 0 || b.length == 0) return NO; + + if (a.location <= b.location && a.location + a.length >= b.location) return YES; + if (b.location <= a.location && b.location + b.length >= a.location) return YES; + return NO; +} + +/*! Returns a range containing the union of the given ranges. These ranges must either intersect or be adjacent: there cannot be any "holes" between them. */ +static inline HFRange HFUnionRange(HFRange a, HFRange b) { + assert(HFIntersectsRange(a, b) || HFMaxRange(a) == b.location || HFMaxRange(b) == a.location); + HFRange result; + result.location = MIN(a.location, b.location); + assert(HFSumDoesNotOverflow(a.location, a.length)); + assert(HFSumDoesNotOverflow(b.location, b.length)); + result.length = MAX(a.location + a.length, b.location + b.length) - result.location; + return result; +} + + +/*! Returns whether a+b > c+d, as if there were no overflow (so ULLONG_MAX + 1 > 10 + 20) */ +static inline BOOL HFSumIsLargerThanSum(unsigned long long a, unsigned long long b, unsigned long long c, unsigned long long d) { +#if 1 + // Theory: compare a/2 + b/2 to c/2 + d/2, and if they're equal, compare a%2 + b%2 to c%2 + d%2. We may get into trouble if a and b are both even and c and d are both odd: e.g. a = 2, b = 2, c = 1, d = 3. We would compare 1 + 1 vs 0 + 1, and therefore that 2 + 2 > 1 + 3. To address this, if both remainders are 1, we add this to the sum. We know this cannot overflow because ULLONG_MAX is odd, so (ULLONG_MAX/2) + (ULLONG_MAX/2) + 1 does not overflow. + unsigned int rem1 = (unsigned)(a%2 + b%2); + unsigned int rem2 = (unsigned)(c%2 + d%2); + unsigned long long sum1 = a/2 + b/2 + rem1/2; + unsigned long long sum2 = c/2 + d/2 + rem2/2; + if (sum1 > sum2) return YES; + else if (sum1 < sum2) return NO; + else { + // sum1 == sum2, so compare the remainders. But we have already added in the remainder / 2, so compare the remainders mod 2. + if (rem1%2 > rem2%2) return YES; + else return NO; + } +#else + /* Faster version, but not thoroughly tested yet. */ + unsigned long long xor1 = a^b; + unsigned long long xor2 = c^d; + unsigned long long avg1 = (a&b)+(xor1/2); + unsigned long long avg2 = (c&d)+(xor2/2); + unsigned s1l = avg1 > avg2; + unsigned eq = (avg1 == avg2); + return s1l | ((xor1 & ~xor2) & eq); +#endif +} + +/*! Returns the absolute value of a - b. */ +static inline unsigned long long HFAbsoluteDifference(unsigned long long a, unsigned long long b) { + if (a > b) return a - b; + else return b - a; +} + +/*! Returns true if the end of A is larger than the end of B. */ +static inline BOOL HFRangeExtendsPastRange(HFRange a, HFRange b) { + return HFSumIsLargerThanSum(a.location, a.length, b.location, b.length); +} + +/*! Returns a range containing all indexes in common betwen the two ranges. If there are no indexes in common, returns {0, 0}. */ +static inline HFRange HFIntersectionRange(HFRange range1, HFRange range2) { + unsigned long long minend = HFRangeExtendsPastRange(range2, range1) ? range1.location + range1.length : range2.location + range2.length; + if (range2.location <= range1.location && range1.location - range2.location < range2.length) { + return HFRangeMake(range1.location, minend - range1.location); + } + else if (range1.location <= range2.location && range2.location - range1.location < range1.length) { + return HFRangeMake(range2.location, minend - range2.location); + } + return HFRangeMake(0, 0); +} + +/*! ceil() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFCeil(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)ceilf((float)a); + else return (CGFloat)ceil((double)a); +} + +/*! floor() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFFloor(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)floorf((float)a); + else return (CGFloat)floor((double)a); +} + +/*! round() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFRound(CGFloat a) { + if (sizeof(a) == sizeof(float)) return (CGFloat)roundf((float)a); + else return (CGFloat)round((double)a); +} + +/*! fmin() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFMin(CGFloat a, CGFloat b) { + if (sizeof(a) == sizeof(float)) return (CGFloat)fminf((float)a, (float)b); + else return (CGFloat)fmin((double)a, (double)b); +} + +/*! fmax() for a CGFloat, for compatibility with OSes that do not have the CG versions. */ +static inline CGFloat HFMax(CGFloat a, CGFloat b) { + if (sizeof(a) == sizeof(float)) return (CGFloat)fmaxf((float)a, (float)b); + else return (CGFloat)fmax((double)a, (double)b); +} + +/*! Returns true if the given HFFPRanges are equal. */ +static inline BOOL HFFPRangeEqualsRange(HFFPRange a, HFFPRange b) { + return a.location == b.location && a.length == b.length; +} + +/*! copysign() for a CGFloat */ +static inline CGFloat HFCopysign(CGFloat a, CGFloat b) { +#if CGFLOAT_IS_DOUBLE + return copysign(a, b); +#else + return copysignf(a, b); +#endif +} + +/*! Atomically increments an NSUInteger, returning the new value. Optionally invokes a memory barrier. */ +static inline NSUInteger HFAtomicIncrement(volatile NSUInteger *ptr, BOOL barrier) { + return _Generic(ptr, + volatile unsigned *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr), +#if ULONG_MAX == UINT32_MAX + volatile unsigned long *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr), +#else + volatile unsigned long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr), +#endif + volatile unsigned long long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr)); +} + +/*! Atomically decrements an NSUInteger, returning the new value. Optionally invokes a memory barrier. */ +static inline NSUInteger HFAtomicDecrement(volatile NSUInteger *ptr, BOOL barrier) { + return _Generic(ptr, + volatile unsigned *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr), +#if ULONG_MAX == UINT32_MAX + volatile unsigned long *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr), +#else + volatile unsigned long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr), +#endif + volatile unsigned long long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr)); +} + +/*! Converts a long double to unsigned long long. Assumes that val is already an integer - use floorl or ceill */ +static inline unsigned long long HFFPToUL(long double val) { + assert(val >= 0); + assert(val <= ULLONG_MAX); + unsigned long long result = (unsigned long long)val; + assert((long double)result == val); + return result; +} + +/*! Converts an unsigned long long to a long double. */ +static inline long double HFULToFP(unsigned long long val) { + long double result = (long double)val; + assert(HFFPToUL(result) == val); + return result; +} + +/*! Convenience to return information about a CGAffineTransform for logging. */ +static inline NSString *HFDescribeAffineTransform(CGAffineTransform t) { + return [NSString stringWithFormat:@"%f %f 0\n%f %f 0\n%f %f 1", t.a, t.b, t.c, t.d, t.tx, t.ty]; +} + +/*! Returns 1 + floor(log base 10 of val). If val is 0, returns 1. */ +static inline NSUInteger HFCountDigitsBase10(unsigned long long val) { + const unsigned long long kValues[] = {0ULL, 9ULL, 99ULL, 999ULL, 9999ULL, 99999ULL, 999999ULL, 9999999ULL, 99999999ULL, 999999999ULL, 9999999999ULL, 99999999999ULL, 999999999999ULL, 9999999999999ULL, 99999999999999ULL, 999999999999999ULL, 9999999999999999ULL, 99999999999999999ULL, 999999999999999999ULL, 9999999999999999999ULL}; + NSUInteger low = 0, high = sizeof kValues / sizeof *kValues; + while (high > low) { + NSUInteger mid = (low + high)/2; //low + high cannot overflow + if (val > kValues[mid]) { + low = mid + 1; + } + else { + high = mid; + } + } + return MAX(1u, low); +} + +/*! Returns 1 + floor(log base 16 of val). If val is 0, returns 1. This works by computing the log base 2 based on the number of leading zeros, and then dividing by 4. */ +static inline NSUInteger HFCountDigitsBase16(unsigned long long val) { + /* __builtin_clzll doesn't like being passed 0 */ + if (val == 0) return 1; + + /* Compute the log base 2 */ + NSUInteger leadingZeros = (NSUInteger)__builtin_clzll(val); + NSUInteger logBase2 = (CHAR_BIT * sizeof val) - leadingZeros - 1; + return 1 + logBase2/4; +} + +/*! Returns YES if the given string encoding is a superset of ASCII. */ +BOOL HFStringEncodingIsSupersetOfASCII(NSStringEncoding encoding); + +/*! Returns the "granularity" of an encoding, in bytes. ASCII is 1, UTF-16 is 2, etc. Variable width encodings return the smallest (e.g. Shift-JIS returns 1). */ +uint8_t HFStringEncodingCharacterLength(NSStringEncoding encoding); + +/*! Converts an unsigned long long to NSUInteger. The unsigned long long should be no more than ULONG_MAX. */ +static inline NSUInteger ll2l(unsigned long long val) { assert(val <= ULONG_MAX); return (unsigned long)val; } + +/*! Converts an unsigned long long to uintptr_t. The unsigned long long should be no more than UINTPTR_MAX. */ +static inline uintptr_t ll2p(unsigned long long val) { assert(val <= UINTPTR_MAX); return (uintptr_t)val; } + +/*! Returns an unsigned long long, which must be no more than ULLONG_MAX, as an unsigned long. */ +static inline CGFloat ld2f(long double val) { +#if ! NDEBUG + if (isfinite(val)) { + assert(val <= CGFLOAT_MAX); + assert(val >= -CGFLOAT_MAX); + if ((val > 0 && val < CGFLOAT_MIN) || (val < 0 && val > -CGFLOAT_MIN)) { + NSLog(@"Warning - conversion of long double %Lf to CGFloat will result in the non-normal CGFloat %f", val, (CGFloat)val); + } + } +#endif + return (CGFloat)val; +} + +/*! Returns the quotient of a divided by b, rounding up, for unsigned long longs. Will not overflow. */ +static inline unsigned long long HFDivideULLRoundingUp(unsigned long long a, unsigned long long b) { + if (a == 0) return 0; + else return ((a - 1) / b) + 1; +} + +/*! Returns the quotient of a divided by b, rounding up, for NSUIntegers. Will not overflow. */ +static inline NSUInteger HFDivideULRoundingUp(NSUInteger a, NSUInteger b) { + if (a == 0) return 0; + else return ((a - 1) / b) + 1; +} + +/*! Draws a shadow. */ +void HFDrawShadow(CGContextRef context, NSRect rect, CGFloat size, NSRectEdge rectEdge, BOOL active, NSRect clip); + +/*! Registers a view to have the given notificationSEL invoked (taking the NSNotification object) when the window becomes or loses key. If appToo is YES, this also registers with NSApplication for Activate and Deactivate methods. */ +void HFRegisterViewForWindowAppearanceChanges(NSView *view, SEL notificationSEL, BOOL appToo); + +/*! Unregisters a view to have the given notificationSEL invoked when the window becomes or loses key. If appToo is YES, this also unregisters with NSApplication. */ +void HFUnregisterViewForWindowAppearanceChanges(NSView *view, BOOL appToo); + +/*! Returns a description of the given byte count (e.g. "24 kilobytes") */ +NSString *HFDescribeByteCount(unsigned long long count); + +/*! @brief An object wrapper for the HFRange type. + + A simple class responsible for holding an immutable HFRange as an object. Methods that logically work on multiple HFRanges usually take or return arrays of HFRangeWrappers. */ +@interface HFRangeWrapper : NSObject { + @public + HFRange range; +} + +/*! Returns the HFRange for this HFRangeWrapper. */ +- (HFRange)HFRange; + +/*! Creates an autoreleased HFRangeWrapper for this HFRange. */ ++ (HFRangeWrapper *)withRange:(HFRange)range; + +/*! Creates an NSArray of HFRangeWrappers for this HFRange. */ ++ (NSArray *)withRanges:(const HFRange *)ranges count:(NSUInteger)count; + +/*! Given an NSArray of HFRangeWrappers, get all of the HFRanges into a C array. */ ++ (void)getRanges:(HFRange *)ranges fromArray:(NSArray *)array; + +/*! Given an array of HFRangeWrappers, returns a "cleaned up" array of equivalent ranges. This new array represents the same indexes, but overlapping ranges will have been merged, and the ranges will be sorted in ascending order. */ ++ (NSArray *)organizeAndMergeRanges:(NSArray *)inputRanges; + +@end + +/*! @brief A set of HFRanges. HFRangeSet takes the interpetation that all zero-length ranges are identical. + + Essentially, a mutable array of ranges that is maintained to be sorted and minimized (i.e. merged with overlapping neighbors). + + TODO: The HexFiend codebase currently uses arrays of HFRangeWrappers that have been run through organizeAndMergeRanges:, and not HFRangeSet. The advantage of HFRangeSet is that the sorting & merging is implied by the type, instead of just tacitly assumed. This should lead to less confusion and fewer extra applications of organizeAndMergeRanges. + + TODO: HFRangeSet needs to be tested! I guarantee it has bugs! (Which doesn't matter right now because it's all dead code...) + */ +@interface HFRangeSet : NSObject { + @private + CFMutableArrayRef array; +} + +/*! Create a range set with just one range. */ ++ (HFRangeSet *)withRange:(HFRange)range; + +/*! Create a range set with a C array of ranges. No prior sorting is necessary. */ ++ (HFRangeSet *)withRanges:(const HFRange *)ranges count:(NSUInteger)count; + +/*! Create a range set with an array of HFRangeWrappers. No prior sorting is necessary. */ ++ (HFRangeSet *)withRangeWrappers:(NSArray *)ranges; + +/*! Create a range set as a copy of another. */ ++ (HFRangeSet *)withRangeSet:(HFRangeSet *)rangeSet; + +/*! Equivalent to HFRangeSet *x = [HFRangeSet withRange:range]; [x removeRange:rangeSet]; */ ++ (HFRangeSet *)complementOfRangeSet:(HFRangeSet *)rangeSet inRange:(HFRange)range; + +- (void)addRange:(HFRange)range; /*!< Union with range */ +- (void)removeRange:(HFRange)range; /*!< Subtract range */ +- (void)clipToRange:(HFRange)range; /*!< Intersect with range */ +- (void)toggleRange:(HFRange)range; /*!< Symmetric difference with range */ + +- (void)addRangeSet:(HFRangeSet *)rangeSet; /*!< Union with range set */ +- (void)removeRangeSet:(HFRangeSet *)rangeSet; /*!< Subtract range set */ +- (void)clipToRangeSet:(HFRangeSet *)rangeSet; /*!< Intersect with range set */ +- (void)toggleRangeSet:(HFRangeSet *)rangeSet; /*!< Symmetric difference with range set */ + + +- (BOOL)isEqualToRangeSet:(HFRangeSet *)rangeSet; /*!< Test if two range sets are equivalent. */ +- (BOOL)isEmpty; /*!< Test if range set is empty. */ + +- (BOOL)containsAllRange:(HFRange)range; /*!< Check if the range set covers all of a range. Always true if 'range' is zero length. */ +- (BOOL)overlapsAnyRange:(HFRange)range; /*!< Check if the range set covers any of a range. Never true if 'range' is zero length. */ +- (BOOL)containsAllRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range is a superset of another. */ +- (BOOL)overlapsAnyRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range has a nonempty intersection with another. */ + +- (HFRange)spanningRange; /*!< Return a single range that covers the entire range set */ + +- (void)assertIntegrity; + +@end + +#ifndef NDEBUG +void HFStartTiming(const char *name); +void HFStopTiming(void); +#endif diff --git a/thirdparty/SameBoy-old/HexFiend/HFFunctions.m b/thirdparty/SameBoy-old/HexFiend/HFFunctions.m new file mode 100644 index 000000000..b41a12ff9 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFunctions.m @@ -0,0 +1,1172 @@ +#import +#import + +#import "HFFunctions_Private.h" + +#ifndef NDEBUG +//#define USE_CHUD 1 +#endif + +#ifndef USE_CHUD +#define USE_CHUD 0 +#endif + +#if USE_CHUD +#import +#endif + +NSImage *HFImageNamed(NSString *name) { + HFASSERT(name != NULL); + NSImage *image = [NSImage imageNamed:name]; + if (image == NULL) { + NSString *imagePath = [[NSBundle bundleForClass:[HFController class]] pathForResource:name ofType:@"tiff"]; + if (! imagePath) { + NSLog(@"Unable to find image named %@.tiff", name); + } + else { + image = [[NSImage alloc] initByReferencingFile:imagePath]; + if (image == nil || ! [image isValid]) { + NSLog(@"Couldn't load image at path %@", imagePath); + [image release]; + image = nil; + } + else { + [image setName:name]; + } + } + } + return image; +} + +@implementation HFRangeWrapper + +- (HFRange)HFRange { return range; } + ++ (HFRangeWrapper *)withRange:(HFRange)range { + HFRangeWrapper *result = [[self alloc] init]; + result->range = range; + return [result autorelease]; +} + ++ (NSArray *)withRanges:(const HFRange *)ranges count:(NSUInteger)count { + HFASSERT(count == 0 || ranges != NULL); + NSUInteger i; + NSArray *result; + NEW_ARRAY(HFRangeWrapper *, wrappers, count); + for (i=0; i < count; i++) wrappers[i] = [self withRange:ranges[i]]; + result = [NSArray arrayWithObjects:wrappers count:count]; + FREE_ARRAY(wrappers); + return result; +} + +- (BOOL)isEqual:(id)obj { + if (! [obj isKindOfClass:[HFRangeWrapper class]]) return NO; + else return HFRangeEqualsRange(range, [obj HFRange]); +} + +- (NSUInteger)hash { + return (NSUInteger)(range.location + (range.length << 16)); +} + +- (id)copyWithZone:(NSZone *)zone { + USE(zone); + return [self retain]; +} + +- (NSString *)description { + return HFRangeToString(range); +} + +static int hfrange_compare(const void *ap, const void *bp) { + const HFRange *a = ap; + const HFRange *b = bp; + if (a->location < b->location) return -1; + else if (a->location > b->location) return 1; + else if (a->length < b->length) return -1; + else if (a->length > b->length) return 1; + else return 0; +} + ++ (NSArray *)organizeAndMergeRanges:(NSArray *)inputRanges { + HFASSERT(inputRanges != NULL); + NSUInteger leading = 0, trailing = 0, length = [inputRanges count]; + if (length == 0) return @[]; + else if (length == 1) return [NSArray arrayWithArray:inputRanges]; + + NEW_ARRAY(HFRange, ranges, length); + [self getRanges:ranges fromArray:inputRanges]; + qsort(ranges, length, sizeof ranges[0], hfrange_compare); + leading = 0; + while (leading < length) { + leading++; + if (leading < length) { + HFRange leadRange = ranges[leading], trailRange = ranges[trailing]; + if (HFIntersectsRange(leadRange, trailRange) || HFMaxRange(leadRange) == trailRange.location || HFMaxRange(trailRange) == leadRange.location) { + ranges[trailing] = HFUnionRange(leadRange, trailRange); + } + else { + trailing++; + ranges[trailing] = ranges[leading]; + } + } + } + NSArray *result = [HFRangeWrapper withRanges:ranges count:trailing + 1]; + FREE_ARRAY(ranges); + return result; +} + ++ (void)getRanges:(HFRange *)ranges fromArray:(NSArray *)array { + HFASSERT(ranges != NULL || [array count] == 0); + if (ranges) { + FOREACH(HFRangeWrapper*, wrapper, array) *ranges++ = [wrapper HFRange]; + } +} + +@end + +@implementation HFRangeSet +// HFRangeSet is implemented as a CFMutableArray of uintptr_t "fenceposts". The array +// is even in length, sorted, duplicate free, and considered to include the ranges +// [array[0], array[1]), [array[2], array[3]), ..., [array[2n], array[2n+1]) + +CFComparisonResult uintptrComparator(const void *val1, const void *val2, void *context) { + (void)context; + uintptr_t a = (uintptr_t)val1; + uintptr_t b = (uintptr_t)val2; + if(a < b) return kCFCompareLessThan; + if(a > b) return kCFCompareGreaterThan; + return kCFCompareEqualTo; +} + +static void HFRangeSetAddRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + + const void *x[2] = { (void*)a, (void*)b }; + if(idxa >= count) { + CFArrayReplaceValues(array, CFRangeMake(count, 0), x, 2); + return; + } + if(idxb == 0) { + CFArrayReplaceValues(array, CFRangeMake(0, 0), x, 2); + return; + } + + // Clear fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex cutloc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex cutlen = idxb - cutloc; + + bool inca = cutloc % 2 == 0; // Include 'a' if it would begin an included range + bool incb = (count - cutlen + inca) % 2 == 1; // The set must be even, which tells us about 'b'. + + CFArrayReplaceValues(array, CFRangeMake(cutloc, cutlen), x+inca, inca+incb); + assert(CFArrayGetCount(array) % 2 == 0); +} + +static void HFRangeSetRemoveRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return; + + // Remove fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex cutloc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex cutlen = idxb - cutloc; + + bool inca = cutloc % 2 == 1; // Include 'a' if it would end an included range + bool incb = (count - cutlen + inca) % 2 == 1; // The set must be even, which tells us about 'b'. + + const void *x[2] = { (void*)a, (void*)b }; + CFArrayReplaceValues(array, CFRangeMake(cutloc, cutlen), x+inca, inca+incb); + assert(CFArrayGetCount(array) % 2 == 0); +} + +static void HFRangeSetToggleRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + + // In the fencepost representation, simply toggling the existence of + // fenceposts 'a' and 'b' achieves symmetric difference. + + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + if((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a) { + CFArrayRemoveValueAtIndex(array, idxa); + } else { + CFArrayInsertValueAtIndex(array, idxa, (void*)a); + } + + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if((uintptr_t)CFArrayGetValueAtIndex(array, idxb) == b) { + CFArrayRemoveValueAtIndex(array, idxb); + } else { + CFArrayInsertValueAtIndex(array, idxb, (void*)b); + } + + assert(CFArrayGetCount(array) % 2 == 0); +} + +static BOOL HFRangeSetContainsAllRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return NO; + + // Optimization: if the indexes are far enough apart, then obviouly there's a gap. + if(idxb - idxa >= 2) return NO; + + // The first fencepost >= 'b' must end an include range, a must be in the same range. + return idxb%2 == 1 && idxa == ((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxb-1 : idxb); +} + +static BOOL HFRangeSetOverlapsAnyRange(CFMutableArrayRef array, uintptr_t a, uintptr_t b) { + CFIndex count = CFArrayGetCount(array); + assert(a < b); assert(count % 2 == 0); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) return NO; + + // Optimization: if the indexes are far enough apart, then obviouly there's overlap. + if(idxb - idxa >= 2) return YES; + + if((uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a) { + // 'a' is an included fencepost, or instead 'b' makes it past an included fencepost. + return idxa % 2 == 0 || b > (uintptr_t)CFArrayGetValueAtIndex(array, idxa+1); + } else { + // 'a' lies in an included range, or instead 'b' makes it past an included fencepost. + return idxa % 2 == 1 || b > (uintptr_t)CFArrayGetValueAtIndex(array, idxa); + } +} + +- (instancetype)init { + if(!(self = [super init])) return nil; + array = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + return self; +} + +- (void)dealloc { + CFRelease(array); + [super dealloc]; +} + ++ (HFRangeSet *)withRange:(HFRange)range { + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + if(range.length > 0) { + CFArrayAppendValue(newSet->array, (void*)ll2p(range.location)); + CFArrayAppendValue(newSet->array, (void*)ll2p(HFMaxRange(range))); + } + return newSet; +} + ++ (HFRangeSet *)withRanges:(const HFRange *)ranges count:(NSUInteger)count { + // FIXME: Stub. Don't rely on the thing we're replacing! + return [HFRangeSet withRangeWrappers:[HFRangeWrapper withRanges:ranges count:count]]; +} + ++ (HFRangeSet *)withRangeWrappers:(NSArray *)ranges { + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + FOREACH(HFRangeWrapper *, wrapper, [HFRangeWrapper organizeAndMergeRanges:ranges]) { + if(wrapper->range.length > 0) { + CFArrayAppendValue(newSet->array, (void*)ll2p(wrapper->range.location)); + CFArrayAppendValue(newSet->array, (void*)ll2p(HFMaxRange(wrapper->range))); + } + } + return newSet; +} + ++ (HFRangeSet *)withRangeSet:(HFRangeSet *)rangeSet { + return [[rangeSet copy] autorelease]; +} + ++ (HFRangeSet *)complementOfRangeSet:(HFRangeSet *)rangeSet inRange:(HFRange)range { + if(range.length <= 0) { + // Complement in empty is... empty! + return [HFRangeSet withRange:HFZeroRange]; + } + uintptr_t a = ll2p(range.location); + uintptr_t b = ll2p(HFMaxRange(range)); + CFIndex count = CFArrayGetCount(rangeSet->array); + CFIndex idxa = CFArrayBSearchValues(rangeSet->array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(rangeSet->array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) + return [HFRangeSet withRange:range]; + + // Alright, the trivial responses are past. We'll need to build a new set. + // Given the fencepost representation of sets, we can efficiently produce an + // inverted set by just copying the fenceposts between 'a' and 'b', and then + // maybe including 'a' and 'b'. + + HFRangeSet *newSet = [[[HFRangeSet alloc] init] autorelease]; + + // newSet must contain all the fenceposts strictly between 'a' and 'b' + CFIndex copyloc = (uintptr_t)CFArrayGetValueAtIndex(rangeSet->array, idxa) == a ? idxa+1 : idxa; + CFIndex copylen = idxb - copyloc; + + // Include 'a' if it's needed to invert the parity of the copy. + if(copyloc % 2 == 0) CFArrayAppendValue(newSet->array, &a); + + CFArrayAppendArray(newSet->array, rangeSet->array, CFRangeMake(copyloc, copylen)); + + // Include 'b' if it's needed to close off the set. + if(CFArrayGetCount(newSet->array) % 2 == 1) + CFArrayAppendValue(newSet->array, &b); + + assert(CFArrayGetCount(newSet->array) % 2 == 0); + return newSet; +} + + +- (void)addRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetAddRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} +- (void)removeRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetRemoveRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} +- (void)toggleRange:(HFRange)range { + if(range.length == 0) return; + HFRangeSetToggleRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (void)clipToRange:(HFRange)range { + if(range.length <= 0) { + CFArrayRemoveAllValues(array); + return; + } + uintptr_t a = ll2p(range.location); + uintptr_t b = ll2p(HFMaxRange(range)); + CFIndex count = CFArrayGetCount(array); + CFIndex idxa = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)a, uintptrComparator, NULL); + CFIndex idxb = CFArrayBSearchValues(array, CFRangeMake(0, count), (void*)b, uintptrComparator, NULL); + if(idxa >= count || idxb == 0) { + CFArrayRemoveAllValues(array); + return; + } + + // Keep only fenceposts strictly between 'a' and 'b', and then possibly + // add 'a' or 'b' as fenceposts. + CFIndex keeploc = (uintptr_t)CFArrayGetValueAtIndex(array, idxa) == a ? idxa+1 : idxa; + CFIndex keeplen = idxb - keeploc; + + // Include 'a' if it's needed to keep the parity straight. + if(keeploc % 2 == 1) { + keeploc--; keeplen++; + CFArraySetValueAtIndex(array, keeploc, (void*)a); + } + + if(keeploc > 0) + CFArrayReplaceValues(array, CFRangeMake(0, keeploc), NULL, 0); + if(keeploc+keeplen < count) + CFArrayReplaceValues(array, CFRangeMake(0, keeplen), NULL, 0); + + // Include 'b' if it's needed to keep the length even. + if(keeplen % 2 == 1) { + CFArrayAppendValue(array, (void*)b); + } + + assert(CFArrayGetCount(array) % 2 == 0); +} + + +- (void)addRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetAddRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} +- (void)removeRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetRemoveRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} +- (void)toggleRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + HFRangeSetToggleRange(array, (uintptr_t)CFArrayGetValueAtIndex(a, i2), (uintptr_t)CFArrayGetValueAtIndex(a, i2+1)); + } +} + +- (void)clipToRangeSet:(HFRangeSet *)rangeSet { + HFRange span = [rangeSet spanningRange]; + [self clipToRange:span]; + [self removeRangeSet:[HFRangeSet complementOfRangeSet:rangeSet inRange:span]]; +} + +- (BOOL)isEqualToRangeSet:(HFRangeSet *)rangeSet { + // Because our arrays are fully normalized, this just checks for array equality. + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + if(c != CFArrayGetCount(array)) + return NO; + + // Optimization: For long arrays, check the last few first, + // since appending to ranges is probably a common usage pattern. + const CFIndex opt_end = 10; + if(c > 2*opt_end) { + for(CFIndex i = c - 2*opt_end; i < c; i++) { + if(CFArrayGetValueAtIndex(a, i) != CFArrayGetValueAtIndex(array, i)) + return NO; + } + c -= 2*opt_end; + } + + for(CFIndex i = 0; i < c; i++) { + if(CFArrayGetValueAtIndex(a, i) != CFArrayGetValueAtIndex(array, i)) + return NO; + } + + return YES; +} + +- (BOOL)isEmpty { + return CFArrayGetCount(array) == 0; +} + +- (BOOL)containsAllRange:(HFRange)range { + if(range.length == 0) return YES; + return HFRangeSetContainsAllRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (BOOL)overlapsAnyRange:(HFRange)range { + if(range.length == 0) return NO; + return HFRangeSetOverlapsAnyRange(array, ll2p(range.location), ll2p(HFMaxRange(range))); +} + +- (BOOL)containsAllRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + + // Optimization: check if containment is possible. + if(!HFRangeIsSubrangeOfRange([rangeSet spanningRange], [self spanningRange])) { + return NO; + } + + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + uintptr_t x = (uintptr_t)CFArrayGetValueAtIndex(a, i2); + uintptr_t y = (uintptr_t)CFArrayGetValueAtIndex(a, i2+1); + if(!HFRangeSetContainsAllRange(array, x, y)) return NO; + } + return YES; +} + +- (BOOL)overlapsAnyRangeSet:(HFRangeSet *)rangeSet { + CFArrayRef a = rangeSet->array; + CFIndex c = CFArrayGetCount(a); + + // Optimization: check if overlap is possible. + if(!HFIntersectsRange([rangeSet spanningRange], [self spanningRange])) { + return NO; + } + + for(CFIndex i2 = 0; i2 < c; i2 += 2) { + uintptr_t x = (uintptr_t)CFArrayGetValueAtIndex(a, i2); + uintptr_t y = (uintptr_t)CFArrayGetValueAtIndex(a, i2+1); + if(!HFRangeSetOverlapsAnyRange(array, x, y)) return YES; + } + return NO; +} + + +- (HFRange)spanningRange { + CFIndex count = CFArrayGetCount(array); + if(count == 0) return HFZeroRange; + + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, 0); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, count-2) + (uintptr_t)CFArrayGetValueAtIndex(array, count-1); + + return HFRangeMake(a, b-a); +} + +- (void)assertIntegrity { + CFIndex count = CFArrayGetCount(array); + HFASSERT(count % 2 == 0); + if(count == 0) return; + + uintptr_t prev = (uintptr_t)CFArrayGetValueAtIndex(array, 0); + for(CFIndex i = 1; i < count; i++) { + uintptr_t val = (uintptr_t)CFArrayGetValueAtIndex(array, i); + HFASSERT(val > prev); + prev = val; + } +} + +- (BOOL)isEqual:(id)object { + if(![object isKindOfClass:[HFRangeSet class]]) + return false; + return [self isEqualToRangeSet:object]; +} + +- (NSUInteger)hash { + CFIndex count = CFArrayGetCount(array); + NSUInteger x = 0; + for(CFIndex i2 = 0; i2 < count; i2 += 2) { + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, i2); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, i2+1); +#if 6364136223846793005 < NSUIntegerMax + x = (6364136223846793005 * (uint64_t)x + a); +#else + x = (NSUInteger)(1103515245 * (uint64_t)x + a); +#endif + x ^= (NSUInteger)b; + } + return x; +} + +- (id)copyWithZone:(NSZone *)zone { + HFRangeSet *newSet = [[HFRangeSet allocWithZone:zone] init]; + CFRelease(newSet->array); + newSet->array = (CFMutableArrayRef)[[NSMutableArray allocWithZone:zone] initWithArray:(NSArray*)array copyItems:NO]; + return newSet; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + NSUInteger count = CFArrayGetCount(array); + NEW_ARRAY(uint64_t, values, count); + + // Fill array with 64-bit, little endian bytes. + if(sizeof(const void *) == sizeof(uint64_t)) { + // Hooray, we can just use CFArrayGetValues + CFArrayGetValues(array, CFRangeMake(0, count), (const void **)&values); +#if __LITTLE_ENDIAN__ +#else + // Boo, we have to swap everything. + for(NSUInteger i = 0; i < count; i++) { + values[i] = CFSwapInt64HostToLittle(values[i]); + } +#endif + } else { + // Boo, we have to iterate through the array. + NSUInteger i = 0; + FOREACH(id, val, (NSArray*)array) { + values[i++] = CFSwapInt64HostToLittle((uint64_t)(const void *)val); + } + } + [aCoder encodeBytes:values length:count * sizeof(*values)]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if(!(self = [super init])) return nil; + + NSUInteger count; + uint64_t *values = [aDecoder decodeBytesWithReturnedLength:&count]; + array = CFArrayCreateMutable(kCFAllocatorDefault, count+1, NULL); + + for(NSUInteger i = 0; i < count; i++) { + uint64_t x = CFSwapInt64LittleToHost(values[i]); + if(x > UINTPTR_MAX) + goto fail; + CFArrayAppendValue(array, (const void *)(uintptr_t)x); + } + if(CFArrayGetCount(array)%2 != 0) + goto fail; + return self; + +fail: + CFRelease(array); + [super release]; + return nil; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { + NSUInteger base = state->state; + NSUInteger length = CFArrayGetCount(array)/2; + NSUInteger i = 0; + + while(i < len && base + i < length) { + uintptr_t a = (uintptr_t)CFArrayGetValueAtIndex(array, 2*i); + uintptr_t b = (uintptr_t)CFArrayGetValueAtIndex(array, 2*i+1); + stackbuf[i] = [HFRangeWrapper withRange:HFRangeMake(a, b-a)]; + } + + state->state = base + i; + state->itemsPtr = stackbuf; + state->mutationsPtr = &state->extra[0]; // Use simple mutation checking. + state->extra[0] = length; + + return i; +} + +@end + + +BOOL HFStringEncodingIsSupersetOfASCII(NSStringEncoding encoding) { + switch (CFStringConvertNSStringEncodingToEncoding(encoding)) { + case kCFStringEncodingMacRoman: return YES; + case kCFStringEncodingWindowsLatin1: return YES; + case kCFStringEncodingISOLatin1: return YES; + case kCFStringEncodingNextStepLatin: return YES; + case kCFStringEncodingASCII: return YES; + case kCFStringEncodingUnicode: return NO; + case kCFStringEncodingUTF8: return YES; + case kCFStringEncodingNonLossyASCII: return NO; +// case kCFStringEncodingUTF16: return NO; + case kCFStringEncodingUTF16BE: return NO; + case kCFStringEncodingUTF16LE: return NO; + case kCFStringEncodingUTF32: return NO; + case kCFStringEncodingUTF32BE: return NO; + case kCFStringEncodingUTF32LE: return NO; + case kCFStringEncodingMacJapanese: return NO; + case kCFStringEncodingMacChineseTrad: return YES; + case kCFStringEncodingMacKorean: return YES; + case kCFStringEncodingMacArabic: return NO; + case kCFStringEncodingMacHebrew: return NO; + case kCFStringEncodingMacGreek: return YES; + case kCFStringEncodingMacCyrillic: return YES; + case kCFStringEncodingMacDevanagari: return YES; + case kCFStringEncodingMacGurmukhi: return YES; + case kCFStringEncodingMacGujarati: return YES; + case kCFStringEncodingMacOriya: return YES; + case kCFStringEncodingMacBengali: return YES; + case kCFStringEncodingMacTamil: return YES; + case kCFStringEncodingMacTelugu: return YES; + case kCFStringEncodingMacKannada: return YES; + case kCFStringEncodingMacMalayalam: return YES; + case kCFStringEncodingMacSinhalese: return YES; + case kCFStringEncodingMacBurmese: return YES; + case kCFStringEncodingMacKhmer: return YES; + case kCFStringEncodingMacThai: return YES; + case kCFStringEncodingMacLaotian: return YES; + case kCFStringEncodingMacGeorgian: return YES; + case kCFStringEncodingMacArmenian: return YES; + case kCFStringEncodingMacChineseSimp: return YES; + case kCFStringEncodingMacTibetan: return YES; + case kCFStringEncodingMacMongolian: return YES; + case kCFStringEncodingMacEthiopic: return YES; + case kCFStringEncodingMacCentralEurRoman: return YES; + case kCFStringEncodingMacVietnamese: return YES; + case kCFStringEncodingMacExtArabic: return YES; + case kCFStringEncodingMacSymbol: return NO; + case kCFStringEncodingMacDingbats: return NO; + case kCFStringEncodingMacTurkish: return YES; + case kCFStringEncodingMacCroatian: return YES; + case kCFStringEncodingMacIcelandic: return YES; + case kCFStringEncodingMacRomanian: return YES; + case kCFStringEncodingMacCeltic: return YES; + case kCFStringEncodingMacGaelic: return YES; + case kCFStringEncodingMacFarsi: return YES; + case kCFStringEncodingMacUkrainian: return NO; + case kCFStringEncodingMacInuit: return YES; + case kCFStringEncodingMacVT100: return YES; + case kCFStringEncodingMacHFS: return YES; + case kCFStringEncodingISOLatin2: return YES; + case kCFStringEncodingISOLatin3: return YES; + case kCFStringEncodingISOLatin4: return YES; + case kCFStringEncodingISOLatinCyrillic: return YES; + case kCFStringEncodingISOLatinArabic: return NO; + case kCFStringEncodingISOLatinGreek: return YES; + case kCFStringEncodingISOLatinHebrew: return YES; + case kCFStringEncodingISOLatin5: return YES; + case kCFStringEncodingISOLatin6: return YES; + case kCFStringEncodingISOLatinThai: return YES; + case kCFStringEncodingISOLatin7: return YES; + case kCFStringEncodingISOLatin8: return YES; + case kCFStringEncodingISOLatin9: return YES; + case kCFStringEncodingISOLatin10: return YES; + case kCFStringEncodingDOSLatinUS: return YES; + case kCFStringEncodingDOSGreek: return YES; + case kCFStringEncodingDOSBalticRim: return YES; + case kCFStringEncodingDOSLatin1: return YES; + case kCFStringEncodingDOSGreek1: return YES; + case kCFStringEncodingDOSLatin2: return YES; + case kCFStringEncodingDOSCyrillic: return YES; + case kCFStringEncodingDOSTurkish: return YES; + case kCFStringEncodingDOSPortuguese: return YES; + case kCFStringEncodingDOSIcelandic: return YES; + case kCFStringEncodingDOSHebrew: return YES; + case kCFStringEncodingDOSCanadianFrench: return YES; + case kCFStringEncodingDOSArabic: return YES; + case kCFStringEncodingDOSNordic: return YES; + case kCFStringEncodingDOSRussian: return YES; + case kCFStringEncodingDOSGreek2: return YES; + case kCFStringEncodingDOSThai: return YES; + case kCFStringEncodingDOSJapanese: return YES; + case kCFStringEncodingDOSChineseSimplif: return YES; + case kCFStringEncodingDOSKorean: return YES; + case kCFStringEncodingDOSChineseTrad: return YES; + case kCFStringEncodingWindowsLatin2: return YES; + case kCFStringEncodingWindowsCyrillic: return YES; + case kCFStringEncodingWindowsGreek: return YES; + case kCFStringEncodingWindowsLatin5: return YES; + case kCFStringEncodingWindowsHebrew: return YES; + case kCFStringEncodingWindowsArabic: return YES; + case kCFStringEncodingWindowsBalticRim: return YES; + case kCFStringEncodingWindowsVietnamese: return YES; + case kCFStringEncodingWindowsKoreanJohab: return YES; + case kCFStringEncodingANSEL: return NO; + case kCFStringEncodingJIS_X0201_76: return NO; + case kCFStringEncodingJIS_X0208_83: return NO; + case kCFStringEncodingJIS_X0208_90: return NO; + case kCFStringEncodingJIS_X0212_90: return NO; + case kCFStringEncodingJIS_C6226_78: return NO; + case 0x0628/*kCFStringEncodingShiftJIS_X0213*/: return NO; + case kCFStringEncodingShiftJIS_X0213_MenKuTen: return NO; + case kCFStringEncodingGB_2312_80: return NO; + case kCFStringEncodingGBK_95: return NO; + case kCFStringEncodingGB_18030_2000: return NO; + case kCFStringEncodingKSC_5601_87: return NO; + case kCFStringEncodingKSC_5601_92_Johab: return NO; + case kCFStringEncodingCNS_11643_92_P1: return NO; + case kCFStringEncodingCNS_11643_92_P2: return NO; + case kCFStringEncodingCNS_11643_92_P3: return NO; + case kCFStringEncodingISO_2022_JP: return NO; + case kCFStringEncodingISO_2022_JP_2: return NO; + case kCFStringEncodingISO_2022_JP_1: return NO; + case kCFStringEncodingISO_2022_JP_3: return NO; + case kCFStringEncodingISO_2022_CN: return NO; + case kCFStringEncodingISO_2022_CN_EXT: return NO; + case kCFStringEncodingISO_2022_KR: return NO; + case kCFStringEncodingEUC_JP: return YES; + case kCFStringEncodingEUC_CN: return YES; + case kCFStringEncodingEUC_TW: return YES; + case kCFStringEncodingEUC_KR: return YES; + case kCFStringEncodingShiftJIS: return NO; + case kCFStringEncodingKOI8_R: return YES; + case kCFStringEncodingBig5: return YES; + case kCFStringEncodingMacRomanLatin1: return YES; + case kCFStringEncodingHZ_GB_2312: return NO; + case kCFStringEncodingBig5_HKSCS_1999: return YES; + case kCFStringEncodingVISCII: return YES; // though not quite + case kCFStringEncodingKOI8_U: return YES; + case kCFStringEncodingBig5_E: return YES; + case kCFStringEncodingNextStepJapanese: return YES; + case kCFStringEncodingEBCDIC_US: return NO; + case kCFStringEncodingEBCDIC_CP037: return NO; + default: + NSLog(@"Unknown string encoding %lu in %s", (unsigned long)encoding, __FUNCTION__); + return NO; + } +} + +uint8_t HFStringEncodingCharacterLength(NSStringEncoding encoding) { + switch (CFStringConvertNSStringEncodingToEncoding(encoding)) { + case kCFStringEncodingMacRoman: return 1; + case kCFStringEncodingWindowsLatin1: return 1; + case kCFStringEncodingISOLatin1: return 1; + case kCFStringEncodingNextStepLatin: return 1; + case kCFStringEncodingASCII: return 1; + case kCFStringEncodingUnicode: return 2; + case kCFStringEncodingUTF8: return 1; + case kCFStringEncodingNonLossyASCII: return 1; + // case kCFStringEncodingUTF16: return 2; + case kCFStringEncodingUTF16BE: return 2; + case kCFStringEncodingUTF16LE: return 2; + case kCFStringEncodingUTF32: return 4; + case kCFStringEncodingUTF32BE: return 4; + case kCFStringEncodingUTF32LE: return 4; + case kCFStringEncodingMacJapanese: return 1; + case kCFStringEncodingMacChineseTrad: return 1; // ?? + case kCFStringEncodingMacKorean: return 1; + case kCFStringEncodingMacArabic: return 1; + case kCFStringEncodingMacHebrew: return 1; + case kCFStringEncodingMacGreek: return 1; + case kCFStringEncodingMacCyrillic: return 1; + case kCFStringEncodingMacDevanagari: return 1; + case kCFStringEncodingMacGurmukhi: return 1; + case kCFStringEncodingMacGujarati: return 1; + case kCFStringEncodingMacOriya: return 1; + case kCFStringEncodingMacBengali: return 1; + case kCFStringEncodingMacTamil: return 1; + case kCFStringEncodingMacTelugu: return 1; + case kCFStringEncodingMacKannada: return 1; + case kCFStringEncodingMacMalayalam: return 1; + case kCFStringEncodingMacSinhalese: return 1; + case kCFStringEncodingMacBurmese: return 1; + case kCFStringEncodingMacKhmer: return 1; + case kCFStringEncodingMacThai: return 1; + case kCFStringEncodingMacLaotian: return 1; + case kCFStringEncodingMacGeorgian: return 1; + case kCFStringEncodingMacArmenian: return 1; + case kCFStringEncodingMacChineseSimp: return 1; + case kCFStringEncodingMacTibetan: return 1; + case kCFStringEncodingMacMongolian: return 1; + case kCFStringEncodingMacEthiopic: return 1; + case kCFStringEncodingMacCentralEurRoman: return 1; + case kCFStringEncodingMacVietnamese: return 1; + case kCFStringEncodingMacExtArabic: return 1; + case kCFStringEncodingMacSymbol: return 1; + case kCFStringEncodingMacDingbats: return 1; + case kCFStringEncodingMacTurkish: return 1; + case kCFStringEncodingMacCroatian: return 1; + case kCFStringEncodingMacIcelandic: return 1; + case kCFStringEncodingMacRomanian: return 1; + case kCFStringEncodingMacCeltic: return 1; + case kCFStringEncodingMacGaelic: return 1; + case kCFStringEncodingMacFarsi: return 1; + case kCFStringEncodingMacUkrainian: return 1; + case kCFStringEncodingMacInuit: return 1; + case kCFStringEncodingMacVT100: return 1; + case kCFStringEncodingMacHFS: return 1; + case kCFStringEncodingISOLatin2: return 1; + case kCFStringEncodingISOLatin3: return 1; + case kCFStringEncodingISOLatin4: return 1; + case kCFStringEncodingISOLatinCyrillic: return 1; + case kCFStringEncodingISOLatinArabic: return 1; + case kCFStringEncodingISOLatinGreek: return 1; + case kCFStringEncodingISOLatinHebrew: return 1; + case kCFStringEncodingISOLatin5: return 1; + case kCFStringEncodingISOLatin6: return 1; + case kCFStringEncodingISOLatinThai: return 1; + case kCFStringEncodingISOLatin7: return 1; + case kCFStringEncodingISOLatin8: return 1; + case kCFStringEncodingISOLatin9: return 1; + case kCFStringEncodingISOLatin10: return 1; + case kCFStringEncodingDOSLatinUS: return 1; + case kCFStringEncodingDOSGreek: return 1; + case kCFStringEncodingDOSBalticRim: return 1; + case kCFStringEncodingDOSLatin1: return 1; + case kCFStringEncodingDOSGreek1: return 1; + case kCFStringEncodingDOSLatin2: return 1; + case kCFStringEncodingDOSCyrillic: return 1; + case kCFStringEncodingDOSTurkish: return 1; + case kCFStringEncodingDOSPortuguese: return 1; + case kCFStringEncodingDOSIcelandic: return 1; + case kCFStringEncodingDOSHebrew: return 1; + case kCFStringEncodingDOSCanadianFrench: return 1; + case kCFStringEncodingDOSArabic: return 1; + case kCFStringEncodingDOSNordic: return 1; + case kCFStringEncodingDOSRussian: return 1; + case kCFStringEncodingDOSGreek2: return 1; + case kCFStringEncodingDOSThai: return 1; + case kCFStringEncodingDOSJapanese: return 1; + case kCFStringEncodingDOSChineseSimplif: return 1; + case kCFStringEncodingDOSKorean: return 1; + case kCFStringEncodingDOSChineseTrad: return 1; + case kCFStringEncodingWindowsLatin2: return 1; + case kCFStringEncodingWindowsCyrillic: return 1; + case kCFStringEncodingWindowsGreek: return 1; + case kCFStringEncodingWindowsLatin5: return 1; + case kCFStringEncodingWindowsHebrew: return 1; + case kCFStringEncodingWindowsArabic: return 1; + case kCFStringEncodingWindowsBalticRim: return 1; + case kCFStringEncodingWindowsVietnamese: return 1; + case kCFStringEncodingWindowsKoreanJohab: return 1; + case kCFStringEncodingANSEL: return 1; + case kCFStringEncodingJIS_X0201_76: return 1; + case kCFStringEncodingJIS_X0208_83: return 1; + case kCFStringEncodingJIS_X0208_90: return 1; + case kCFStringEncodingJIS_X0212_90: return 1; + case kCFStringEncodingJIS_C6226_78: return 1; + case 0x0628/*kCFStringEncodingShiftJIS_X0213*/: return 1; + case kCFStringEncodingShiftJIS_X0213_MenKuTen: return 1; + case kCFStringEncodingGB_2312_80: return 1; + case kCFStringEncodingGBK_95: return 1; + case kCFStringEncodingGB_18030_2000: return 1; + case kCFStringEncodingKSC_5601_87: return 1; + case kCFStringEncodingKSC_5601_92_Johab: return 1; + case kCFStringEncodingCNS_11643_92_P1: return 1; + case kCFStringEncodingCNS_11643_92_P2: return 1; + case kCFStringEncodingCNS_11643_92_P3: return 1; + case kCFStringEncodingISO_2022_JP: return 1; + case kCFStringEncodingISO_2022_JP_2: return 1; + case kCFStringEncodingISO_2022_JP_1: return 1; + case kCFStringEncodingISO_2022_JP_3: return 1; + case kCFStringEncodingISO_2022_CN: return 1; + case kCFStringEncodingISO_2022_CN_EXT: return 1; + case kCFStringEncodingISO_2022_KR: return 1; + case kCFStringEncodingEUC_JP: return 1; + case kCFStringEncodingEUC_CN: return 1; + case kCFStringEncodingEUC_TW: return 1; + case kCFStringEncodingEUC_KR: return 1; + case kCFStringEncodingShiftJIS: return 1; + case kCFStringEncodingKOI8_R: return 1; + case kCFStringEncodingBig5: return 2; //yay, a 2 + case kCFStringEncodingMacRomanLatin1: return 1; + case kCFStringEncodingHZ_GB_2312: return 2; + case kCFStringEncodingBig5_HKSCS_1999: return 1; + case kCFStringEncodingVISCII: return 1; + case kCFStringEncodingKOI8_U: return 1; + case kCFStringEncodingBig5_E: return 2; + case kCFStringEncodingNextStepJapanese: return YES; // ?? + case kCFStringEncodingEBCDIC_US: return 1; //lol + case kCFStringEncodingEBCDIC_CP037: return 1; + case kCFStringEncodingUTF7: return 1; + case kCFStringEncodingUTF7_IMAP : return 1; + default: + NSLog(@"Unknown string encoding %lx in %s", (long)encoding, __FUNCTION__); + return 1; + } +} + +/* Converts a hexadecimal digit into a corresponding 4 bit unsigned int; returns -1 on failure. The ... is a gcc extension. */ +static NSInteger char2hex(unichar c) { + switch (c) { + case '0' ... '9': return c - '0'; + case 'a' ... 'f': return c - 'a' + 10; + case 'A' ... 'F': return c - 'A' + 10; + default: return -1; + } +} + +static unsigned char hex2char(NSUInteger c) { + HFASSERT(c < 16); + return "0123456789ABCDEF"[c]; +} + +NSData *HFDataFromHexString(NSString *string, BOOL* isMissingLastNybble) { + REQUIRE_NOT_NULL(string); + NSUInteger stringIndex=0, resultIndex=0, max=[string length]; + NSMutableData* result = [NSMutableData dataWithLength:(max + 1)/2]; + unsigned char* bytes = [result mutableBytes]; + + NSUInteger numNybbles = 0; + unsigned char byteValue = 0; + + for (stringIndex = 0; stringIndex < max; stringIndex++) { + NSInteger val = char2hex([string characterAtIndex:stringIndex]); + if (val < 0) continue; + numNybbles++; + byteValue = byteValue * 16 + (unsigned char)val; + if (! (numNybbles % 2)) { + bytes[resultIndex++] = byteValue; + byteValue = 0; + } + } + + if (isMissingLastNybble) *isMissingLastNybble = (numNybbles % 2); + + //final nibble + if (numNybbles % 2) { + bytes[resultIndex++] = byteValue; + } + + [result setLength:resultIndex]; + return result; +} + +NSString *HFHexStringFromData(NSData *data) { + REQUIRE_NOT_NULL(data); + NSUInteger dataLength = [data length]; + NSUInteger stringLength = HFProductInt(dataLength, 2); + const unsigned char *bytes = [data bytes]; + unsigned char *charBuffer = check_malloc(stringLength); + NSUInteger charIndex = 0, byteIndex; + for (byteIndex = 0; byteIndex < dataLength; byteIndex++) { + unsigned char byte = bytes[byteIndex]; + charBuffer[charIndex++] = hex2char(byte >> 4); + charBuffer[charIndex++] = hex2char(byte & 0xF); + } + return [[[NSString alloc] initWithBytesNoCopy:charBuffer length:stringLength encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; +} + +void HFSetFDShouldCache(int fd, BOOL shouldCache) { + int result = fcntl(fd, F_NOCACHE, !shouldCache); + if (result == -1) { + int err = errno; + NSLog(@"fcntl(%d, F_NOCACHE, %d) returned error %d: %s", fd, !shouldCache, err, strerror(err)); + } +} + +NSString *HFDescribeByteCount(unsigned long long count) { + return HFDescribeByteCountWithPrefixAndSuffix(NULL, count, NULL); +} + +/* A big_num represents a number in some base. Here it is value = big * base + little. */ +typedef struct big_num { + unsigned int big; + unsigned long long little; +} big_num; + +static inline big_num divide_bignum_by_2(big_num a, unsigned long long base) { + //value = a.big * base + a.little; + big_num result; + result.big = a.big / 2; + unsigned int shiftedRemainder = (unsigned int)(a.little & 1); + result.little = a.little / 2; + if (a.big & 1) { + //need to add base/2 to result.little. We know that won't overflow because result.little is already a.little / 2 + result.little += base / 2; + + // If we shift off a bit for base/2, and we also shifted off a bit for a.little/2, then we have a carry bit we need to add + if ((base & 1) && shiftedRemainder) { + /* Is there a chance that adding 1 will overflow? We know base is odd (base & 1), so consider an example of base = 9. Then the largest that result.little could be is (9 - 1)/2 + base/2 = 8. We could add 1 and get back to base, but we can never exceed base, so we cannot overflow an unsigned long long. */ + result.little += 1; + HFASSERT(result.little <= base); + if (result.little == base) { + result.big++; + result.little = 0; + } + } + } + HFASSERT(result.little < base); + return result; +} + +static inline big_num add_big_nums(big_num a, big_num b, unsigned long long base) { + /* Perform the addition result += left. The addition is: + result.big = a.big + b.big + (a.little + b.little) / base + result.little = (a.little + b.little) % base + + a.little + b.little may overflow, so we have to take some care in how we calculate them. + Since both a.little and b.little are less than base, we know that if we overflow, we can subtract base from it to underflow and still get the same remainder. + */ + unsigned long long remainder = a.little + b.little; + unsigned int dividend = 0; + // remainder < a.little detects overflow, and remainder >= base detects the case where we did not overflow but are larger than base + if (remainder < a.little || remainder >= base) { + remainder -= base; + dividend++; + } + HFASSERT(remainder < base); + + big_num result = {a.big + b.big + dividend, remainder}; + return result; +} + + +/* Returns the first digit after the decimal point for a / b, rounded off, without overflow. This may return 10, indicating that the digit is 0 and we should carry. */ +static unsigned int computeRemainderPrincipalDigit(unsigned long long a, unsigned long long base) { + struct big_num result = {0, 0}, left = {(unsigned)(a / base), a % base}, right = {(unsigned)(100 / base), 100 % base}; + while (right.big > 0 || right.little > 0) { + /* Determine the least significant bit of right, which is right.big * base + right.little */ + unsigned int bigTermParity = (base & 1) && (right.big & 1); + unsigned int littleTermParity = (unsigned)(right.little & 1); + if (bigTermParity != littleTermParity) result = add_big_nums(result, left, base); + + right = divide_bignum_by_2(right, base); + left = add_big_nums(left, left, base); + } + + //result.big now contains 100 * a / base + unsigned int principalTwoDigits = (unsigned int)(result.big % 100); + unsigned int principalDigit = (principalTwoDigits / 10) + ((principalTwoDigits % 10) >= 5); + return principalDigit; +} + +NSString *HFDescribeByteCountWithPrefixAndSuffix(const char *stringPrefix, unsigned long long count, const char *stringSuffix) { + if (! stringPrefix) stringPrefix = ""; + if (! stringSuffix) stringSuffix = ""; + + if (count == 0) return [NSString stringWithFormat:@"%s0 bytes%s", stringPrefix, stringSuffix]; + + const struct { + unsigned long long size; + const char *suffix; + } suffixes[] = { + {1ULL<<0, "byte"}, + {1ULL<<10, "byte"}, + {1ULL<<20, "kilobyte"}, + {1ULL<<30, "megabyte"}, + {1ULL<<40, "gigabyte"}, + {1ULL<<50, "terabyte"}, + {1ULL<<60, "petabyte"}, + {ULLONG_MAX, "exabyte"} + }; + const unsigned numSuffixes = sizeof suffixes / sizeof *suffixes; + //HFASSERT((sizeof sizes / sizeof *sizes) == (sizeof suffixes / sizeof *suffixes)); + unsigned i; + unsigned long long base; + for (i=0; i < numSuffixes; i++) { + if (count < suffixes[i].size || suffixes[i].size == ULLONG_MAX) break; + } + + if (i >= numSuffixes) return [NSString stringWithFormat:@"%san unbelievable number of bytes%s", stringPrefix, stringSuffix]; + base = suffixes[i-1].size; + + unsigned long long dividend = count / base; + unsigned int remainderPrincipalDigit = computeRemainderPrincipalDigit(count % base, base); + HFASSERT(remainderPrincipalDigit <= 10); + if (remainderPrincipalDigit == 10) { + /* Carry */ + dividend++; + remainderPrincipalDigit = 0; + } + + BOOL needsPlural = (dividend != 1 || remainderPrincipalDigit > 0); + + char remainderBuff[64]; + if (remainderPrincipalDigit > 0) snprintf(remainderBuff, sizeof remainderBuff, ".%u", remainderPrincipalDigit); + else remainderBuff[0] = 0; + + char* resultPointer = NULL; + int numChars = asprintf(&resultPointer, "%s%llu%s %s%s%s", stringPrefix, dividend, remainderBuff, suffixes[i].suffix, needsPlural ? "s" : "", stringSuffix); + if (numChars < 0) return NULL; + return [[[NSString alloc] initWithBytesNoCopy:resultPointer length:numChars encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; +} + +static CGFloat interpolateShadow(CGFloat val) { + //A value of 1 means we are at the rightmost, and should return our max value. By adjusting the scale, we control how quickly the shadow drops off. + CGFloat scale = 1.4; + return (CGFloat)(expm1(val * scale) / expm1(scale)); +} + +void HFDrawShadow(CGContextRef ctx, NSRect rect, CGFloat shadowSize, NSRectEdge rectEdge, BOOL drawActive, NSRect clip) { + NSRect remainingRect, unused; + NSDivideRect(rect, &remainingRect, &unused, shadowSize, rectEdge); + + CGFloat maxAlpha = (drawActive ? .25 : .10); + + for (CGFloat i=0; i < shadowSize; i++) { + NSRect shadowLine; + NSDivideRect(remainingRect, &shadowLine, &remainingRect, 1, rectEdge); + + NSRect clippedLine = NSIntersectionRect(shadowLine, clip); + if (! NSIsEmptyRect(clippedLine)) { + CGFloat gray = 0.; + CGFloat alpha = maxAlpha * interpolateShadow((shadowSize - i) / shadowSize); + CGContextSetGrayFillColor(ctx, gray, alpha); + CGContextFillRect(ctx, NSRectToCGRect(clippedLine)); + } + } + +} + +void HFRegisterViewForWindowAppearanceChanges(NSView *self, SEL notificationSEL, BOOL appToo) { + NSWindow *window = [self window]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + if (window) { + [center addObserver:self selector:notificationSEL name:NSWindowDidBecomeKeyNotification object:window]; + [center addObserver:self selector:notificationSEL name:NSWindowDidResignKeyNotification object:window]; + } + if (appToo) { + [center addObserver:self selector:notificationSEL name:NSApplicationDidBecomeActiveNotification object:nil]; + [center addObserver:self selector:notificationSEL name:NSApplicationDidResignActiveNotification object:nil]; + } +} + +void HFUnregisterViewForWindowAppearanceChanges(NSView *self, BOOL appToo) { + NSWindow *window = [self window]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + if (window) { + [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window]; + [center removeObserver:self name:NSWindowDidResignKeyNotification object:window]; + } + if (appToo) { + [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; + [center removeObserver:self name:NSApplicationDidResignActiveNotification object:nil]; + } +} + +#if USE_CHUD +void HFStartTiming(const char *name) { + static BOOL inited; + if (! inited) { + inited = YES; + chudInitialize(); + chudSetErrorLogFile(stderr); + chudAcquireRemoteAccess(); + } + chudStartRemotePerfMonitor(name); + +} + +void HFStopTiming(void) { + chudStopRemotePerfMonitor(); +} +#else +void HFStartTiming(const char *name) { USE(name); } +void HFStopTiming(void) { } +#endif diff --git a/thirdparty/SameBoy-old/HexFiend/HFFunctions_Private.h b/thirdparty/SameBoy-old/HexFiend/HFFunctions_Private.h new file mode 100644 index 000000000..3595d3da5 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFFunctions_Private.h @@ -0,0 +1,52 @@ +#import + +@class HFController; + +static inline BOOL HFIsRunningOnMountainLionOrLater(void) { + return NSAppKitVersionNumber >= NSAppKitVersionNumber10_8; +} + +/* Returns the first index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the smaller length; if they are the same length and do not differ, returns NSUIntegerMax */ +static inline NSUInteger HFIndexOfFirstByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) { + NSUInteger endIndex = MIN(len1, len2); + for (NSUInteger i = 0; i < endIndex; i++) { + if (a[i] != b[i]) return i; + } + if (len1 != len2) return endIndex; + return NSUIntegerMax; +} + +/* Returns the last index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the larger length; if they are the same length and do not differ, returns NSUIntegerMax */ +static inline NSUInteger HFIndexOfLastByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) { + if (len1 != len2) return MAX(len1, len2); + NSUInteger i = len1; + while (i--) { + if (a[i] != b[i]) return i; + } + return NSUIntegerMax; +} + +static inline unsigned long long llmin(unsigned long long a, unsigned long long b) { + return a < b ? a : b; +} + +__private_extern__ NSImage *HFImageNamed(NSString *name); + +/* Returns an NSData from an NSString containing hexadecimal characters. Characters that are not hexadecimal digits are silently skipped. Returns by reference whether the last byte contains only one nybble, in which case it will be returned in the low 4 bits of the last byte. */ +__private_extern__ NSData *HFDataFromHexString(NSString *string, BOOL* isMissingLastNybble); + +__private_extern__ NSString *HFHexStringFromData(NSData *data); + +/* Modifies F_NOCACHE for a given file descriptor */ +__private_extern__ void HFSetFDShouldCache(int fd, BOOL shouldCache); + +__private_extern__ NSString *HFDescribeByteCountWithPrefixAndSuffix(const char *stringPrefix, unsigned long long count, const char *stringSuffix); + +/* Function for OSAtomicAdd64 that just does a non-atomic add on PowerPC. This should not be used where atomicity is critical; an example where this is used is updating a progress bar. */ +static inline int64_t HFAtomicAdd64(int64_t a, volatile int64_t *b) { +#if __ppc__ + return *b += a; +#else + return OSAtomicAdd64(a, b); +#endif +} diff --git a/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.h b/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.h new file mode 100644 index 000000000..36d4b08c7 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.h @@ -0,0 +1,49 @@ +/* HFGlyphTrie is used to represent a trie of glyphs that allows multiple concurrent readers, along with one writer. */ + +#import + +/* BranchFactor is in bits */ +#define kHFGlyphTrieBranchFactor 4 +#define kHFGlyphTrieBranchCount (1 << kHFGlyphTrieBranchFactor) + +typedef uint16_t HFGlyphFontIndex; +#define kHFGlyphFontIndexInvalid ((HFGlyphFontIndex)(-1)) + +#define kHFGlyphInvalid kCGFontIndexInvalid + +struct HFGlyph_t { + HFGlyphFontIndex fontIndex; + CGGlyph glyph; +}; + +static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) __attribute__((unused)); +static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) { + return a.glyph == b.glyph && a.fontIndex == b.fontIndex; +} + +struct HFGlyphTrieBranch_t { + __strong void *children[kHFGlyphTrieBranchCount]; +}; + +struct HFGlyphTrieLeaf_t { + struct HFGlyph_t glyphs[kHFGlyphTrieBranchCount]; +}; + +struct HFGlyphTrie_t { + uint8_t branchingDepth; + struct HFGlyphTrieBranch_t root; +}; + +/* Initializes a trie witha given key size */ +__private_extern__ void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize); + +/* Inserts a glyph into the trie */ +__private_extern__ void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value); + +/* Attempts to fetch a glyph. If the glyph is not present, returns an HFGlyph_t set to all bits 0. */ +__private_extern__ struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key); + +/* Frees all storage associated with a glyph tree. This is not necessary to call under GC. */ +__private_extern__ void HFGlyphTreeFree(struct HFGlyphTrie_t * trie); + + diff --git a/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.m b/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.m new file mode 100644 index 000000000..db94782ac --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFGlyphTrie.m @@ -0,0 +1,93 @@ +#import "HFGlyphTrie.h" +#import + +/* If branchingDepth is 1, then this is a leaf and there's nothing to free (a parent frees its children). If branchingDepth is 2, then this is a branch whose children are leaves, so we have to free the leaves but we do not recurse. If branchingDepth is greater than 2, we do have to recurse. */ +static void freeTrie(struct HFGlyphTrieBranch_t *branch, uint8_t branchingDepth) { + HFASSERT(branchingDepth >= 1); + NSUInteger i; + if (branchingDepth > 2) { + /* Recurse */ + for (i=0; i < kHFGlyphTrieBranchCount; i++) { + if (branch->children[i]) { + freeTrie(branch->children[i], branchingDepth - 1); + } + } + } + if (branchingDepth > 1) { + /* Free our children */ + for (i=0; i < kHFGlyphTrieBranchCount; i++) { + free(branch->children[i]); + } + } +} + +static void insertTrie(void *node, uint8_t branchingDepth, NSUInteger key, struct HFGlyph_t value) { + HFASSERT(node != NULL); + HFASSERT(branchingDepth >= 1); + if (branchingDepth == 1) { + /* Leaf */ + HFASSERT(key < kHFGlyphTrieBranchCount); + ((struct HFGlyphTrieLeaf_t *)node)->glyphs[key] = value; + } else { + /* Branch */ + struct HFGlyphTrieBranch_t *branch = node; + NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor; + __strong void *child = branch->children[keySlice]; + if (child == NULL) { + if (branchingDepth == 2) { + child = calloc(1, sizeof(struct HFGlyphTrieLeaf_t)); + } else { + child = calloc(1, sizeof(struct HFGlyphTrieBranch_t)); + } + /* We just zeroed out a block of memory and we are about to write its address somewhere where another thread could read it, so we need a memory barrier. */ + OSMemoryBarrier(); + branch->children[keySlice] = child; + } + insertTrie(child, branchingDepth - 1, keyRemainder, value); + } +} + +static struct HFGlyph_t getTrie(const void *node, uint8_t branchingDepth, NSUInteger key) { + HFASSERT(node != NULL); + HFASSERT(branchingDepth >= 1); + if (branchingDepth == 1) { + /* Leaf */ + HFASSERT(key < kHFGlyphTrieBranchCount); + return ((const struct HFGlyphTrieLeaf_t *)node)->glyphs[key]; + } else { + /* Branch */ + const struct HFGlyphTrieBranch_t *branch = node; + NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor; + if (branch->children[keySlice] == NULL) { + /* Not found */ + return (struct HFGlyph_t){0, 0}; + } else { + /* This dereference requires a data dependency barrier */ + return getTrie(branch->children[keySlice], branchingDepth - 1, keyRemainder); + } + } +} + +void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value) { + insertTrie(&trie->root, trie->branchingDepth, key, value); +} + +struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key) { + struct HFGlyph_t result = getTrie(&trie->root, trie->branchingDepth, key); + return result; +} + +void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize) { + /* If the branch factor is 4 (bits) and the key size is 2 bytes = 16 bits, initialize branching depth to 16/4 = 4 */ + uint8_t keyBits = keySize * CHAR_BIT; + HFASSERT(keyBits % kHFGlyphTrieBranchFactor == 0); + trie->branchingDepth = keyBits / kHFGlyphTrieBranchFactor; + memset(&trie->root, 0, sizeof(trie->root)); +} + +void HFGlyphTreeFree(struct HFGlyphTrie_t * trie) { + /* Don't try to free under GC. And don't free if it's never been initialized. */ + if (trie->branchingDepth > 0) { + freeTrie(&trie->root, trie->branchingDepth); + } +} diff --git a/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.h new file mode 100644 index 000000000..ecbb1a0b0 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.h @@ -0,0 +1,21 @@ +// +// HFHexTextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFHexTextRepresenter + + @brief HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form. + + HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form. It has no methods except those inherited from HFTextRepresenter. +*/ +@interface HFHexTextRepresenter : HFTextRepresenter { + unsigned long long omittedNybbleLocation; + unsigned char unpartneredLastNybble; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.m new file mode 100644 index 000000000..4ff9f2028 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFHexTextRepresenter.m @@ -0,0 +1,203 @@ +// +// HFHexTextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@interface HFHexPasteboardOwner : HFPasteboardOwner { + NSUInteger _bytesPerColumn; +} +@property (nonatomic) NSUInteger bytesPerColumn; +@end + +static inline unsigned char hex2char(NSUInteger c) { + HFASSERT(c < 16); + return "0123456789ABCDEF"[c]; +} + +@implementation HFHexPasteboardOwner + +@synthesize bytesPerColumn = _bytesPerColumn; + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { + if(!dataLength) return 0; + // -1 because no trailing space for an exact multiple. + unsigned long long spaces = _bytesPerColumn ? (dataLength-1)/_bytesPerColumn : 0; + if ((ULLONG_MAX - spaces)/2 <= dataLength) return ULLONG_MAX; + else return dataLength*2 + spaces; +} + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + HFASSERT([type isEqual:NSStringPboardType]); + if(length == 0) { + [pboard setString:@"" forType:type]; + return; + } + HFByteArray *byteArray = [self byteArray]; + HFASSERT(length <= NSUIntegerMax); + NSUInteger dataLength = ll2l(length); + NSUInteger stringLength = ll2l([self stringLengthForDataLength:length]); + HFASSERT(stringLength < ULLONG_MAX); + NSUInteger offset = 0, stringOffset = 0, remaining = dataLength; + unsigned char * restrict const stringBuffer = check_malloc(stringLength); + while (remaining > 0) { + unsigned char dataBuffer[64 * 1024]; + NSUInteger amountToCopy = MIN(sizeof dataBuffer, remaining); + NSUInteger bound = offset + amountToCopy - 1; + [byteArray copyBytes:dataBuffer range:HFRangeMake(offset, amountToCopy)]; + + if(_bytesPerColumn > 0 && offset > 0) { // ensure offset > 0 to skip adding a leading space + NSUInteger left = _bytesPerColumn - (offset % _bytesPerColumn); + if(left != _bytesPerColumn) { + while(left-- > 0 && offset <= bound) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + } + if(offset <= bound) + stringBuffer[stringOffset++] = ' '; + } + + if(_bytesPerColumn > 0) while(offset+_bytesPerColumn <= bound) { + for(NSUInteger j = 0; j < _bytesPerColumn; j++) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + stringBuffer[stringOffset++] = ' '; + } + + while (offset <= bound) { + unsigned char c = dataBuffer[offset++]; + stringBuffer[stringOffset] = hex2char(c >> 4); + stringBuffer[stringOffset + 1] = hex2char(c & 0xF); + stringOffset += 2; + } + + remaining -= amountToCopy; + } + + NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; + [pboard setString:string forType:type]; + [string release]; +} + +@end + +@implementation HFHexTextRepresenter + +/* No extra NSCoder support needed */ + +- (Class)_textViewClass { + return [HFRepresenterHexTextView class]; +} + +- (void)initializeView { + [super initializeView]; + [[self view] setBytesBetweenVerticalGuides:4]; + unpartneredLastNybble = UCHAR_MAX; + omittedNybbleLocation = ULLONG_MAX; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, 0); +} + +- (void)_clearOmittedNybble { + unpartneredLastNybble = UCHAR_MAX; + omittedNybbleLocation = ULLONG_MAX; +} + +- (BOOL)_insertionShouldDeleteLastNybble { + /* Either both the omittedNybbleLocation and unpartneredLastNybble are invalid (set to their respective maxima), or neither are */ + HFASSERT((omittedNybbleLocation == ULLONG_MAX) == (unpartneredLastNybble == UCHAR_MAX)); + /* We should delete the last nybble if our omittedNybbleLocation is the point where we would insert */ + BOOL result = NO; + if (omittedNybbleLocation != ULLONG_MAX) { + HFController *controller = [self controller]; + NSArray *selectedRanges = [controller selectedContentsRanges]; + if ([selectedRanges count] == 1) { + HFRange selectedRange = [selectedRanges[0] HFRange]; + result = (selectedRange.length == 0 && selectedRange.location > 0 && selectedRange.location - 1 == omittedNybbleLocation); + } + } + return result; +} + +- (BOOL)_canInsertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"]; + return [text rangeOfCharacterFromSet:characterSet].location != NSNotFound; +} + +- (void)insertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + if (! [self _canInsertText:text]) { + /* The user typed invalid data, and we can ignore it */ + return; + } + + BOOL shouldReplacePriorByte = [self _insertionShouldDeleteLastNybble]; + if (shouldReplacePriorByte) { + HFASSERT(unpartneredLastNybble < 16); + /* Prepend unpartneredLastNybble as a nybble */ + text = [NSString stringWithFormat:@"%1X%@", unpartneredLastNybble, text]; + } + BOOL isMissingLastNybble; + NSData *data = HFDataFromHexString(text, &isMissingLastNybble); + HFASSERT([data length] > 0); + HFASSERT(shouldReplacePriorByte != isMissingLastNybble); + HFController *controller = [self controller]; + BOOL success = [controller insertData:data replacingPreviousBytes: (shouldReplacePriorByte ? 1 : 0) allowUndoCoalescing:YES]; + if (isMissingLastNybble && success) { + HFASSERT([data length] > 0); + HFASSERT(unpartneredLastNybble == UCHAR_MAX); + [data getBytes:&unpartneredLastNybble range:NSMakeRange([data length] - 1, 1)]; + NSArray *selectedRanges = [controller selectedContentsRanges]; + HFASSERT([selectedRanges count] >= 1); + HFRange selectedRange = [selectedRanges[0] HFRange]; + HFASSERT(selectedRange.location > 0); + omittedNybbleLocation = HFSubtract(selectedRange.location, 1); + } + else { + [self _clearOmittedNybble]; + } +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + REQUIRE_NOT_NULL(string); + return HFDataFromHexString(string, NULL); +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & HFControllerHideNullBytes) { + [[self view] setHidesNullBytes:[[self controller] shouldHideNullBytes]]; + } + [super controllerDidChange:bits]; + if (bits & (HFControllerSelectedRanges)) { + [self _clearOmittedNybble]; + } +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges]; + HFASSERT(selection != NULL); + if ([selection length] == 0) { + NSBeep(); + } else { + HFHexPasteboardOwner *owner = [HFHexPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]]; + [owner setBytesPerLine:[self bytesPerLine]]; + owner.bytesPerColumn = self.bytesPerColumn; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFHexTextView.h b/thirdparty/SameBoy-old/HexFiend/HFHexTextView.h new file mode 100644 index 000000000..3222b65e7 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFHexTextView.h @@ -0,0 +1,15 @@ +// +// HFHexTextView.h +// HexFiend_2 +// +// Copyright 2007 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface HFHexTextView : NSTextView { + +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFHexTextView.m b/thirdparty/SameBoy-old/HexFiend/HFHexTextView.m new file mode 100644 index 000000000..9e6ae47b3 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFHexTextView.m @@ -0,0 +1,13 @@ +// +// HFHexTextView.m +// HexFiend_2 +// +// Copyright 2007 __MyCompanyName__. All rights reserved. +// + +#import "HFHexTextView.h" + + +@implementation HFHexTextView + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.h new file mode 100644 index 000000000..a493304b5 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.h @@ -0,0 +1,80 @@ +// +// HFLayoutRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFLayoutRepresenter + @brief An HFRepresenter responsible for arranging the views of other HFRepresenters attached to the same HFController. + + HFLayoutRepresenter is an HFRepresenter that manages the views of other HFRepresenters. It arranges their views in its own view, mediating between them to determine their position and size, as well as global properties such as bytes per line. + + HFLayoutRepresenter has an array of representers attached to it. When you add an HFRepresenter to this array, HFLayoutRepresenter will add the view of the representer as a subview of its own view. + + \b Layout + + HFLayoutRepresenter is capable of arranging the views of other HFRepresenters to fit within the bounds of its view. The layout process depends on three things: + + -# The \c frame and \c autoresizingMask of the representers' views. + -# The \c minimumViewWidthForBytesPerLine: method, which determines the largest number of bytes per line that the representer can display for a given view width. + -# The representer's \c layoutPosition. This is an NSPoint, but it is not used geometrically. Instead, the relative values of the X and Y coordinates of the \c layoutPosition determine the relative positioning of the views, as described below. + + Thus, to have your subclass of HFRepresenter participate in the HFLayoutRepresenter system, override \c defaultLayoutPosition: to control its positioning, and possibly \\c minimumViewWidthForBytesPerLine: if your representer requires a certain width to display some bytes per line. Then ensure your view has its autoresizing mask set properly, and if its frame is fixed size, ensure that its frame is correct as well. + + The layout process, in detail, is: + + -# The views are sorted vertically by the Y component of their representers' \c layoutPosition into "slices." Smaller values appear towards the bottom of the layout view. There is no space between slices. + -# Views with equal Y components are sorted horizontally by the X component of their representers' \c layoutPosition, with smaller values appearing on the left. + -# The height of each slice is determined by the tallest view within it, excluding views that have \c NSViewHeightSizable set. If there is any leftover vertical space, it is distributed equally among all slices with at least one view with \c NSViewHeightSizable set. + -# If the layout representer is not set to maximize the bytes per line (BPL), then the BPL from the HFController is used. Otherwise: + -# Each representer is queried for its \c minimumViewWidthForBytesPerLine: + -# The largest BPL allowing each row to fit within the layout width is determined via a binary search. + -# The BPL is rounded down to a multiple of the bytes per column (if non-zero). + -# The BPL is then set on the controller. + -# For each row, each view is assigned its minimum view width for the BPL. + -# If there is any horizontal space left over, it is divided evenly between all views in that slice that have \c NSViewWidthSizable set in their autoresizing mask. + +*/ +@interface HFLayoutRepresenter : HFRepresenter { + NSMutableArray *representers; + BOOL maximizesBytesPerLine; +} + +/*! @name Managed representers + Managing the list of representers laid out by the receiver +*/ +//@{ +/// Return the array of representers managed by the receiver. */ +@property (readonly, copy) NSArray *representers; + +/*! Adds a new representer to the receiver, triggering relayout. */ +- (void)addRepresenter:(HFRepresenter *)representer; + +/*! Removes a representer to the receiver (which must be present in the receiver's array of representers), triggering relayout. */ +- (void)removeRepresenter:(HFRepresenter *)representer; +//@} + +/*! When enabled, the receiver will attempt to maximize the bytes per line so as to consume as much as possible of the bounds rect. If this is YES, then upon relayout, the receiver will recalculate the maximum number of bytes per line that can fit in its boundsRectForLayout. If this is NO, then the receiver will not change the bytes per line. */ +@property (nonatomic) BOOL maximizesBytesPerLine; + +/*! @name Layout + Methods to get information about layout, and to explicitly trigger it. +*/ +//@{ +/*! Returns the smallest width that produces the same layout (and, if maximizes bytesPerLine, the same bytes per line) as the proposed width. */ +- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth; + +/*! Returns the maximum bytes per line that can fit in the proposed width (ignoring maximizesBytesPerLine). This is always a multiple of the bytesPerColumn, and always at least bytesPerColumn. */ +- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth; + +/*! Returns the smallest width that can support the given bytes per line. */ +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +/*! Relayouts are triggered when representers are added and removed, or when the view is resized. You may call this explicitly to trigger a relayout. */ +- (void)performLayout; +//@} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.m new file mode 100644 index 000000000..c36c0ea1b --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLayoutRepresenter.m @@ -0,0 +1,361 @@ +// +// HFRepresenterLayoutView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +@interface HFRepresenterLayoutViewInfo : NSObject { +@public + HFRepresenter *rep; + NSView *view; + NSPoint layoutPosition; + NSRect frame; + NSUInteger autoresizingMask; +} + +@end + +@implementation HFRepresenterLayoutViewInfo + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ : %@>", view, NSStringFromRect(frame)]; +} + +@end + +@implementation HFLayoutRepresenter + +static NSInteger sortByLayoutPosition(id a, id b, void *self) { + USE(self); + NSPoint pointA = [a layoutPosition]; + NSPoint pointB = [b layoutPosition]; + if (pointA.y < pointB.y) return -1; + else if (pointA.y > pointB.y) return 1; + else if (pointA.x < pointB.x) return -1; + else if (pointA.x > pointB.x) return 1; + else return 0; +} + +- (NSArray *)arraysOfLayoutInfos { + if (! representers) return nil; + + NSMutableArray *result = [NSMutableArray array]; + NSArray *reps = [representers sortedArrayUsingFunction:sortByLayoutPosition context:self]; + NSMutableArray *currentReps = [NSMutableArray array]; + CGFloat currentRepY = - CGFLOAT_MAX; + FOREACH(HFRepresenter*, rep, reps) { + HFRepresenterLayoutViewInfo *info = [[HFRepresenterLayoutViewInfo alloc] init]; + info->rep = rep; + info->view = [rep view]; + info->frame = [info->view frame]; + info->layoutPosition = [rep layoutPosition]; + info->autoresizingMask = [info->view autoresizingMask]; + if (info->layoutPosition.y != currentRepY && [currentReps count] > 0) { + [result addObject:[[currentReps copy] autorelease]]; + [currentReps removeAllObjects]; + } + currentRepY = info->layoutPosition.y; + [currentReps addObject:info]; + [info release]; + } + if ([currentReps count]) [result addObject:[[currentReps copy] autorelease]]; + return result; +} + +- (NSRect)boundsRectForLayout { + NSRect result = [[self view] bounds]; + /* Sometimes when we are not yet in a window, we get wonky bounds, so be paranoid. */ + if (result.size.width < 0 || result.size.height < 0) result = NSZeroRect; + return result; +} + +- (CGFloat)_computeMinHeightForLayoutInfos:(NSArray *)infos { + CGFloat result = 0; + HFASSERT(infos != NULL); + HFASSERT([infos count] > 0); + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + if (! (info->autoresizingMask & NSViewHeightSizable)) result = MAX(result, NSHeight([info->view frame])); + } + return result; +} + +- (void)_applyYLocation:(CGFloat)yLocation andMinHeight:(CGFloat)height toInfos:(NSArray *)layoutInfos { + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + info->frame.origin.y = yLocation; + if (info->autoresizingMask & NSViewHeightSizable) info->frame.size.height = height; + } +} + +- (void)_layoutInfosHorizontally:(NSArray *)infos inRect:(NSRect)layoutRect withBytesPerLine:(NSUInteger)bytesPerLine { + CGFloat nextX = NSMinX(layoutRect); + NSUInteger numHorizontallyResizable = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + CGFloat minWidth = [info->rep minimumViewWidthForBytesPerLine:bytesPerLine]; + info->frame.origin.x = nextX; + info->frame.size.width = minWidth; + nextX += minWidth; + numHorizontallyResizable += !! (info->autoresizingMask & NSViewWidthSizable); + } + + CGFloat remainingWidth = NSMaxX(layoutRect) - nextX; + if (numHorizontallyResizable > 0 && remainingWidth > 0) { + NSView *view = [self view]; + CGFloat remainingPixels = [view convertSize:NSMakeSize(remainingWidth, 0) toView:nil].width; + HFASSERT(remainingPixels > 0); + CGFloat pixelsPerView = HFFloor(HFFloor(remainingPixels) / (CGFloat)numHorizontallyResizable); + if (pixelsPerView > 0) { + CGFloat pointsPerView = [view convertSize:NSMakeSize(pixelsPerView, 0) fromView:nil].width; + CGFloat pointsAdded = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, infos) { + info->frame.origin.x += pointsAdded; + if (info->autoresizingMask & NSViewWidthSizable) { + info->frame.size.width += pointsPerView; + pointsAdded += pointsPerView; + } + } + } + } +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + CGFloat result = 0; + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + CGFloat minWidthForRow = 0; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + minWidthForRow += [info->rep minimumViewWidthForBytesPerLine:bytesPerLine]; + } + result = MAX(result, minWidthForRow); + } + return result; +} + +- (NSUInteger)_computeBytesPerLineForArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos forLayoutInRect:(NSRect)layoutRect { + /* The granularity is our own granularity (probably 1), LCMed with the granularities of all other representers */ + NSUInteger granularity = [self byteGranularity]; + FOREACH(HFRepresenter *, representer, representers) { + granularity = HFLeastCommonMultiple(granularity, [representer byteGranularity]); + } + HFASSERT(granularity >= 1); + + NSUInteger newNumGranules = (NSUIntegerMax - 1) / granularity; + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + NSUInteger maxKnownGood = 0, minKnownBad = newNumGranules + 1; + while (maxKnownGood + 1 < minKnownBad) { + CGFloat requiredSpace = 0; + NSUInteger proposedNumGranules = maxKnownGood + (minKnownBad - maxKnownGood)/2; + NSUInteger proposedBytesPerLine = proposedNumGranules * granularity; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + requiredSpace += [info->rep minimumViewWidthForBytesPerLine:proposedBytesPerLine]; + if (requiredSpace > NSWidth(layoutRect)) break; + } + if (requiredSpace > NSWidth(layoutRect)) minKnownBad = proposedNumGranules; + else maxKnownGood = proposedNumGranules; + } + newNumGranules = maxKnownGood; + } + return MAX(1u, newNumGranules) * granularity; +} + +- (BOOL)_anyLayoutInfoIsVerticallyResizable:(NSArray *)vals { + HFASSERT(vals != NULL); + FOREACH(HFRepresenterLayoutViewInfo *, info, vals) { + if (info->autoresizingMask & NSViewHeightSizable) return YES; + } + return NO; +} + +- (BOOL)_addVerticalHeight:(CGFloat)heightPoints andOffset:(CGFloat)offsetPoints toLayoutInfos:(NSArray *)layoutInfos { + BOOL isVerticallyResizable = [self _anyLayoutInfoIsVerticallyResizable:layoutInfos]; + CGFloat totalHeight = [self _computeMinHeightForLayoutInfos:layoutInfos] + heightPoints; + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) { + info->frame.origin.y += offsetPoints; + if (isVerticallyResizable) { + if (info->autoresizingMask & NSViewHeightSizable) { + info->frame.size.height = totalHeight; + } + else { + CGFloat diff = totalHeight - info->frame.size.height; + HFASSERT(diff >= 0); + info->frame.origin.y += HFFloor(diff); + } + } + } + return isVerticallyResizable; +} + +- (void)_distributeVerticalSpace:(CGFloat)space toArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos { + HFASSERT(space >= 0); + HFASSERT(arraysOfLayoutInfos != NULL); + + NSUInteger consumers = 0; + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + if ([self _anyLayoutInfoIsVerticallyResizable:layoutInfos]) consumers++; + } + if (consumers > 0) { + NSView *view = [self view]; + CGFloat availablePixels = [view convertSize:NSMakeSize(0, space) toView:nil].height; + HFASSERT(availablePixels > 0); + CGFloat pixelsPerView = HFFloor(HFFloor(availablePixels) / (CGFloat)consumers); + CGFloat pointsPerView = [view convertSize:NSMakeSize(0, pixelsPerView) fromView:nil].height; + CGFloat yOffset = 0; + if (pointsPerView > 0) { + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + if ([self _addVerticalHeight:pointsPerView andOffset:yOffset toLayoutInfos:layoutInfos]) { + yOffset += pointsPerView; + } + } + } + } +} + +- (void)performLayout { + HFController *controller = [self controller]; + if (! controller) return; + if (! representers) return; + + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + if (! [arraysOfLayoutInfos count]) return; + + NSUInteger transaction = [controller beginPropertyChangeTransaction]; + + NSRect layoutRect = [self boundsRectForLayout]; + + NSUInteger bytesPerLine; + if (maximizesBytesPerLine) bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect]; + else bytesPerLine = [controller bytesPerLine]; + + CGFloat yPosition = NSMinY(layoutRect); + FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) { + HFASSERT([layoutInfos count] > 0); + CGFloat minHeight = [self _computeMinHeightForLayoutInfos:layoutInfos]; + [self _applyYLocation:yPosition andMinHeight:minHeight toInfos:layoutInfos]; + yPosition += minHeight; + [self _layoutInfosHorizontally:layoutInfos inRect:layoutRect withBytesPerLine:bytesPerLine]; + } + + CGFloat remainingVerticalSpace = NSMaxY(layoutRect) - yPosition; + if (remainingVerticalSpace > 0) { + [self _distributeVerticalSpace:remainingVerticalSpace toArraysOfLayoutInfos:arraysOfLayoutInfos]; + } + + FOREACH(NSArray *, layoutInfoArray, arraysOfLayoutInfos) { + FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfoArray) { + [info->view setFrame:info->frame]; + } + } + + [controller endPropertyChangeTransaction:transaction]; +} + +- (NSArray *)representers { + return representers ? [[representers copy] autorelease] : @[]; +} + +- (instancetype)init { + self = [super init]; + maximizesBytesPerLine = YES; + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self view]]; + [representers release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:representers forKey:@"HFRepresenters"]; + [coder encodeBool:maximizesBytesPerLine forKey:@"HFMaximizesBytesPerLine"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain]; + maximizesBytesPerLine = [coder decodeBoolForKey:@"HFMaximizesBytesPerLine"]; + NSView *view = [self view]; + [view setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view]; + return self; +} + +- (void)addRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + if (! representers) representers = [[NSMutableArray alloc] init]; + HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound); + [representers addObject:representer]; + HFASSERT([[representer view] superview] != [self view]); + [[self view] addSubview:[representer view]]; + [self performLayout]; +} + +- (void)removeRepresenter:(HFRepresenter *)representer { + REQUIRE_NOT_NULL(representer); + HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound); + NSView *view = [representer view]; + HFASSERT([view superview] == [self view]); + [view removeFromSuperview]; + [representers removeObjectIdenticalTo:representer]; + [self performLayout]; +} + +- (void)frameChanged:(NSNotification *)note { + USE(note); + [self performLayout]; +} + +- (void)initializeView { + NSView *view = [self view]; + [view setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view]; +} + +- (NSView *)createView { + return [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; +} + +- (void)setMaximizesBytesPerLine:(BOOL)val { + maximizesBytesPerLine = val; +} + +- (BOOL)maximizesBytesPerLine { + return maximizesBytesPerLine; +} + +- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth { + NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos]; + if (! [arraysOfLayoutInfos count]) return 0; + + NSRect layoutRect = [self boundsRectForLayout]; + layoutRect.size.width = proposedWidth; + + NSUInteger bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect]; + return bytesPerLine; +} + +- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth { + NSUInteger bytesPerLine; + if ([self maximizesBytesPerLine]) { + bytesPerLine = [self maximumBytesPerLineForLayoutInProposedWidth:proposedWidth]; + } else { + bytesPerLine = [[self controller] bytesPerLine]; + } + CGFloat newWidth = [self minimumViewWidthForBytesPerLine:bytesPerLine]; + return newWidth; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + [super controllerDidChange:bits]; + if (bits & (HFControllerViewSizeRatios | HFControllerBytesPerColumn | HFControllerByteGranularity)) { + [self performLayout]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.h new file mode 100644 index 000000000..b711e4eec --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.h @@ -0,0 +1,67 @@ +// +// HFLineCountingRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @enum HFLineNumberFormat + HFLineNumberFormat is a simple enum used to determine whether line numbers are in decimal or hexadecimal format. +*/ +typedef NS_ENUM(NSUInteger, HFLineNumberFormat) { + HFLineNumberFormatDecimal, //!< Decimal line numbers + HFLineNumberFormatHexadecimal, //!< Hexadecimal line numbers + HFLineNumberFormatMAXIMUM //!< One more than the maximum valid line number format, so that line number formats can be cycled through easily +}; + +/*! @class HFLineCountingRepresenter + @brief The HFRepresenter used to show the "line number gutter." + + HFLineCountingRepresenter is the HFRepresenter used to show the "line number gutter." HFLineCountingRepresenter makes space for a certain number of digits. +*/ +@interface HFLineCountingRepresenter : HFRepresenter { + CGFloat lineHeight; + NSUInteger digitsToRepresentContentsLength; + NSUInteger minimumDigitCount; + HFLineNumberFormat lineNumberFormat; + NSInteger interiorShadowEdge; + CGFloat preferredWidth; + CGFloat digitAdvance; +} + +/// The minimum digit count. The receiver will always ensure it is big enough to display at least the minimum digit count. The default is 2. +@property (nonatomic) NSUInteger minimumDigitCount; + +/// The number of digits we are making space for. +@property (readonly) NSUInteger digitCount; + +/// The current width that the HFRepresenter prefers to be laid out with. +@property (readonly) CGFloat preferredWidth; + +/// The line number format. +@property (nonatomic) HFLineNumberFormat lineNumberFormat; + +/// Switches to the next line number format. This is called from the view. +- (void)cycleLineNumberFormat; + +/// The edge (as an NSRectEdge) on which the view draws an interior shadow. -1 means no edge. +@property (nonatomic) NSInteger interiorShadowEdge; + +/// The border color used at the edges specified by -borderedEdges. +@property (nonatomic, copy) NSColor *borderColor; + +/*! The edges on which borders are drawn. The edge returned by interiorShadowEdge always has a border drawn. The edges are specified by a bitwise or of 1 left shifted by the NSRectEdge values. For example, to draw a border on the min x and max y edges use: (1 << NSMinXEdge) | (1 << NSMaxYEdge). 0 (or -1) specfies no edges. */ +@property (nonatomic) NSInteger borderedEdges; + +/// The background color +@property (nonatomic, copy) NSColor *backgroundColor; + +@property NSUInteger valueOffset; + +@end + +/*! Notification posted when the HFLineCountingRepresenter's width has changed because the number of digits it wants to show has increased or decreased. The object is the HFLineCountingRepresenter; there is no user info. +*/ +extern NSString *const HFLineCountingRepresenterMinimumViewWidthChanged; diff --git a/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.m new file mode 100644 index 000000000..4a81fc83d --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLineCountingRepresenter.m @@ -0,0 +1,250 @@ +// +// HFLineCountingRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +NSString *const HFLineCountingRepresenterMinimumViewWidthChanged = @"HFLineCountingRepresenterMinimumViewWidthChanged"; + +/* Returns the maximum advance in points for a hexadecimal digit for the given font (interpreted as a screen font) */ +static CGFloat maximumDigitAdvanceForFont(NSFont *font) { + REQUIRE_NOT_NULL(font); + font = [font screenFont]; + CGFloat maxDigitAdvance = 0; + NSDictionary *attributesDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:font, NSFontAttributeName, nil]; + NSTextStorage *storage = [[NSTextStorage alloc] init]; + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + [storage setFont:font]; + [storage addLayoutManager:manager]; + + NSSize advancements[16] = {}; + NSGlyph glyphs[16]; + + /* Generate a glyph for every hex digit */ + for (NSUInteger i=0; i < 16; i++) { + char c = "0123456789ABCDEF"[i]; + NSString *string = [[NSString alloc] initWithBytes:&c length:1 encoding:NSASCIIStringEncoding]; + [storage replaceCharactersInRange:NSMakeRange(0, (i ? 1 : 0)) withString:string]; + [string release]; + glyphs[i] = [manager glyphAtIndex:0 isValidIndex:NULL]; + HFASSERT(glyphs[i] != NSNullGlyph); + } + + /* Get the advancements of each of those glyphs */ + [font getAdvancements:advancements forGlyphs:glyphs count:sizeof glyphs / sizeof *glyphs]; + + [manager release]; + [attributesDictionary release]; + [storage release]; + + /* Find the widest digit */ + for (NSUInteger i=0; i < sizeof glyphs / sizeof *glyphs; i++) { + maxDigitAdvance = HFMax(maxDigitAdvance, advancements[i].width); + } + return maxDigitAdvance; +} + +@implementation HFLineCountingRepresenter + +- (instancetype)init { + if ((self = [super init])) { + minimumDigitCount = 2; + digitsToRepresentContentsLength = minimumDigitCount; + interiorShadowEdge = NSMaxXEdge; + + _borderedEdges = (1 << NSMaxXEdge); + _borderColor = [[NSColor controlShadowColor] retain]; + _backgroundColor = [[NSColor windowBackgroundColor] retain]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeDouble:lineHeight forKey:@"HFLineHeight"]; + [coder encodeInt64:minimumDigitCount forKey:@"HFMinimumDigitCount"]; + [coder encodeInt64:lineNumberFormat forKey:@"HFLineNumberFormat"]; + [coder encodeObject:self.backgroundColor forKey:@"HFBackgroundColor"]; + [coder encodeObject:self.borderColor forKey:@"HFBorderColor"]; + [coder encodeInt64:self.borderedEdges forKey:@"HFBorderedEdges"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + minimumDigitCount = (NSUInteger)[coder decodeInt64ForKey:@"HFMinimumDigitCount"]; + lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"]; + + _borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0; + _borderColor = [[NSColor controlShadowColor] retain]; + _backgroundColor = [[NSColor windowBackgroundColor] retain]; + + return self; +} + +- (void)dealloc { + [_borderColor release]; + [_backgroundColor release]; + [super dealloc]; +} + +- (NSView *)createView { + HFLineCountingView *result = [[HFLineCountingView alloc] initWithFrame:NSMakeRect(0, 0, 60, 10)]; + [result setRepresenter:self]; + [result setAutoresizingMask:NSViewHeightSizable]; + return result; +} + +- (void)postMinimumViewWidthChangedNotification { + [[NSNotificationCenter defaultCenter] postNotificationName:HFLineCountingRepresenterMinimumViewWidthChanged object:self]; +} + +- (void)updateDigitAdvanceWithFont:(NSFont *)font { + CGFloat newDigitAdvance = maximumDigitAdvanceForFont(font); + if (digitAdvance != newDigitAdvance) { + digitAdvance = newDigitAdvance; + [self postMinimumViewWidthChangedNotification]; + } +} + +- (void)updateFontAndLineHeight { + HFLineCountingView *view = [self view]; + HFController *controller = [self controller]; + NSFont *font = controller ? [controller font] : [NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]; + [view setFont:font]; + [view setLineHeight: controller ? [controller lineHeight] : HFDEFAULT_FONTSIZE]; + [self updateDigitAdvanceWithFont:font]; +} + +- (void)updateLineNumberFormat { + [[self view] setLineNumberFormat:lineNumberFormat]; +} + +- (void)updateBytesPerLine { + [[self view] setBytesPerLine:[[self controller] bytesPerLine]]; +} + +- (void)updateLineRangeToDraw { + HFFPRange lineRange = {0, 0}; + HFController *controller = [self controller]; + if (controller) { + lineRange = [controller displayedLineRange]; + } + [[self view] setLineRangeToDraw:lineRange]; +} + +- (CGFloat)preferredWidth { + if (digitAdvance == 0) { + /* This may happen if we were loaded from a nib. We are lazy about fetching the controller's font to avoid ordering issues with nib unarchival. */ + [self updateFontAndLineHeight]; + } + return (CGFloat)10. + digitsToRepresentContentsLength * digitAdvance; +} + +- (void)updateMinimumViewWidth { + HFController *controller = [self controller]; + if (controller) { + unsigned long long contentsLength = [controller contentsLength]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + /* We want to know how many lines are displayed. That's equal to the contentsLength divided by bytesPerLine rounded down, except in the case that we're at the end of a line, in which case we need to show one more. Hence adding 1 and dividing gets us the right result. */ + unsigned long long lineCount = contentsLength / bytesPerLine; + unsigned long long contentsLengthRoundedToLine = HFProductULL(lineCount, bytesPerLine) - 1; + NSUInteger digitCount = [HFLineCountingView digitsRequiredToDisplayLineNumber:contentsLengthRoundedToLine inFormat:lineNumberFormat]; + NSUInteger digitWidth = MAX(minimumDigitCount, digitCount); + if (digitWidth != digitsToRepresentContentsLength) { + digitsToRepresentContentsLength = digitWidth; + [self postMinimumViewWidthChangedNotification]; + } + } +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return [self preferredWidth]; +} + +- (HFLineNumberFormat)lineNumberFormat { + return lineNumberFormat; +} + +- (void)setLineNumberFormat:(HFLineNumberFormat)format { + HFASSERT(format < HFLineNumberFormatMAXIMUM); + lineNumberFormat = format; + [self updateLineNumberFormat]; + [self updateMinimumViewWidth]; +} + + +- (void)cycleLineNumberFormat { + lineNumberFormat = (lineNumberFormat + 1) % HFLineNumberFormatMAXIMUM; + [self updateLineNumberFormat]; + [self updateMinimumViewWidth]; +} + +- (void)initializeView { + [self updateFontAndLineHeight]; + [self updateLineNumberFormat]; + [self updateBytesPerLine]; + [self updateLineRangeToDraw]; + [self updateMinimumViewWidth]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & HFControllerDisplayedLineRange) [self updateLineRangeToDraw]; + if (bits & HFControllerBytesPerLine) [self updateBytesPerLine]; + if (bits & (HFControllerFont | HFControllerLineHeight)) [self updateFontAndLineHeight]; + if (bits & (HFControllerContentLength)) [self updateMinimumViewWidth]; +} + +- (void)setMinimumDigitCount:(NSUInteger)width { + minimumDigitCount = width; + [self updateMinimumViewWidth]; +} + +- (NSUInteger)minimumDigitCount { + return minimumDigitCount; +} + +- (NSUInteger)digitCount { + return digitsToRepresentContentsLength; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(-1, 0); +} + +- (void)setInteriorShadowEdge:(NSInteger)edge { + self->interiorShadowEdge = edge; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +- (NSInteger)interiorShadowEdge { + return interiorShadowEdge; +} + + +- (void)setBorderColor:(NSColor *)color { + [_borderColor autorelease]; + _borderColor = [color copy]; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +- (void)setBackgroundColor:(NSColor *)color { + [_backgroundColor autorelease]; + _backgroundColor = [color copy]; + if ([self isViewLoaded]) { + [[self view] setNeedsDisplay:YES]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.h b/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.h new file mode 100644 index 000000000..2eb90af98 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.h @@ -0,0 +1,32 @@ +// +// HFLineCountingView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +@interface HFLineCountingView : NSView { + NSLayoutManager *layoutManager; + NSTextStorage *textStorage; + NSTextContainer *textContainer; + NSDictionary *textAttributes; + + unsigned long long storedLineIndex; + NSUInteger storedLineCount; + BOOL useStringDrawingPath; + BOOL registeredForAppNotifications; +} + +@property (nonatomic, copy) NSFont *font; +@property (nonatomic) CGFloat lineHeight; +@property (nonatomic) HFFPRange lineRangeToDraw; +@property (nonatomic) NSUInteger bytesPerLine; +@property (nonatomic) HFLineNumberFormat lineNumberFormat; +@property (nonatomic, assign) HFLineCountingRepresenter *representer; + ++ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.m b/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.m new file mode 100644 index 000000000..080599b00 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFLineCountingView.m @@ -0,0 +1,675 @@ +// +// HFLineCountingView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +#define TIME_LINE_NUMBERS 0 + +#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0 + +#define INVALID_LINE_COUNT NSUIntegerMax + +#if TIME_LINE_NUMBERS +@interface HFTimingTextView : NSTextView +@end +@implementation HFTimingTextView +- (void)drawRect:(NSRect)rect { + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); + [super drawRect:rect]; + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"TextView line number time: %f", endTime - startTime); +} +@end +#endif + +@implementation HFLineCountingView + +- (void)_sharedInitLineCountingView { + layoutManager = [[NSLayoutManager alloc] init]; + textStorage = [[NSTextStorage alloc] init]; + [textStorage addLayoutManager:layoutManager]; + textContainer = [[NSTextContainer alloc] init]; + [textContainer setLineFragmentPadding:(CGFloat)5]; + [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)]; + [layoutManager addTextContainer:textContainer]; +} + +- (instancetype)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self _sharedInitLineCountingView]; + } + return self; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications); + [_font release]; + [layoutManager release]; + [textContainer release]; + [textStorage release]; + [textAttributes release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeDouble:_lineHeight forKey:@"HFLineHeight"]; + [coder encodeObject:_representer forKey:@"HFRepresenter"]; + [coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"]; + [coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"]; + [coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self _sharedInitLineCountingView]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + _lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"]; + _representer = [coder decodeObjectForKey:@"HFRepresenter"]; + _bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"]; + _lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"]; + useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"]; + return self; +} + +- (BOOL)isFlipped { return YES; } + +- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length { + HFLineNumberFormat format = self.lineNumberFormat; + if (format == HFLineNumberFormatDecimal) { + strlcpy(outString, "%llu", length); + } + else if (format == HFLineNumberFormatHexadecimal) { +#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX + // we want a format string like 0x%08llX + snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2); +#else + // we want a format string like %08llX + snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount); +#endif + } + else { + strlcpy(outString, "", length); + } +} + +- (void)windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications); + registeredForAppNotifications = YES; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO); + [super viewWillMoveToWindow:newWindow]; +} + +- (void)drawDividerWithClip:(NSRect)clipRect { + USE(clipRect); + + +#if 1 + NSInteger edges = _representer.borderedEdges; + NSRect bounds = self.bounds; + + + // -1 means to draw no edges + if (edges == -1) { + edges = 0; + } + + [_representer.borderColor set]; + + if ((edges & (1 << NSMinXEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.width = 1; + lineRect.origin.x = 0; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMaxXEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.width = 1; + lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMinYEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.height = 1; + lineRect.origin.y = 0; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + if ((edges & (1 << NSMaxYEdge)) > 0) { + NSRect lineRect = bounds; + lineRect.size.height = 1; + lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height; + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + } + + + // Backwards compatibility to always draw a border on the edge with the interior shadow + + NSRect lineRect = bounds; + lineRect.size.width = 1; + NSInteger shadowEdge = _representer.interiorShadowEdge; + if (shadowEdge == NSMaxXEdge) { + lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width; + } else if (shadowEdge == NSMinXEdge) { + lineRect.origin.x = NSMinX(bounds); + } else { + lineRect = NSZeroRect; + } + + if (NSIntersectsRect(lineRect, clipRect)) { + NSRectFill(lineRect); + } + +#else + + + if (NSIntersectsRect(lineRect, clipRect)) { + // this looks better when we have no shadow + [[NSColor lightGrayColor] set]; + NSRect bounds = self.bounds; + NSRect lineRect = bounds; + lineRect.origin.x += lineRect.size.width - 2; + lineRect.size.width = 1; + NSRectFill(NSIntersectionRect(lineRect, clipRect)); + [[NSColor whiteColor] set]; + lineRect.origin.x += 1; + NSRectFill(NSIntersectionRect(lineRect, clipRect)); + } +#endif +} + +static inline int common_prefix_length(const char *a, const char *b) { + int i; + for (i=0; ; i++) { + char ac = a[i]; + char bc = b[i]; + if (ac != bc || ac == 0 || bc == 0) break; + } + return i; +} + +/* Drawing with NSLayoutManager is necessary because the 10_2 typesetting behavior used by the old string drawing does the wrong thing for fonts like Bitstream Vera Sans Mono. Also it's an optimization for drawing the shadow. */ +- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect { +#if TIME_LINE_NUMBERS + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); +#endif + NSUInteger previousTextStorageCharacterCount = [textStorage length]; + + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + NSRect textRect = self.bounds; + textRect.size.height = _lineHeight; + textRect.origin.y -= verticalOffset * _lineHeight; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + unsigned long long lineValue = lineIndex * _bytesPerLine; + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + char previousBuff[256]; + int previousStringLength = (int)previousTextStorageCharacterCount; + BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding]; + HFASSERT(conversionResult); + while (linesRemaining--) { + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + if (NSIntersectsRect(textRect, clipRect)) { + NSString *replacementCharacters = nil; + NSRange replacementRange; + char buff[256]; + int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue); + HFASSERT(newStringLength > 0); + int prefixLength = common_prefix_length(previousBuff, buff); + HFASSERT(prefixLength <= newStringLength); + HFASSERT(prefixLength <= previousStringLength); + replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength); + replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO]; + NSUInteger glyphCount; + [textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters]; + if (previousTextStorageCharacterCount == 0) { + NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil]; + [textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)]; + [atts release]; + } + glyphCount = [layoutManager numberOfGlyphs]; + if (glyphCount > 0) { + CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]); + [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)]; + } + previousTextStorageCharacterCount = newStringLength; + [replacementCharacters release]; + memcpy(previousBuff, buff, newStringLength + 1); + previousStringLength = newStringLength; + } + textRect.origin.y += _lineHeight; + lineIndex++; + lineValue = HFSum(lineValue, _bytesPerLine); + } +#if TIME_LINE_NUMBERS + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"Line number time: %f", endTime - startTime); +#endif +} + +- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect { + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + NSRect textRect = self.bounds; + textRect.size.height = _lineHeight; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset * _lineHeight + 1; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + unsigned long long lineValue = lineIndex * _bytesPerLine; + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + } + + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + while (linesRemaining--) { + if (NSIntersectsRect(textRect, clipRect)) { + char buff[256]; + int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue); + HFASSERT(newStringLength > 0); + NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO]; + [string drawInRect:textRect withAttributes:textAttributes]; + [string release]; + } + textRect.origin.y += _lineHeight; + lineIndex++; + if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow + } +} + +- (NSUInteger)characterCountForLineRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + NSUInteger characterCount; + + NSUInteger lineCount = ll2l(range.length); + const NSUInteger stride = _bytesPerLine; + HFLineCountingRepresenter *rep = self.representer; + HFLineNumberFormat format = self.lineNumberFormat; + if (format == HFLineNumberFormatDecimal) { + unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine); + characterCount = lineCount /* newlines */; + while (lineCount--) { + characterCount += HFCountDigitsBase10(lineValue); + lineValue += stride; + } + } + else if (format == HFLineNumberFormatHexadecimal) { + characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines + } + else { + characterCount = -1; + } + return characterCount; +} + +- (NSString *)newLineStringForRange:(HFRange)range { + HFASSERT(range.length <= NSUIntegerMax); + if(range.length == 0) + return [[NSString alloc] init]; // Placate the analyzer. + + NSUInteger lineCount = ll2l(range.length); + const NSUInteger stride = _bytesPerLine; + unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine); + NSUInteger characterCount = [self characterCountForLineRange:range]; + char *buffer = check_malloc(characterCount); + NSUInteger bufferIndex = 0; + + char formatString[64]; + [self getLineNumberFormatString:formatString length:sizeof formatString]; + + while (lineCount--) { + int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset); + HFASSERT(charCount > 0); + bufferIndex += charCount; + buffer[bufferIndex++] = '\n'; + lineValue += stride; + } + HFASSERT(bufferIndex == characterCount); + + NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES]; + return string; +} + +- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining { + const BOOL debug = NO; + [textStorage beginEditing]; + + if (storedLineCount == INVALID_LINE_COUNT) { + /* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */ + NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)]; + [textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string]; + [string release]; + + } + else { + HFRange leftRangeToReplace, rightRangeToReplace; + HFRange leftRangeToStore, rightRangeToStore; + + HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount); + HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining); + HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange); + + if (rangeToPreserve.length == 0) { + leftRangeToReplace = oldRange; + leftRangeToStore = newRange; + rightRangeToReplace = HFZeroRange; + rightRangeToStore = HFZeroRange; + } + else { + if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length); + HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange)); + HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange)); + const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve); + leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location); + leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location); + rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve); + rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve); + } + + if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange)); + if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore)); + if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore)); + + HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange)); + HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange)); + + if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) { + NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace]; + NSRange rangeToDelete = NSMakeRange(0, charactersToDelete); + if (leftRangeToStore.length == 0) { + [textStorage deleteCharactersInRange:rangeToDelete]; + if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete)); + } + else { + NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore]; + [textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString]; + if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString); + [leftRangeString release]; + } + } + + if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) { + NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace]; + NSUInteger stringLength = [textStorage length]; + HFASSERT(charactersToDelete <= stringLength); + NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete); + if (rightRangeToStore.length == 0) { + [textStorage deleteCharactersInRange:rangeToDelete]; + if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete)); + } + else { + NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore]; + [textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString]; + if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore)); + [rightRangeString release]; + } + } + } + + if (!textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + [textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])]; + } + + [textStorage endEditing]; + +#if ! NDEBUG + NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)]; + if (! [comparisonString isEqualToString:[textStorage string]]) { + NSLog(@"Not equal!"); + NSLog(@"Expected:\n%@", comparisonString); + NSLog(@"Actual:\n%@", [textStorage string]); + } + HFASSERT([comparisonString isEqualToString:[textStorage string]]); + [comparisonString release]; +#endif + + storedLineIndex = startingLineIndex; + storedLineCount = linesRemaining; +} + +- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect { + USE(clipRect); + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + + CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1.5; + NSRect textRect = self.bounds; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset; + textRect.size.height += verticalOffset + _lineHeight; + + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + [mutableStyle setMinimumLineHeight:_lineHeight]; + [mutableStyle setMaximumLineHeight:_lineHeight]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + NSColor *color = [[NSColor textColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + color = [NSColor colorWithRed:color.redComponent green:color.greenComponent blue:color.blueComponent alpha:0.75]; + NSDictionary *_textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:_font.fontName size:9], NSFontAttributeName, color, NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + + + NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)]; + [string drawInRect:textRect withAttributes:_textAttributes]; + [string release]; + [_textAttributes release]; +} + +- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect { + USE(clipRect); + const CGFloat cellTextContainerPadding = 2.f; + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + + CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1; + NSRect textRect = self.bounds; + textRect.size.width -= 5; + textRect.origin.y -= verticalOffset; + textRect.origin.x += cellTextContainerPadding; + textRect.size.height += verticalOffset; + + if (! textAttributes) { + NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + [mutableStyle setAlignment:NSRightTextAlignment]; + [mutableStyle setMinimumLineHeight:_lineHeight]; + [mutableStyle setMaximumLineHeight:_lineHeight]; + NSParagraphStyle *paragraphStyle = [mutableStyle copy]; + [mutableStyle release]; + textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil]; + [paragraphStyle release]; + } + + NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)]; + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes]; + [string release]; + NSCell *cell = [[NSCell alloc] initTextCell:@""]; + [cell setAttributedStringValue:attributedString]; + [cell drawWithFrame:textRect inView:self]; + [[NSColor purpleColor] set]; + NSFrameRect(textRect); + [cell release]; + [attributedString release]; +} + +- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect { + USE(clipRect); + unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location)); + NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location))); + if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) { + [self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining]; + } + + CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location)); + + NSPoint textPoint = self.bounds.origin; + textPoint.y -= verticalOffset * _lineHeight; + [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint]; +} + +- (void)drawLineNumbersWithClip:(NSRect)clipRect { +#if TIME_LINE_NUMBERS + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); +#endif + NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3); + switch (drawingMode) { + // Drawing can't be done right if fonts are wider than expected, but all + // of these have rather nasty behavior in that case. I've commented what + // that behavior is; the comment is hypothetical 'could' if it shouldn't + // actually be a problem in practice. + // TODO: Make a drawing mode that is "Fonts could get clipped if too wide" + // because that seems like better behavior than any of these. + case 0: + // Most fonts are too wide and every character gets piled on right (unreadable). + [self drawLineNumbersWithClipLayoutManagerPerLine:clipRect]; + break; + case 1: + // Last characters could get omitted (*not* clipped) if too wide. + // Also, most fonts have bottoms clipped (very unsigntly). + [self drawLineNumbersWithClipStringDrawing:clipRect]; + break; + case 2: + // Most fonts are too wide and wrap (breaks numbering). + [self drawLineNumbersWithClipFullLayoutManager:clipRect]; + break; + case 3: + // Fonts could wrap if too wide (breaks numbering). + // *Note that that this is the only mode that generally works.* + [self drawLineNumbersWithClipSingleStringDrawing:clipRect]; + break; + case 4: + // Most fonts are too wide and wrap (breaks numbering). + [self drawLineNumbersWithClipSingleStringCellDrawing:clipRect]; + break; + } +#if TIME_LINE_NUMBERS + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"Line number time: %f", endTime - startTime); +#endif +} + +- (void)drawRect:(NSRect)clipRect { + [self drawDividerWithClip:clipRect]; + [self drawLineNumbersWithClip:clipRect]; +} + +- (void)setLineRangeToDraw:(HFFPRange)range { + if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) { + _lineRangeToDraw = range; + [self setNeedsDisplay:YES]; + } +} + +- (void)setBytesPerLine:(NSUInteger)val { + if (_bytesPerLine != val) { + _bytesPerLine = val; + storedLineCount = INVALID_LINE_COUNT; + [self setNeedsDisplay:YES]; + } +} + +- (void)setLineNumberFormat:(HFLineNumberFormat)format { + if (format != _lineNumberFormat) { + _lineNumberFormat = format; + storedLineCount = INVALID_LINE_COUNT; + [self setNeedsDisplay:YES]; + } +} + +- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont { + NSString *name = [testFont fontName]; + // No, Menlo does not work here. + return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"]; +} + +- (void)setFont:(NSFont *)val { + if (val != _font) { + [_font release]; + _font = [val copy]; + [textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render + [textAttributes release]; + textAttributes = nil; + storedLineCount = INVALID_LINE_COUNT; + useStringDrawingPath = [self canUseStringDrawingPathForFont:_font]; + [self setNeedsDisplay:YES]; + } +} + +- (void)setLineHeight:(CGFloat)height { + if (_lineHeight != height) { + _lineHeight = height; + [self setNeedsDisplay:YES]; + } +} + +- (void)setFrameSize:(NSSize)size { + [super setFrameSize:size]; + [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)]; +} + +- (void)mouseDown:(NSEvent *)event { + USE(event); + // [_representer cycleLineNumberFormat]; +} + +- (void)scrollWheel:(NSEvent *)scrollEvent { + [_representer.controller scrollWithScrollEvent:scrollEvent]; +} + ++ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format { + switch (format) { + case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber); +#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX + case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber); +#else + case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber); +#endif + default: return 0; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.h b/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.h new file mode 100644 index 000000000..d09c57944 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.h @@ -0,0 +1,51 @@ +// +// HFPasteboardOwner.h +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import + +@class HFByteArray; + +extern NSString *const HFPrivateByteArrayPboardType; + +@interface HFPasteboardOwner : NSObject { + @private + HFByteArray *byteArray; + NSPasteboard *pasteboard; //not retained + unsigned long long dataAmountToCopy; + NSUInteger bytesPerLine; + BOOL retainedSelfOnBehalfOfPboard; + BOOL backgroundCopyOperationFinished; + BOOL didStartModalSessionForBackgroundCopyOperation; +} + +/* Creates an HFPasteboardOwner to own the given pasteboard with the given types. Note that the NSPasteboard retains its owner. */ ++ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types; +- (HFByteArray *)byteArray; + +/* Performs a copy to pasteboard with progress reporting. This must be overridden if you support types other than the private pboard type. */ +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker; + +/* NSPasteboard delegate methods, declared here to indicate that subclasses should call super */ +- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type; +- (void)pasteboardChangedOwner:(NSPasteboard *)pboard; + +/* Useful property that several pasteboard types want to know */ +@property (nonatomic) NSUInteger bytesPerLine; + +/* For efficiency, Hex Fiend writes pointers to HFByteArrays into pasteboards. In the case that the user quits and relaunches Hex Fiend, we don't want to read a pointer from the old process, so each process we generate a UUID. This is constant for the lifetime of the process. */ ++ (NSString *)uuid; + +/* Unpacks a byte array from a pasteboard, preferring HFPrivateByteArrayPboardType */ ++ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard; + +/* Used to handle the case where copying data will require a lot of memory and give the user a chance to confirm. */ +- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength; + +/* Must be overridden to return the length of a string containing this number of bytes. */ +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.m b/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.m new file mode 100644 index 000000000..0ca341d63 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFPasteboardOwner.m @@ -0,0 +1,287 @@ +// +// HFPasteboardOwner.m +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import + +NSString *const HFPrivateByteArrayPboardType = @"HFPrivateByteArrayPboardType"; + +@implementation HFPasteboardOwner + ++ (void)initialize { + if (self == [HFPasteboardOwner class]) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareCommonPasteboardsForChangeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil]; + } +} + +- (instancetype)initWithPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types { + REQUIRE_NOT_NULL(pboard); + REQUIRE_NOT_NULL(array); + REQUIRE_NOT_NULL(types); + self = [super init]; + byteArray = [array retain]; + pasteboard = pboard; + [pasteboard declareTypes:types owner:self]; + + // get notified when we're about to write a file, so that if they're overwriting a file backing part of our byte array, we can properly clear or preserve our pasteboard + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil]; + + return self; +} ++ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types { + return [[[self alloc] initWithPasteboard:pboard forByteArray:array withTypes:types] autorelease]; +} + +- (void)tearDownPasteboardReferenceIfExists { + if (pasteboard) { + pasteboard = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:HFPrepareForChangeInFileNotification object:nil]; + } + if (retainedSelfOnBehalfOfPboard) { + CFRelease(self); + retainedSelfOnBehalfOfPboard = NO; + } +} + + ++ (HFByteArray *)_unpackByteArrayFromDictionary:(NSDictionary *)byteArrayDictionary { + HFByteArray *result = nil; + if (byteArrayDictionary) { + NSString *uuid = byteArrayDictionary[@"HFUUID"]; + if ([uuid isEqual:[self uuid]]) { + result = (HFByteArray *)[byteArrayDictionary[@"HFByteArray"] unsignedLongValue]; + } + } + return result; +} + ++ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard { + REQUIRE_NOT_NULL(pasteboard); + HFByteArray *result = [self _unpackByteArrayFromDictionary:[pasteboard propertyListForType:HFPrivateByteArrayPboardType]]; + return result; +} + +/* Try to fix up commonly named pasteboards when a file is about to be saved */ ++ (void)prepareCommonPasteboardsForChangeInFileNotification:(NSNotification *)notification { + const BOOL *cancellationPointer = [[notification userInfo][HFChangeInFileShouldCancelKey] pointerValue]; + if (*cancellationPointer) return; //don't do anything if someone requested cancellation + + NSDictionary *userInfo = [notification userInfo]; + NSArray *changedRanges = userInfo[HFChangeInFileModifiedRangesKey]; + HFFileReference *fileReference = [notification object]; + NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey]; + + NSString * const names[] = {NSGeneralPboard, NSFindPboard, NSDragPboard}; + NSUInteger i; + for (i=0; i < sizeof names / sizeof *names; i++) { + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:names[i]]; + HFByteArray *byteArray = [self unpackByteArrayFromPasteboard:pboard]; + if (byteArray && ! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) { + /* This pasteboard no longer works */ + [pboard declareTypes:@[] owner:nil]; + } + } +} + +- (void)changeInFileNotification:(NSNotification *)notification { + HFASSERT(pasteboard != nil); + HFASSERT(byteArray != nil); + NSDictionary *userInfo = [notification userInfo]; + const BOOL *cancellationPointer = [userInfo[HFChangeInFileShouldCancelKey] pointerValue]; + if (*cancellationPointer) return; //don't do anything if someone requested cancellation + NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey]; + + NSArray *changedRanges = [notification userInfo][HFChangeInFileModifiedRangesKey]; + HFFileReference *fileReference = [notification object]; + if (! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) { + /* We can't do it */ + [self tearDownPasteboardReferenceIfExists]; + } +} + +- (void)dealloc { + [self tearDownPasteboardReferenceIfExists]; + [byteArray release]; + [super dealloc]; +} + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + USE(length); + USE(pboard); + USE(type); + USE(tracker); + UNIMPLEMENTED_VOID(); +} + +- (void)backgroundMoveDataToPasteboard:(NSString *)type { + @autoreleasepool { + [self writeDataInBackgroundToPasteboard:pasteboard ofLength:dataAmountToCopy forType:type trackingProgress:nil]; + [self performSelectorOnMainThread:@selector(backgroundMoveDataFinished:) withObject:nil waitUntilDone:NO]; + } +} + +- (void)backgroundMoveDataFinished:unused { + USE(unused); + HFASSERT(backgroundCopyOperationFinished == NO); + backgroundCopyOperationFinished = YES; + if (! didStartModalSessionForBackgroundCopyOperation) { + /* We haven't started the modal session, so make sure it never happens */ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(beginModalSessionForBackgroundCopyOperation:) object:nil]; + CFRunLoopWakeUp(CFRunLoopGetCurrent()); + } + else { + /* We have started the modal session, so end it. */ + [NSApp stopModalWithCode:0]; + //stopModal: won't trigger unless we post a do-nothing event + NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:0 data1:0 data2:0]; + [NSApp postEvent:event atStart:NO]; + } +} + +- (void)beginModalSessionForBackgroundCopyOperation:(id)unused { + USE(unused); + HFASSERT(backgroundCopyOperationFinished == NO); + HFASSERT(didStartModalSessionForBackgroundCopyOperation == NO); + didStartModalSessionForBackgroundCopyOperation = YES; +} + +- (BOOL)moveDataWithProgressReportingToPasteboard:(NSPasteboard *)pboard forType:(NSString *)type { + // The -[NSRunLoop runMode:beforeDate:] call in the middle of this function can cause it to be + // called reentrantly, which was previously causing leaks and use-after-free crashes. For + // some reason this happens basically always when copying lots of data into VMware Fusion. + // I'm not even sure what the ideal behavior would be here, but am fairly certain that this + // is the best that can be done without rewriting a portion of the background copying code. + // TODO: Figure out what the ideal behavior should be here. + + HFASSERT(pboard == pasteboard); + [self retain]; //resolving the pasteboard may release us, which deallocates us, which deallocates our tracker...make sure we survive through this function + /* Give the user a chance to request a smaller amount if it's really big */ + unsigned long long availableAmount = [byteArray length]; + unsigned long long amountToCopy = [self amountToCopyForDataLength:availableAmount stringLength:[self stringLengthForDataLength:availableAmount]]; + if (amountToCopy > 0) { + + backgroundCopyOperationFinished = NO; + didStartModalSessionForBackgroundCopyOperation = NO; + dataAmountToCopy = amountToCopy; + [NSThread detachNewThreadSelector:@selector(backgroundMoveDataToPasteboard:) toTarget:self withObject:type]; + [self performSelector:@selector(beginModalSessionForBackgroundCopyOperation:) withObject:nil afterDelay:1.0 inModes:@[NSModalPanelRunLoopMode]]; + while (! backgroundCopyOperationFinished) { + [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate distantFuture]]; + } + } + [self release]; + return YES; +} + +- (void)pasteboardChangedOwner:(NSPasteboard *)pboard { + HFASSERT(pasteboard == pboard); + [self tearDownPasteboardReferenceIfExists]; +} + +- (HFByteArray *)byteArray { + return byteArray; +} + +- (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type { + if (! pasteboard) { + /* Don't do anything, because we've torn down our pasteboard */ + return; + } + if ([type isEqualToString:HFPrivateByteArrayPboardType]) { + if (! retainedSelfOnBehalfOfPboard) { + retainedSelfOnBehalfOfPboard = YES; + CFRetain(self); + } + NSDictionary *dict = @{@"HFByteArray": @((unsigned long)byteArray), + @"HFUUID": [[self class] uuid]}; + [pboard setPropertyList:dict forType:type]; + } + else { + if (! [self moveDataWithProgressReportingToPasteboard:pboard forType:type]) { + [pboard setData:[NSData data] forType:type]; + } + } +} + +- (void)setBytesPerLine:(NSUInteger)val { bytesPerLine = val; } +- (NSUInteger)bytesPerLine { return bytesPerLine; } + ++ (NSString *)uuid { + static NSString *uuid; + if (! uuid) { + CFUUIDRef uuidRef = CFUUIDCreate(NULL); + uuid = (NSString *)CFUUIDCreateString(NULL, uuidRef); + CFRelease(uuidRef); + } + return uuid; +} + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { USE(dataLength); UNIMPLEMENTED(); } + +- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength { + unsigned long long dataLengthResult, stringLengthResult; + NSInteger alertReturn = NSIntegerMax; + const unsigned long long copyOption1 = MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT; + const unsigned long long copyOption2 = MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT; + NSString *option1String = HFDescribeByteCount(copyOption1); + NSString *option2String = HFDescribeByteCount(copyOption2); + NSString* dataSizeDescription = HFDescribeByteCount(stringLength); + if (stringLength >= MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT) { + NSString *option1 = [@"Copy " stringByAppendingString:option1String]; + NSString *option2 = [@"Copy " stringByAppendingString:option2String]; + alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. This is larger than the system clipboard supports. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription); + switch (alertReturn) { + case NSAlertDefaultReturn: + default: + stringLengthResult = 0; + break; + case NSAlertAlternateReturn: + stringLengthResult = copyOption1; + break; + case NSAlertOtherReturn: + stringLengthResult = copyOption2; + break; + } + + } + else if (stringLength >= MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT) { + NSString *option1 = [@"Copy " stringByAppendingString:HFDescribeByteCount(stringLength)]; + NSString *option2 = [@"Copy " stringByAppendingString:HFDescribeByteCount(copyOption2)]; + alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. Performing this copy may take a long time. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription); + switch (alertReturn) { + case NSAlertDefaultReturn: + default: + stringLengthResult = 0; + break; + case NSAlertAlternateReturn: + stringLengthResult = stringLength; + break; + case NSAlertOtherReturn: + stringLengthResult = copyOption2; + break; + } + } + else { + /* Small enough to copy it all */ + stringLengthResult = stringLength; + } + + /* Convert from string length to data length */ + if (stringLengthResult == stringLength) { + dataLengthResult = numBytes; + } + else { + unsigned long long divisor = stringLength / numBytes; + dataLengthResult = stringLengthResult / divisor; + } + + return dataLengthResult; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFPrivilegedHelperConnection.h b/thirdparty/SameBoy-old/HexFiend/HFPrivilegedHelperConnection.h new file mode 100644 index 000000000..bbee7a9a6 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFPrivilegedHelperConnection.h @@ -0,0 +1,3 @@ +#ifndef HF_NO_PRIVILEGED_FILE_OPERATIONS +#define HF_NO_PRIVILEGED_FILE_OPERATIONS +#endif \ No newline at end of file diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenter.h new file mode 100644 index 000000000..1a15f3e73 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenter.h @@ -0,0 +1,121 @@ +// +// HFRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/*! @class HFRepresenter + @brief The principal view class of Hex Fiend's MVC architecture. + + HFRepresenter is a class that visually represents some property of the HFController, such as the data (in various formats), the scroll position, the line number, etc. An HFRepresenter is added to an HFController and then gets notified of changes to various properties, through the controllerDidChange: methods. + + HFRepresenters also have a view, accessible through the -view method. The HFRepresenter is expected to update its view to reflect the relevant properties of its HFController. If the user can interact with the view, then the HFRepresenter should pass any changes down to the HFController, which will subsequently notify all HFRepresenters of the change. + + HFRepresenter is an abstract class, with a different subclass for each possible view type. Because HFController interacts with HFRepresenters, rather than views directly, an HFRepresenter can use standard Cocoa views and controls. + + To add a new view type: + + -# Create a subclass of HFRepresenter + -# Override \c -createView to return a view (note that this method should transfer ownership) + -# Override \c -controllerDidChange:, checking the bitmask to see what properties have changed and updating your view as appropriate + -# If you plan on using this view together with other views, override \c +defaultLayoutPosition to control how your view gets positioned in an HFLayoutRepresenter + -# If your view's width depends on the properties of the controller, override some of the measurement methods, such as \c +maximumBytesPerLineForViewWidth:, so that your view gets sized correctly + +*/ +@interface HFRepresenter : NSObject { + @private + id view; + HFController *controller; + NSPoint layoutPosition; +} + +/*! @name View management + Methods related to accessing and initializing the representer's view. +*/ +//@{ +/*! Returns the view for the receiver, creating it if necessary. The view for the HFRepresenter is initially nil. When the \c -view method is called, if the view is nil, \c -createView is called and then the result is stored. This method should not be overridden; however you may want to call it to access the view. +*/ +- (id)view; + +/*! Returns YES if the view has been created, NO if it has not. To create the view, call the view method. + */ +- (BOOL)isViewLoaded; + +/*! Override point for creating the view displaying this representation. This is called on your behalf the first time the \c -view method is called, so you would not want to call this explicitly; however this method must be overridden. This follows the "create" rule, and so it should return a retained view. +*/ +- (NSView *)createView NS_RETURNS_RETAINED; + +/*! Override point for initialization of view, after the HFRepresenter has the view set as its -view property. The default implementation does nothing. +*/ +- (void)initializeView; + +//@} + +/*! @name Accessing the HFController +*/ +//@{ +/*! Returns the HFController for the receiver. This is set by the controller from the call to \c addRepresenter:. A representer can only be in one controller at a time. */ +- (HFController *)controller; +//@} + +/*! @name Property change notifications +*/ +//@{ +/*! Indicates that the properties indicated by the given bits did change, and the view should be updated as to reflect the appropriate properties. This is the main mechanism by which representers are notified of changes to the controller. +*/ +- (void)controllerDidChange:(HFControllerPropertyBits)bits; +//@} + +/*! @name HFController convenience methods + Convenience covers for certain HFController methods +*/ +//@{ +/*! Equivalent to [[self controller] bytesPerLine] */ +- (NSUInteger)bytesPerLine; + +/*! Equivalent to [[self controller] bytesPerColumn] */ +- (NSUInteger)bytesPerColumn; + +/*! Equivalent to [[self controller] representer:self changedProperties:properties] . You may call this when some internal aspect of the receiver's view (such as its frame) has changed in a way that may globally change some property of the controller, and the controller should recalculate those properties. For example, the text representers call this with HFControllerDisplayedLineRange when the view grows vertically, because more data may be displayed. +*/ +- (void)representerChangedProperties:(HFControllerPropertyBits)properties; +//@} + +/*! @name Measurement + Methods related to measuring the HFRepresenter, so that it can be laid out properly by an HFLayoutController. All of these methods are candidates for overriding. +*/ +//@{ +/*! Returns the maximum number of bytes per line for the given view size. The default value is NSUIntegerMax, which means that the representer can display any number of lines for the given view size. */ +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth; + +/*! Returns the minimum view frame size for the given bytes per line. Default is to return 0, which means that the representer can display the given bytes per line in any view size. Fixed width views should return their fixed width. */ +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +/*! Returns the maximum number of lines that could be displayed at once for a given view height. Default is to return DBL_MAX. */ +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight; +//@} + +/*! Returns the required byte granularity. HFLayoutRepresenter will constrain the bytes per line to a multiple of the granularity, e.g. so that UTF-16 characters are not split across lines. If different representers have different granularities, then it will constrain it to a multiple of all granularities, which may be very large. The default implementation returns 1. */ +- (NSUInteger)byteGranularity; + +/*! @name Auto-layout methods + Methods for simple auto-layout by HFLayoutRepresenter. See the HFLayoutRepresenter class for discussion of how it lays out representer views. +*/ +//@{ + + +/// The layout position for the receiver. +@property (nonatomic) NSPoint layoutPosition; + +/*! Returns the default layout position for representers of this class. Within the -init method, the view's layout position is set to the default for this class. You may override this to control the default layout position. See HFLayoutRepresenter for a discussion of the significance of the layout postition. +*/ ++ (NSPoint)defaultLayoutPosition; + +//@} + + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFRepresenter.m new file mode 100644 index 000000000..510e3a0ce --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenter.m @@ -0,0 +1,120 @@ +// +// HFRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import "HFRepresenter.h" + +@implementation HFRepresenter + +- (id)view { + if (! view) { + view = [self createView]; + [self initializeView]; + } + return view; +} + +- (BOOL)isViewLoaded { + return !! view; +} + +- (void)initializeView { + +} + +- (instancetype)init { + self = [super init]; + [self setLayoutPosition:[[self class] defaultLayoutPosition]]; + return self; +} + +- (void)dealloc { + [view release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [coder encodeObject:controller forKey:@"HFController"]; + [coder encodePoint:layoutPosition forKey:@"HFLayoutPosition"]; + [coder encodeObject:view forKey:@"HFRepresenterView"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super init]; + layoutPosition = [coder decodePointForKey:@"HFLayoutPosition"]; + controller = [coder decodeObjectForKey:@"HFController"]; // not retained + view = [[coder decodeObjectForKey:@"HFRepresenterView"] retain]; + return self; +} + +- (NSView *)createView { + UNIMPLEMENTED(); +} + +- (HFController *)controller { + return controller; +} + +- (void)_setController:(HFController *)val { + controller = val; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + USE(bits); +} + +- (NSUInteger)bytesPerLine { + HFASSERT([self controller] != nil); + return [[self controller] bytesPerLine]; +} + +- (NSUInteger)bytesPerColumn { + HFASSERT([self controller] != nil); + return [[self controller] bytesPerColumn]; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + USE(viewWidth); + return NSUIntegerMax; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return 0; +} + +- (NSUInteger)byteGranularity { + return 1; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + USE(viewHeight); + return DBL_MAX; +} + +- (void)selectAll:sender { + [[self controller] selectAll:sender]; +} + +- (void)representerChangedProperties:(HFControllerPropertyBits)properties { + [[self controller] representer:self changedProperties:properties]; +} + +- (void)setLayoutPosition:(NSPoint)position { + layoutPosition = position; +} + +- (NSPoint)layoutPosition { + return layoutPosition; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, 0); +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.h new file mode 100644 index 000000000..8098846ed --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.h @@ -0,0 +1,21 @@ +// +// HFRepresenterHexTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + + +@interface HFRepresenterHexTextView : HFRepresenterTextView { + CGGlyph glyphTable[17]; + CGFloat glyphAdvancement; + CGFloat spaceAdvancement; + + BOOL hidesNullBytes; +} + +@property(nonatomic) BOOL hidesNullBytes; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.m b/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.m new file mode 100644 index 000000000..6df41d80f --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterHexTextView.m @@ -0,0 +1,95 @@ +// +// HFRepresenterHexTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@implementation HFRepresenterHexTextView + +- (void)generateGlyphTable { + const UniChar hexchars[17] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',' '/* Plus a space char at the end for null bytes. */}; + _Static_assert(sizeof(CGGlyph[17]) == sizeof(glyphTable), "glyphTable is the wrong type"); + NSFont *font = [[self font] screenFont]; + + bool t = CTFontGetGlyphsForCharacters((CTFontRef)font, hexchars, glyphTable, 17); + HFASSERT(t); // We don't take kindly to strange fonts around here. + + CGFloat maxAdv = 0.0; + for(int i = 0; i < 17; i++) maxAdv = HFMax(maxAdv, [font advancementForGlyph:glyphTable[i]].width); + glyphAdvancement = maxAdv; + spaceAdvancement = maxAdv; +} + +- (void)setFont:(NSFont *)font { + [super setFont:font]; + [self generateGlyphTable]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self generateGlyphTable]; + return self; +} + +//no need for encodeWithCoder + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + HFASSERT(bytes != NULL); + HFASSERT(glyphs != NULL); + HFASSERT(numBytes <= NSUIntegerMax); + HFASSERT(resultGlyphCount != NULL); + const NSUInteger bytesPerColumn = [self bytesPerColumn]; + NSUInteger glyphIndex = 0, byteIndex = 0; + NSUInteger remainingBytesInThisColumn = (bytesPerColumn ? bytesPerColumn - offsetIntoLine % bytesPerColumn : NSUIntegerMax); + CGFloat advanceBetweenColumns = [self advanceBetweenColumns]; + while (byteIndex < numBytes) { + unsigned char byte = bytes[byteIndex++]; + + CGFloat glyphAdvancementPlusAnySpace = glyphAdvancement; + if (--remainingBytesInThisColumn == 0) { + remainingBytesInThisColumn = bytesPerColumn; + glyphAdvancementPlusAnySpace += advanceBetweenColumns; + } + + BOOL useBlank = (hidesNullBytes && byte == 0); + advances[glyphIndex] = CGSizeMake(glyphAdvancement, 0); + glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte >> 4)]}; + advances[glyphIndex] = CGSizeMake(glyphAdvancementPlusAnySpace, 0); + glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte & 0xF)]}; + } + + *resultGlyphCount = glyphIndex; +} + +- (CGFloat)advancePerCharacter { + return 2 * glyphAdvancement; +} + +- (CGFloat)advanceBetweenColumns { + return glyphAdvancement; +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + return 2 * byteCount; +} + +- (BOOL)hidesNullBytes { + return hidesNullBytes; +} + +- (void)setHidesNullBytes:(BOOL)flag +{ + flag = !! flag; + if (hidesNullBytes != flag) { + hidesNullBytes = flag; + [self setNeedsDisplay:YES]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.h new file mode 100644 index 000000000..2a87adaed --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.h @@ -0,0 +1,37 @@ +// +// HFRepresenterStringEncodingTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +@interface HFRepresenterStringEncodingTextView : HFRepresenterTextView { + /* Tier 0 data (always up to date) */ + NSStringEncoding encoding; + uint8_t bytesPerChar; + + /* Tier 1 data (computed synchronously on-demand) */ + BOOL tier1DataIsStale; + struct HFGlyph_t replacementGlyph; + CGFloat glyphAdvancement; + + /* Tier 2 data (computed asynchronously on-demand) */ + struct HFGlyphTrie_t glyphTable; + + NSArray *fontCache; + + /* Background thread */ + OSSpinLock glyphLoadLock; + BOOL requestedCancel; + NSMutableArray *fonts; + NSMutableIndexSet *requestedCharacters; + NSOperationQueue *glyphLoader; +} + +/// Set and get the NSStringEncoding that is used +@property (nonatomic) NSStringEncoding encoding; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.m b/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.m new file mode 100644 index 000000000..fa8bcb1b5 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterStringEncodingTextView.m @@ -0,0 +1,540 @@ +// +// HFRepresenterStringEncodingTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#include + +@implementation HFRepresenterStringEncodingTextView + +static NSString *copy1CharStringForByteValue(unsigned long long byteValue, NSUInteger bytesPerChar, NSStringEncoding encoding) { + NSString *result = nil; + unsigned char bytes[sizeof byteValue]; + /* If we are little endian, then the bytesPerChar doesn't matter, because it will all come out the same. If we are big endian, then it does matter. */ +#if ! __BIG_ENDIAN__ + *(unsigned long long *)bytes = byteValue; +#else + if (bytesPerChar == sizeof(uint8_t)) { + *(uint8_t *)bytes = (uint8_t)byteValue; + } else if (bytesPerChar == sizeof(uint16_t)) { + *(uint16_t *)bytes = (uint16_t)byteValue; + } else if (bytesPerChar == sizeof(uint32_t)) { + *(uint32_t *)bytes = (uint32_t)byteValue; + } else if (bytesPerChar == sizeof(uint64_t)) { + *(uint64_t *)bytes = (uint64_t)byteValue; + } else { + [NSException raise:NSInvalidArgumentException format:@"Unsupported bytesPerChar of %u", bytesPerChar]; + } +#endif + + /* ASCII is mishandled :( */ + BOOL encodingOK = YES; + if (encoding == NSASCIIStringEncoding && bytesPerChar == 1 && bytes[0] > 0x7F) { + encodingOK = NO; + } + + + + /* Now create a string from these bytes */ + if (encodingOK) { + result = [[NSString alloc] initWithBytes:bytes length:bytesPerChar encoding:encoding]; + + if ([result length] > 1) { + /* Try precomposing it */ + NSString *temp = [[result precomposedStringWithCompatibilityMapping] copy]; + [result release]; + result = temp; + } + + /* Ensure it has exactly one character */ + if ([result length] != 1) { + [result release]; + result = nil; + } + } + + /* All done */ + return result; +} + +static BOOL getGlyphs(CGGlyph *glyphs, NSString *string, NSFont *inputFont) { + NSUInteger length = [string length]; + HFASSERT(inputFont != nil); + NEW_ARRAY(UniChar, chars, length); + [string getCharacters:chars range:NSMakeRange(0, length)]; + bool result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length); + /* A NO return means some or all characters were not mapped. This is OK. We'll use the replacement glyph. Unless we're calculating the replacement glyph! Hmm...maybe we should have a series of replacement glyphs that we try? */ + + //////////////////////// + // Workaround for a Mavericks bug. Still present as of 10.9.5 + // TODO: Hmm, still? Should look into this again, either it's not a bug or Apple needs a poke. + if(!result) for(NSUInteger i = 0; i < length; i+=15) { + CFIndex x = length-i; + if(x > 15) x = 15; + result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars+i, glyphs+i, x); + if(!result) break; + } + //////////////////////// + + FREE_ARRAY(chars); + return result; +} + +static void generateGlyphs(NSFont *baseFont, NSMutableArray *fonts, struct HFGlyph_t *outGlyphs, NSInteger bytesPerChar, NSStringEncoding encoding, const NSUInteger *charactersToLoad, NSUInteger charactersToLoadCount, CGFloat *outMaxAdvance) { + /* If the caller wants the advance, initialize it to 0 */ + if (outMaxAdvance) *outMaxAdvance = 0; + + /* Invalid glyph marker */ + const struct HFGlyph_t invalidGlyph = {.fontIndex = kHFGlyphFontIndexInvalid, .glyph = -1}; + + NSCharacterSet *coveredSet = [baseFont coveredCharacterSet]; + NSMutableString *coveredGlyphFetchingString = [[NSMutableString alloc] init]; + NSMutableIndexSet *coveredGlyphIndexes = [[NSMutableIndexSet alloc] init]; + NSMutableString *substitutionFontsGlyphFetchingString = [[NSMutableString alloc] init]; + NSMutableIndexSet *substitutionGlyphIndexes = [[NSMutableIndexSet alloc] init]; + + /* Loop over all the characters, appending them to our glyph fetching string */ + NSUInteger idx; + for (idx = 0; idx < charactersToLoadCount; idx++) { + NSString *string = copy1CharStringForByteValue(charactersToLoad[idx], bytesPerChar, encoding); + if (string == nil) { + /* This byte value is not represented in this char set (e.g. upper 128 in ASCII) */ + outGlyphs[idx] = invalidGlyph; + } else { + if ([coveredSet characterIsMember:[string characterAtIndex:0]]) { + /* It's covered by our base font */ + [coveredGlyphFetchingString appendString:string]; + [coveredGlyphIndexes addIndex:idx]; + } else { + /* Maybe there's a substitution font */ + [substitutionFontsGlyphFetchingString appendString:string]; + [substitutionGlyphIndexes addIndex:idx]; + } + } + [string release]; + } + + + /* Fetch the non-substitute glyphs */ + { + NEW_ARRAY(CGGlyph, cgglyphs, [coveredGlyphFetchingString length]); + BOOL success = getGlyphs(cgglyphs, coveredGlyphFetchingString, baseFont); + HFASSERT(success == YES); + NSUInteger numGlyphs = [coveredGlyphFetchingString length]; + + /* Fill in our glyphs array */ + NSUInteger coveredGlyphIdx = [coveredGlyphIndexes firstIndex]; + for (NSUInteger i=0; i < numGlyphs; i++) { + outGlyphs[coveredGlyphIdx] = (struct HFGlyph_t){.fontIndex = 0, .glyph = cgglyphs[i]}; + coveredGlyphIdx = [coveredGlyphIndexes indexGreaterThanIndex:coveredGlyphIdx]; + + /* Record the advancement. Note that this may be more efficient to do in bulk. */ + if (outMaxAdvance) *outMaxAdvance = HFMax(*outMaxAdvance, [baseFont advancementForGlyph:cgglyphs[i]].width); + + } + HFASSERT(coveredGlyphIdx == NSNotFound); //we must have exhausted the table + FREE_ARRAY(cgglyphs); + } + + /* Now do substitution glyphs. */ + { + NSUInteger substitutionGlyphIndex = [substitutionGlyphIndexes firstIndex], numSubstitutionChars = [substitutionFontsGlyphFetchingString length]; + for (NSUInteger i=0; i < numSubstitutionChars; i++) { + CTFontRef substitutionFont = CTFontCreateForString((CTFontRef)baseFont, (CFStringRef)substitutionFontsGlyphFetchingString, CFRangeMake(i, 1)); + if (substitutionFont) { + /* We have a font for this string */ + CGGlyph glyph; + unichar c = [substitutionFontsGlyphFetchingString characterAtIndex:i]; + NSString *substring = [[NSString alloc] initWithCharacters:&c length:1]; + BOOL success = getGlyphs(&glyph, substring, (NSFont *)substitutionFont); + [substring release]; + + if (! success) { + /* Turns out there wasn't a glyph like we thought there would be, so set an invalid glyph marker */ + outGlyphs[substitutionGlyphIndex] = invalidGlyph; + } else { + /* Find the index in fonts. If none, add to it. */ + HFASSERT(fonts != nil); + NSUInteger fontIndex = [fonts indexOfObject:(id)substitutionFont]; + if (fontIndex == NSNotFound) { + [fonts addObject:(id)substitutionFont]; + fontIndex = [fonts count] - 1; + } + + /* Now make the glyph */ + HFASSERT(fontIndex < UINT16_MAX); + outGlyphs[substitutionGlyphIndex] = (struct HFGlyph_t){.fontIndex = (uint16_t)fontIndex, .glyph = glyph}; + } + + /* We're done with this */ + CFRelease(substitutionFont); + + } + substitutionGlyphIndex = [substitutionGlyphIndexes indexGreaterThanIndex:substitutionGlyphIndex]; + } + } + + [coveredGlyphFetchingString release]; + [coveredGlyphIndexes release]; + [substitutionFontsGlyphFetchingString release]; + [substitutionGlyphIndexes release]; +} + +static int compareGlyphFontIndexes(const void *p1, const void *p2) { + const struct HFGlyph_t *g1 = p1, *g2 = p2; + if (g1->fontIndex != g2->fontIndex) { + /* Prefer to sort by font index */ + return (g1->fontIndex > g2->fontIndex) - (g2->fontIndex > g1->fontIndex); + } else { + /* If they have equal font indexes, sort by glyph value */ + return (g1->glyph > g2->glyph) - (g2->glyph > g1->glyph); + } +} + +- (void)threadedPrecacheGlyphs:(const struct HFGlyph_t *)glyphs withFonts:(NSArray *)localFonts count:(NSUInteger)count { + /* This method draws glyphs anywhere, so that they get cached by CG and drawing them a second time can be fast. */ + NSUInteger i, validGlyphCount; + + /* We can use 0 advances */ + NEW_ARRAY(CGSize, advances, count); + bzero(advances, count * sizeof *advances); + + /* Make a local copy of the glyphs, and sort them according to their font index so that we can draw them with the fewest runs. */ + NEW_ARRAY(struct HFGlyph_t, validGlyphs, count); + + validGlyphCount = 0; + for (i=0; i < count; i++) { + if (glyphs[i].glyph <= kCGGlyphMax && glyphs[i].fontIndex != kHFGlyphFontIndexInvalid) { + validGlyphs[validGlyphCount++] = glyphs[i]; + } + } + qsort(validGlyphs, validGlyphCount, sizeof *validGlyphs, compareGlyphFontIndexes); + + /* Remove duplicate glyphs */ + NSUInteger trailing = 0; + struct HFGlyph_t lastGlyph = {.glyph = kCGFontIndexInvalid, .fontIndex = kHFGlyphFontIndexInvalid}; + for (i=0; i < validGlyphCount; i++) { + if (! HFGlyphEqualsGlyph(lastGlyph, validGlyphs[i])) { + lastGlyph = validGlyphs[i]; + validGlyphs[trailing++] = lastGlyph; + } + } + validGlyphCount = trailing; + + /* Draw the glyphs in runs */ + NEW_ARRAY(CGGlyph, cgglyphs, count); + NSImage *glyphDrawingImage = [[NSImage alloc] initWithSize:NSMakeSize(100, 100)]; + [glyphDrawingImage lockFocus]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + HFGlyphFontIndex runFontIndex = -1; + NSUInteger runLength = 0; + for (i=0; i <= validGlyphCount; i++) { + if (i == validGlyphCount || validGlyphs[i].fontIndex != runFontIndex) { + /* End the current run */ + if (runLength > 0) { + NSLog(@"Drawing with %@", [localFonts[runFontIndex] screenFont]); + [[localFonts[runFontIndex] screenFont] set]; + CGContextSetTextPosition(ctx, 0, 50); + CGContextShowGlyphsWithAdvances(ctx, cgglyphs, advances, runLength); + } + NSLog(@"Drew a run of length %lu", (unsigned long)runLength); + runLength = 0; + if (i < validGlyphCount) runFontIndex = validGlyphs[i].fontIndex; + } + if (i < validGlyphCount) { + /* Append to the current run */ + cgglyphs[runLength++] = validGlyphs[i].glyph; + } + } + + /* All done */ + [glyphDrawingImage unlockFocus]; + [glyphDrawingImage release]; + FREE_ARRAY(advances); + FREE_ARRAY(validGlyphs); + FREE_ARRAY(cgglyphs); +} + +- (void)threadedLoadGlyphs:(id)unused { + /* Note that this is running on a background thread */ + USE(unused); + + /* Do some things under the lock. Someone else may wish to read fonts, and we're going to write to it, so make a local copy. Also figure out what characters to load. */ + NSMutableArray *localFonts; + NSIndexSet *charactersToLoad; + OSSpinLockLock(&glyphLoadLock); + localFonts = [fonts mutableCopy]; + charactersToLoad = requestedCharacters; + /* Set requestedCharacters to nil so that the caller knows we aren't going to check again, and will have to re-invoke us. */ + requestedCharacters = nil; + OSSpinLockUnlock(&glyphLoadLock); + + /* The base font is the first font */ + NSFont *font = localFonts[0]; + + NSUInteger charVal, glyphIdx, charCount = [charactersToLoad count]; + NEW_ARRAY(struct HFGlyph_t, glyphs, charCount); + + /* Now generate our glyphs */ + NEW_ARRAY(NSUInteger, characters, charCount); + [charactersToLoad getIndexes:characters maxCount:charCount inIndexRange:NULL]; + generateGlyphs(font, localFonts, glyphs, bytesPerChar, encoding, characters, charCount, NULL); + FREE_ARRAY(characters); + + /* The first time we draw glyphs, it's slow, so pre-cache them by drawing them now. */ + // This was disabled because it blows up the CG glyph cache + // [self threadedPrecacheGlyphs:glyphs withFonts:localFonts count:charCount]; + + /* Replace fonts. Do this before we insert into the glyph trie, because the glyph trie references fonts that we're just now putting in the fonts array. */ + id oldFonts; + OSSpinLockLock(&glyphLoadLock); + oldFonts = fonts; + fonts = localFonts; + OSSpinLockUnlock(&glyphLoadLock); + [oldFonts release]; + + /* Now insert all of the glyphs into the glyph trie */ + glyphIdx = 0; + for (charVal = [charactersToLoad firstIndex]; charVal != NSNotFound; charVal = [charactersToLoad indexGreaterThanIndex:charVal]) { + HFGlyphTrieInsert(&glyphTable, charVal, glyphs[glyphIdx++]); + } + FREE_ARRAY(glyphs); + + /* Trigger a redisplay */ + [self performSelectorOnMainThread:@selector(triggerRedisplay:) withObject:nil waitUntilDone:NO]; + + /* All done. We inherited the retain on requestedCharacters, so release it. */ + [charactersToLoad release]; +} + +- (void)triggerRedisplay:unused { + USE(unused); + [self setNeedsDisplay:YES]; +} + +- (void)beginLoadGlyphsForCharacters:(NSIndexSet *)charactersToLoad { + /* Create the operation (and maybe the operation queue itself) */ + if (! glyphLoader) { + glyphLoader = [[NSOperationQueue alloc] init]; + [glyphLoader setMaxConcurrentOperationCount:1]; + } + if (! fonts) { + NSFont *font = [self font]; + fonts = [[NSMutableArray alloc] initWithObjects:&font count:1]; + } + + BOOL needToStartOperation; + OSSpinLockLock(&glyphLoadLock); + if (requestedCharacters) { + /* There's a pending request, so just add to it */ + [requestedCharacters addIndexes:charactersToLoad]; + needToStartOperation = NO; + } else { + /* There's no pending request, so we will create one */ + requestedCharacters = [charactersToLoad mutableCopy]; + needToStartOperation = YES; + } + OSSpinLockUnlock(&glyphLoadLock); + + if (needToStartOperation) { + NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadedLoadGlyphs:) object:charactersToLoad]; + [glyphLoader addOperation:op]; + [op release]; + } +} + +- (void)dealloc { + HFGlyphTreeFree(&glyphTable); + [fonts release]; + [super dealloc]; +} + +- (void)staleTieredProperties { + tier1DataIsStale = YES; + /* We have to free the glyph table */ + requestedCancel = YES; + [glyphLoader waitUntilAllOperationsAreFinished]; + requestedCancel = NO; + HFGlyphTreeFree(&glyphTable); + HFGlyphTrieInitialize(&glyphTable, bytesPerChar); + [fonts release]; + fonts = nil; + [fontCache release]; + fontCache = nil; +} + +- (void)setFont:(NSFont *)font { + [self staleTieredProperties]; + /* fonts is preloaded with our one font */ + if (! fonts) fonts = [[NSMutableArray alloc] init]; + [fonts addObject:font]; + [super setFont:font]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + encoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"]; + bytesPerChar = HFStringEncodingCharacterLength(encoding); + [self staleTieredProperties]; + return self; +} + +- (instancetype)initWithFrame:(NSRect)frameRect { + self = [super initWithFrame:frameRect]; + encoding = NSMacOSRomanStringEncoding; + bytesPerChar = HFStringEncodingCharacterLength(encoding); + [self staleTieredProperties]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:encoding forKey:@"HFStringEncoding"]; +} + +- (NSStringEncoding)encoding { + return encoding; +} + +- (void)setEncoding:(NSStringEncoding)val { + if (encoding != val) { + /* Our glyph table is now stale. Call this first to ensure our background operation is complete. */ + [self staleTieredProperties]; + + /* Store the new encoding. */ + encoding = val; + + /* Compute bytes per character */ + bytesPerChar = HFStringEncodingCharacterLength(encoding); + HFASSERT(bytesPerChar > 0); + + /* Ensure the tree knows about the new bytes per character */ + HFGlyphTrieInitialize(&glyphTable, bytesPerChar); + + /* Redraw ourselves with our new glyphs */ + [self setNeedsDisplay:YES]; + } +} + +- (void)loadTier1Data { + NSFont *font = [self font]; + + /* Use the max advance as the glyph advance */ + glyphAdvancement = HFCeil([font maximumAdvancement].width); + + /* Generate replacementGlyph */ + CGGlyph glyph[1]; + BOOL foundReplacement = NO; + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @".", font); + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"*", font); + if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"!", font); + if (! foundReplacement) { + /* Really we should just fall back to another font in this case */ + [NSException raise:NSInternalInconsistencyException format:@"Unable to find replacement glyph for font %@", font]; + } + replacementGlyph.fontIndex = 0; + replacementGlyph.glyph = glyph[0]; + + /* We're no longer stale */ + tier1DataIsStale = NO; +} + +/* Override of base class method for font substitution */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx { + HFASSERT(idx != kHFGlyphFontIndexInvalid); + if (idx >= [fontCache count]) { + /* Our font cache is out of date. Take the lock and update the cache. */ + NSArray *newFonts = nil; + OSSpinLockLock(&glyphLoadLock); + HFASSERT(idx < [fonts count]); + newFonts = [fonts copy]; + OSSpinLockUnlock(&glyphLoadLock); + + /* Store the new cache */ + [fontCache release]; + fontCache = newFonts; + + /* Now our cache should be up to date */ + HFASSERT(idx < [fontCache count]); + } + return fontCache[idx]; +} + +/* Override of base class method in case we are 16 bit */ +- (NSUInteger)bytesPerCharacter { + return bytesPerChar; +} + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + HFASSERT(bytes != NULL); + HFASSERT(glyphs != NULL); + HFASSERT(resultGlyphCount != NULL); + HFASSERT(advances != NULL); + USE(offsetIntoLine); + + /* Ensure we have advance, etc. before trying to use it */ + if (tier1DataIsStale) [self loadTier1Data]; + + CGSize advance = CGSizeMake(glyphAdvancement, 0); + NSMutableIndexSet *charactersToLoad = nil; //note: in UTF-32 this may have to move to an NSSet + + const uint8_t localBytesPerChar = bytesPerChar; + NSUInteger charIndex, numChars = numBytes / localBytesPerChar, byteIndex = 0; + for (charIndex = 0; charIndex < numChars; charIndex++) { + NSUInteger character = -1; + if (localBytesPerChar == 1) { + character = *(const uint8_t *)(bytes + byteIndex); + } else if (localBytesPerChar == 2) { + character = *(const uint16_t *)(bytes + byteIndex); + } else if (localBytesPerChar == 4) { + character = *(const uint32_t *)(bytes + byteIndex); + } + + struct HFGlyph_t glyph = HFGlyphTrieGet(&glyphTable, character); + if (glyph.glyph == 0 && glyph.fontIndex == 0) { + /* Unloaded glyph, so load it */ + if (! charactersToLoad) charactersToLoad = [[NSMutableIndexSet alloc] init]; + [charactersToLoad addIndex:character]; + glyph = replacementGlyph; + } else if (glyph.glyph == (uint16_t)-1 && glyph.fontIndex == kHFGlyphFontIndexInvalid) { + /* Missing glyph, so ignore it */ + glyph = replacementGlyph; + } else { + /* Valid glyph */ + } + + HFASSERT(glyph.fontIndex != kHFGlyphFontIndexInvalid); + + advances[charIndex] = advance; + glyphs[charIndex] = glyph; + byteIndex += localBytesPerChar; + } + *resultGlyphCount = numChars; + + if (charactersToLoad) { + [self beginLoadGlyphsForCharacters:charactersToLoad]; + [charactersToLoad release]; + } +} + +- (CGFloat)advancePerCharacter { + /* The glyph advancement is determined by our glyph table */ + if (tier1DataIsStale) [self loadTier1Data]; + return glyphAdvancement; +} + +- (CGFloat)advanceBetweenColumns { + return 0; //don't have any space between columns +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + return byteCount / [self bytesPerCharacter]; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.h new file mode 100644 index 000000000..7e9edbb68 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.h @@ -0,0 +1,146 @@ +// +// HFRepresenterTextView.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/* Bytes per column philosophy + + _hftvflags.bytesPerColumn is the number of bytes that should be displayed consecutively, as one column. A space separates one column from the next. HexFiend 1.0 displayed 1 byte per column, and setting bytesPerColumn to 1 in this version reproduces that behavior. The vertical guidelines displayed by HexFiend 1.0 are only drawn when bytesPerColumn is set to 1. + + We use some number of bits to hold the number of bytes per column, so the highest value we can store is ((2 ^ numBits) - 1). We can't tell the user that the max is not a power of 2, so we pin the value to the highest representable power of 2, or (2 ^ (numBits - 1)). We allow integral values from 0 to the pinned maximum, inclusive; powers of 2 are not required. The setter method uses HFTV_BYTES_PER_COLUMN_MAX_VALUE to stay within the representable range. + + Since a value of zero is nonsensical, we can use it to specify no spaces at all. +*/ + +#define HFTV_BYTES_PER_COLUMN_MAX_VALUE (1 << (HFTV_BYTES_PER_COLUMN_BITFIELD_SIZE - 1)) + +@class HFTextRepresenter; + + +/* The base class for HFTextRepresenter views - such as the hex or ASCII text view */ +@interface HFRepresenterTextView : NSView { +@private; + HFTextRepresenter *representer; + NSArray *cachedSelectedRanges; + CGFloat verticalOffset; + CGFloat horizontalContainerInset; + CGFloat defaultLineHeight; + NSTimer *caretTimer; + NSWindow *pulseWindow; + NSRect pulseWindowBaseFrameInScreenCoordinates; + NSRect lastDrawnCaretRect; + NSRect caretRectToDraw; + NSUInteger bytesBetweenVerticalGuides; + NSUInteger startingLineBackgroundColorIndex; + NSArray *rowBackgroundColors; + NSMutableDictionary *callouts; + + void (^byteColoring)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a); + + struct { + unsigned antialias:1; + unsigned drawCallouts:1; + unsigned editable:1; + unsigned caretVisible:1; + unsigned registeredForAppNotifications:1; + unsigned withinMouseDown:1; + unsigned receivedMouseUp:1; + } _hftvflags; +} + +- (instancetype)initWithRepresenter:(HFTextRepresenter *)rep; +- (void)clearRepresenter; + +- (HFTextRepresenter *)representer; + +@property (nonatomic, copy) NSFont *font; + +/* Set and get data. setData: will invalidate the correct regions (perhaps none) */ +@property (nonatomic, copy) NSData *data; +@property (nonatomic) CGFloat verticalOffset; +@property (nonatomic) NSUInteger startingLineBackgroundColorIndex; +@property (nonatomic, getter=isEditable) BOOL editable; +@property (nonatomic, copy) NSArray *styles; +@property (nonatomic) BOOL shouldAntialias; + +- (BOOL)behavesAsTextField; +- (BOOL)showsFocusRing; +- (BOOL)isWithinMouseDown; + +- (NSRect)caretRect; + +@property (nonatomic) BOOL shouldDrawCallouts; + +- (void)setByteColoring:(void (^)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a))coloring; + +- (NSPoint)originForCharacterAtByteIndex:(NSInteger)index; +- (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point; + +/* The amount of padding space to inset from the left and right side. */ +@property (nonatomic) CGFloat horizontalContainerInset; + +/* The number of bytes between vertical guides. 0 means no drawing of guides. */ +@property (nonatomic) NSUInteger bytesBetweenVerticalGuides; + +/* To be invoked from drawRect:. */ +- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect; +- (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect; + +/* For font substitution. An index of 0 means the default (base) font. */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx; + +/* Uniformly "rounds" the byte range so that it contains an integer number of characters. The algorithm is to "floor:" any character intersecting the min of the range are included, and any character extending beyond the end of the range is excluded. If both the min and the max are within a single character, then an empty range is returned. */ +- (NSRange)roundPartialByteRange:(NSRange)byteRange; + +- (void)drawTextWithClip:(NSRect)clipRect restrictingToTextInRanges:(NSArray *)restrictingToRanges; + +/* Must be overridden */ +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount; + +- (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount; + +/* Must be overridden - returns the max number of glyphs for a given number of bytes */ +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount; + +- (void)updateSelectedRanges; +- (void)terminateSelectionPulse; // Start fading the pulse. + +/* Given a rect edge, return an NSRect representing the maximum edge in that direction. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect. + */ +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)range; + +/* The background color for the line at the given index. You may override this to return different colors. You may return nil to draw no color in this line (and then the empty space color will appear) */ +- (NSColor *)backgroundColorForLine:(NSUInteger)line; +- (NSColor *)backgroundColorForEmptySpace; + +/* Defaults to 1, may override */ +- (NSUInteger)bytesPerCharacter; + +/* Cover method for [[self representer] bytesPerLine] and [[self representer] bytesPerColumn] */ +- (NSUInteger)bytesPerLine; +- (NSUInteger)bytesPerColumn; + +- (CGFloat)lineHeight; + +/* Following two must be overridden */ +- (CGFloat)advanceBetweenColumns; +- (CGFloat)advancePerCharacter; + +- (CGFloat)advancePerColumn; +- (CGFloat)totalAdvanceForBytesInRange:(NSRange)range; + +/* Returns the number of lines that could be shown in this view at its given height (expressed in its local coordinate space) */ +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight; + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth; +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine; + +- (IBAction)selectAll:sender; + + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.m b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.m new file mode 100644 index 000000000..7fcbd0c77 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView.m @@ -0,0 +1,1766 @@ +// +// HFRepresenterTextView.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import +#import + +static const NSTimeInterval HFCaretBlinkFrequency = 0.56; + +@implementation HFRepresenterTextView + +- (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)inputFont { + NSUInteger length = [string length]; + UniChar chars[256]; + HFASSERT(length <= sizeof chars / sizeof *chars); + HFASSERT(inputFont != nil); + [string getCharacters:chars range:NSMakeRange(0, length)]; + if (! CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length)) { + /* Some or all characters were not mapped. This is OK. We'll use the replacement glyph. */ + } + return length; +} + +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)layoutManager glyphs:(CGGlyph *)glyphs { + HFASSERT(layoutManager != NULL); + HFASSERT(string != NULL); + NSGlyph nsglyphs[GLYPH_BUFFER_SIZE]; + [[[layoutManager textStorage] mutableString] setString:string]; + NSUInteger glyphIndex, glyphCount = [layoutManager getGlyphs:nsglyphs range:NSMakeRange(0, MIN(GLYPH_BUFFER_SIZE, [layoutManager numberOfGlyphs]))]; + if (glyphs != NULL) { + /* Convert from unsigned int NSGlyphs to unsigned short CGGlyphs */ + for (glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) { + /* Get rid of NSControlGlyph */ + NSGlyph modifiedGlyph = nsglyphs[glyphIndex] == NSControlGlyph ? NSNullGlyph : nsglyphs[glyphIndex]; + HFASSERT(modifiedGlyph <= USHRT_MAX); + glyphs[glyphIndex] = (CGGlyph)modifiedGlyph; + } + } + return glyphCount; +} + +/* Returns the number of glyphs for the given string, using the given text view, and generating the glyphs if the glyphs parameter is not NULL */ +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs { + HFASSERT(string != NULL); + HFASSERT(textView != NULL); + [textView setString:string]; + [textView setNeedsDisplay:YES]; //ligature generation doesn't seem to happen without this, for some reason. This seems very fragile! We should find a better way to get this ligature information!! + return [self _glyphsForString:string withGeneratingLayoutManager:[textView layoutManager] glyphs:glyphs]; +} + +- (NSArray *)displayedSelectedContentsRanges { + if (! cachedSelectedRanges) { + cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy]; + } + return cachedSelectedRanges; +} + +- (BOOL)_shouldHaveCaretTimer { + NSWindow *window = [self window]; + if (window == NULL) return NO; + if (! [window isKeyWindow]) return NO; + if (self != [window firstResponder]) return NO; + if (! _hftvflags.editable) return NO; + NSArray *ranges = [self displayedSelectedContentsRanges]; + if ([ranges count] != 1) return NO; + NSRange range = [ranges[0] rangeValue]; + if (range.length != 0) return NO; + return YES; +} + +- (NSUInteger)_effectiveBytesPerColumn { + /* returns the bytesPerColumn, unless it's larger than the bytes per character, in which case it returns 0 */ + NSUInteger bytesPerColumn = [self bytesPerColumn], bytesPerCharacter = [self bytesPerCharacter]; + return bytesPerColumn >= bytesPerCharacter ? bytesPerColumn : 0; +} + +// note: index may be negative +- (NSPoint)originForCharacterAtByteIndex:(NSInteger)index { + NSPoint result; + NSInteger bytesPerLine = (NSInteger)[self bytesPerLine]; + + // We want a nonnegative remainder + NSInteger lineIndex = index / bytesPerLine; + NSInteger byteIndexIntoLine = index % bytesPerLine; + while (byteIndexIntoLine < 0) { + byteIndexIntoLine += bytesPerLine; + lineIndex--; + } + + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + NSUInteger numConsumedColumns = (bytesPerColumn ? byteIndexIntoLine / bytesPerColumn : 0); + NSUInteger characterIndexIntoLine = byteIndexIntoLine / [self bytesPerCharacter]; + + result.x = [self horizontalContainerInset] + characterIndexIntoLine * [self advancePerCharacter] + numConsumedColumns * [self advanceBetweenColumns]; + result.y = (lineIndex - [self verticalOffset]) * [self lineHeight]; + + return result; +} + +- (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + HFASSERT(bytesPerLine % bytesPerCharacter == 0); + CGFloat advancePerCharacter = [self advancePerCharacter]; + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat floatRow = (CGFloat)floor([self verticalOffset] + point.y / [self lineHeight]); + NSUInteger byteIndexWithinRow; + + // to compute the column, we need to solve for byteIndexIntoLine in something like this: point.x = [self advancePerCharacter] * charIndexIntoLine + [self spaceBetweenColumns] * floor(byteIndexIntoLine / [self bytesPerColumn]). Start by computing the column (or if bytesPerColumn is 0, we don't have columns) + CGFloat insetX = point.x - [self horizontalContainerInset]; + if (insetX < 0) { + //handle the case of dragging within the container inset + byteIndexWithinRow = 0; + } + else if (bytesPerColumn == 0) { + /* We don't have columns */ + byteIndexWithinRow = bytesPerCharacter * (NSUInteger)(insetX / advancePerCharacter); + } + else { + CGFloat advancePerColumn = [self advancePerColumn]; + HFASSERT(advancePerColumn > 0); + CGFloat floatColumn = insetX / advancePerColumn; + HFASSERT(floatColumn >= 0 && floatColumn <= NSUIntegerMax); + CGFloat startOfColumn = advancePerColumn * HFFloor(floatColumn); + HFASSERT(startOfColumn <= insetX); + CGFloat xOffsetWithinColumn = insetX - startOfColumn; + CGFloat charIndexWithinColumn = xOffsetWithinColumn / advancePerCharacter; //charIndexWithinColumn may be larger than bytesPerColumn if the user clicked on the space between columns + HFASSERT(charIndexWithinColumn >= 0 && charIndexWithinColumn <= NSUIntegerMax / bytesPerCharacter); + NSUInteger byteIndexWithinColumn = bytesPerCharacter * (NSUInteger)charIndexWithinColumn; + byteIndexWithinRow = bytesPerColumn * (NSUInteger)floatColumn + byteIndexWithinColumn; //this may trigger overflow to the next column, but that's OK + byteIndexWithinRow = MIN(byteIndexWithinRow, bytesPerLine); //don't let clicking to the right of the line overflow to the next line + } + HFASSERT(floatRow >= 0 && floatRow <= NSUIntegerMax); + NSUInteger row = (NSUInteger)floatRow; + return (row * bytesPerLine + byteIndexWithinRow) / bytesPerCharacter; +} + +- (NSRect)caretRect { + NSArray *ranges = [self displayedSelectedContentsRanges]; + HFASSERT([ranges count] == 1); + NSRange range = [ranges[0] rangeValue]; + HFASSERT(range.length == 0); + + NSPoint caretBaseline = [self originForCharacterAtByteIndex:range.location]; + return NSMakeRect(caretBaseline.x - 1, caretBaseline.y, 1, [self lineHeight]); +} + +- (void)_blinkCaret:(NSTimer *)timer { + HFASSERT(timer == caretTimer); + if (_hftvflags.caretVisible) { + _hftvflags.caretVisible = NO; + [self setNeedsDisplayInRect:lastDrawnCaretRect]; + caretRectToDraw = NSZeroRect; + } + else { + _hftvflags.caretVisible = YES; + caretRectToDraw = [self caretRect]; + [self setNeedsDisplayInRect:caretRectToDraw]; + } +} + +- (void)_updateCaretTimerWithFirstResponderStatus:(BOOL)treatAsHavingFirstResponder { + BOOL hasCaretTimer = !! caretTimer; + BOOL shouldHaveCaretTimer = treatAsHavingFirstResponder && [self _shouldHaveCaretTimer]; + if (shouldHaveCaretTimer == YES && hasCaretTimer == NO) { + caretTimer = [[NSTimer timerWithTimeInterval:HFCaretBlinkFrequency target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES] retain]; + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + [loop addTimer:caretTimer forMode:NSDefaultRunLoopMode]; + [loop addTimer:caretTimer forMode:NSModalPanelRunLoopMode]; + if ([self enclosingMenuItem] != NULL) { + [loop addTimer:caretTimer forMode:NSEventTrackingRunLoopMode]; + } + } + else if (shouldHaveCaretTimer == NO && hasCaretTimer == YES) { + [caretTimer invalidate]; + [caretTimer release]; + caretTimer = nil; + caretRectToDraw = NSZeroRect; + if (! NSIsEmptyRect(lastDrawnCaretRect)) { + [self setNeedsDisplayInRect:lastDrawnCaretRect]; + } + } + HFASSERT(shouldHaveCaretTimer == !! caretTimer); +} + +- (void)_updateCaretTimer { + [self _updateCaretTimerWithFirstResponderStatus: self == [[self window] firstResponder]]; +} + +/* When you click or type, the caret appears immediately - do that here */ +- (void)_forceCaretOnIfHasCaretTimer { + if (caretTimer) { + [caretTimer invalidate]; + [caretTimer release]; + caretTimer = nil; + [self _updateCaretTimer]; + + _hftvflags.caretVisible = YES; + caretRectToDraw = [self caretRect]; + [self setNeedsDisplayInRect:caretRectToDraw]; + } +} + +/* Returns the range of lines containing the selected contents ranges (as NSValues containing NSRanges), or {NSNotFound, 0} if ranges is nil or empty */ +- (NSRange)_lineRangeForContentsRanges:(NSArray *)ranges { + NSUInteger minLine = NSUIntegerMax; + NSUInteger maxLine = 0; + NSUInteger bytesPerLine = [self bytesPerLine]; + FOREACH(NSValue *, rangeValue, ranges) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + NSUInteger lineForRangeStart = range.location / bytesPerLine; + NSUInteger lineForRangeEnd = NSMaxRange(range) / bytesPerLine; + HFASSERT(lineForRangeStart <= lineForRangeEnd); + minLine = MIN(minLine, lineForRangeStart); + maxLine = MAX(maxLine, lineForRangeEnd); + } + } + if (minLine > maxLine) return NSMakeRange(NSNotFound, 0); + else return NSMakeRange(minLine, maxLine - minLine + 1); +} + +- (NSRect)_rectForLineRange:(NSRange)lineRange { + HFASSERT(lineRange.location != NSNotFound); + NSUInteger bytesPerLine = [self bytesPerLine]; + NSRect bounds = [self bounds]; + NSRect result; + result.origin.x = NSMinX(bounds); + result.size.width = NSWidth(bounds); + result.origin.y = [self originForCharacterAtByteIndex:lineRange.location * bytesPerLine].y; + result.size.height = [self lineHeight] * lineRange.length; + return result; +} + +static int range_compare(const void *ap, const void *bp) { + const NSRange *a = ap; + const NSRange *b = bp; + if (a->location < b->location) return -1; + if (a->location > b->location) return 1; + if (a->length < b->length) return -1; + if (a->length > b->length) return 1; + return 0; +} + +enum LineCoverage_t { + eCoverageNone, + eCoveragePartial, + eCoverageFull +}; + +- (void)_linesWithParityChangesFromRanges:(const NSRange *)oldRanges count:(NSUInteger)oldRangeCount toRanges:(const NSRange *)newRanges count:(NSUInteger)newRangeCount intoIndexSet:(NSMutableIndexSet *)result { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger oldParity=0, newParity=0; + NSUInteger oldRangeIndex = 0, newRangeIndex = 0; + NSUInteger currentCharacterIndex = MIN(oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location); + oldParity = (currentCharacterIndex >= oldRanges[oldRangeIndex].location); + newParity = (currentCharacterIndex >= newRanges[newRangeIndex].location); + // NSLog(@"Old %s, new %s at %u (%u, %u)", oldParity ? "on" : "off", newParity ? "on" : "off", currentCharacterIndex, oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location); + for (;;) { + NSUInteger oldDivision = NSUIntegerMax, newDivision = NSUIntegerMax; + /* Move up to the next parity change */ + if (oldRangeIndex < oldRangeCount) { + const NSRange oldRange = oldRanges[oldRangeIndex]; + oldDivision = oldRange.location + (oldParity ? oldRange.length : 0); + } + if (newRangeIndex < newRangeCount) { + const NSRange newRange = newRanges[newRangeIndex]; + newDivision = newRange.location + (newParity ? newRange.length : 0); + } + + NSUInteger division = MIN(oldDivision, newDivision); + HFASSERT(division > currentCharacterIndex); + + // NSLog(@"Division %u", division); + + if (division == NSUIntegerMax) break; + + if (oldParity != newParity) { + /* The parities did not match through this entire range, so add all intersected lines to the result index set */ + NSUInteger startLine = currentCharacterIndex / bytesPerLine; + NSUInteger endLine = HFDivideULRoundingUp(division, bytesPerLine); + HFASSERT(endLine >= startLine); + // NSLog(@"Adding lines %u -> %u", startLine, endLine); + [result addIndexesInRange:NSMakeRange(startLine, endLine - startLine)]; + } + if (division == oldDivision) { + oldRangeIndex += oldParity; + oldParity = ! oldParity; + // NSLog(@"Old range switching %s at %u", oldParity ? "on" : "off", division); + } + if (division == newDivision) { + newRangeIndex += newParity; + newParity = ! newParity; + // NSLog(@"New range switching %s at %u", newParity ? "on" : "off", division); + } + currentCharacterIndex = division; + } +} + +- (void)_addLinesFromRanges:(const NSRange *)ranges count:(NSUInteger)count toIndexSet:(NSMutableIndexSet *)set { + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger i; + for (i=0; i < count; i++) { + NSUInteger firstLine = ranges[i].location / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(NSMaxRange(ranges[i]), bytesPerLine); + [set addIndexesInRange:NSMakeRange(firstLine, lastLine - firstLine)]; + } +} + +- (NSIndexSet *)_indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:(NSArray *)oldSelectedRangeArray toRanges:(NSArray *)newSelectedRangeArray { + NSUInteger oldRangeCount = 0, newRangeCount = 0; + + NEW_ARRAY(NSRange, oldRanges, [oldSelectedRangeArray count]); + NEW_ARRAY(NSRange, newRanges, [newSelectedRangeArray count]); + + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + + /* Extract all the ranges into a local array */ + FOREACH(NSValue *, rangeValue1, oldSelectedRangeArray) { + NSRange range = [rangeValue1 rangeValue]; + if (range.length > 0) { + oldRanges[oldRangeCount++] = range; + } + } + FOREACH(NSValue *, rangeValue2, newSelectedRangeArray) { + NSRange range = [rangeValue2 rangeValue]; + if (range.length > 0) { + newRanges[newRangeCount++] = range; + } + } + +#if ! NDEBUG + /* Assert that ranges of arrays do not have any self-intersection; this is supposed to be enforced by our HFController. Also assert that they aren't "just touching"; if they are they should be merged into a single range. */ + for (NSUInteger i=0; i < oldRangeCount; i++) { + for (NSUInteger j=i+1; j < oldRangeCount; j++) { + HFASSERT(NSIntersectionRange(oldRanges[i], oldRanges[j]).length == 0); + HFASSERT(NSMaxRange(oldRanges[i]) != oldRanges[j].location && NSMaxRange(oldRanges[j]) != oldRanges[i].location); + } + } + for (NSUInteger i=0; i < newRangeCount; i++) { + for (NSUInteger j=i+1; j < newRangeCount; j++) { + HFASSERT(NSIntersectionRange(newRanges[i], newRanges[j]).length == 0); + HFASSERT(NSMaxRange(newRanges[i]) != newRanges[j].location && NSMaxRange(newRanges[j]) != newRanges[i].location); + } + } +#endif + + if (newRangeCount == 0) { + [self _addLinesFromRanges:oldRanges count:oldRangeCount toIndexSet:result]; + } + else if (oldRangeCount == 0) { + [self _addLinesFromRanges:newRanges count:newRangeCount toIndexSet:result]; + } + else { + /* Sort the arrays, since _linesWithParityChangesFromRanges needs it */ + qsort(oldRanges, oldRangeCount, sizeof *oldRanges, range_compare); + qsort(newRanges, newRangeCount, sizeof *newRanges, range_compare); + + [self _linesWithParityChangesFromRanges:oldRanges count:oldRangeCount toRanges:newRanges count:newRangeCount intoIndexSet:result]; + } + + FREE_ARRAY(oldRanges); + FREE_ARRAY(newRanges); + + return result; +} + +- (void)updateSelectedRanges { + NSArray *oldSelectedRanges = cachedSelectedRanges; + cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy]; + NSIndexSet *indexSet = [self _indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:oldSelectedRanges toRanges:cachedSelectedRanges]; + BOOL lastCaretRectNeedsRedraw = ! NSIsEmptyRect(lastDrawnCaretRect); + NSRange lineRangeToInvalidate = NSMakeRange(NSUIntegerMax, 0); + for (NSUInteger lineIndex = [indexSet firstIndex]; ; lineIndex = [indexSet indexGreaterThanIndex:lineIndex]) { + if (lineIndex != NSNotFound && NSMaxRange(lineRangeToInvalidate) == lineIndex) { + lineRangeToInvalidate.length++; + } + else { + if (lineRangeToInvalidate.length > 0) { + NSRect rectToInvalidate = [self _rectForLineRange:lineRangeToInvalidate]; + [self setNeedsDisplayInRect:rectToInvalidate]; + lastCaretRectNeedsRedraw = lastCaretRectNeedsRedraw && ! NSContainsRect(rectToInvalidate, lastDrawnCaretRect); + } + lineRangeToInvalidate = NSMakeRange(lineIndex, 1); + } + if (lineIndex == NSNotFound) break; + } + + if (lastCaretRectNeedsRedraw) [self setNeedsDisplayInRect:lastDrawnCaretRect]; + [oldSelectedRanges release]; //balance the retain we borrowed from the ivar + [self _updateCaretTimer]; + [self _forceCaretOnIfHasCaretTimer]; + + // A new pulse window will be created at the new selected range if necessary. + [self terminateSelectionPulse]; +} + +- (void)drawPulseBackgroundInRect:(NSRect)pulseRect { + [[NSColor yellowColor] set]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(ctx); + [[NSBezierPath bezierPathWithRoundedRect:pulseRect xRadius:25 yRadius:25] addClip]; + NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor yellowColor] endingColor:[NSColor colorWithCalibratedRed:(CGFloat)1. green:(CGFloat).75 blue:0 alpha:1]]; + [gradient drawInRect:pulseRect angle:90]; + [gradient release]; + CGContextRestoreGState(ctx); +} + +- (void)fadePulseWindowTimer:(NSTimer *)timer { + // TODO: close & invalidate immediatley if view scrolls. + NSWindow *window = [timer userInfo]; + CGFloat alpha = [window alphaValue]; + alpha -= (CGFloat)(3. / 30.); + if (alpha < 0) { + [window close]; + [timer invalidate]; + } + else { + [window setAlphaValue:alpha]; + } +} + +- (void)terminateSelectionPulse { + if (pulseWindow) { + [[self window] removeChildWindow:pulseWindow]; + [pulseWindow setFrame:pulseWindowBaseFrameInScreenCoordinates display:YES animate:NO]; + [NSTimer scheduledTimerWithTimeInterval:1. / 30. target:self selector:@selector(fadePulseWindowTimer:) userInfo:pulseWindow repeats:YES]; + //release is not necessary, since it relases when closed by default + pulseWindow = nil; + pulseWindowBaseFrameInScreenCoordinates = NSZeroRect; + } +} + +- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect { + NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect); + if (! NSIsEmptyRect(caretRect)) { + [[NSColor controlTextColor] set]; + NSRectFill(caretRect); + lastDrawnCaretRect = caretRect; + } + if (NSIsEmptyRect(caretRectToDraw)) lastDrawnCaretRect = NSZeroRect; +} + + +/* This is the color when we are the first responder in the key window */ +- (NSColor *)primaryTextSelectionColor { + return [NSColor selectedTextBackgroundColor]; +} + +/* This is the color when we are not in the key window */ +- (NSColor *)inactiveTextSelectionColor { + if (@available(macOS 10.14, *)) { + return [NSColor unemphasizedSelectedTextBackgroundColor]; + } + return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1]; +} + +/* This is the color when we are not the first responder, but we are in the key window */ +- (NSColor *)secondaryTextSelectionColor { + if (@available(macOS 10.14, *)) { + return [NSColor unemphasizedSelectedTextBackgroundColor]; + } + return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1]; +} + +- (NSColor *)textSelectionColor { + NSWindow *window = [self window]; + if (window == nil) return [self primaryTextSelectionColor]; + else if (! [window isKeyWindow]) return [self inactiveTextSelectionColor]; + else if (self != [window firstResponder]) return [self secondaryTextSelectionColor]; + else return [self primaryTextSelectionColor]; +} + +- (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect { + NSArray *ranges = [self displayedSelectedContentsRanges]; + NSUInteger bytesPerLine = [self bytesPerLine]; + [[self textSelectionColor] set]; + CGFloat lineHeight = [self lineHeight]; + FOREACH(NSValue *, rangeValue, ranges) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + NSUInteger startByteIndex = range.location; + NSUInteger endByteIndexForThisRange = range.location + range.length - 1; + NSUInteger byteIndex = startByteIndex; + while (byteIndex <= endByteIndexForThisRange) { + NSUInteger endByteIndexForLine = ((byteIndex / bytesPerLine) + 1) * bytesPerLine - 1; + NSUInteger endByteForThisLineOfRange = MIN(endByteIndexForThisRange, endByteIndexForLine); + NSPoint startPoint = [self originForCharacterAtByteIndex:byteIndex]; + NSPoint endPoint = [self originForCharacterAtByteIndex:endByteForThisLineOfRange]; + NSRect selectionRect = NSMakeRect(startPoint.x, startPoint.y, endPoint.x + [self advancePerCharacter] - startPoint.x, lineHeight); + NSRect clippedSelectionRect = NSIntersectionRect(selectionRect, clipRect); + if (! NSIsEmptyRect(clippedSelectionRect)) { + NSRectFill(clippedSelectionRect); + } + byteIndex = endByteForThisLineOfRange + 1; + } + } + } +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (BOOL)hasVisibleDisplayedSelectedContentsRange { + FOREACH(NSValue *, rangeValue, [self displayedSelectedContentsRanges]) { + NSRange range = [rangeValue rangeValue]; + if (range.length > 0) { + return YES; + } + } + return NO; +} + +- (BOOL)becomeFirstResponder { + BOOL result = [super becomeFirstResponder]; + [self _updateCaretTimerWithFirstResponderStatus:YES]; + if ([self showsFocusRing] || [self hasVisibleDisplayedSelectedContentsRange]) { + [self setNeedsDisplay:YES]; + } + return result; +} + +- (BOOL)resignFirstResponder { + BOOL result = [super resignFirstResponder]; + [self _updateCaretTimerWithFirstResponderStatus:NO]; + BOOL needsRedisplay = NO; + if ([self showsFocusRing]) needsRedisplay = YES; + else if (! NSIsEmptyRect(lastDrawnCaretRect)) needsRedisplay = YES; + else if ([self hasVisibleDisplayedSelectedContentsRange]) needsRedisplay = YES; + if (needsRedisplay) [self setNeedsDisplay:YES]; + return result; +} + +- (instancetype)initWithRepresenter:(HFTextRepresenter *)rep { + self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]; + horizontalContainerInset = 4; + representer = rep; + _hftvflags.editable = YES; + + return self; +} + +- (void)clearRepresenter { + representer = nil; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeObject:representer forKey:@"HFRepresenter"]; + [coder encodeObject:_font forKey:@"HFFont"]; + [coder encodeObject:_data forKey:@"HFData"]; + [coder encodeDouble:verticalOffset forKey:@"HFVerticalOffset"]; + [coder encodeDouble:horizontalContainerInset forKey:@"HFHorizontalContainerOffset"]; + [coder encodeDouble:defaultLineHeight forKey:@"HFDefaultLineHeight"]; + [coder encodeInt64:bytesBetweenVerticalGuides forKey:@"HFBytesBetweenVerticalGuides"]; + [coder encodeInt64:startingLineBackgroundColorIndex forKey:@"HFStartingLineBackgroundColorIndex"]; + [coder encodeObject:rowBackgroundColors forKey:@"HFRowBackgroundColors"]; + [coder encodeBool:_hftvflags.antialias forKey:@"HFAntialias"]; + [coder encodeBool:_hftvflags.drawCallouts forKey:@"HFDrawCallouts"]; + [coder encodeBool:_hftvflags.editable forKey:@"HFEditable"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + representer = [coder decodeObjectForKey:@"HFRepresenter"]; + _font = [[coder decodeObjectForKey:@"HFFont"] retain]; + _data = [[coder decodeObjectForKey:@"HFData"] retain]; + verticalOffset = (CGFloat)[coder decodeDoubleForKey:@"HFVerticalOffset"]; + horizontalContainerInset = (CGFloat)[coder decodeDoubleForKey:@"HFHorizontalContainerOffset"]; + defaultLineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFDefaultLineHeight"]; + bytesBetweenVerticalGuides = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesBetweenVerticalGuides"]; + startingLineBackgroundColorIndex = (NSUInteger)[coder decodeInt64ForKey:@"HFStartingLineBackgroundColorIndex"]; + rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain]; + _hftvflags.antialias = [coder decodeBoolForKey:@"HFAntialias"]; + _hftvflags.drawCallouts = [coder decodeBoolForKey:@"HFDrawCallouts"]; + _hftvflags.editable = [coder decodeBoolForKey:@"HFEditable"]; + return self; +} + +- (CGFloat)horizontalContainerInset { + return horizontalContainerInset; +} + +- (void)setHorizontalContainerInset:(CGFloat)inset { + horizontalContainerInset = inset; +} + +- (void)setBytesBetweenVerticalGuides:(NSUInteger)val { + bytesBetweenVerticalGuides = val; +} + +- (NSUInteger)bytesBetweenVerticalGuides { + return bytesBetweenVerticalGuides; +} + + +- (void)setFont:(NSFont *)val { + if (val != _font) { + [_font release]; + _font = [val retain]; + NSLayoutManager *manager = [[NSLayoutManager alloc] init]; + defaultLineHeight = [manager defaultLineHeightForFont:_font]; + [manager release]; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)lineHeight { + return defaultLineHeight; +} + +/* The base implementation does not support font substitution, so we require that it be the base font. */ +- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx { + HFASSERT(idx == 0); + USE(idx); + return _font; +} + +- (NSRange)roundPartialByteRange:(NSRange)byteRange { + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + /* Get the left and right edges of the range */ + NSUInteger left = byteRange.location, right = NSMaxRange(byteRange); + + /* Round both to the left. This may make the range bigger or smaller, or empty! */ + left -= left % bytesPerCharacter; + right -= right % bytesPerCharacter; + + /* Done */ + HFASSERT(right >= left); + return NSMakeRange(left, right - left); + +} + +- (void)setNeedsDisplayForLinesInRange:(NSRange)lineRange { + // redisplay the lines in the given range + if (lineRange.length == 0) return; + NSUInteger firstLine = lineRange.location, lastLine = NSMaxRange(lineRange); + CGFloat lineHeight = [self lineHeight]; + CGFloat vertOffset = [self verticalOffset]; + CGFloat yOrigin = (firstLine - vertOffset) * lineHeight; + CGFloat lastLineBottom = (lastLine - vertOffset) * lineHeight; + NSRect bounds = [self bounds]; + NSRect dirtyRect = NSMakeRect(bounds.origin.x, bounds.origin.y + yOrigin, NSWidth(bounds), lastLineBottom - yOrigin); + [self setNeedsDisplayInRect:dirtyRect]; +} + +- (void)setData:(NSData *)val { + if (val != _data) { + NSUInteger oldLength = [_data length]; + NSUInteger newLength = [val length]; + const unsigned char *oldBytes = (const unsigned char *)[_data bytes]; + const unsigned char *newBytes = (const unsigned char *)[val bytes]; + NSUInteger firstDifferingIndex = HFIndexOfFirstByteThatDiffers(oldBytes, oldLength, newBytes, newLength); + if (firstDifferingIndex == NSUIntegerMax) { + /* Nothing to do! Data is identical! */ + } + else { + NSUInteger lastDifferingIndex = HFIndexOfLastByteThatDiffers(oldBytes, oldLength, newBytes, newLength); + HFASSERT(lastDifferingIndex != NSUIntegerMax); //if we have a first different byte, we must have a last different byte + /* Expand to encompass characters that they touch */ + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + firstDifferingIndex -= firstDifferingIndex % bytesPerCharacter; + lastDifferingIndex = HFRoundUpToMultipleInt(lastDifferingIndex, bytesPerCharacter); + + /* Now figure out the line range they touch */ + const NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger firstLine = firstDifferingIndex / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(MAX(oldLength, newLength), bytesPerLine); + /* The +1 is for the following case - if we change the last character, then it may push the caret into the next line (even though there's no text there). This last line may have a background color, so we need to make it draw if it did not draw before (or vice versa - when deleting the last character which pulls the caret from the last line). */ + NSUInteger lastDifferingLine = (lastDifferingIndex == NSNotFound ? lastLine : HFDivideULRoundingUp(lastDifferingIndex + 1, bytesPerLine)); + if (lastDifferingLine > firstLine) { + [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastDifferingLine - firstLine)]; + } + } + [_data release]; + _data = [val copy]; + [self _updateCaretTimer]; + } +} + +- (void)setStyles:(NSArray *)newStyles { + if (! [_styles isEqual:newStyles]) { + + /* Figure out which styles changed - that is, we want to compute those objects that are not in oldStyles or newStyles, but not both. */ + NSMutableSet *changedStyles = _styles ? [[NSMutableSet alloc] initWithArray:_styles] : [[NSMutableSet alloc] init]; + FOREACH(HFTextVisualStyleRun *, run, newStyles) { + if ([changedStyles containsObject:run]) { + [changedStyles removeObject:run]; + } + else { + [changedStyles addObject:run]; + } + } + + /* Now figure out the first and last indexes of changed ranges. */ + NSUInteger firstChangedIndex = NSUIntegerMax, lastChangedIndex = 0; + FOREACH(HFTextVisualStyleRun *, changedRun, changedStyles) { + NSRange range = [changedRun range]; + if (range.length > 0) { + firstChangedIndex = MIN(firstChangedIndex, range.location); + lastChangedIndex = MAX(lastChangedIndex, NSMaxRange(range) - 1); + } + } + + /* Don't need this any more */ + [changedStyles release]; + + /* Expand to cover all touched characters */ + NSUInteger bytesPerCharacter = [self bytesPerCharacter]; + firstChangedIndex -= firstChangedIndex % bytesPerCharacter; + lastChangedIndex = HFRoundUpToMultipleInt(lastChangedIndex, bytesPerCharacter); + + /* Figure out the changed lines, and trigger redisplay */ + if (firstChangedIndex <= lastChangedIndex) { + const NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger firstLine = firstChangedIndex / bytesPerLine; + NSUInteger lastLine = HFDivideULRoundingUp(lastChangedIndex, bytesPerLine); + [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastLine - firstLine + 1)]; + } + + /* Do the usual Cocoa thing */ + [_styles release]; + _styles = [newStyles copy]; + } +} + +- (void)setVerticalOffset:(CGFloat)val { + if (val != verticalOffset) { + verticalOffset = val; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)verticalOffset { + return verticalOffset; +} + +- (NSUInteger)startingLineBackgroundColorIndex { + return startingLineBackgroundColorIndex; +} + +- (void)setStartingLineBackgroundColorIndex:(NSUInteger)val { + startingLineBackgroundColorIndex = val; +} + +- (BOOL)isFlipped { + return YES; +} + +- (HFTextRepresenter *)representer { + return representer; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, _hftvflags.registeredForAppNotifications /* appToo */); + [caretTimer invalidate]; + [caretTimer release]; + [_font release]; + [_data release]; + [_styles release]; + [cachedSelectedRanges release]; + [callouts release]; + if(byteColoring) Block_release(byteColoring); + [super dealloc]; +} + +- (NSColor *)backgroundColorForEmptySpace { + NSArray *colors = [[self representer] rowBackgroundColors]; + if (! [colors count]) return [NSColor clearColor]; + else return colors[0]; +} + +- (NSColor *)backgroundColorForLine:(NSUInteger)line { + NSArray *colors = [[self representer] rowBackgroundColors]; + NSUInteger colorCount = [colors count]; + if (colorCount == 0) return [NSColor clearColor]; + NSUInteger colorIndex = (line + startingLineBackgroundColorIndex) % colorCount; + if (colorIndex == 0) return nil; //will be drawn by empty space + else return colors[colorIndex]; +} + +- (NSUInteger)bytesPerLine { + HFASSERT([self representer] != nil); + return [[self representer] bytesPerLine]; +} + +- (NSUInteger)bytesPerColumn { + HFASSERT([self representer] != nil); + return [[self representer] bytesPerColumn]; +} + +- (void)_drawDefaultLineBackgrounds:(NSRect)clip withLineHeight:(CGFloat)lineHeight maxLines:(NSUInteger)maxLines { + NSRect bounds = [self bounds]; + NSUInteger lineIndex; + NSRect lineRect = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight); + if ([self showsFocusRing]) lineRect = NSInsetRect(lineRect, 2, 0); + lineRect.origin.y -= [self verticalOffset] * [self lineHeight]; + NSUInteger drawableLineIndex = 0; + NEW_ARRAY(NSRect, lineRects, maxLines); + NEW_ARRAY(NSColor*, lineColors, maxLines); + for (lineIndex = 0; lineIndex < maxLines; lineIndex++) { + NSRect clippedLineRect = NSIntersectionRect(lineRect, clip); + if (! NSIsEmptyRect(clippedLineRect)) { + NSColor *lineColor = [self backgroundColorForLine:lineIndex]; + if (lineColor) { + lineColors[drawableLineIndex] = lineColor; + lineRects[drawableLineIndex] = clippedLineRect; + drawableLineIndex++; + } + } + lineRect.origin.y += lineHeight; + } + + if (drawableLineIndex > 0) { + NSRectFillListWithColorsUsingOperation(lineRects, lineColors, drawableLineIndex, NSCompositeSourceOver); + } + + FREE_ARRAY(lineRects); + FREE_ARRAY(lineColors); +} + +- (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex { + HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init]; + [run setRange:NSMakeRange(0, NSUIntegerMax)]; + [run setForegroundColor:[NSColor textColor]]; + return [run autorelease]; +} + +/* Given a list of rects and a parallel list of values, find cases of equal adjacent values, and union together their corresponding rects, deleting the second element from the list. Next, delete all nil values. Returns the new count of the list. */ +static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count) { + size_t trailing = 0, leading = 0; + while (leading < count) { + /* Copy our value left */ + valueList[trailing] = valueList[leading]; + rectList[trailing] = rectList[leading]; + + /* Skip one - no point unioning with ourselves */ + leading += 1; + + /* Sweep right, unioning until we reach a different value or the end */ + id targetValue = valueList[trailing]; + for (; leading < count; leading++) { + id testValue = valueList[leading]; + if (targetValue == testValue || (testValue && [targetValue isEqual:testValue])) { + /* Values match, so union the two rects */ + rectList[trailing] = NSUnionRect(rectList[trailing], rectList[leading]); + } + else { + /* Values don't match, we're done sweeping */ + break; + } + } + + /* We're done with this index */ + trailing += 1; + } + + /* trailing keeps track of how many values we have */ + count = trailing; + + /* Now do the same thing, except delete nil values */ + for (trailing = leading = 0; leading < count; leading++) { + if (valueList[leading] != nil) { + valueList[trailing] = valueList[leading]; + rectList[trailing] = rectList[leading]; + trailing += 1; + } + } + count = trailing; + + /* All done */ + return count; +} + +/* Draw vertical guidelines every four bytes */ +- (void)drawVerticalGuideLines:(NSRect)clip { + if (bytesBetweenVerticalGuides == 0) return; + + NSUInteger bytesPerLine = [self bytesPerLine]; + NSRect bounds = [self bounds]; + CGFloat advancePerCharacter = [self advancePerCharacter]; + CGFloat spaceAdvancement = advancePerCharacter / 2; + CGFloat advanceAmount = (advancePerCharacter + spaceAdvancement) * bytesBetweenVerticalGuides; + CGFloat lineOffset = (CGFloat)(NSMinX(bounds) + [self horizontalContainerInset] + advanceAmount - spaceAdvancement / 2.); + CGFloat endOffset = NSMaxX(bounds) - [self horizontalContainerInset]; + + NSUInteger numGuides = (bytesPerLine - 1) / bytesBetweenVerticalGuides; // -1 is a trick to avoid drawing the last line + NSUInteger guideIndex = 0, rectIndex = 0; + NEW_ARRAY(NSRect, lineRects, numGuides); + + while (lineOffset < endOffset && guideIndex < numGuides) { + NSRect lineRect = NSMakeRect(lineOffset - 1, NSMinY(bounds), 1, NSHeight(bounds)); + NSRect clippedLineRect = NSIntersectionRect(lineRect, clip); + if (! NSIsEmptyRect(clippedLineRect)) { + lineRects[rectIndex++] = clippedLineRect; + } + lineOffset += advanceAmount; + guideIndex++; + } + if (rectIndex > 0) { + [[NSColor gridColor] set]; + NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver); + } + FREE_ARRAY(lineRects); +} + +- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount { + USE(byteCount); + UNIMPLEMENTED(); +} + +- (void)setByteColoring:(void (^)(uint8_t, uint8_t*, uint8_t*, uint8_t*, uint8_t*))coloring { + Block_release(byteColoring); + byteColoring = coloring ? Block_copy(coloring) : NULL; + [self setNeedsDisplay:YES]; +} + +- (void)drawByteColoringBackground:(NSRange)range inRect:(NSRect)rect { + if(!byteColoring) return; + + size_t width = (size_t)rect.size.width; + + // A rgba, 8-bit, single row image. + // +1 in case messing around with floats makes us overshoot a bit. + uint32_t *buffer = calloc(width+1, 4); + + const uint8_t *bytes = [_data bytes]; + bytes += range.location; + + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat advancePerCharacter = [self advancePerCharacter]; + CGFloat advanceBetweenColumns = [self advanceBetweenColumns]; + + // For each character, draw the corresponding part of the image + CGFloat offset = [self horizontalContainerInset]; + for(NSUInteger i = 0; i < range.length; i++) { + uint8_t r, g, b, a; + byteColoring(bytes[i], &r, &g, &b, &a); + uint32_t c = ((uint32_t)r<<0) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); + memset_pattern4(&buffer[(size_t)offset], &c, 4*(size_t)(advancePerCharacter+1)); + offset += advancePerCharacter; + if(bytesPerColumn && (i+1) % bytesPerColumn == 0) + offset += advanceBetweenColumns; + } + + // Do a CGImage dance to draw the buffer + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, 4 * width, NULL); + CGColorSpaceRef cgcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGImageRef image = CGImageCreate(width, 1, 8, 32, 4 * width, cgcolorspace, + (CGBitmapInfo)kCGImageAlphaLast, provider, NULL, false, kCGRenderingIntentDefault); + CGContextDrawImage([[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect), image); + CGColorSpaceRelease(cgcolorspace); + CGImageRelease(image); + CGDataProviderRelease(provider); + free(buffer); +} + +- (void)drawStyledBackgroundsForByteRange:(NSRange)range inRect:(NSRect)rect { + NSRect remainingRunRect = rect; + NSRange remainingRange = range; + + /* Our caller lies to us a little */ + remainingRunRect.origin.x += [self horizontalContainerInset]; + + const NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + + /* Here are the properties we care about */ + struct PropertyInfo_t { + SEL stylePropertyAccessor; // the selector we use to get the property + NSRect *rectList; // the list of rects corresponding to the property values + id *propertyValueList; // the list of the property values + size_t count; //list count, only gets set after cleaning up our lists + } propertyInfos[] = { + {.stylePropertyAccessor = @selector(backgroundColor)}, + {.stylePropertyAccessor = @selector(bookmarkStarts)}, + {.stylePropertyAccessor = @selector(bookmarkExtents)}, + {.stylePropertyAccessor = @selector(bookmarkEnds)} + }; + + /* Each list has the same capacity, and (initially) the same count */ + size_t listCount = 0, listCapacity = 0; + + /* The function pointer we use to get our property values */ + id (* const funcPtr)(id, SEL) = (id (*)(id, SEL))objc_msgSend; + + size_t propertyIndex; + const size_t propertyInfoCount = sizeof propertyInfos / sizeof *propertyInfos; + + while (remainingRange.length > 0) { + /* Get the next run for the remaining range. */ + HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:remainingRange.location]; + + /* The length of the run is the end of the style run or the end of the range we're given (whichever is smaller), minus the beginning of the range we care about. */ + NSUInteger runStart = remainingRange.location; + NSUInteger runLength = MIN(NSMaxRange(range), NSMaxRange([styleRun range])) - runStart; + + /* Get the width of this run and use it to compute the rect */ + CGFloat runRectWidth = [self totalAdvanceForBytesInRange:NSMakeRange(remainingRange.location, runLength)]; + NSRect runRect = remainingRunRect; + runRect.size.width = runRectWidth; + + /* Update runRect and remainingRunRect based on what we just learned */ + remainingRunRect.origin.x += runRectWidth; + remainingRunRect.size.width -= runRectWidth; + + /* Do a hack - if we end at a column boundary, subtract the advance between columns. If the next run has the same value for this property, then we'll end up unioning the rects together and the column gap will be filled. This is the primary purpose of this function. */ + if (bytesPerColumn > 0 && (runStart + runLength) % bytesPerColumn == 0) { + runRect.size.width -= MIN([self advanceBetweenColumns], runRect.size.width); + } + + /* Extend our lists if necessary */ + if (listCount == listCapacity) { + /* Our list is too small, extend it */ + listCapacity = listCapacity + 16; + + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + p->rectList = check_realloc(p->rectList, listCapacity * sizeof *p->rectList); + p->propertyValueList = check_realloc(p->propertyValueList, listCapacity * sizeof *p->propertyValueList); + } + } + + /* Now append our values to our lists, even if it's nil */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + id value = funcPtr(styleRun, p->stylePropertyAccessor); + p->rectList[listCount] = runRect; + p->propertyValueList[listCount] = value; + } + + listCount++; + + /* Update remainingRange */ + remainingRange.location += runLength; + remainingRange.length -= runLength; + + } + + /* Now clean up our lists, to delete the gaps we may have introduced */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + struct PropertyInfo_t *p = propertyInfos + propertyIndex; + p->count = unionAndCleanLists(p->rectList, p->propertyValueList, listCount); + } + + /* Finally we can draw them! First, draw byte backgrounds. */ + [self drawByteColoringBackground:range inRect:rect]; + + const struct PropertyInfo_t *p; + + /* Draw backgrounds */ + p = propertyInfos + 0; + if (p->count > 0) NSRectFillListWithColorsUsingOperation(p->rectList, p->propertyValueList, p->count, NSCompositeSourceOver); + + /* Clean up */ + for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) { + p = propertyInfos + propertyIndex; + free(p->rectList); + free(p->propertyValueList); + } +} + +- (void)drawGlyphs:(const struct HFGlyph_t *)glyphs atPoint:(NSPoint)point withAdvances:(const CGSize *)advances withStyleRun:(HFTextVisualStyleRun *)styleRun count:(NSUInteger)glyphCount { + HFASSERT(glyphs != NULL); + HFASSERT(advances != NULL); + HFASSERT(glyphCount > 0); + if ([styleRun shouldDraw]) { + [styleRun set]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + /* Get all the CGGlyphs together */ + NEW_ARRAY(CGGlyph, cgglyphs, glyphCount); + for (NSUInteger j=0; j < glyphCount; j++) { + cgglyphs[j] = glyphs[j].glyph; + } + + NSUInteger runStart = 0; + HFGlyphFontIndex runFontIndex = glyphs[0].fontIndex; + CGFloat runAdvance = 0; + for (NSUInteger i=1; i <= glyphCount; i++) { + /* Check if this run is finished, or if we are using a substitution font */ + if (i == glyphCount || glyphs[i].fontIndex != runFontIndex || runFontIndex > 0) { + /* Draw this run */ + NSFont *fontToUse = [self fontAtSubstitutionIndex:runFontIndex]; + [[fontToUse screenFont] set]; + CGContextSetTextPosition(ctx, point.x + runAdvance, point.y); + + if (runFontIndex > 0) { + /* A substitution font. Here we should only have one glyph */ + HFASSERT(i - runStart == 1); + /* Get the advance for this glyph. */ + NSSize nativeAdvance; + NSGlyph nativeGlyph = cgglyphs[runStart]; + [fontToUse getAdvancements:&nativeAdvance forGlyphs:&nativeGlyph count:1]; + if (nativeAdvance.width > advances[runStart].width) { + /* This glyph is too wide! We'll have to scale it. Here we only scale horizontally. */ + CGFloat horizontalScale = advances[runStart].width / nativeAdvance.width; + CGAffineTransform textCTM = CGContextGetTextMatrix(ctx); + textCTM.a *= horizontalScale; + CGContextSetTextMatrix(ctx, textCTM); + /* Note that we don't have to restore the text matrix, because the next call to set the font will overwrite it. */ + } + } + + /* Draw the glyphs */ + CGContextShowGlyphsWithAdvances(ctx, cgglyphs + runStart, advances + runStart, i - runStart); + + /* Record the new run */ + if (i < glyphCount) { + /* Sum the advances */ + for (NSUInteger j = runStart; j < i; j++) { + runAdvance += advances[j].width; + } + + /* Record the new run start and index */ + runStart = i; + runFontIndex = glyphs[i].fontIndex; + HFASSERT(runFontIndex != kHFGlyphFontIndexInvalid); + } + } + } + } +} + + +- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount { + USE(bytes); + USE(numBytes); + USE(offsetIntoLine); + USE(glyphs); + USE(advances); + USE(resultGlyphCount); + UNIMPLEMENTED_VOID(); +} + +- (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount { + NSParameterAssert(glyphs != NULL && advances != NULL && restrictingToRanges != nil && bytePtr != NULL); + NSRange priorIntersectionRange = {NSUIntegerMax, NSUIntegerMax}; + NSUInteger glyphBufferIndex = 0; + NSUInteger bytesPerLine = [self bytesPerLine]; + NSUInteger restrictionRangeCount = [restrictingToRanges count]; + for (NSUInteger rangeIndex = 0; rangeIndex < restrictionRangeCount; rangeIndex++) { + NSRange inclusionRange = [restrictingToRanges[rangeIndex] rangeValue]; + NSRange intersectionRange = NSIntersectionRange(inclusionRange, byteRange); + if (intersectionRange.length == 0) continue; + + NSUInteger offsetIntoLine = intersectionRange.location % bytesPerLine; + + NSRange byteRangeToSkip; + if (priorIntersectionRange.location == NSUIntegerMax) { + byteRangeToSkip = NSMakeRange(byteRange.location, intersectionRange.location - byteRange.location); + } + else { + HFASSERT(intersectionRange.location >= NSMaxRange(priorIntersectionRange)); + byteRangeToSkip.location = NSMaxRange(priorIntersectionRange); + byteRangeToSkip.length = intersectionRange.location - byteRangeToSkip.location; + } + + if (byteRangeToSkip.length > 0) { + CGFloat additionalAdvance = [self totalAdvanceForBytesInRange:byteRangeToSkip]; + if (glyphBufferIndex == 0) { + *initialTextOffset = *initialTextOffset + additionalAdvance; + } + else { + advances[glyphBufferIndex - 1].width += additionalAdvance; + } + } + + NSUInteger glyphCountForRange = NSUIntegerMax; + [self extractGlyphsForBytes:bytePtr + intersectionRange.location count:intersectionRange.length offsetIntoLine:offsetIntoLine intoArray:glyphs + glyphBufferIndex advances:advances + glyphBufferIndex resultingGlyphCount:&glyphCountForRange]; + HFASSERT(glyphCountForRange != NSUIntegerMax); + glyphBufferIndex += glyphCountForRange; + priorIntersectionRange = intersectionRange; + } + if (resultingGlyphCount) *resultingGlyphCount = glyphBufferIndex; +} + +- (void)drawTextWithClip:(NSRect)clip restrictingToTextInRanges:(NSArray *)restrictingToRanges { + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + NSRect bounds = [self bounds]; + CGFloat lineHeight = [self lineHeight]; + + CGAffineTransform textTransform = CGContextGetTextMatrix(ctx); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + NSUInteger lineStartIndex, bytesPerLine = [self bytesPerLine]; + NSData *dataObject = [self data]; + NSFont *fontObject = [[self font] screenFont]; + //const NSUInteger bytesPerChar = [self bytesPerCharacter]; + const NSUInteger byteCount = [dataObject length]; + + const unsigned char * const bytePtr = [dataObject bytes]; + + NSRect lineRectInBoundsSpace = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight); + lineRectInBoundsSpace.origin.y -= [self verticalOffset] * lineHeight; + + /* Start us off with the horizontal inset and move the baseline down by the ascender so our glyphs just graze the top of our view */ + textTransform.tx += [self horizontalContainerInset]; + textTransform.ty += [fontObject ascender] - lineHeight * [self verticalOffset]; + NSUInteger lineIndex = 0; + const NSUInteger maxGlyphCount = [self maximumGlyphCountForByteCount:bytesPerLine]; + NEW_ARRAY(struct HFGlyph_t, glyphs, maxGlyphCount); + NEW_ARRAY(CGSize, advances, maxGlyphCount); + for (lineStartIndex = 0; lineStartIndex < byteCount; lineStartIndex += bytesPerLine) { + if (lineStartIndex > 0) { + textTransform.ty += lineHeight; + lineRectInBoundsSpace.origin.y += lineHeight; + } + if (NSIntersectsRect(lineRectInBoundsSpace, clip)) { + const NSUInteger bytesInThisLine = MIN(bytesPerLine, byteCount - lineStartIndex); + + /* Draw the backgrounds of any styles. */ + [self drawStyledBackgroundsForByteRange:NSMakeRange(lineStartIndex, bytesInThisLine) inRect:lineRectInBoundsSpace]; + + NSUInteger byteIndexInLine = 0; + CGFloat advanceIntoLine = 0; + while (byteIndexInLine < bytesInThisLine) { + const NSUInteger byteIndex = lineStartIndex + byteIndexInLine; + HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:byteIndex]; + HFASSERT(styleRun != nil); + HFASSERT(byteIndex >= [styleRun range].location); + const NSUInteger bytesInThisRun = MIN(NSMaxRange([styleRun range]) - byteIndex, bytesInThisLine - byteIndexInLine); + const NSRange characterRange = [self roundPartialByteRange:NSMakeRange(byteIndex, bytesInThisRun)]; + if (characterRange.length > 0) { + NSUInteger resultGlyphCount = 0; + CGFloat initialTextOffset = 0; + if (restrictingToRanges == nil) { + [self extractGlyphsForBytes:bytePtr + characterRange.location count:characterRange.length offsetIntoLine:byteIndexInLine intoArray:glyphs advances:advances resultingGlyphCount:&resultGlyphCount]; + } + else { + [self extractGlyphsForBytes:bytePtr range:NSMakeRange(byteIndex, bytesInThisRun) intoArray:glyphs advances:advances withInclusionRanges:restrictingToRanges initialTextOffset:&initialTextOffset resultingGlyphCount:&resultGlyphCount]; + } + HFASSERT(resultGlyphCount <= maxGlyphCount); + +#if ! NDEBUG + for (NSUInteger q=0; q < resultGlyphCount; q++) { + HFASSERT(glyphs[q].fontIndex != kHFGlyphFontIndexInvalid); + } +#endif + + if (resultGlyphCount > 0) { + textTransform.tx += initialTextOffset + advanceIntoLine; + CGContextSetTextMatrix(ctx, textTransform); + /* Draw them */ + [self drawGlyphs:glyphs atPoint:NSMakePoint(textTransform.tx, textTransform.ty) withAdvances:advances withStyleRun:styleRun count:resultGlyphCount]; + + /* Undo the work we did before so as not to screw up the next run */ + textTransform.tx -= initialTextOffset + advanceIntoLine; + + /* Record how far into our line this made us move */ + NSUInteger glyphIndex; + for (glyphIndex = 0; glyphIndex < resultGlyphCount; glyphIndex++) { + advanceIntoLine += advances[glyphIndex].width; + } + } + } + byteIndexInLine += bytesInThisRun; + } + } + else if (NSMinY(lineRectInBoundsSpace) > NSMaxY(clip)) { + break; + } + lineIndex++; + } + FREE_ARRAY(glyphs); + FREE_ARRAY(advances); +} + + +- (void)drawFocusRingWithClip:(NSRect)clip { + USE(clip); + [NSGraphicsContext saveGraphicsState]; + NSSetFocusRingStyle(NSFocusRingOnly); + [[NSColor clearColor] set]; + NSRectFill([self bounds]); + [NSGraphicsContext restoreGraphicsState]; +} + +- (BOOL)shouldDrawCallouts { + return _hftvflags.drawCallouts; +} + +- (void)setShouldDrawCallouts:(BOOL)val { + _hftvflags.drawCallouts = val; + [self setNeedsDisplay:YES]; +} + +- (void)drawBookmarksWithClip:(NSRect)clip { + if([self shouldDrawCallouts]) { + /* Figure out which callouts we're going to draw */ + NSRect allCalloutsRect = NSZeroRect; + NSMutableArray *localCallouts = [[NSMutableArray alloc] initWithCapacity:[callouts count]]; + FOREACH(HFRepresenterTextViewCallout *, callout, [callouts objectEnumerator]) { + NSRect calloutRect = [callout rect]; + if (NSIntersectsRect(clip, calloutRect)) { + [localCallouts addObject:callout]; + allCalloutsRect = NSUnionRect(allCalloutsRect, calloutRect); + } + } + allCalloutsRect = NSIntersectionRect(allCalloutsRect, clip); + + if ([localCallouts count]) { + /* Draw shadows first */ + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect(allCalloutsRect), NULL); + FOREACH(HFRepresenterTextViewCallout *, callout, localCallouts) { + [callout drawShadowWithClip:clip]; + } + CGContextEndTransparencyLayer(ctx); + + FOREACH(HFRepresenterTextViewCallout *, newCallout, localCallouts) { + // NSRect rect = [callout rect]; + // [[NSColor greenColor] set]; + // NSFrameRect(rect); + [newCallout drawWithClip:clip]; + } + } + [localCallouts release]; + } +} + +- (void)drawRect:(NSRect)clip { + [[self backgroundColorForEmptySpace] set]; + NSRectFillUsingOperation(clip, NSCompositeSourceOver); + BOOL antialias = [self shouldAntialias]; + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + [[self.font screenFont] set]; + + if ([self showsFocusRing]) { + NSWindow *window = [self window]; + if (self == [window firstResponder] && [window isKeyWindow]) { + [self drawFocusRingWithClip:clip]; + } + } + + NSUInteger bytesPerLine = [self bytesPerLine]; + if (bytesPerLine == 0) return; + NSUInteger byteCount = [_data length]; + + [self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)]; + [self drawSelectionIfNecessaryWithClip:clip]; + + NSColor *textColor = [NSColor textColor]; + [textColor set]; + + if (! antialias) { + CGContextSaveGState(ctx); + CGContextSetShouldAntialias(ctx, NO); + } + [self drawTextWithClip:clip restrictingToTextInRanges:nil]; + if (! antialias) { + CGContextRestoreGState(ctx); + } + + // Vertical dividers only make sense in single byte mode. + if ([self _effectiveBytesPerColumn] == 1) { + [self drawVerticalGuideLines:clip]; + } + + [self drawCaretIfNecessaryWithClip:clip]; + + [self drawBookmarksWithClip:clip]; +} + +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)byteRange { + HFASSERT(edge == NSMinXEdge || edge == NSMaxXEdge || edge == NSMinYEdge || edge == NSMaxYEdge); + const NSUInteger bytesPerLine = [self bytesPerLine]; + CGFloat lineHeight = [self lineHeight]; + CGFloat vertOffset = [self verticalOffset]; + NSUInteger firstLine = byteRange.location / bytesPerLine, lastLine = (NSMaxRange(byteRange) - 1) / bytesPerLine; + NSRect result = NSZeroRect; + + if (edge == NSMinYEdge || edge == NSMaxYEdge) { + /* This is the top (MinY) or bottom (MaxY). We only have to look at one line. */ + NSUInteger lineIndex = (edge == NSMinYEdge ? firstLine : lastLine); + NSRange lineRange = NSMakeRange(lineIndex * bytesPerLine, bytesPerLine); + NSRange intersection = NSIntersectionRange(lineRange, byteRange); + HFASSERT(intersection.length > 0); + CGFloat yOrigin = (lineIndex - vertOffset) * lineHeight; + CGFloat xStart = [self originForCharacterAtByteIndex:intersection.location].x; + CGFloat xEnd = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter]; + result = NSMakeRect(xStart, yOrigin, xEnd - xStart, 0); + } + else { + if (firstLine == lastLine) { + /* We only need to consider this one line */ + NSRange lineRange = NSMakeRange(firstLine * bytesPerLine, bytesPerLine); + NSRange intersection = NSIntersectionRange(lineRange, byteRange); + HFASSERT(intersection.length > 0); + CGFloat yOrigin = (firstLine - vertOffset) * lineHeight; + CGFloat xCoord; + if (edge == NSMinXEdge) { + xCoord = [self originForCharacterAtByteIndex:intersection.location].x; + } + else { + xCoord = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter]; + } + result = NSMakeRect(xCoord, yOrigin, 0, lineHeight); + } + else { + /* We have more than one line. If we are asking for the left edge, sum up the left edge of every line but the first, and handle the first specially. Likewise for the right edge (except handle the last specially) */ + BOOL includeFirstLine, includeLastLine; + CGFloat xCoord; + if (edge == NSMinXEdge) { + /* Left edge, include the first line only if it starts at the beginning of the line or there's only one line */ + includeFirstLine = (byteRange.location % bytesPerLine == 0); + includeLastLine = YES; + xCoord = [self horizontalContainerInset]; + } + else { + /* Right edge, include the last line only if it starts at the beginning of the line or there's only one line */ + includeFirstLine = YES; + includeLastLine = (NSMaxRange(byteRange) % bytesPerLine == 0); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + /* Don't add in space for the advance after the last column, hence subtract 1. */ + NSUInteger numColumns = (bytesPerColumn ? (bytesPerLine / bytesPerColumn - 1) : 0); + xCoord = [self horizontalContainerInset] + ([self advancePerCharacter] * bytesPerLine / [self bytesPerCharacter]) + [self advanceBetweenColumns] * numColumns; + } + NSUInteger firstLineToInclude = (includeFirstLine ? firstLine : firstLine + 1), lastLineToInclude = (includeLastLine ? lastLine : lastLine - 1); + result = NSMakeRect(xCoord, (firstLineToInclude - [self verticalOffset]) * lineHeight, 0, (lastLineToInclude - firstLineToInclude + 1) * lineHeight); + } + } + return result; +} + +- (NSUInteger)availableLineCount { + CGFloat result = (CGFloat)ceil(NSHeight([self bounds]) / [self lineHeight]); + HFASSERT(result >= 0.); + HFASSERT(result <= NSUIntegerMax); + return (NSUInteger)result; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + return viewHeight / [self lineHeight]; +} + +- (void)setFrameSize:(NSSize)size { + NSUInteger currentBytesPerLine = [self bytesPerLine]; + double currentLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])]; + [super setFrameSize:size]; + NSUInteger newBytesPerLine = [self maximumBytesPerLineForViewWidth:size.width]; + double newLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])]; + HFControllerPropertyBits bits = 0; + if (newBytesPerLine != currentBytesPerLine) bits |= (HFControllerBytesPerLine | HFControllerDisplayedLineRange); + if (newLineCount != currentLineCount) bits |= HFControllerDisplayedLineRange; + if (bits) [[self representer] representerChangedProperties:bits]; +} + +- (CGFloat)advanceBetweenColumns { + UNIMPLEMENTED(); +} + +- (CGFloat)advancePerCharacter { + UNIMPLEMENTED(); +} + +- (CGFloat)advancePerColumn { + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + if (bytesPerColumn == 0) { + return 0; + } + else { + return [self advancePerCharacter] * (bytesPerColumn / [self bytesPerCharacter]) + [self advanceBetweenColumns]; + } +} + +- (CGFloat)totalAdvanceForBytesInRange:(NSRange)range { + if (range.length == 0) return 0; + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + HFASSERT(bytesPerColumn == 0 || [self bytesPerLine] % bytesPerColumn == 0); + CGFloat result = (range.length * [self advancePerCharacter] / [self bytesPerCharacter]) ; + if (bytesPerColumn > 0) { + NSUInteger numColumnSpaces = NSMaxRange(range) / bytesPerColumn - range.location / bytesPerColumn; //note that integer division does not distribute + result += numColumnSpaces * [self advanceBetweenColumns]; + } + return result; +} + +/* Returns the number of bytes in a character, e.g. if we are UTF-16 this would be 2. */ +- (NSUInteger)bytesPerCharacter { + return 1; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + CGFloat availableSpace = (CGFloat)(viewWidth - 2. * [self horizontalContainerInset]); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn], bytesPerCharacter = [self bytesPerCharacter]; + if (bytesPerColumn == 0) { + /* No columns */ + NSUInteger numChars = (NSUInteger)(availableSpace / [self advancePerCharacter]); + /* Return it, except it's at least one character */ + return MAX(numChars, 1u) * bytesPerCharacter; + } + else { + /* We have some columns */ + CGFloat advancePerColumn = [self advancePerColumn]; + //spaceRequiredForNColumns = N * (advancePerColumn) - spaceBetweenColumns + CGFloat fractionalColumns = (availableSpace + [self advanceBetweenColumns]) / advancePerColumn; + NSUInteger columnCount = (NSUInteger)fmax(1., HFFloor(fractionalColumns)); + return columnCount * bytesPerColumn; + } +} + + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + HFASSERT(bytesPerLine > 0); + NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn]; + CGFloat result; + if (bytesPerColumn == 0) { + result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerCharacter] * (bytesPerLine / [self bytesPerCharacter])); + } + else { + HFASSERT(bytesPerLine % bytesPerColumn == 0); + result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerColumn] * (bytesPerLine / bytesPerColumn) - [self advanceBetweenColumns]); + } + return result; +} + +- (BOOL)isEditable { + return _hftvflags.editable; +} + +- (void)setEditable:(BOOL)val { + if (val != _hftvflags.editable) { + _hftvflags.editable = val; + [self _updateCaretTimer]; + } +} + +- (BOOL)shouldAntialias { + return _hftvflags.antialias; +} + +- (void)setShouldAntialias:(BOOL)val { + _hftvflags.antialias = !!val; + [self setNeedsDisplay:YES]; +} + +- (BOOL)behavesAsTextField { + return [[self representer] behavesAsTextField]; +} + +- (BOOL)showsFocusRing { + return [[self representer] behavesAsTextField]; +} + +- (BOOL)isWithinMouseDown { + return _hftvflags.withinMouseDown; +} + +- (void)_windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self _updateCaretTimer]; + if ([[note name] isEqualToString:NSWindowDidBecomeKeyNotification]) { + [self _forceCaretOnIfHasCaretTimer]; + } + if ([self showsFocusRing] && self == [[self window] firstResponder]) { + [[self superview] setNeedsDisplayInRect:NSInsetRect([self frame], -6, -6)]; + } + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + [self _updateCaretTimer]; + HFRegisterViewForWindowAppearanceChanges(self, @selector(_windowDidChangeKeyStatus:), ! _hftvflags.registeredForAppNotifications); + _hftvflags.registeredForAppNotifications = YES; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO /* appToo */); + [super viewWillMoveToWindow:newWindow]; +} + +/* Computes the character at the given index for selection, properly handling the case where the point is outside the bounds */ +- (NSUInteger)characterAtPointForSelection:(NSPoint)point { + NSPoint mungedPoint = point; + // shift us right by half an advance so that we trigger at the midpoint of each character, rather than at the x origin + mungedPoint.x += [self advancePerCharacter] / (CGFloat)2.; + // make sure we're inside the bounds + const NSRect bounds = [self bounds]; + mungedPoint.x = HFMax(NSMinX(bounds), mungedPoint.x); + mungedPoint.x = HFMin(NSMaxX(bounds), mungedPoint.x); + mungedPoint.y = HFMax(NSMinY(bounds), mungedPoint.y); + mungedPoint.y = HFMin(NSMaxY(bounds), mungedPoint.y); + return [self indexOfCharacterAtPoint:mungedPoint]; +} + +- (NSUInteger)maximumCharacterIndex { + //returns the maximum character index that the selection may lie on. It is one beyond the last byte index, to represent the cursor at the end of the document. + return [[self data] length] / [self bytesPerCharacter]; +} + +- (void)mouseDown:(NSEvent *)event { + HFASSERT(_hftvflags.withinMouseDown == 0); + _hftvflags.withinMouseDown = 1; + [self _forceCaretOnIfHasCaretTimer]; + NSPoint mouseDownLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:mouseDownLocation]; + + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); //characterIndex may be one beyond the last index, to represent the cursor at the end of the document + [[self representer] beginSelectionWithEvent:event forCharacterIndex:characterIndex]; + + /* Drive the event loop in event tracking mode until we're done */ + HFASSERT(_hftvflags.receivedMouseUp == NO); //paranoia - detect any weird recursive invocations + NSDate *endDate = [NSDate distantFuture]; + + /* Start periodic events for autoscroll */ + [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05]; + + NSPoint autoscrollLocation = mouseDownLocation; + while (! _hftvflags.receivedMouseUp) { + @autoreleasepool { + NSEvent *ev = [NSApp nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask untilDate:endDate inMode:NSEventTrackingRunLoopMode dequeue:YES]; + + if ([ev type] == NSPeriodic) { + // autoscroll if drag is out of view bounds + CGFloat amountToScroll = 0; + NSRect bounds = [self bounds]; + if (autoscrollLocation.y < NSMinY(bounds)) { + amountToScroll = (autoscrollLocation.y - NSMinY(bounds)) / [self lineHeight]; + } + else if (autoscrollLocation.y > NSMaxY(bounds)) { + amountToScroll = (autoscrollLocation.y - NSMaxY(bounds)) / [self lineHeight]; + } + if (amountToScroll != 0.) { + [[[self representer] controller] scrollByLines:amountToScroll]; + characterIndex = [self characterAtPointForSelection:autoscrollLocation]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] continueSelectionWithEvent:ev forCharacterIndex:characterIndex]; + } + } + else if ([ev type] == NSLeftMouseDragged) { + autoscrollLocation = [self convertPoint:[ev locationInWindow] fromView:nil]; + } + + [NSApp sendEvent:ev]; + } // @autoreleasepool + } + + [NSEvent stopPeriodicEvents]; + + _hftvflags.receivedMouseUp = NO; + _hftvflags.withinMouseDown = 0; +} + +- (void)mouseDragged:(NSEvent *)event { + if (! _hftvflags.withinMouseDown) return; + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:location]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] continueSelectionWithEvent:event forCharacterIndex:characterIndex]; +} + +- (void)mouseUp:(NSEvent *)event { + if (! _hftvflags.withinMouseDown) return; + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + NSUInteger characterIndex = [self characterAtPointForSelection:location]; + characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); + [[self representer] endSelectionWithEvent:event forCharacterIndex:characterIndex]; + _hftvflags.receivedMouseUp = YES; +} + +- (void)keyDown:(NSEvent *)event { + HFASSERT(event != NULL); + [self interpretKeyEvents:@[event]]; +} + +- (void)scrollWheel:(NSEvent *)event { + [[self representer] scrollWheel:event]; +} + +- (void)insertText:(id)string { + if (! [self isEditable]) { + NSBeep(); + } + else { + if ([string isKindOfClass:[NSAttributedString class]]) string = [string string]; + [NSCursor setHiddenUntilMouseMoves:YES]; + [[self representer] insertText:string]; + } +} + +- (BOOL)handleCommand:(SEL)sel { + if (sel == @selector(insertTabIgnoringFieldEditor:)) { + [self insertText:@"\t"]; + } + else if ([self respondsToSelector:sel]) { + [self performSelector:sel withObject:nil]; + } + else { + return NO; + } + return YES; +} + +- (void)doCommandBySelector:(SEL)sel { + HFRepresenter *rep = [self representer]; + // NSLog(@"%s%s", _cmd, sel); + if ([self handleCommand:sel]) { + /* Nothing to do */ + } + else if ([rep respondsToSelector:sel]) { + [rep performSelector:sel withObject:self]; + } + else { + [super doCommandBySelector:sel]; + } +} + +- (IBAction)selectAll:sender { + [[self representer] selectAll:sender]; +} + +/* Indicates whether at least one byte is selected */ +- (BOOL)_selectionIsNonEmpty { + NSArray *selection = [[[self representer] controller] selectedContentsRanges]; + FOREACH(HFRangeWrapper *, rangeWrapper, selection) { + if ([rangeWrapper HFRange].length > 0) return YES; + } + return NO; +} + +- (SEL)_pasteboardOwnerStringTypeWritingSelector { + UNIMPLEMENTED(); +} + +- (void)paste:sender { + if (! [self isEditable]) { + NSBeep(); + } + else { + USE(sender); + [[self representer] pasteBytesFromPasteboard:[NSPasteboard generalPasteboard]]; + } +} + +- (void)copy:sender { + USE(sender); + [[self representer] copySelectedBytesToPasteboard:[NSPasteboard generalPasteboard]]; +} + +- (void)cut:sender { + USE(sender); + [[self representer] cutSelectedBytesToPasteboard:[NSPasteboard generalPasteboard]]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)item { + SEL action = [item action]; + if (action == @selector(selectAll:)) return YES; + else if (action == @selector(cut:)) return [[self representer] canCut]; + else if (action == @selector(copy:)) return [self _selectionIsNonEmpty]; + else if (action == @selector(paste:)) return [[self representer] canPasteFromPasteboard:[NSPasteboard generalPasteboard]]; + else return YES; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.h new file mode 100644 index 000000000..42ae7e3ae --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.h @@ -0,0 +1,31 @@ +// +// HFRepresenterTextViewCallout.h +// HexFiend_2 +// +// Copyright 2011 ridiculous_fish. All rights reserved. +// + +#import + +@class HFRepresenterTextView; + +#define kHFRepresenterTextViewCalloutMaxGlyphCount 2u + +@interface HFRepresenterTextViewCallout : NSObject { + CGFloat rotation; + NSPoint tipOrigin; + NSPoint pinStart, pinEnd; +} + +@property(nonatomic) NSInteger byteOffset; +@property(nonatomic, copy) NSColor *color; +@property(nonatomic, copy) NSString *label; +@property(nonatomic, retain) id representedObject; +@property(readonly) NSRect rect; + ++ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView; + +- (void)drawShadowWithClip:(NSRect)clip; +- (void)drawWithClip:(NSRect)clip; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.m b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.m new file mode 100644 index 000000000..bb4b58e89 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextViewCallout.m @@ -0,0 +1,477 @@ +// +// HFRepresenterTextViewCallout.m +// HexFiend_2 +// +// Copyright 2011 ridiculous_fish. All rights reserved. +// + +#import "HFRepresenterTextViewCallout.h" +#import "HFRepresenterTextView.h" + +static const CGFloat HFTeardropRadius = 12; +static const CGFloat HFTeadropTipScale = 2.5; + +static const CGFloat HFShadowXOffset = -6; +static const CGFloat HFShadowYOffset = 0; +static const CGFloat HFShadowOffscreenHack = 3100; + +static NSPoint rotatePoint(NSPoint center, NSPoint point, CGFloat percent) { + CGFloat radians = percent * M_PI * 2; + CGFloat x = point.x - center.x; + CGFloat y = point.y - center.y; + CGFloat newX = x * cos(radians) + y * sin(radians); + CGFloat newY = x * -sin(radians) + y * cos(radians); + return NSMakePoint(center.x + newX, center.y + newY); +} + +static NSPoint scalePoint(NSPoint center, NSPoint point, CGFloat percent) { + CGFloat x = point.x - center.x; + CGFloat y = point.y - center.y; + CGFloat newX = x * percent; + CGFloat newY = y * percent; + return NSMakePoint(center.x + newX, center.y + newY); +} + +static NSBezierPath *copyTeardropPath(void) { + static NSBezierPath *sPath = nil; + if (! sPath) { + + CGFloat radius = HFTeardropRadius; + CGFloat rotation = 0; + CGFloat droppiness = .15; + CGFloat tipScale = HFTeadropTipScale; + CGFloat tipLengthFromCenter = radius * tipScale; + NSPoint bulbCenter = NSMakePoint(-tipLengthFromCenter, 0); + + NSPoint triangleCenter = rotatePoint(bulbCenter, NSMakePoint(bulbCenter.x + radius, bulbCenter.y), rotation); + NSPoint dropCorner1 = rotatePoint(bulbCenter, triangleCenter, droppiness / 2); + NSPoint dropCorner2 = rotatePoint(bulbCenter, triangleCenter, -droppiness / 2); + NSPoint dropTip = scalePoint(bulbCenter, triangleCenter, tipScale); + + NSBezierPath *path = [[NSBezierPath alloc] init]; + [path appendBezierPathWithArcWithCenter:bulbCenter radius:radius startAngle:-rotation * 360 + droppiness * 180. endAngle:-rotation * 360 - droppiness * 180. clockwise:NO]; + + [path moveToPoint:dropCorner1]; + [path lineToPoint:dropTip]; + [path lineToPoint:dropCorner2]; + [path closePath]; + + sPath = path; + } + return [sPath retain]; +} + + +@implementation HFRepresenterTextViewCallout + +/* A helpful struct for representing a wedge (portion of a circle). Wedges are counterclockwise. */ +typedef struct { + double offset; // 0 <= offset < 1 + double length; // 0 <= length <= 1 +} Wedge_t; + + +static inline double normalizeAngle(double x) { + /* Convert an angle to the range [0, 1). We typically only generate angles that are off by a full rotation, so a loop isn't too bad. */ + while (x >= 1.) x -= 1.; + while (x < 0.) x += 1.; + return x; +} + +static inline double distanceCCW(double a, double b) { return normalizeAngle(b-a); } + +static inline double wedgeMax(Wedge_t wedge) { + return normalizeAngle(wedge.offset + wedge.length); +} + +/* Computes the smallest wedge containing the two given wedges. Compute the wedge from the min of one to the furthest part of the other, and pick the smaller. */ +static Wedge_t wedgeUnion(Wedge_t wedge1, Wedge_t wedge2) { + // empty wedges don't participate + if (wedge1.length <= 0) return wedge2; + if (wedge2.length <= 0) return wedge1; + + Wedge_t union1 = wedge1; + union1.length = fmin(1., fmax(union1.length, distanceCCW(union1.offset, wedge2.offset) + wedge2.length)); + + Wedge_t union2 = wedge2; + union2.length = fmin(1., fmax(union2.length, distanceCCW(union2.offset, wedge1.offset) + wedge1.length)); + + Wedge_t result = (union1.length <= union2.length ? union1 : union2); + HFASSERT(result.length <= 1); + return result; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)dealloc { + [_representedObject release]; + [_color release]; + [_label release]; + [super dealloc]; +} + +- (NSComparisonResult)compare:(HFRepresenterTextViewCallout *)callout { + return [_representedObject compare:callout.representedObject]; +} + +static Wedge_t computeForbiddenAngle(double distanceFromEdge, double angleToEdge) { + Wedge_t newForbiddenAngle; + + /* This is how far it is to the center of our teardrop */ + const double teardropLength = HFTeardropRadius * HFTeadropTipScale; + + if (distanceFromEdge <= 0) { + /* We're above or below. */ + if (-distanceFromEdge >= (teardropLength + HFTeardropRadius)) { + /* We're so far above or below we won't be visible at all. No hope. */ + newForbiddenAngle = (Wedge_t){.offset = 0, .length = 1}; + } else { + /* We're either above or below the bounds, but there's a hope we can be visible */ + + double invertedAngleToEdge = normalizeAngle(angleToEdge + .5); + double requiredAngle; + if (-distanceFromEdge >= teardropLength) { + // We're too far north or south that all we can do is point in the right direction + requiredAngle = 0; + } else { + // By confining ourselves to required angles, we can make ourselves visible + requiredAngle = acos(-distanceFromEdge / teardropLength) / (2 * M_PI); + } + // Require at least a small spread + requiredAngle = fmax(requiredAngle, .04); + + double requiredMin = invertedAngleToEdge - requiredAngle; + double requiredMax = invertedAngleToEdge + requiredAngle; + + newForbiddenAngle = (Wedge_t){.offset = requiredMax, .length = distanceCCW(requiredMax, requiredMin) }; + } + } else if (distanceFromEdge < teardropLength) { + // We're onscreen, but some angle will be forbidden + double forbiddenAngle = acos(distanceFromEdge / teardropLength) / (2 * M_PI); + + // This is a wedge out of the top (or bottom) + newForbiddenAngle = (Wedge_t){.offset = angleToEdge - forbiddenAngle, .length = 2 * forbiddenAngle}; + } else { + /* Nothing prohibited at all */ + newForbiddenAngle = (Wedge_t){0, 0}; + } + return newForbiddenAngle; +} + + +static double distanceMod1(double a, double b) { + /* Assuming 0 <= a, b < 1, returns the distance between a and b, mod 1 */ + if (a > b) { + return fmin(a-b, b-a+1); + } else { + return fmin(b-a, a-b+1); + } +} + ++ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView { + + // Keep track of how many drops are at a given location + NSCountedSet *dropsPerByteLoc = [[NSCountedSet alloc] init]; + + const CGFloat lineHeight = [textView lineHeight]; + const NSRect bounds = [textView bounds]; + + NSMutableArray *remainingCallouts = [[callouts mutableCopy] autorelease]; + [remainingCallouts sortUsingSelector:@selector(compare:)]; + + while ([remainingCallouts count] > 0) { + /* Get the next callout to lay out */ + const NSInteger byteLoc = [remainingCallouts[0] byteOffset]; + + /* Get all the callouts that share that byteLoc */ + NSMutableArray *sharedCallouts = [NSMutableArray array]; + FOREACH(HFRepresenterTextViewCallout *, testCallout, remainingCallouts) { + if ([testCallout byteOffset] == byteLoc) { + [sharedCallouts addObject:testCallout]; + } + } + + /* We expect to get at least one */ + const NSUInteger calloutCount = [sharedCallouts count]; + HFASSERT(calloutCount > 0); + + /* Get the character origin */ + const NSPoint characterOrigin = [textView originForCharacterAtByteIndex:byteLoc]; + + Wedge_t forbiddenAngle = {0, 0}; + + // Compute how far we are from the top (or bottom) + BOOL isNearerTop = (characterOrigin.y < NSMidY(bounds)); + double verticalDistance = (isNearerTop ? characterOrigin.y - NSMinY(bounds) : NSMaxY(bounds) - characterOrigin.y); + forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(verticalDistance, (isNearerTop ? .25 : .75))); + + // Compute how far we are from the left (or right) + BOOL isNearerLeft = (characterOrigin.x < NSMidX(bounds)); + double horizontalDistance = (isNearerLeft ? characterOrigin.x - NSMinX(bounds) : NSMaxX(bounds) - characterOrigin.x); + forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(horizontalDistance, (isNearerLeft ? .5 : 0.))); + + + /* How much will each callout rotate? No more than 1/8th. */ + HFASSERT(forbiddenAngle.length <= 1); + double changeInRotationPerCallout = fmin(.125, (1. - forbiddenAngle.length) / calloutCount); + double totalConsumedAmount = changeInRotationPerCallout * calloutCount; + + /* We would like to center around .375. */ + const double goalCenter = .375; + + /* We're going to pretend to work on a line segment that extends from the max prohibited angle all the way back to min */ + double segmentLength = 1. - forbiddenAngle.length; + double goalSegmentCenter = normalizeAngle(goalCenter - wedgeMax(forbiddenAngle)); //may exceed segmentLength! + + /* Now center us on the goal, or as close as we can get. */ + double consumedSegmentCenter; + + /* We only need to worry about wrapping around if we have some prohibited angle */ + if (forbiddenAngle.length <= 0) { //never expect < 0, but be paranoid + consumedSegmentCenter = goalSegmentCenter; + } else { + + /* The consumed segment center is confined to the segment range [amount/2, length - amount/2] */ + double consumedSegmentCenterMin = totalConsumedAmount/2; + double consumedSegmentCenterMax = segmentLength - totalConsumedAmount/2; + if (goalSegmentCenter >= consumedSegmentCenterMin && goalSegmentCenter < consumedSegmentCenterMax) { + /* We can hit our goal */ + consumedSegmentCenter = goalSegmentCenter; + } else { + /* Pick either the min or max location, depending on which one gets us closer to the goal segment center mod 1. */ + if (distanceMod1(goalSegmentCenter, consumedSegmentCenterMin) <= distanceMod1(goalSegmentCenter, consumedSegmentCenterMax)) { + consumedSegmentCenter = consumedSegmentCenterMin; + } else { + consumedSegmentCenter = consumedSegmentCenterMax; + } + + } + } + + /* Now convert this back to an angle */ + double consumedAngleCenter = normalizeAngle(wedgeMax(forbiddenAngle) + consumedSegmentCenter); + + // move us slightly towards the character + NSPoint teardropTipOrigin = NSMakePoint(characterOrigin.x + 1, characterOrigin.y + floor(lineHeight / 8.)); + + // make the pin + NSPoint pinStart, pinEnd; + pinStart = NSMakePoint(characterOrigin.x + .25, characterOrigin.y); + pinEnd = NSMakePoint(pinStart.x, pinStart.y + lineHeight); + + // store it all, invalidating as necessary + NSInteger i = 0; + FOREACH(HFRepresenterTextViewCallout *, callout, sharedCallouts) { + + /* Compute the rotation */ + double seq = (i+1)/2; //0, 1, -1, 2, -2... + if ((i & 1) == 0) seq = -seq; + //if we've got an even number of callouts, we want -.5, .5, -1.5, 1.5... + if (! (calloutCount & 1)) seq -= .5; + // compute the angle of rotation + double angle = consumedAngleCenter + seq * changeInRotationPerCallout; + // our notion of rotation has 0 meaning pointing right and going counterclockwise, but callouts with 0 pointing left and going clockwise, so convert + angle = normalizeAngle(.5 - angle); + + + NSRect beforeRect = [callout rect]; + + callout->rotation = angle; + callout->tipOrigin = teardropTipOrigin; + callout->pinStart = pinStart; + callout->pinEnd = pinEnd; + + // Only the first gets a pin + pinStart = pinEnd = NSZeroPoint; + + NSRect afterRect = [callout rect]; + + if (! NSEqualRects(beforeRect, afterRect)) { + [textView setNeedsDisplayInRect:beforeRect]; + [textView setNeedsDisplayInRect:afterRect]; + } + + i++; + } + + + /* We're done laying out these callouts */ + [remainingCallouts removeObjectsInArray:sharedCallouts]; + } + + [dropsPerByteLoc release]; +} + +- (CGAffineTransform)teardropTransform { + CGAffineTransform trans = CGAffineTransformMakeTranslation(tipOrigin.x, tipOrigin.y); + trans = CGAffineTransformRotate(trans, rotation * M_PI * 2); + return trans; +} + +- (NSRect)teardropBaseRect { + NSSize teardropSize = NSMakeSize(HFTeardropRadius * (1 + HFTeadropTipScale), HFTeardropRadius*2); + NSRect result = NSMakeRect(-teardropSize.width, -teardropSize.height/2, teardropSize.width, teardropSize.height); + return result; +} + +- (CGAffineTransform)shadowTransform { + CGFloat shadowXOffset = HFShadowXOffset; + CGFloat shadowYOffset = HFShadowYOffset; + CGFloat offscreenOffset = HFShadowOffscreenHack; + + // Figure out how much movement the shadow offset produces + CGFloat shadowTranslationDistance = hypot(shadowXOffset, shadowYOffset); + + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformTranslate(transform, tipOrigin.x + offscreenOffset - shadowXOffset, tipOrigin.y - shadowYOffset); + transform = CGAffineTransformRotate(transform, rotation * M_PI * 2 - atan2(shadowTranslationDistance, 2*HFTeardropRadius /* bulbHeight */)); + return transform; +} + +- (void)drawShadowWithClip:(NSRect)clip { + USE(clip); + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + + // Set the shadow. Note that these shadows are pretty unphysical for high rotations. + NSShadow *shadow = [[NSShadow alloc] init]; + [shadow setShadowBlurRadius:5.]; + [shadow setShadowOffset:NSMakeSize(HFShadowXOffset - HFShadowOffscreenHack, HFShadowYOffset)]; + [shadow setShadowColor:[NSColor colorWithDeviceWhite:0. alpha:.5]]; + [shadow set]; + [shadow release]; + + // Draw the shadow first and separately + CGAffineTransform transform = [self shadowTransform]; + CGContextConcatCTM(ctx, transform); + + NSBezierPath *teardrop = copyTeardropPath(); + [teardrop fill]; + [teardrop release]; + + // Clear the shadow + CGContextSetShadowWithColor(ctx, CGSizeZero, 0, NULL); + + // Undo the transform + CGContextConcatCTM(ctx, CGAffineTransformInvert(transform)); +} + +- (void)drawWithClip:(NSRect)clip { + USE(clip); + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + // Here's the font we'll use + CTFontRef ctfont = CTFontCreateWithName(CFSTR("Helvetica-Bold"), 1., NULL); + if (ctfont) { + // Set the font + [(NSFont *)ctfont set]; + + // Get characters + NSUInteger labelLength = MIN([_label length], kHFRepresenterTextViewCalloutMaxGlyphCount); + UniChar calloutUniLabel[kHFRepresenterTextViewCalloutMaxGlyphCount]; + [_label getCharacters:calloutUniLabel range:NSMakeRange(0, labelLength)]; + + // Get our glyphs and advances + CGGlyph glyphs[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CGSize advances[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CTFontGetGlyphsForCharacters(ctfont, calloutUniLabel, glyphs, labelLength); + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, glyphs, advances, labelLength); + + // Count our glyphs. Note: this won't work with any label containing spaces, etc. + NSUInteger glyphCount; + for (glyphCount = 0; glyphCount < labelLength; glyphCount++) { + if (glyphs[glyphCount] == 0) break; + } + + // Set our color. + [_color set]; + + // Draw the pin first + if (! NSEqualPoints(pinStart, pinEnd)) { + [NSBezierPath setDefaultLineWidth:1.25]; + [NSBezierPath strokeLineFromPoint:pinStart toPoint:pinEnd]; + } + + CGContextSaveGState(ctx); + CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect([self rect]), NULL); + + // Rotate and translate in preparation for drawing the teardrop + CGContextConcatCTM(ctx, [self teardropTransform]); + + // Draw the teardrop + NSBezierPath *teardrop = copyTeardropPath(); + [teardrop fill]; + [teardrop release]; + + // Draw the text with white and alpha. Use blend mode copy so that we clip out the shadow, and when the transparency layer is ended we'll composite over the text. + CGFloat textScale = (glyphCount == 1 ? 24 : 20); + + // we are flipped by default, so invert the rotation's sign to get the text direction. Use a little slop so we don't get jitter. + const CGFloat textDirection = (rotation <= .27 || rotation >= .73) ? -1 : 1; + + CGPoint positions[kHFRepresenterTextViewCalloutMaxGlyphCount]; + CGFloat totalAdvance = 0; + for (NSUInteger i=0; i < glyphCount; i++) { + // make sure to provide negative advances if necessary + positions[i].x = copysign(totalAdvance, -textDirection); + positions[i].y = 0; + CGFloat advance = advances[i].width; + // Workaround 5834794 + advance *= textScale; + // Tighten up the advances a little + advance *= .85; + totalAdvance += advance; + } + + + // Compute the vertical offset + CGFloat textYOffset = (glyphCount == 1 ? 4 : 5); + // LOL + if ([_label isEqualToString:@"6"]) textYOffset -= 1; + + + // Apply this text matrix + NSRect bulbRect = [self teardropBaseRect]; + CGAffineTransform textMatrix = CGAffineTransformMakeScale(-copysign(textScale, textDirection), copysign(textScale, textDirection)); //roughly the font size we want + textMatrix.tx = NSMinX(bulbRect) + HFTeardropRadius + copysign(totalAdvance/2, textDirection); + + + if (textDirection < 0) { + textMatrix.ty = NSMaxY(bulbRect) - textYOffset; + } else { + textMatrix.ty = NSMinY(bulbRect) + textYOffset; + } + + // Draw + CGContextSetTextMatrix(ctx, textMatrix); + CGContextSetTextDrawingMode(ctx, kCGTextClip); + CGContextShowGlyphsAtPositions(ctx, glyphs, positions, glyphCount); + + CGContextSetBlendMode(ctx, kCGBlendModeCopy); + CGContextSetGrayFillColor(ctx, 1., .66); //faint white fill + CGContextFillRect(ctx, NSRectToCGRect(NSInsetRect(bulbRect, -20, -20))); + + // Done drawing, so composite + CGContextEndTransparencyLayer(ctx); + CGContextRestoreGState(ctx); // this also restores the clip, which is important + + // Done with the font + CFRelease(ctfont); + } +} + +- (NSRect)rect { + // get the transformed teardrop rect + NSRect result = NSRectFromCGRect(CGRectApplyAffineTransform(NSRectToCGRect([self teardropBaseRect]), [self teardropTransform])); + + // outset a bit for the shadow + result = NSInsetRect(result, -8, -8); + return result; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView_Internal.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView_Internal.h new file mode 100644 index 000000000..70eed2338 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenterTextView_Internal.h @@ -0,0 +1,11 @@ +#import + +#define GLYPH_BUFFER_SIZE 16u + +@interface HFRepresenterTextView (HFInternal) + +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)textView glyphs:(CGGlyph *)glyphs; +- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs; +- (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)font; //uses CoreText. Here glyphs must have space for [string length] glyphs. + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFRepresenter_Internal.h b/thirdparty/SameBoy-old/HexFiend/HFRepresenter_Internal.h new file mode 100644 index 000000000..9a0b704a7 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFRepresenter_Internal.h @@ -0,0 +1,7 @@ +#import + +@interface HFRepresenter (HFInternalStuff) + +- (void)_setController:(HFController *)controller; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.h b/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.h new file mode 100644 index 000000000..204492dff --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.h @@ -0,0 +1,32 @@ +// +// HFSharedMemoryByteSlice.h +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFSharedMemoryByteSlice + @brief A subclass of HFByteSlice for working with data stored in memory. + + HFSharedMemoryByteSlice is a subclass of HFByteSlice that represents a portion of data from memory, e.g. typed or pasted in by the user. The term "shared" refers to the ability for mutiple HFSharedMemoryByteSlices to reference the same NSData; it does not mean that the data is in shared memory or shared between processes. + + Instances of HFSharedMemoryByteSlice are immutable (like all instances of HFByteSlice). However, to support efficient typing, the backing data is an instance of NSMutableData that may be grown. A referenced range of the NSMutableData will never have its contents changed, but it may be allowed to grow larger, so that the data does not have to be copied merely to append a single byte. This is implemented by overriding the -byteSliceByAppendingSlice: method of HFByteSlice. +*/ +@interface HFSharedMemoryByteSlice : HFByteSlice { + NSMutableData *data; + NSUInteger offset; + NSUInteger length; + unsigned char inlineTailLength; + unsigned char inlineTail[15]; //size chosen to exhaust padding of 32-byte allocator +} + +// copies the data +- (instancetype)initWithUnsharedData:(NSData *)data; + +// retains, does not copy +- (instancetype)initWithData:(NSMutableData *)data; +- (instancetype)initWithData:(NSMutableData *)data offset:(NSUInteger)offset length:(NSUInteger)length; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.m b/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.m new file mode 100644 index 000000000..fe7f43cc6 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFSharedMemoryByteSlice.m @@ -0,0 +1,209 @@ +// +// HFSharedMemoryByteSlice.m +// HexFiend_2 +// +// Copyright 2008 ridiculous_fish. All rights reserved. +// + +#import +#import + +#define MAX_FAST_PATH_SIZE (1 << 13) + +#define MAX_TAIL_LENGTH (sizeof ((HFSharedMemoryByteSlice *)NULL)->inlineTail / sizeof *((HFSharedMemoryByteSlice *)NULL)->inlineTail) + +@implementation HFSharedMemoryByteSlice + +- (instancetype)initWithUnsharedData:(NSData *)unsharedData { + self = [super init]; + REQUIRE_NOT_NULL(unsharedData); + NSUInteger dataLength = [unsharedData length]; + NSUInteger inlineAmount = MIN(dataLength, MAX_TAIL_LENGTH); + NSUInteger sharedAmount = dataLength - inlineAmount; + HFASSERT(inlineAmount <= UCHAR_MAX); + inlineTailLength = (unsigned char)inlineAmount; + length = sharedAmount; + if (inlineAmount > 0) { + [unsharedData getBytes:inlineTail range:NSMakeRange(dataLength - inlineAmount, inlineAmount)]; + } + if (sharedAmount > 0) { + data = [[NSMutableData alloc] initWithBytes:[unsharedData bytes] length:sharedAmount]; + } + return self; +} + +// retains, does not copy +- (instancetype)initWithData:(NSMutableData *)dat { + REQUIRE_NOT_NULL(dat); + return [self initWithData:dat offset:0 length:[dat length]]; +} + +- (instancetype)initWithData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len { + self = [super init]; + REQUIRE_NOT_NULL(dat); + HFASSERT(off + len >= off); //check for overflow + HFASSERT(off + len <= [dat length]); + offset = off; + length = len; + data = [dat retain]; + return self; +} + +- (instancetype)initWithSharedData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len tail:(const void *)tail tailLength:(NSUInteger)tailLen { + self = [super init]; + if (off || len) REQUIRE_NOT_NULL(dat); + if (tailLen) REQUIRE_NOT_NULL(tail); + HFASSERT(tailLen <= MAX_TAIL_LENGTH); + HFASSERT(off + len >= off); + HFASSERT(off + len <= [dat length]); + offset = off; + length = len; + data = [dat retain]; + HFASSERT(tailLen <= UCHAR_MAX); + inlineTailLength = (unsigned char)tailLen; + memcpy(inlineTail, tail, tailLen); + HFASSERT([self length] == tailLen + len); + return self; +} + +- (void)dealloc { + [data release]; + [super dealloc]; +} + +- (unsigned long long)length { + return length + inlineTailLength; +} + +- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange { + HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange)); + NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length)); + NSRange dataRange = NSMakeRange(0, length); + NSRange tailRange = NSMakeRange(length, inlineTailLength); + NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange); + NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange); + HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length); + + if (dataRangeToCopy.length > 0) { + HFASSERT(HFSum(NSMaxRange(dataRangeToCopy), offset) <= [data length]); + const void *bytes = [data bytes]; + memcpy(dst, bytes + dataRangeToCopy.location + offset, dataRangeToCopy.length); + } + if (tailRangeToCopy.length > 0) { + HFASSERT(tailRangeToCopy.location >= length); + HFASSERT(NSMaxRange(tailRangeToCopy) - length <= inlineTailLength); + memcpy(dst + dataRangeToCopy.length, inlineTail + tailRangeToCopy.location - length, tailRangeToCopy.length); + } +} + +- (HFByteSlice *)subsliceWithRange:(HFRange)lrange { + if (HFRangeEqualsRange(lrange, HFRangeMake(0, HFSum(length, inlineTailLength)))) return [[self retain] autorelease]; + + HFByteSlice *result; + HFASSERT(lrange.length > 0); + HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange)); + NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length)); + NSRange dataRange = NSMakeRange(0, length); + NSRange tailRange = NSMakeRange(length, inlineTailLength); + NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange); + NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange); + HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length); + + NSMutableData *resultData = NULL; + NSUInteger resultOffset = 0; + NSUInteger resultLength = 0; + const unsigned char *tail = NULL; + NSUInteger tailLength = 0; + if (dataRangeToCopy.length > 0) { + resultData = data; + HFASSERT(resultData != NULL); + resultOffset = offset + dataRangeToCopy.location; + resultLength = dataRangeToCopy.length; + HFASSERT(HFSum(resultOffset, resultLength) <= [data length]); + } + if (tailRangeToCopy.length > 0) { + tail = inlineTail + tailRangeToCopy.location - length; + tailLength = tailRangeToCopy.length; + HFASSERT(tail >= inlineTail && tail + tailLength <= inlineTail + inlineTailLength); + } + HFASSERT(resultLength + tailLength == lrange.length); + result = [[[[self class] alloc] initWithSharedData:resultData offset:resultOffset length:resultLength tail:tail tailLength:tailLength] autorelease]; + HFASSERT([result length] == lrange.length); + return result; +} + +- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice { + REQUIRE_NOT_NULL(slice); + const unsigned long long sliceLength = [slice length]; + if (sliceLength == 0) return self; + + const unsigned long long thisLength = [self length]; + + HFASSERT(inlineTailLength <= MAX_TAIL_LENGTH); + NSUInteger spaceRemainingInTail = MAX_TAIL_LENGTH - inlineTailLength; + + if (sliceLength <= spaceRemainingInTail) { + /* We can do our work entirely within the tail */ + NSUInteger newTailLength = (NSUInteger)sliceLength + inlineTailLength; + unsigned char newTail[MAX_TAIL_LENGTH]; + memcpy(newTail, inlineTail, inlineTailLength); + [slice copyBytes:newTail + inlineTailLength range:HFRangeMake(0, sliceLength)]; + HFByteSlice *result = [[[[self class] alloc] initWithSharedData:data offset:offset length:length tail:newTail tailLength:newTailLength] autorelease]; + HFASSERT([result length] == HFSum(sliceLength, thisLength)); + return result; + } + else { + /* We can't do our work entirely in the tail; see if we can append some shared data. */ + HFASSERT(offset + length >= offset); + if (offset + length == [data length]) { + /* We can append some shared data. But impose some reasonable limit on how big our slice can get; this is 16 MB */ + if (HFSum(thisLength, sliceLength) < (1ULL << 24)) { + NSUInteger newDataOffset = offset; + NSUInteger newDataLength = length; + unsigned char newDataTail[MAX_TAIL_LENGTH]; + unsigned char newDataTailLength = MAX_TAIL_LENGTH; + NSMutableData *newData = (data ? data : [[[NSMutableData alloc] init] autorelease]); + + NSUInteger sliceLengthInt = ll2l(sliceLength); + NSUInteger newTotalTailLength = sliceLengthInt + inlineTailLength; + HFASSERT(newTotalTailLength >= MAX_TAIL_LENGTH); + NSUInteger amountToShiftIntoSharedData = newTotalTailLength - MAX_TAIL_LENGTH; + NSUInteger amountToShiftIntoSharedDataFromTail = MIN(amountToShiftIntoSharedData, inlineTailLength); + NSUInteger amountToShiftIntoSharedDataFromNewSlice = amountToShiftIntoSharedData - amountToShiftIntoSharedDataFromTail; + + if (amountToShiftIntoSharedDataFromTail > 0) { + HFASSERT(amountToShiftIntoSharedDataFromTail <= inlineTailLength); + [newData appendBytes:inlineTail length:amountToShiftIntoSharedDataFromTail]; + newDataLength += amountToShiftIntoSharedDataFromTail; + } + if (amountToShiftIntoSharedDataFromNewSlice > 0) { + HFASSERT(amountToShiftIntoSharedDataFromNewSlice <= [slice length]); + NSUInteger dataLength = offset + length + amountToShiftIntoSharedDataFromTail; + HFASSERT([newData length] == dataLength); + [newData setLength:dataLength + amountToShiftIntoSharedDataFromNewSlice]; + [slice copyBytes:[newData mutableBytes] + dataLength range:HFRangeMake(0, amountToShiftIntoSharedDataFromNewSlice)]; + newDataLength += amountToShiftIntoSharedDataFromNewSlice; + } + + /* We've updated our data; now figure out the tail */ + NSUInteger amountOfTailFromNewSlice = sliceLengthInt - amountToShiftIntoSharedDataFromNewSlice; + HFASSERT(amountOfTailFromNewSlice <= MAX_TAIL_LENGTH); + [slice copyBytes:newDataTail + MAX_TAIL_LENGTH - amountOfTailFromNewSlice range:HFRangeMake(sliceLengthInt - amountOfTailFromNewSlice, amountOfTailFromNewSlice)]; + + /* Copy the rest, if any, from the end of self */ + NSUInteger amountOfTailFromSelf = MAX_TAIL_LENGTH - amountOfTailFromNewSlice; + HFASSERT(amountOfTailFromSelf <= inlineTailLength); + if (amountOfTailFromSelf > 0) { + memcpy(newDataTail, inlineTail + inlineTailLength - amountOfTailFromSelf, amountOfTailFromSelf); + } + + HFByteSlice *result = [[[[self class] alloc] initWithSharedData:newData offset:newDataOffset length:newDataLength tail:newDataTail tailLength:newDataTailLength] autorelease]; + HFASSERT([result length] == HFSum([slice length], [self length])); + return result; + } + } + } + return nil; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.h new file mode 100644 index 000000000..e70b893f0 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.h @@ -0,0 +1,31 @@ +// +// HFStatusBarRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @enum HFStatusBarMode + The HFStatusBarMode enum is used to describe the format of the byte counts displayed by the status bar. +*/ +typedef NS_ENUM(NSUInteger, HFStatusBarMode) { + HFStatusModeDecimal, ///< The status bar should display byte counts in decimal + HFStatusModeHexadecimal, ///< The status bar should display byte counts in hexadecimal + HFStatusModeApproximate, ///< The text should display byte counts approximately (e.g. "56.3 KB") + HFSTATUSMODECOUNT ///< The number of modes, to allow easy cycling +}; + +/*! @class HFStatusBarRepresenter + @brief The HFRepresenter for the status bar. + + HFStatusBarRepresenter is a subclass of HFRepresenter responsible for showing the status bar, which displays information like the total length of the document, or the number of selected bytes. +*/ +@interface HFStatusBarRepresenter : HFRepresenter { + HFStatusBarMode statusMode; +} + +@property (nonatomic) HFStatusBarMode statusMode; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.m new file mode 100644 index 000000000..883677f9b --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFStatusBarRepresenter.m @@ -0,0 +1,240 @@ +// +// HFStatusBarRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +#define kHFStatusBarDefaultModeUserDefaultsKey @"HFStatusBarDefaultMode" + +@interface HFStatusBarView : NSView { + NSCell *cell; + NSSize cellSize; + HFStatusBarRepresenter *representer; + NSDictionary *cellAttributes; + BOOL registeredForAppNotifications; +} + +- (void)setRepresenter:(HFStatusBarRepresenter *)rep; +- (void)setString:(NSString *)string; + +@end + + +@implementation HFStatusBarView + +- (void)_sharedInitStatusBarView { + NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + [style setAlignment:NSCenterTextAlignment]; + cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor windowFrameTextColor], NSForegroundColorAttributeName, [NSFont labelFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil]; + cell = [[NSCell alloc] initTextCell:@""]; + [cell setAlignment:NSCenterTextAlignment]; + [cell setBackgroundStyle:NSBackgroundStyleRaised]; +} + +- (instancetype)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + [self _sharedInitStatusBarView]; + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + [self _sharedInitStatusBarView]; + return self; +} + +// nothing to do in encodeWithCoder + +- (BOOL)isFlipped { return YES; } + +- (void)setRepresenter:(HFStatusBarRepresenter *)rep { + representer = rep; +} + +- (void)setString:(NSString *)string { + [cell setAttributedStringValue:[[[NSAttributedString alloc] initWithString:string attributes:cellAttributes] autorelease]]; + cellSize = [cell cellSize]; + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(NSRect)clip { + USE(clip); + NSRect bounds = [self bounds]; + // [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set]; + // NSRectFill(clip); + + + NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height); + [cell drawWithFrame:cellRect inView:self]; +} + +- (void)setFrame:(NSRect)frame +{ + [super setFrame:frame]; + [self.window setContentBorderThickness:frame.origin.y + frame.size.height forEdge:NSMinYEdge]; +} + + +- (void)mouseDown:(NSEvent *)event { + USE(event); + HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT; + [representer setStatusMode:newMode]; + [[NSUserDefaults standardUserDefaults] setInteger:newMode forKey:kHFStatusBarDefaultModeUserDefaultsKey]; +} + +- (void)windowDidChangeKeyStatus:(NSNotification *)note { + USE(note); + [self setNeedsDisplay:YES]; +} + +- (void)viewDidMoveToWindow { + HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications); + registeredForAppNotifications = YES; + [self.window setContentBorderThickness:self.frame.origin.y + self.frame.size.height forEdge:NSMinYEdge]; + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow { + HFUnregisterViewForWindowAppearanceChanges(self, NO); + [super viewWillMoveToWindow:newWindow]; +} + +- (void)dealloc { + HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications); + [cell release]; + [cellAttributes release]; + [super dealloc]; +} + +@end + +@implementation HFStatusBarRepresenter + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:statusMode forKey:@"HFStatusMode"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + statusMode = (NSUInteger)[coder decodeInt64ForKey:@"HFStatusMode"]; + return self; +} + +- (instancetype)init { + self = [super init]; + statusMode = [[NSUserDefaults standardUserDefaults] integerForKey:kHFStatusBarDefaultModeUserDefaultsKey]; + return self; +} + +- (NSView *)createView { + HFStatusBarView *view = [[HFStatusBarView alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)]; + [view setRepresenter:self]; + [view setAutoresizingMask:NSViewWidthSizable]; + return view; +} + +- (NSString *)describeLength:(unsigned long long)length { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu byte%s", length, length == 1 ? "" : "s"]; + case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX byte%s", length, length == 1 ? "" : "s"]; + case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(length)]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +- (NSString *)describeOffset:(unsigned long long)offset { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset]; + case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX", offset]; + case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(offset)]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +/* same as describeOffset, except we treat Approximate like Hexadecimal */ +- (NSString *)describeOffsetExcludingApproximate:(unsigned long long)offset { + switch (statusMode) { + case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset]; + case HFStatusModeHexadecimal: + case HFStatusModeApproximate: return [NSString stringWithFormat:@"0x%llX", offset]; + default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @""; + } +} + +- (NSString *)stringForEmptySelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ out of %@", [self describeOffset:offset], [self describeLength:length]]; +} + +- (NSString *)stringForSingleByteSelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length { + return [NSString stringWithFormat:@"Byte %@ selected out of %@", [self describeOffset:offset], [self describeLength:length]]; +} + +- (NSString *)stringForSingleRangeSelection:(HFRange)range length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ selected at offset %@ out of %@", [self describeLength:range.length], [self describeOffsetExcludingApproximate:range.location], [self describeLength:length]]; +} + +- (NSString *)stringForMultipleSelectionsWithLength:(unsigned long long)multipleSelectionLength length:(unsigned long long)length { + return [NSString stringWithFormat:@"%@ selected at multiple offsets out of %@", [self describeLength:multipleSelectionLength], [self describeLength:length]]; +} + + +- (void)updateString { + NSString *string = nil; + HFController *controller = [self controller]; + if (controller) { + unsigned long long length = [controller contentsLength]; + NSArray *ranges = [controller selectedContentsRanges]; + NSUInteger rangeCount = [ranges count]; + if (rangeCount == 1) { + HFRange range = [ranges[0] HFRange]; + if (range.length == 0) { + string = [self stringForEmptySelectionAtOffset:range.location length:length]; + } + else if (range.length == 1) { + string = [self stringForSingleByteSelectionAtOffset:range.location length:length]; + } + else { + string = [self stringForSingleRangeSelection:range length:length]; + } + } + else { + unsigned long long totalSelectionLength = 0; + FOREACH(HFRangeWrapper *, wrapper, ranges) { + HFRange range = [wrapper HFRange]; + totalSelectionLength = HFSum(totalSelectionLength, range.length); + } + string = [self stringForMultipleSelectionsWithLength:totalSelectionLength length:length]; + } + } + if (! string) string = @""; + [[self view] setString:string]; +} + +- (HFStatusBarMode)statusMode { + return statusMode; +} + +- (void)setStatusMode:(HFStatusBarMode)mode { + statusMode = mode; + [self updateString]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerContentLength | HFControllerSelectedRanges)) { + [self updateString]; + } +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(0, -1); +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.h new file mode 100644 index 000000000..2c5da7b57 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.h @@ -0,0 +1,26 @@ +// +// HFASCIITextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFStringEncodingTextRepresenter + + @brief An HFRepresenter responsible for showing data interpreted via an NSStringEncoding. + + HFHexTextRepresenter is an HFRepresenter responsible for showing and editing data interpreted via an NSStringEncoding. Currently only supersets of ASCII are supported. +*/ +@interface HFStringEncodingTextRepresenter : HFTextRepresenter { + NSStringEncoding stringEncoding; + +} + +/*! Get the string encoding for this representer. The default encoding is [NSString defaultCStringEncoding]. */ +@property (nonatomic) NSStringEncoding encoding; + +/*! Set the string encoding for this representer. */ + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.m new file mode 100644 index 000000000..27ea995cf --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFStringEncodingTextRepresenter.m @@ -0,0 +1,121 @@ +// +// HFASCIITextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +@interface HFStringEncodingPasteboardOwner : HFPasteboardOwner { + NSStringEncoding encoding; +} +@property (nonatomic) NSStringEncoding encoding; +@end + +@implementation HFStringEncodingPasteboardOwner +- (void)setEncoding:(NSStringEncoding)val { encoding = val; } +- (NSStringEncoding)encoding { return encoding; } + +- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker { + HFASSERT([type isEqual:NSStringPboardType]); + HFByteArray *byteArray = [self byteArray]; + HFASSERT(length <= NSUIntegerMax); + NSUInteger dataLength = ll2l(length); + NSUInteger stringLength = dataLength; + NSUInteger offset = 0, remaining = dataLength; + unsigned char * restrict const stringBuffer = check_malloc(stringLength); + while (remaining > 0) { + NSUInteger amountToCopy = MIN(32u * 1024u, remaining); + [byteArray copyBytes:stringBuffer + offset range:HFRangeMake(offset, amountToCopy)]; + offset += amountToCopy; + remaining -= amountToCopy; + } + NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:encoding freeWhenDone:YES]; + [pboard setString:string forType:type]; + [string release]; +} + +- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { + return dataLength; +} + +@end + +@implementation HFStringEncodingTextRepresenter + +- (instancetype)init { + self = [super init]; + stringEncoding = [NSString defaultCStringEncoding]; + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + stringEncoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeInt64:stringEncoding forKey:@"HFStringEncoding"]; +} + +- (Class)_textViewClass { + return [HFRepresenterStringEncodingTextView class]; +} + +- (NSStringEncoding)encoding { + return stringEncoding; +} + +- (void)setEncoding:(NSStringEncoding)encoding { + stringEncoding = encoding; + [[self view] setEncoding:encoding]; + [[self controller] representer:self changedProperties:HFControllerViewSizeRatios]; +} + +- (void)initializeView { + [[self view] setEncoding:stringEncoding]; + [super initializeView]; +} + +- (void)insertText:(NSString *)text { + REQUIRE_NOT_NULL(text); + NSData *data = [text dataUsingEncoding:[self encoding] allowLossyConversion:NO]; + if (! data) { + NSBeep(); + } + else if ([data length]) { // a 0 length text can come about via e.g. option-e + [[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:YES]; + } +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + REQUIRE_NOT_NULL(string); + return [string dataUsingEncoding:[self encoding] allowLossyConversion:NO]; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(1, 0); +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges]; + HFASSERT(selection != NULL); + if ([selection length] == 0) { + NSBeep(); + } + else { + HFStringEncodingPasteboardOwner *owner = [HFStringEncodingPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]]; + [owner setEncoding:[self encoding]]; + [owner setBytesPerLine:[self bytesPerLine]]; + } +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.h new file mode 100644 index 000000000..306a197ff --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.h @@ -0,0 +1,39 @@ +// +// HFTextRepresenter.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import + +/*! @class HFTextRepresenter + @brief An HFRepresenter that draws text (e.g. the hex or ASCII view). + + HFTextRepresenter is an abstract subclass of HFRepresenter that is responsible for displaying text. There are two concrete subclass, HFHexTextRepresenter and HFStringEncodingTextRepresenter. + + Most of the functionality of HFTextRepresenter is private, and there is not yet enough exposed to allow creating new representers based on it. However, there is a small amount of configurability. +*/ +@interface HFTextRepresenter : HFRepresenter {} +/*! Given a rect edge, return an NSRect representing the maximum edge in that direction, in the coordinate system of the receiver's view. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect. + + If range is entirely above the visible region, returns an NSRect whose width and height are 0, and whose origin is -CGFLOAT_MAX (the most negative CGFloat). If range is entirely below the visible region, returns the same except with CGFLOAT_MAX (positive). + + This raises an exception if range is empty. +*/ +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)range; + +/*! Returns the origin of the character at the given byte index. The returned point is in the coordinate space of the receiver's view. If the character is not displayed because it would be above the displayed range, returns {0, -CGFLOAT_MAX}. If it is not displayed because it is below the displayed range, returns {0, CGFLOAT_MAX}. As a special affordance, you may pass a byte index one greater than the contents length of the controller, and it will return the result as if the byte existed. + */ +- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)byteIndex; + +/*! The per-row background colors. Each row is drawn with the next color in turn, cycling back to the beginning when the array is exhausted. Any empty space is filled with the first color in the array. If the array is empty, then the background is drawn with \c clearColor. + */ +@property (nonatomic, copy) NSArray *rowBackgroundColors; + +/*! Whether the text view behaves like a text field (YES) or a text view (NO). Currently this determines whether it draws a focus ring when it is the first responder. +*/ +@property (nonatomic) BOOL behavesAsTextField; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.m new file mode 100644 index 000000000..b8793b457 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter.m @@ -0,0 +1,377 @@ +// +// HFTextRepresenter.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import +#import +#import + +@implementation HFTextRepresenter + +- (Class)_textViewClass { + UNIMPLEMENTED(); +} + +- (instancetype)init { + self = [super init]; + + if (@available(macOS 10.14, *)) { + _rowBackgroundColors = [[NSColor alternatingContentBackgroundColors] retain]; + } else { + NSColor *color1 = [NSColor windowBackgroundColor]; + NSColor *color2 = [NSColor colorWithDeviceWhite:0.96 alpha:1]; + _rowBackgroundColors = [@[color1, color2] retain]; + } + + return self; +} + +- (void)dealloc { + if ([self isViewLoaded]) { + [[self view] clearRepresenter]; + } + [_rowBackgroundColors release]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + [super encodeWithCoder:coder]; + [coder encodeBool:_behavesAsTextField forKey:@"HFBehavesAsTextField"]; + [coder encodeObject:_rowBackgroundColors forKey:@"HFRowBackgroundColors"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + HFASSERT([coder allowsKeyedCoding]); + self = [super initWithCoder:coder]; + _behavesAsTextField = [coder decodeBoolForKey:@"HFBehavesAsTextField"]; + _rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain]; + return self; +} + +- (NSView *)createView { + HFRepresenterTextView *view = [[[self _textViewClass] alloc] initWithRepresenter:self]; + [view setAutoresizingMask:NSViewHeightSizable]; + return view; +} + +- (HFByteArrayDataStringType)byteArrayDataStringType { + UNIMPLEMENTED(); +} + +- (HFRange)entireDisplayedRange { + HFController *controller = [self controller]; + unsigned long long contentsLength = [controller contentsLength]; + HFASSERT(controller != NULL); + HFFPRange displayedLineRange = [controller displayedLineRange]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + unsigned long long lineStart = HFFPToUL(floorl(displayedLineRange.location)); + unsigned long long lineEnd = HFFPToUL(ceill(displayedLineRange.location + displayedLineRange.length)); + HFASSERT(lineEnd >= lineStart); + HFRange byteRange = HFRangeMake(HFProductULL(bytesPerLine, lineStart), HFProductULL(lineEnd - lineStart, bytesPerLine)); + if (byteRange.length == 0) { + /* This can happen if we are too small to even show one line */ + return HFRangeMake(0, 0); + } + else { + HFASSERT(byteRange.location <= contentsLength); + byteRange.length = MIN(byteRange.length, contentsLength - byteRange.location); + HFASSERT(HFRangeIsSubrangeOfRange(byteRange, HFRangeMake(0, [controller contentsLength]))); + return byteRange; + } +} + +- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)byteRange { + HFASSERT(byteRange.length > 0); + HFRange displayedRange = [self entireDisplayedRange]; + HFRange intersection = HFIntersectionRange(displayedRange, byteRange); + NSRect result = {{0,},}; + if (intersection.length > 0) { + NSRange intersectionNSRange = NSMakeRange(ll2l(intersection.location - displayedRange.location), ll2l(intersection.length)); + if (intersectionNSRange.length > 0) { + result = [[self view] furthestRectOnEdge:edge forRange:intersectionNSRange]; + } + } + else if (byteRange.location < displayedRange.location) { + /* We're below it. */ + return NSMakeRect(-CGFLOAT_MAX, -CGFLOAT_MAX, 0, 0); + } + else if (byteRange.location >= HFMaxRange(displayedRange)) { + /* We're above it */ + return NSMakeRect(CGFLOAT_MAX, CGFLOAT_MAX, 0, 0); + } + else { + /* Shouldn't be possible to get here */ + [NSException raise:NSInternalInconsistencyException format:@"furthestRectOnEdge: expected an intersection, or a range below or above the byte range, but nothin'"]; + } + return result; +} + +- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)index { + NSPoint result; + HFRange displayedRange = [self entireDisplayedRange]; + if (HFLocationInRange(index, displayedRange) || index == HFMaxRange(displayedRange)) { + NSUInteger location = ll2l(index - displayedRange.location); + result = [[self view] originForCharacterAtByteIndex:location]; + } + else if (index < displayedRange.location) { + result = NSMakePoint(-CGFLOAT_MAX, -CGFLOAT_MAX); + } + else { + result = NSMakePoint(CGFLOAT_MAX, CGFLOAT_MAX); + } + return result; +} + +- (HFTextVisualStyleRun *)styleForAttributes:(NSSet *)attributes range:(NSRange)range { + HFTextVisualStyleRun *run = [[[HFTextVisualStyleRun alloc] init] autorelease]; + [run setRange:range]; + [run setForegroundColor:[NSColor blackColor]]; + + return run; +} + +- (NSArray *)stylesForRange:(HFRange)range { + return nil; +} + +- (void)updateText { + HFController *controller = [self controller]; + HFRepresenterTextView *view = [self view]; + HFRange entireDisplayedRange = [self entireDisplayedRange]; + [view setData:[controller dataForRange:entireDisplayedRange]]; + [view setStyles:[self stylesForRange:entireDisplayedRange]]; + HFFPRange lineRange = [controller displayedLineRange]; + long double offsetLongDouble = lineRange.location - floorl(lineRange.location); + CGFloat offset = ld2f(offsetLongDouble); + [view setVerticalOffset:offset]; + [view setStartingLineBackgroundColorIndex:ll2l(HFFPToUL(floorl(lineRange.location)) % NSUIntegerMax)]; +} + +- (void)initializeView { + [super initializeView]; + HFRepresenterTextView *view = [self view]; + HFController *controller = [self controller]; + if (controller) { + [view setFont:[controller font]]; + [view setEditable:[controller editable]]; + [self updateText]; + } + else { + [view setFont:[NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]]; + } +} + +- (void)scrollWheel:(NSEvent *)event { + [[self controller] scrollWithScrollEvent:event]; +} + +- (void)selectAll:(id)sender { + [[self controller] selectAll:sender]; +} + +- (double)selectionPulseAmount { + return [[self controller] selectionPulseAmount]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerFont | HFControllerLineHeight)) { + [[self view] setFont:[[self controller] font]]; + } + if (bits & (HFControllerContentValue | HFControllerDisplayedLineRange | HFControllerByteRangeAttributes)) { + [self updateText]; + } + if (bits & (HFControllerSelectedRanges | HFControllerDisplayedLineRange)) { + [[self view] updateSelectedRanges]; + } + if (bits & (HFControllerEditable)) { + [[self view] setEditable:[[self controller] editable]]; + } + if (bits & (HFControllerAntialias)) { + [[self view] setShouldAntialias:[[self controller] shouldAntialias]]; + } + if (bits & (HFControllerShowCallouts)) { + [[self view] setShouldDrawCallouts:[[self controller] shouldShowCallouts]]; + } + if (bits & (HFControllerColorBytes)) { + if([[self controller] shouldColorBytes]) { + [[self view] setByteColoring: ^(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a){ + *r = *g = *b = (uint8_t)(255 * ((255-byte)/255.0*0.6+0.4)); + *a = (uint8_t)(255 * 0.7); + }]; + } else { + [[self view] setByteColoring:NULL]; + } + } + [super controllerDidChange:bits]; +} + +- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight { + return [[self view] maximumAvailableLinesForViewHeight:viewHeight]; +} + +- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth { + return [[self view] maximumBytesPerLineForViewWidth:viewWidth]; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + return [[self view] minimumViewWidthForBytesPerLine:bytesPerLine]; +} + +- (NSUInteger)byteGranularity { + HFRepresenterTextView *view = [self view]; + NSUInteger bytesPerColumn = MAX([view bytesPerColumn], 1u), bytesPerCharacter = [view bytesPerCharacter]; + return HFLeastCommonMultiple(bytesPerColumn, bytesPerCharacter); +} + +- (NSArray *)displayedSelectedContentsRanges { + HFController *controller = [self controller]; + NSArray *result; + NSArray *selectedRanges = [controller selectedContentsRanges]; + HFRange displayedRange = [self entireDisplayedRange]; + + HFASSERT(displayedRange.length <= NSUIntegerMax); + NEW_ARRAY(NSValue *, clippedSelectedRanges, [selectedRanges count]); + NSUInteger clippedRangeIndex = 0; + FOREACH(HFRangeWrapper *, wrapper, selectedRanges) { + HFRange selectedRange = [wrapper HFRange]; + BOOL clippedRangeIsVisible; + NSRange clippedSelectedRange = {0,}; + /* Necessary because zero length ranges do not intersect anything */ + if (selectedRange.length == 0) { + /* Remember that {6, 0} is considered a subrange of {3, 3} */ + clippedRangeIsVisible = HFRangeIsSubrangeOfRange(selectedRange, displayedRange); + if (clippedRangeIsVisible) { + HFASSERT(selectedRange.location >= displayedRange.location); + clippedSelectedRange.location = ll2l(selectedRange.location - displayedRange.location); + clippedSelectedRange.length = 0; + } + } + else { + // selectedRange.length > 0 + clippedRangeIsVisible = HFIntersectsRange(selectedRange, displayedRange); + if (clippedRangeIsVisible) { + HFRange intersectionRange = HFIntersectionRange(selectedRange, displayedRange); + HFASSERT(intersectionRange.location >= displayedRange.location); + clippedSelectedRange.location = ll2l(intersectionRange.location - displayedRange.location); + clippedSelectedRange.length = ll2l(intersectionRange.length); + } + } + if (clippedRangeIsVisible) clippedSelectedRanges[clippedRangeIndex++] = [NSValue valueWithRange:clippedSelectedRange]; + } + result = [NSArray arrayWithObjects:clippedSelectedRanges count:clippedRangeIndex]; + FREE_ARRAY(clippedSelectedRanges); + return result; +} + +//maps bookmark keys as NSNumber to byte locations as NSNumbers. Because bookmark callouts may extend beyond the lines containing them, allow a larger range by 10 lines. +- (NSDictionary *)displayedBookmarkLocations { + NSMutableDictionary *result = nil; + HFController *controller = [self controller]; + NSUInteger rangeExtension = 10 * [controller bytesPerLine]; + HFRange displayedRange = [self entireDisplayedRange]; + + HFRange includedRange = displayedRange; + + /* Extend the bottom */ + unsigned long long bottomExtension = MIN(includedRange.location, rangeExtension); + includedRange.location -= bottomExtension; + includedRange.length += bottomExtension; + + /* Extend the top */ + unsigned long long topExtension = MIN([controller contentsLength] - HFMaxRange(includedRange), rangeExtension); + includedRange.length = HFSum(includedRange.length, topExtension); + + return result; +} + +- (unsigned long long)byteIndexForCharacterIndex:(NSUInteger)characterIndex { + HFController *controller = [self controller]; + HFFPRange lineRange = [controller displayedLineRange]; + unsigned long long scrollAmount = HFFPToUL(floorl(lineRange.location)); + unsigned long long byteIndex = HFProductULL(scrollAmount, [controller bytesPerLine]) + characterIndex * [[self view] bytesPerCharacter]; + return byteIndex; +} + +- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] beginSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] continueSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex { + [[self controller] endSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]]; +} + +- (void)insertText:(NSString *)text { + USE(text); + UNIMPLEMENTED_VOID(); +} + +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb { + USE(pb); + UNIMPLEMENTED_VOID(); +} + +- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb { + [self copySelectedBytesToPasteboard:pb]; + [[self controller] deleteSelection]; +} + +- (NSData *)dataFromPasteboardString:(NSString *)string { + USE(string); + UNIMPLEMENTED(); +} + +- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + if ([[self controller] editable]) { + // we can paste if the pboard contains text or contains an HFByteArray + return [HFPasteboardOwner unpackByteArrayFromPasteboard:pb] || [pb availableTypeFromArray:@[NSStringPboardType]]; + } + return NO; +} + +- (BOOL)canCut { + /* We can cut if we are editable, we have at least one byte selected, and we are not in overwrite mode */ + HFController *controller = [self controller]; + if ([controller editMode] != HFInsertMode) return NO; + if (! [controller editable]) return NO; + + FOREACH(HFRangeWrapper *, rangeWrapper, [controller selectedContentsRanges]) { + if ([rangeWrapper HFRange].length > 0) return YES; //we have something selected + } + return NO; // we did not find anything selected +} + +- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb { + REQUIRE_NOT_NULL(pb); + BOOL result = NO; + HFByteArray *byteArray = [HFPasteboardOwner unpackByteArrayFromPasteboard:pb]; + if (byteArray) { + [[self controller] insertByteArray:byteArray replacingPreviousBytes:0 allowUndoCoalescing:NO]; + result = YES; + } + else { + NSString *stringType = [pb availableTypeFromArray:@[NSStringPboardType]]; + if (stringType) { + NSString *stringValue = [pb stringForType:stringType]; + if (stringValue) { + NSData *data = [self dataFromPasteboardString:stringValue]; + if (data) { + [[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:NO]; + } + } + } + } + return result; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_Internal.h b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_Internal.h new file mode 100644 index 000000000..c1e9f018b --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_Internal.h @@ -0,0 +1,33 @@ +#import + +@interface HFTextRepresenter (HFInternal) + +- (NSArray *)displayedSelectedContentsRanges; //returns an array of NSValues representing the selected ranges (as NSRanges) clipped to the displayed range. + +- (NSDictionary *)displayedBookmarkLocations; //returns an dictionary mapping bookmark names to bookmark locations. Bookmark locations may be negative. + +- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; +- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; +- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex; + +// Copy/Paste methods +- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb; +- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb; +- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb; +- (BOOL)canCut; +- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb; + +// Must be implemented by subclasses +- (void)insertText:(NSString *)text; + +// Must be implemented by subclasses. Return NSData representing the string value. +- (NSData *)dataFromPasteboardString:(NSString *)string; + +// Value between [0, 1] +- (double)selectionPulseAmount; + +- (void)scrollWheel:(NSEvent *)event; + +- (void)selectAll:(id)sender; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_KeyBinding.m b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_KeyBinding.m new file mode 100644 index 000000000..b6e16d69a --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextRepresenter_KeyBinding.m @@ -0,0 +1,128 @@ +// +// HFTextRepresenter_KeyBinding.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import +#import +#import + +#define FORWARD(x) - (void)x : sender { USE(sender); UNIMPLEMENTED_VOID(); } + +@implementation HFTextRepresenter (HFKeyBinding) + +- (void)moveRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:NO]; } +- (void)moveLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:NO]; } +- (void)moveUp:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:NO]; } +- (void)moveDown:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:NO]; } +- (void)moveWordRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:NO]; } +- (void)moveWordLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:NO]; } + +- (void)moveRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:YES]; } +- (void)moveLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:YES]; } +- (void)moveUpAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:YES]; } +- (void)moveDownAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:YES]; } +- (void)moveWordRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:YES]; } +- (void)moveWordLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:YES]; } + +- (void)moveForward:unused { USE(unused); [self moveRight:unused]; } +- (void)moveBackward:unused { USE(unused); [self moveLeft:unused]; } + +- (void)moveWordForward:unused { USE(unused); [self moveWordRight:unused]; } +- (void)moveWordBackward:unused { USE(unused); [self moveWordLeft:unused]; } +- (void)moveForwardAndModifySelection:unused { USE(unused); [self moveRightAndModifySelection:unused]; } +- (void)moveBackwardAndModifySelection:unused { USE(unused); [self moveLeftAndModifySelection:unused]; } +- (void)moveWordForwardAndModifySelection:unused { USE(unused); [self moveForwardAndModifySelection:unused]; } +- (void)moveWordBackwardAndModifySelection:unused { USE(unused); [self moveBackwardAndModifySelection:unused]; } + +- (void)deleteBackward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionLeft]; } +- (void)deleteForward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionRight]; } +- (void)deleteWordForward:unused { USE(unused); [self deleteForward:unused]; } +- (void)deleteWordBackward:unused { USE(unused); [self deleteBackward:unused]; } + +- (void)delete:unused { USE(unused); [self deleteForward:unused]; } + + //todo: implement these + +- (void)deleteToBeginningOfLine:(id)sender { USE(sender); } +- (void)deleteToEndOfLine:(id)sender { USE(sender); } +- (void)deleteToBeginningOfParagraph:(id)sender { USE(sender); } +- (void)deleteToEndOfParagraph:(id)sender { USE(sender); } + +- (void)moveToBeginningOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:NO]; } +- (void)moveToEndOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:NO]; } +- (void)moveToBeginningOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:NO]; } +- (void)moveToEndOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:NO]; } + +- (void)moveToBeginningOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:YES]; } +- (void)moveToEndOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:YES]; } +- (void)moveToBeginningOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:YES]; } +- (void)moveToEndOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:YES]; } + +- (void)moveToBeginningOfParagraph:unused { USE(unused); [self moveToBeginningOfLine:unused]; } +- (void)moveToEndOfParagraph:unused { USE(unused); [self moveToEndOfLine:unused]; } +- (void)moveToBeginningOfParagraphAndModifySelection:unused { USE(unused); [self moveToBeginningOfLineAndModifySelection:unused]; } +- (void)moveToEndOfParagraphAndModifySelection:unused { USE(unused); [self moveToEndOfLineAndModifySelection:unused]; } + +- (void)scrollPageDown:unused { USE(unused); [[self controller] scrollByLines:[[self controller] displayedLineRange].length]; } +- (void)scrollPageUp:unused { USE(unused); [[self controller] scrollByLines: - [[self controller] displayedLineRange].length]; } +- (void)pageDown:unused { USE(unused); [self scrollPageDown:unused]; } +- (void)pageUp:unused { USE(unused); [self scrollPageUp:unused]; } + +- (void)centerSelectionInVisibleArea:unused { + USE(unused); + HFController *controller = [self controller]; + NSArray *selection = [controller selectedContentsRanges]; + unsigned long long min = ULLONG_MAX, max = 0; + HFASSERT([selection count] >= 1); + FOREACH(HFRangeWrapper *, wrapper, selection) { + HFRange range = [wrapper HFRange]; + min = MIN(min, range.location); + max = MAX(max, HFMaxRange(range)); + } + HFASSERT(max >= min); + [controller maximizeVisibilityOfContentsRange:HFRangeMake(min, max - min)]; +} + +- (void)insertTab:unused { + USE(unused); + [[[self view] window] selectNextKeyView:nil]; +} + +- (void)insertBacktab:unused { + USE(unused); + [[[self view] window] selectPreviousKeyView:nil]; +} + +FORWARD(scrollLineUp) +FORWARD(scrollLineDown) +FORWARD(transpose) +FORWARD(transposeWords) + +FORWARD(selectParagraph) +FORWARD(selectLine) +FORWARD(selectWord) +FORWARD(indent) +//FORWARD(insertNewline) +FORWARD(insertParagraphSeparator) +FORWARD(insertNewlineIgnoringFieldEditor) +FORWARD(insertTabIgnoringFieldEditor) +FORWARD(insertLineBreak) +FORWARD(insertContainerBreak) +FORWARD(changeCaseOfLetter) +FORWARD(uppercaseWord) +FORWARD(lowercaseWord) +FORWARD(capitalizeWord) +FORWARD(deleteBackwardByDecomposingPreviousCharacter) +FORWARD(yank) +FORWARD(complete) +FORWARD(setMark) +FORWARD(deleteToMark) +FORWARD(selectToMark) +FORWARD(swapWithMark) +//FORWARD(cancelOperation) + +@end + diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.h b/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.h new file mode 100644 index 000000000..0fa2ea74f --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.h @@ -0,0 +1,23 @@ +// +// HFTextVisualStyle.h +// HexFiend_2 +// +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import + +@interface HFTextVisualStyleRun : NSObject {} + +@property (nonatomic, copy) NSColor *foregroundColor; +@property (nonatomic, copy) NSColor *backgroundColor; +@property (nonatomic) NSRange range; +@property (nonatomic) BOOL shouldDraw; +@property (nonatomic) CGFloat scale; +@property (nonatomic, copy) NSIndexSet *bookmarkStarts; +@property (nonatomic, copy) NSIndexSet *bookmarkExtents; +@property (nonatomic, copy) NSIndexSet *bookmarkEnds; + +- (void)set; + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.m b/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.m new file mode 100644 index 000000000..39442d53a --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTextVisualStyleRun.m @@ -0,0 +1,80 @@ +// +// HFTextVisualStyleRun.m +// HexFiend_2 +// +// Copyright 2009 ridiculous_fish. All rights reserved. +// + +#import "HFTextVisualStyleRun.h" + + +@implementation HFTextVisualStyleRun + +- (instancetype)init { + self = [super init]; + _scale = 1.; + _shouldDraw = YES; + return self; +} + +- (void)dealloc { + [_foregroundColor release]; + [_backgroundColor release]; + [_bookmarkStarts release]; + [_bookmarkExtents release]; + [_bookmarkEnds release]; + [super dealloc]; +} + +- (void)set { + [_foregroundColor set]; + if (_scale != (CGFloat)1.0) { + CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; + CGAffineTransform tm = CGContextGetTextMatrix(ctx); + /* Huge hack - adjust downward a little bit if we are scaling */ + tm = CGAffineTransformTranslate(tm, 0, -.25 * (_scale - 1)); + tm = CGAffineTransformScale(tm, _scale, _scale); + CGContextSetTextMatrix(ctx, tm); + } +} + +static inline NSUInteger flip(NSUInteger x) { + return _Generic(x, unsigned: NSSwapInt, unsigned long: NSSwapLong, unsigned long long: NSSwapLongLong)(x); +} +static inline NSUInteger rol(NSUInteger x, unsigned char r) { + r %= sizeof(NSUInteger)*8; + return (x << r) | (x << (sizeof(NSUInteger)*8 - r)); +} +- (NSUInteger)hash { + NSUInteger A = 0; + // All these hashes tend to have only low bits, except the double which has only high bits. +#define Q(x, r) rol(x, sizeof(NSUInteger)*r/6) + A ^= flip([_foregroundColor hash] ^ Q([_backgroundColor hash], 2)); // skew high + A ^= Q(_range.length ^ flip(_range.location), 2); // skew low + A ^= flip([_bookmarkStarts hash]) ^ Q([_bookmarkEnds hash], 3) ^ Q([_bookmarkExtents hash], 4); // skew high + A ^= _shouldDraw ? 0 : (NSUInteger)-1; + A ^= *(NSUInteger*)&_scale; // skew high + return A; +#undef Q +} + +- (BOOL)isEqual:(HFTextVisualStyleRun *)run { + if(![run isKindOfClass:[self class]]) return NO; + /* Check each field for equality. */ + if(!NSEqualRanges(_range, run->_range)) return NO; + if(_scale != run->_scale) return NO; + if(_shouldDraw != run->_shouldDraw) return NO; + if(!!_foregroundColor != !!run->_foregroundColor) return NO; + if(!!_backgroundColor != !!run->_backgroundColor) return NO; + if(!!_bookmarkStarts != !!run->_bookmarkStarts) return NO; + if(!!_bookmarkExtents != !!run->_bookmarkExtents) return NO; + if(!!_bookmarkEnds != !!run->_bookmarkEnds) return NO; + if(![_foregroundColor isEqual: run->_foregroundColor]) return NO; + if(![_backgroundColor isEqual: run->_backgroundColor]) return NO; + if(![_bookmarkStarts isEqual: run->_bookmarkStarts]) return NO; + if(![_bookmarkExtents isEqual: run->_bookmarkExtents]) return NO; + if(![_bookmarkEnds isEqual: run->_bookmarkEnds]) return NO; + return YES; +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFTypes.h b/thirdparty/SameBoy-old/HexFiend/HFTypes.h new file mode 100644 index 000000000..b6ae6818f --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFTypes.h @@ -0,0 +1,13 @@ +/*! @brief HFRange is the 64 bit analog of NSRange, containing a 64 bit location and length. */ +typedef struct { + unsigned long long location; + unsigned long long length; +} HFRange; + +/*! @brief HFFPRange is a struct used for representing floating point ranges, similar to NSRange. It contains two long doubles. + + This is useful for (for example) showing the range of visible lines. A double-precision value has 53 significant bits in the mantissa - so we would start to have precision problems at the high end of the range we can represent. Long double has a 64 bit mantissa on Intel, which means that we would start to run into trouble at the very very end of our range - barely acceptable. */ +typedef struct { + long double location; + long double length; +} HFFPRange; diff --git a/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.h b/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.h new file mode 100644 index 000000000..a14881ea7 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.h @@ -0,0 +1,21 @@ +// +// HFRepresenterVerticalScroller.h +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +#import + +/*! @class HFVerticalScrollerRepresenter + @brief An HFRepresenter responsible for showing a vertical scroll bar. + + HFVerticalScrollerRepresenter is an HFRepresenter whose view is a vertical NSScroller, that represents the current position within an HFController "document." It has no methods beyond those of HFRepresenter. + + As HFVerticalScrollerRepresenter is an especially simple representer, it makes for good sample code. +*/ +@interface HFVerticalScrollerRepresenter : HFRepresenter { + +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.m b/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.m new file mode 100644 index 000000000..371b5687e --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HFVerticalScrollerRepresenter.m @@ -0,0 +1,133 @@ +// +// HFRepresenterVerticalScroller.m +// HexFiend_2 +// +// Copyright 2007 ridiculous_fish. All rights reserved. +// + +/* Note that on Tiger, NSScroller did not support double in any meaningful way; [scroller doubleValue] always returns 0, and setDoubleValue: doesn't look like it works either. */ + +#import + + +@implementation HFVerticalScrollerRepresenter + +/* No special NSCoding support needed */ + +- (NSView *)createView { + NSScroller *scroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy], 64)]; + [scroller setTarget:self]; + [scroller setContinuous:YES]; + [scroller setEnabled:YES]; + [scroller setTarget:self]; + [scroller setAction:@selector(scrollerDidChangeValue:)]; + [scroller setAutoresizingMask:NSViewHeightSizable]; + return scroller; +} + +- (NSUInteger)visibleLines { + HFController *controller = [self controller]; + HFASSERT(controller != NULL); + return ll2l(HFFPToUL(ceill([controller displayedLineRange].length))); +} + +- (void)scrollByKnobToValue:(double)newValue { + HFASSERT(newValue >= 0. && newValue <= 1.); + HFController *controller = [self controller]; + unsigned long long contentsLength = [controller contentsLength]; + NSUInteger bytesPerLine = [controller bytesPerLine]; + HFASSERT(bytesPerLine > 0); + unsigned long long totalLineCountTimesBytesPerLine = HFRoundUpToNextMultipleSaturate(contentsLength - 1, bytesPerLine); + HFASSERT(totalLineCountTimesBytesPerLine == ULLONG_MAX || totalLineCountTimesBytesPerLine % bytesPerLine == 0); + unsigned long long totalLineCount = HFDivideULLRoundingUp(totalLineCountTimesBytesPerLine, bytesPerLine); + HFFPRange currentLineRange = [controller displayedLineRange]; + HFASSERT(currentLineRange.length < HFULToFP(totalLineCount)); + long double maxScroll = totalLineCount - currentLineRange.length; + long double newScroll = maxScroll * (long double)newValue; + [controller setDisplayedLineRange:(HFFPRange){newScroll, currentLineRange.length}]; +} + +- (void)scrollByLines:(long long)linesInt { + if (linesInt == 0) return; + + //note - this properly computes the absolute value even for LLONG_MIN + long double lines = HFULToFP((unsigned long long)llabs(linesInt)); + + HFController *controller = [self controller]; + HFASSERT(controller != NULL); + HFFPRange displayedRange = [[self controller] displayedLineRange]; + if (linesInt < 0) { + displayedRange.location -= MIN(lines, displayedRange.location); + } + else { + long double availableLines = HFULToFP([controller totalLineCount]); + displayedRange.location = MIN(availableLines - displayedRange.length, displayedRange.location + lines); + } + [controller setDisplayedLineRange:displayedRange]; +} + +- (void)scrollerDidChangeValue:(NSScroller *)scroller { + assert(scroller == [self view]); + switch ([scroller hitPart]) { + case NSScrollerDecrementPage: [self scrollByLines: -(long long)[self visibleLines]]; break; + case NSScrollerIncrementPage: [self scrollByLines: (long long)[self visibleLines]]; break; + case NSScrollerDecrementLine: [self scrollByLines: -1LL]; break; + case NSScrollerIncrementLine: [self scrollByLines: 1LL]; break; + case NSScrollerKnob: [self scrollByKnobToValue:[scroller doubleValue]]; break; + default: break; + } +} + +- (void)updateScrollerValue { + HFController *controller = [self controller]; + CGFloat value, proportion; + NSScroller *scroller = [self view]; + BOOL enable = YES; + if (controller == nil) { + value = 0; + proportion = 0; + } + else { + unsigned long long length = [controller contentsLength]; + HFFPRange lineRange = [controller displayedLineRange]; + HFASSERT(lineRange.location >= 0 && lineRange.length >= 0); + if (length == 0) { + value = 0; + proportion = 1; + enable = NO; + } + else { + long double availableLines = HFULToFP([controller totalLineCount]); + long double consumedLines = MAX(1., lineRange.length); + proportion = ld2f(lineRange.length / availableLines); + + long double maxScroll = availableLines - consumedLines; + HFASSERT(maxScroll >= lineRange.location); + if (maxScroll == 0.) { + enable = NO; + value = 0; + } + else { + value = ld2f(lineRange.location / maxScroll); + } + } + } + [scroller setDoubleValue:value]; + [scroller setKnobProportion:proportion]; + [scroller setEnabled:enable]; +} + +- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine { + USE(bytesPerLine); + return [NSScroller scrollerWidthForControlSize:[[self view] controlSize] scrollerStyle:NSScrollerStyleLegacy]; +} + +- (void)controllerDidChange:(HFControllerPropertyBits)bits { + if (bits & (HFControllerContentLength | HFControllerDisplayedLineRange)) [self updateScrollerValue]; +} + ++ (NSPoint)defaultLayoutPosition { + return NSMakePoint(2, 0); +} + +@end diff --git a/thirdparty/SameBoy-old/HexFiend/HexFiend.h b/thirdparty/SameBoy-old/HexFiend/HexFiend.h new file mode 100644 index 000000000..60d69a7e1 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HexFiend.h @@ -0,0 +1,78 @@ +/*! @mainpage HexFiend.framework + * + * @section intro Introduction + * HexFiend.framework (hereafter "Hex Fiend" when there is no risk of confusion with the app by the same name) is a framework designed to enable applications to support viewing and editing of binary data. The emphasis is on editing data in a natural way, following Mac OS X text editing conventions. + * + * Hex Fiend is designed to work efficiently with large amounts (64 bits worth) of data. As such, it can work with arbitrarily large files without reading the entire file into memory. This includes insertions, deletions, and in-place editing. Hex Fiend can also efficiently save such changes back to the file, without requiring any additional temporary disk space. + * + * Hex Fiend has a clean separation between the model, view, and controller layers. The model layer allows for efficient manipulation of raw data of mixed sources, making it useful for tools that need to work with large files. + * + * Both the framework and the app are open source under a BSD-style license. In summary, you may use Hex Fiend in any project as long as you include the copyright notice somewhere in the documentation. + * + * @section requirements Requirements + * Hex Fiend is only available on Mac OS X, and supported on Mountain Lion and later. + * + * @section getting_started Getting Started + * + * The Hex Fiend source code is available at http://ridiculousfish.com/hexfiend/ and on GitHub at https://github.com/ridiculousfish/HexFiend + * + * Hex Fiend comes with some sample code ("HexFiendling"), distributed as part of the project. And of course the Hex Fiend application itself is open source, acting as a more sophisticated sample code. +*/ + + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +/* The following is all for Doxygen */ + + +/*! @defgroup model Model + * Hex Fiend's model classes + */ +///@{ +///@class HFByteArray +///@class HFBTreeByteArray +///@class HFFullMemoryByteArray +///@class HFByteSlice +///@class HFFileByteSlice +///@class HFSharedMemoryByteSlice +///@class HFFullMemoryByteSlice + +///@} + + +/*! @defgroup view View + * Hex Fiend's view classes + */ +///@{ +///@class HFRepresenter +///@class HFHexTextRepresenter +///@class HFStringEncodingTextRepresenter +///@class HFLayoutRepresenter +///@class HFLineCountingRepresenter +///@class HFStatusBarRepresenter +///@class HFVerticalScrollerRepresenter +///@class HFLineCountingRepresenter + +///@} + +/*! @defgroup controller Controller + * Hex Fiend's controller classes + */ +///@{ +///@class HFController + +///@} diff --git a/thirdparty/SameBoy-old/HexFiend/HexFiend_2_Framework_Prefix.pch b/thirdparty/SameBoy-old/HexFiend/HexFiend_2_Framework_Prefix.pch new file mode 100644 index 000000000..96d0fb762 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/HexFiend_2_Framework_Prefix.pch @@ -0,0 +1,99 @@ +// +// Prefix header for all source files of the 'HexFiend_2' target in the 'HexFiend_2' project +// + +#ifdef __OBJC__ + #import + #import +#endif + +#define PRIVATE_EXTERN __private_extern__ + +#include + +#if ! NDEBUG +#define HFASSERT(a) assert(a) +#else +#define HFASSERT(a) if (0 && ! (a)) abort() +#endif + + +#define UNIMPLEMENTED_VOID() [NSException raise:NSGenericException \ + format:@"Message %@ sent to instance of class %@, "\ + @"which does not implement that method",\ + NSStringFromSelector(_cmd), [[self class] description]] + +#define UNIMPLEMENTED() UNIMPLEMENTED_VOID(); return 0 + +/* Macro to "use" a variable to prevent unused variable warnings. */ +#define USE(x) ((void)(x)) + +#define check_malloc(x) ({ size_t _count = (x); void *_result = malloc(_count); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) +#define check_calloc(x) ({ size_t _count = (x); void *_result = calloc(_count, 1); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) +#define check_realloc(p, x) ({ size_t _count = (x); void *_result = realloc((p), x); if(!_result) { fprintf(stderr, "Out of memory reallocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; }) + +#if ! NDEBUG +#define REQUIRE_NOT_NULL(a) do { \ + if ((a)==NULL) {\ + fprintf(stderr, "REQUIRE_NOT_NULL failed: NULL value for parameter " #a " on line %d in file %s\n", __LINE__, __FILE__);\ + abort();\ + }\ +} while (0) + +#define EXPECT_CLASS(e, c) do { \ + if (! [(e) isKindOfClass:[c class]]) {\ + fprintf(stderr, "EXPECT_CLASS failed: Expression " #e " is %s on line %d in file %s\n", (e) ? "(nil)" : [[e description] UTF8String], __LINE__, __FILE__);\ + abort();\ + }\ +} while (0) + +#else +#define REQUIRE_NOT_NULL(a) USE(a) +#define EXPECT_CLASS(e, c) USE(e) +#endif + +#define FOREACH(type, var, exp) for (type var in (exp)) + +#define NEW_ARRAY(type, name, number) \ + type name ## static_ [256];\ + type * name = ((number) <= 256 ? name ## static_ : check_malloc((number) * sizeof(type))) + +#define FREE_ARRAY(name) \ + if (name != name ## static_) free(name) + +#if !defined(MIN) + #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) + #define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#endif + +//How many bytes should we read at a time when doing a find/replace? +#define SEARCH_CHUNK_SIZE 32768 + +//What's the smallest clipboard data size we should offer to avoid copying when quitting? This is 5 MB +#define MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT (5UL << 20) + +//What's the largest clipboard data size we should support exporting (at all?) This is 500 MB. Note that we can still copy more data than this internally, we just can't put it in, say, TextEdit. +#define MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT (500UL << 20) + +// When we save a file, and other byte arrays need to break their dependencies on the file by copying some of its data into memory, what's the max amount we should copy (per byte array)? We currently don't show any progress for this, so this should be a smaller value +#define MAX_MEMORY_TO_USE_FOR_BREAKING_FILE_DEPENDENCIES_ON_SAVE (16 * 1024 * 1024) + +#ifdef __OBJC__ + #import + #import "HFFunctions_Private.h" +#endif + +#ifndef __has_feature // Optional. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +#ifndef NS_RETURNS_RETAINED +#if __has_feature(attribute_ns_returns_retained) +#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) +#else +#define NS_RETURNS_RETAINED +#endif +#endif diff --git a/thirdparty/SameBoy-old/HexFiend/License.txt b/thirdparty/SameBoy-old/HexFiend/License.txt new file mode 100644 index 000000000..7760edbd3 --- /dev/null +++ b/thirdparty/SameBoy-old/HexFiend/License.txt @@ -0,0 +1,21 @@ +Copyright (c) 2005-2009, Peter Ammon +* All rights reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/SameBoy-old/JoyKit/ControllerConfiguration.inc b/thirdparty/SameBoy-old/JoyKit/ControllerConfiguration.inc new file mode 100644 index 000000000..185c7bd86 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/ControllerConfiguration.inc @@ -0,0 +1,532 @@ +#define BUTTON(x) @(JOYButtonUsageGeneric0 + (x)) +#define AXIS(x) @(JOYAxisUsageGeneric0 + (x)) +#define AXES2D(x) @(JOYAxes2DUsageGeneric0 + (x)) + +hacksByManufacturer = @{ + @(0x045E): @{ // Microsoft + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(2), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(2), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageA), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageX), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageLStick), + BUTTON(8): @(JOYButtonUsageRStick), + BUTTON(9): @(JOYButtonUsageStart), + BUTTON(10): @(JOYButtonUsageSelect), + BUTTON(11): @(JOYButtonUsageHome), + }, + + JOYAxisUsageMapping: @{ + AXIS(3): @(JOYAxisUsageL1), + AXIS(6): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), + }, + + JOYEmulateAxisButtons: @YES, + }, + + @(0x054C): @{ // Sony + /* Generally untested, but should work */ + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageY), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageA), + BUTTON(4): @(JOYButtonUsageX), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageL2), + BUTTON(8): @(JOYButtonUsageR2), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageHome), + BUTTON(14): @(JOYButtonUsageMisc), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL2), + AXIS(5): @(JOYAxisUsageR2), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + + // When DualSense mode is activated on BT, The report ID is 0x31 and there's an extra byte + JOYCustomReports: @{ + @(0x31): @[ + /* 1D and 2D axes */ + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x08, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x10, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x18, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x20, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x28, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x30, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0, @"max": @255}, + + /* Hat Switch*/ + @{@"reportID": @(0x31), @"size":@4, @"offset":@0x40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Hatswitch), @"min": @0, @"max": @7}, + + /* Buttons */ + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x44, @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x45, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x46, @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x47, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x48, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x49, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4A, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4B, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4C, @"usagePage":@(kHIDPage_Button), @"usage":@9}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4D, @"usagePage":@(kHIDPage_Button), @"usage":@10}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4E, @"usagePage":@(kHIDPage_Button), @"usage":@11}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4F, @"usagePage":@(kHIDPage_Button), @"usage":@12}, + @{@"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)}, + ], + }, + + JOYIsSony: @YES, + } +}; + +hacksByName = @{ + @"WUP-028": @{ // Nintendo GameCube Controller Adapter + JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageA), + BUTTON(2): @(JOYButtonUsageB), + BUTTON(3): @(JOYButtonUsageX), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageStart), + BUTTON(6): @(JOYButtonUsageZ), + BUTTON(7): @(JOYButtonUsageR1), + BUTTON(8): @(JOYButtonUsageL1), + }, + + JOYAxisUsageMapping: @{ + AXIS(3): @(JOYAxisUsageL1), + AXIS(6): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(2), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(3), + }, + + JOYRumbleUsage: @1, + JOYRumbleUsagePage: @0xFF00, + + JOYConnectedUsage: @2, + JOYConnectedUsagePage: @0xFF00, + + JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1], + + JOYCustomReports: @{ + + // Rumble + @(-17): @[ + @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(2), @"size":@1, @"offset":@8, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(3), @"size":@1, @"offset":@16, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + @{@"reportID": @(4), @"size":@1, @"offset":@24, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, + ], + + @(33): @[ + + // Player 1 + + @{@"reportID": @(1), @"size":@1, @"offset":@4, @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(1), @"size":@1, @"offset":@8, @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(1), @"size":@1, @"offset":@9, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(1), @"size":@1, @"offset":@10,@"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(1), @"size":@1, @"offset":@11,@"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@14, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(1), @"size":@1, @"offset":@15, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(1), @"size":@1, @"offset":@16, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(1), @"size":@1, @"offset":@17, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(1), @"size":@1, @"offset":@18, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(1), @"size":@8, @"offset":@24, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(1), @"size":@8, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@48, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(1), @"size":@8, @"offset":@56, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 2 + + @{@"reportID": @(2), @"size":@1, @"offset":@(4 + 72), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + + @{@"reportID": @(2), @"size":@1, @"offset":@(8 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(2), @"size":@1, @"offset":@(9 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(2), @"size":@1, @"offset":@(10 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(2), @"size":@1, @"offset":@(11 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(2), @"size":@1, @"offset":@(12 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(13 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(14 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(2), @"size":@1, @"offset":@(15 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(2), @"size":@1, @"offset":@(16 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(2), @"size":@1, @"offset":@(17 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(2), @"size":@1, @"offset":@(18 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(2), @"size":@1, @"offset":@(19 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(24 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(32 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(40 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(48 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(2), @"size":@8, @"offset":@(56 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(2), @"size":@8, @"offset":@(64 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 3 + + @{@"reportID": @(3), @"size":@1, @"offset":@(4 + 144), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(3), @"size":@1, @"offset":@(8 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(3), @"size":@1, @"offset":@(9 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(3), @"size":@1, @"offset":@(10 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(3), @"size":@1, @"offset":@(11 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(3), @"size":@1, @"offset":@(12 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(13 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(14 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(3), @"size":@1, @"offset":@(15 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(3), @"size":@1, @"offset":@(16 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(3), @"size":@1, @"offset":@(17 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(3), @"size":@1, @"offset":@(18 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(3), @"size":@1, @"offset":@(19 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(24 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(32 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(40 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(48 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(3), @"size":@8, @"offset":@(56 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(3), @"size":@8, @"offset":@(64 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + // Player 4 + + @{@"reportID": @(4), @"size":@1, @"offset":@(4 + 216), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1}, + + @{@"reportID": @(4), @"size":@1, @"offset":@(8 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(4), @"size":@1, @"offset":@(9 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(4), @"size":@1, @"offset":@(10 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(4), @"size":@1, @"offset":@(11 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@4}, + + @{@"reportID": @(4), @"size":@1, @"offset":@(12 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(13 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(14 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(4), @"size":@1, @"offset":@(15 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + + + @{@"reportID": @(4), @"size":@1, @"offset":@(16 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(4), @"size":@1, @"offset":@(17 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(4), @"size":@1, @"offset":@(18 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(4), @"size":@1, @"offset":@(19 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(24 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(32 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(40 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(48 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0}, + + @{@"reportID": @(4), @"size":@8, @"offset":@(56 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(4), @"size":@8, @"offset":@(64 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + + + ]}, + }, + + @"GameCube Controller Adapter": @{ // GameCube Controller PC Adapter + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageX), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageB), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(8): @(JOYButtonUsageZ), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(13): @(JOYButtonUsageDPadUp), + BUTTON(14): @(JOYButtonUsageDPadRight), + BUTTON(15): @(JOYButtonUsageDPadDown), + BUTTON(16): @(JOYButtonUsageDPadLeft), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL1), + AXIS(5): @(JOYAxisUsageR1), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), + }, + + JOYRumbleUsage: @1, + JOYRumbleUsagePage: @0xFF00, + JOYRumbleMin: @0, + JOYRumbleMax: @255, + JOYSwapZRz: @YES, + }, + + @"Twin USB Joystick": @{ // DualShock PC Adapter + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(kHIDUsage_GD_Rz): @(1), + }, + JOYReportIDFilters: @[@[@1], @[@2]], + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageX), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageB), + BUTTON(4): @(JOYButtonUsageY), + BUTTON(5): @(JOYButtonUsageL2), + BUTTON(6): @(JOYButtonUsageR2), + BUTTON(7): @(JOYButtonUsageL1), + BUTTON(8): @(JOYButtonUsageR1), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageDPadUp), + BUTTON(14): @(JOYButtonUsageDPadRight), + BUTTON(15): @(JOYButtonUsageDPadDown), + BUTTON(16): @(JOYButtonUsageDPadLeft), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(6): @(JOYAxes2DUsageRightStick), + }, + + JOYSwapZRz: @YES, + }, + + @"Pro Controller": @{ // Switch Pro Controller + JOYIsSwitch: @YES, + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(0), + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageB), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageY), + BUTTON(4): @(JOYButtonUsageX), + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(7): @(JOYButtonUsageL2), + BUTTON(8): @(JOYButtonUsageR2), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageHome), + BUTTON(14): @(JOYButtonUsageMisc), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + + JOYCustomReports: @{ + @(0x30): @[ + + @{@"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}, + @{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + + // SR and SL not used on the Pro Controller + @{@"reportID": @(1), @"size":@1, @"offset":@22, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(1), @"size":@1, @"offset":@23, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(1), @"size":@1, @"offset":@24, @"usagePage":@(kHIDPage_Button), @"usage":@9}, + @{@"reportID": @(1), @"size":@1, @"offset":@25, @"usagePage":@(kHIDPage_Button), @"usage":@10}, + @{@"reportID": @(1), @"size":@1, @"offset":@26, @"usagePage":@(kHIDPage_Button), @"usage":@12}, + @{@"reportID": @(1), @"size":@1, @"offset":@27, @"usagePage":@(kHIDPage_Button), @"usage":@11}, + + @{@"reportID": @(1), @"size":@1, @"offset":@28, @"usagePage":@(kHIDPage_Button), @"usage":@13}, + @{@"reportID": @(1), @"size":@1, @"offset":@29, @"usagePage":@(kHIDPage_Button), @"usage":@14}, + + @{@"reportID": @(1), @"size":@1, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(1), @"size":@1, @"offset":@33, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + @{@"reportID": @(1), @"size":@1, @"offset":@34, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@35, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + + // SR and SL not used on the Pro Controller + @{@"reportID": @(1), @"size":@1, @"offset":@38, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(1), @"size":@1, @"offset":@39, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + + /* Sticks */ + @{@"reportID": @(1), @"size":@12, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @0xFFF}, + @{@"reportID": @(1), @"size":@12, @"offset":@52, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @0xFFF, @"max": @0}, + + @{@"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)}, + ], + }, + + JOYIgnoredReports: @[@(0x30)], // Ignore the real 0x30 report as it's broken + }, + @"PLAYSTATION(R)3 Controller": @{ // DualShock 3 + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(3), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageSelect), + BUTTON(2): @(JOYButtonUsageL3), + BUTTON(3): @(JOYButtonUsageR3), + BUTTON(4): @(JOYButtonUsageStart), + BUTTON(5): @(JOYButtonUsageDPadUp), + BUTTON(6): @(JOYButtonUsageDPadRight), + BUTTON(7): @(JOYButtonUsageDPadDown), + BUTTON(8): @(JOYButtonUsageDPadLeft), + BUTTON(9): @(JOYButtonUsageL2), + BUTTON(10): @(JOYButtonUsageR2), + BUTTON(11): @(JOYButtonUsageL1), + BUTTON(12): @(JOYButtonUsageR1), + BUTTON(13): @(JOYButtonUsageX), + BUTTON(14): @(JOYButtonUsageA), + BUTTON(15): @(JOYButtonUsageB), + BUTTON(16): @(JOYButtonUsageY), + BUTTON(17): @(JOYButtonUsageHome), + }, + + JOYAxisUsageMapping: @{ + AXIS(4): @(JOYAxisUsageL1), + AXIS(5): @(JOYAxisUsageR1), + AXIS(8): @(JOYAxisUsageL2), + AXIS(9): @(JOYAxisUsageR2), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), + }, + + JOYCustomReports: @{ + @(0x01): @[ + /* Pressure sensitive inputs */ + @{@"reportID": @(1), @"size":@8, @"offset":@(13 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(14 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(15 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(16 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(17 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Dial), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(18 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Wheel), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(19 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(20 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(21 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(22 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(23 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + @{@"reportID": @(1), @"size":@8, @"offset":@(24 * 8), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Slider), @"min": @0, @"max": @255}, + ] + }, + + JOYIsDualShock3: @YES, + }, + +}; diff --git a/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.h b/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.h new file mode 100644 index 000000000..b6f6d152f --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.h @@ -0,0 +1,24 @@ +#import + +typedef enum { + JOYAxes2DUsageNone, + JOYAxes2DUsageLeftStick, + JOYAxes2DUsageRightStick, + JOYAxes2DUsageMiddleStick, + JOYAxes2DUsagePointer, + JOYAxes2DUsageNonGenericMax, + + JOYAxes2DUsageGeneric0 = 0x10000, +} JOYAxes2DUsage; + +@interface JOYAxes2D : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYAxes2DUsage) usage; +- (uint64_t)uniqueID; +- (double)distance; +- (double)angle; +- (NSPoint)value; +@property JOYAxes2DUsage usage; +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.m b/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.m new file mode 100644 index 000000000..272d34f9c --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYAxes2D.m @@ -0,0 +1,181 @@ +#import "JOYAxes2D.h" +#import "JOYElement.h" + +@implementation JOYAxes2D +{ + JOYElement *_element1, *_element2; + double _state1, _state2; + int32_t initialX, initialY; + int32_t minX, minY; + int32_t maxX, maxY; + +} + ++ (NSString *)usageToString: (JOYAxes2DUsage) usage +{ + if (usage < JOYAxes2DUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"Left Stick", + @"Right Stick", + @"Middle Stick", + @"Pointer", + }[usage]; + } + if (usage >= JOYAxes2DUsageGeneric0) { + return [NSString stringWithFormat:@"Generic 2D Analog Control %d", usage - JOYAxes2DUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage 2D 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 degrees>", self.className, self, self.usageString, self.uniqueID, self.distance * 100, self.angle]; +} + +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 +{ + self = [super init]; + if (!self) return self; + + _element1 = element1; + _element2 = element2; + + + if (element1.usagePage == kHIDPage_GenericDesktop) { + uint16_t usage = element1.usage; + _usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1; + } + initialX = 0; + initialY = 0; + minX = element1.max; + minY = element2.max; + maxX = element1.min; + maxY = element2.min; + + return self; +} + +- (NSPoint)value +{ + return NSMakePoint(_state1, _state2); +} + +-(int32_t) effectiveMinX +{ + int32_t rawMin = _element1.min; + int32_t rawMax = _element1.max; + if (initialX == 0) return rawMin; + if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return minX; + if ((initialX - rawMin) < (rawMax - initialX)) return rawMin; + return initialX - (rawMax - initialX); +} + +-(int32_t) effectiveMinY +{ + int32_t rawMin = _element2.min; + int32_t rawMax = _element2.max; + if (initialY == 0) return rawMin; + if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return minY; + if ((initialY - rawMin) < (rawMax - initialY)) return rawMin; + return initialY - (rawMax - initialY); +} + +-(int32_t) effectiveMaxX +{ + int32_t rawMin = _element1.min; + int32_t rawMax = _element1.max; + if (initialX == 0) return rawMax; + if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return maxX; + if ((initialX - rawMin) > (rawMax - initialX)) return rawMax; + return initialX + (initialX - rawMin); +} + +-(int32_t) effectiveMaxY +{ + int32_t rawMin = _element2.min; + int32_t rawMax = _element2.max; + if (initialY == 0) return rawMax; + if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return maxY; + if ((initialY - rawMin) > (rawMax - initialY)) return rawMax; + return initialY + (initialY - rawMin); +} + +- (bool)updateState +{ + int32_t x = [_element1 value]; + int32_t y = [_element2 value]; + if (x == 0 && y == 0) return false; + + if (initialX == 0 && initialY == 0) { + initialX = x; + initialY = y; + } + + double old1 = _state1, old2 = _state2; + { + int32_t value = x; + + if (initialX != 0) { + minX = MIN(value, minX); + maxX = MAX(value, maxX); + } + + double min = [self effectiveMinX]; + double max = [self effectiveMaxX]; + if (min == max) return false; + + _state1 = (value - min) / (max - min) * 2 - 1; + } + + { + int32_t value = y; + + if (initialY != 0) { + minY = MIN(value, minY); + maxY = MAX(value, maxY); + } + + double min = [self effectiveMinY]; + double max = [self effectiveMaxY]; + if (min == max) return false; + + _state2 = (value - min) / (max - min) * 2 - 1; + } + + if (_state1 < -1 || _state1 > 1 || + _state2 < -1 || _state2 > 1) { + // Makes no sense, recalibrate + _state1 = _state2 = 0; + initialX = initialY = 0; + minX = _element1.max; + minY = _element2.max; + maxX = _element1.min; + maxY = _element2.min; + } + + return old1 != _state1 || old2 != _state2; +} + +- (double)distance +{ + return MIN(sqrt(_state1 * _state1 + _state2 * _state2), 1.0); +} + +- (double)angle { + double temp = atan2(_state2, _state1) * 180 / M_PI; + if (temp >= 0) return temp; + return temp + 360; +} +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYAxes3D.h b/thirdparty/SameBoy-old/JoyKit/JOYAxes3D.h new file mode 100644 index 000000000..5c838079d --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/JoyKit/JOYAxes3D.m b/thirdparty/SameBoy-old/JoyKit/JOYAxes3D.m new file mode 100644 index 000000000..6ec146a59 --- /dev/null +++ b/thirdparty/SameBoy-old/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/thirdparty/SameBoy-old/JoyKit/JOYAxis.h b/thirdparty/SameBoy-old/JoyKit/JOYAxis.h new file mode 100644 index 000000000..8d4b7abe3 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYAxis.h @@ -0,0 +1,36 @@ +#import +#import "JOYButton.h" + +typedef enum { + JOYAxisUsageNone, + JOYAxisUsageL1, + JOYAxisUsageL2, + JOYAxisUsageL3, + JOYAxisUsageR1, + JOYAxisUsageR2, + JOYAxisUsageR3, + + JOYAxisUsageSlider, + JOYAxisUsageDial, + JOYAxisUsageWheel, + + JOYAxisUsageRudder, + JOYAxisUsageThrottle, + JOYAxisUsageAccelerator, + JOYAxisUsageBrake, + + JOYAxisUsageNonGenericMax, + + JOYAxisUsageGeneric0 = 0x10000, +} JOYAxisUsage; + +@interface JOYAxis : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYAxisUsage) usage; +- (uint64_t)uniqueID; +- (double)value; +- (JOYButtonUsage)equivalentButtonUsage; +@property JOYAxisUsage usage; +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYAxis.m b/thirdparty/SameBoy-old/JoyKit/JOYAxis.m new file mode 100644 index 000000000..afe90d268 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYAxis.m @@ -0,0 +1,129 @@ +#import "JOYAxis.h" +#import "JOYElement.h" + +@implementation JOYAxis +{ + JOYElement *_element; + double _state; + double _min; +} + ++ (NSString *)usageToString: (JOYAxisUsage) usage +{ + if (usage < JOYAxisUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"Analog L1", + @"Analog L2", + @"Analog L3", + @"Analog R1", + @"Analog R2", + @"Analog R3", + @"Slider", + @"Dial", + @"Wheel", + @"Rudder", + @"Throttle", + @"Accelerator", + @"Brake", + }[usage]; + } + if (usage >= JOYAxisUsageGeneric0) { + return [NSString stringWithFormat:@"Generic Analog Control %d", usage - JOYAxisUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage Axis %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %f%%>", self.className, self, self.usageString, self.uniqueID, _state * 100]; +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + + + if (element.usagePage == kHIDPage_GenericDesktop) { + switch (element.usage) { + case kHIDUsage_GD_Slider: _usage = JOYAxisUsageSlider; break; + case kHIDUsage_GD_Dial: _usage = JOYAxisUsageDial; break; + case kHIDUsage_GD_Wheel: _usage = JOYAxisUsageWheel; break; + default: + _usage = JOYAxisUsageGeneric0 + element.usage - kHIDUsage_GD_X + 1; + break; + } + } + else if (element.usagePage == kHIDPage_Simulation) { + switch (element.usage) { + case kHIDUsage_Sim_Accelerator: _usage = JOYAxisUsageAccelerator; break; + case kHIDUsage_Sim_Brake: _usage = JOYAxisUsageBrake; break; + case kHIDUsage_Sim_Rudder: _usage = JOYAxisUsageRudder; break; + case kHIDUsage_Sim_Throttle: _usage = JOYAxisUsageThrottle; break; + } + } + _min = 1.0; + + return self; +} + +- (double) value +{ + return _state; +} + +- (bool)updateState +{ + double min = _element.min; + double max = _element.max; + if (min == max) return false; + double old = _state; + double unnormalized = ([_element value] - min) / (max - min); + if (unnormalized < _min) { + _min = unnormalized; + } + if (_min != 1) { + _state = (unnormalized - _min) / (1 - _min); + } + return old != _state; +} + +- (JOYButtonUsage)equivalentButtonUsage +{ + if (self.usage >= JOYAxisUsageGeneric0) { + return self.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0; + } + switch (self.usage) { + case JOYAxisUsageL1: return JOYButtonUsageL1; + case JOYAxisUsageL2: return JOYButtonUsageL2; + case JOYAxisUsageL3: return JOYButtonUsageL3; + case JOYAxisUsageR1: return JOYButtonUsageR1; + case JOYAxisUsageR2: return JOYButtonUsageR2; + case JOYAxisUsageR3: return JOYButtonUsageR3; + case JOYAxisUsageSlider: return JOYButtonUsageSlider; + case JOYAxisUsageDial: return JOYButtonUsageDial; + case JOYAxisUsageWheel: return JOYButtonUsageWheel; + case JOYAxisUsageRudder: return JOYButtonUsageRudder; + case JOYAxisUsageThrottle: return JOYButtonUsageThrottle; + case JOYAxisUsageAccelerator: return JOYButtonUsageAccelerator; + case JOYAxisUsageBrake: return JOYButtonUsageBrake; + default: return JOYButtonUsageNone; + } +} + + +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYButton.h b/thirdparty/SameBoy-old/JoyKit/JOYButton.h new file mode 100644 index 000000000..08c3aced6 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYButton.h @@ -0,0 +1,58 @@ +#import + +typedef enum { + JOYButtonUsageNone, + JOYButtonUsageA, + JOYButtonUsageB, + JOYButtonUsageC, + JOYButtonUsageX, + JOYButtonUsageY, + JOYButtonUsageZ, + JOYButtonUsageStart, + JOYButtonUsageSelect, + JOYButtonUsageHome, + JOYButtonUsageMisc, + JOYButtonUsageLStick, + JOYButtonUsageRStick, + JOYButtonUsageL1, + JOYButtonUsageL2, + JOYButtonUsageL3, + JOYButtonUsageR1, + JOYButtonUsageR2, + JOYButtonUsageR3, + JOYButtonUsageDPadLeft, + JOYButtonUsageDPadRight, + JOYButtonUsageDPadUp, + JOYButtonUsageDPadDown, + + JOYButtonUsageSlider, + JOYButtonUsageDial, + JOYButtonUsageWheel, + + JOYButtonUsageRudder, + JOYButtonUsageThrottle, + JOYButtonUsageAccelerator, + JOYButtonUsageBrake, + + JOYButtonUsageNonGenericMax, + + 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/thirdparty/SameBoy-old/JoyKit/JOYButton.m b/thirdparty/SameBoy-old/JoyKit/JOYButton.m new file mode 100644 index 000000000..568d3835e --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYButton.m @@ -0,0 +1,113 @@ +#import "JOYButton.h" +#import "JOYElement.h" +#import + +@implementation JOYButton +{ + JOYElement *_element; + bool _state; +} + ++ (NSString *)usageToString: (JOYButtonUsage) usage +{ + if (usage < JOYButtonUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"A", + @"B", + @"C", + @"X", + @"Y", + @"Z", + @"Start", + @"Select", + @"Home", + @"Misc", + @"Left Stick", + @"Right Stick", + @"L1", + @"L2", + @"L3", + @"R1", + @"R2", + @"R3", + @"D-Pad Left", + @"D-Pad Right", + @"D-Pad Up", + @"D-Pad Down", + }[usage]; + } + if (usage >= JOYButtonUsageGeneric0) { + return [NSString stringWithFormat:@"Generic Button %d", usage - JOYButtonUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage Button %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %s>", self.className, self, self.usageString, self.uniqueID, _state? "Presssed" : "Released"]; +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + + if (element.usagePage == kHIDPage_Button) { + uint16_t usage = element.usage; + _usage = JOYButtonUsageGeneric0 + usage; + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + switch (element.usage) { + case kHIDUsage_GD_DPadUp: _usage = JOYButtonUsageDPadUp; break; + case kHIDUsage_GD_DPadDown: _usage = JOYButtonUsageDPadDown; break; + case kHIDUsage_GD_DPadRight: _usage = JOYButtonUsageDPadRight; break; + case kHIDUsage_GD_DPadLeft: _usage = JOYButtonUsageDPadLeft; break; + case kHIDUsage_GD_Start: _usage = JOYButtonUsageStart; break; + case kHIDUsage_GD_Select: _usage = JOYButtonUsageSelect; break; + case kHIDUsage_GD_SystemMainMenu: _usage = JOYButtonUsageHome; break; + } + } + else if (element.usagePage == kHIDPage_Consumer) { + switch (element.usage) { + case kHIDUsage_Csmr_ACHome: _usage = JOYButtonUsageHome; break; + case kHIDUsage_Csmr_ACBack: _usage = JOYButtonUsageSelect; break; + } + } + + return self; +} + +- (bool) isPressed +{ + return _state; +} + +- (bool)updateState +{ + bool state = [_element value]; + if (_state != state) { + _state = state; + return true; + } + return false; +} + +- (JOYButtonType)type +{ + return JOYButtonTypeNormal; +} +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYController.h b/thirdparty/SameBoy-old/JoyKit/JOYController.h new file mode 100644 index 000000000..a21175c20 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYController.h @@ -0,0 +1,44 @@ +#import +#import "JOYButton.h" +#import "JOYAxis.h" +#import "JOYAxes2D.h" +#import "JOYAxes3D.h" +#import "JOYHat.h" + +static NSString const *JOYAxes2DEmulateButtonsKey = @"JOYAxes2DEmulateButtons"; +static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; + +@class JOYController; + +@protocol JOYListener + +@optional +-(void) controllerConnected:(JOYController *)controller; +-(void) controllerDisconnected:(JOYController *)controller; +-(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 + +@interface JOYController : NSObject ++ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options; ++ (NSArray *) allControllers; ++ (void) registerListener:(id)listener; ++ (void) unregisterListener:(id)listener; +- (NSString *)deviceName; +- (NSString *)uniqueID; +- (NSArray *) buttons; +- (NSArray *) axes; +- (NSArray *) axes2D; +- (NSArray *) axes3D; +- (NSArray *) hats; +- (void)setRumbleAmplitude:(double)amp; +- (void)setPlayerLEDs:(uint8_t)mask; +- (uint8_t)LEDMaskForPlayer:(unsigned)player; +@property (readonly, getter=isConnected) bool connected; +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYController.m b/thirdparty/SameBoy-old/JoyKit/JOYController.m new file mode 100644 index 000000000..b524df4be --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYController.m @@ -0,0 +1,1168 @@ +#import "JOYController.h" +#import "JOYMultiplayerController.h" +#import "JOYElement.h" +#import "JOYSubElement.h" +#import "JOYFullReportElement.h" +#import "JOYButton.h" +#import "JOYEmulatedButton.h" +#include + +#include +extern NSTextField *globalDebugField; + +#define PWM_RESOLUTION 16 + +static NSString const *JOYAxisGroups = @"JOYAxisGroups"; +static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; +static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; +static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping"; +static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; +static NSString const *JOYCustomReports = @"JOYCustomReports"; +static NSString const *JOYIsSwitch = @"JOYIsSwitch"; +static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; +static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; +static NSString const *JOYConnectedUsage = @"JOYConnectedUsage"; +static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage"; +static NSString const *JOYRumbleMin = @"JOYRumbleMin"; +static NSString const *JOYRumbleMax = @"JOYRumbleMax"; +static NSString const *JOYSwapZRz = @"JOYSwapZRz"; +static NSString const *JOYActivationReport = @"JOYActivationReport"; +static NSString const *JOYIgnoredReports = @"JOYIgnoredReports"; +static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3"; +static NSString const *JOYIsSony = @"JOYIsSony"; +static NSString const *JOYEmulateAxisButtons = @"JOYEmulateAxisButtons"; + +static NSMutableDictionary *controllers; // Physical controllers +static NSMutableArray *exposedControllers; // Logical controllers + +static NSDictionary *hacksByName = nil; +static NSDictionary *hacksByManufacturer = nil; + +static NSMutableSet> *listeners = nil; + +static bool axes2DEmulateButtons = false; +static bool hatsEmulateButtons = false; + +@interface JOYController () ++ (void)controllerAdded:(IOHIDDeviceRef) device; ++ (void)controllerRemoved:(IOHIDDeviceRef) device; +- (void)elementChanged:(IOHIDElementRef) element; +- (void)gotReport:(NSData *)report; + +@end + +@interface JOYButton () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYAxis () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYHat () +- (instancetype)initWithElement:(JOYElement *)element; +- (bool)updateState; +@end + +@interface JOYAxes2D () +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2; +- (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 @{ + @kIOHIDDeviceUsagePageKey: @(page), + @kIOHIDDeviceUsageKey: @(usage), + }; +} + +static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + [JOYController controllerAdded:device]; +} + +static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + [JOYController controllerRemoved:device]; +} + +static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value) +{ + [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)]; +} + +static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, + uint32_t reportID, uint8_t *report, CFIndex reportLength) +{ + if (reportLength) { + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]]; + } +} + +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t sequence; + uint8_t rumbleData[8]; + uint8_t command; + uint8_t commandData[26]; +} JOYSwitchPacket; + +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t padding; + uint8_t rumbleRightDuration; + uint8_t rumbleRightStrength; + uint8_t rumbleLeftDuration; + uint8_t rumbleLeftStrength; + uint32_t padding2; + uint8_t ledsEnabled; + struct { + uint8_t timeEnabled; + uint8_t dutyLength; + uint8_t enabled; + uint8_t dutyOff; + uint8_t dutyOn; + } __attribute__((packed)) led[5]; + uint8_t padding3[13]; +} JOYDualShock3Output; + +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t sequence; + union { + uint8_t tag; + uint8_t reportIDOnUSB; + }; + uint16_t flags; + uint8_t rumbleRightStrength; // Weak + uint8_t rumbleLeftStrength; // Strong + uint8_t reserved[4]; + uint8_t muteButtonLED; + uint8_t powerSaveControl; + uint8_t reserved2[28]; + uint8_t flags2; + uint8_t reserved3[2]; + uint8_t lightbarSetup; + uint8_t LEDBrightness; + uint8_t playerLEDs; + uint8_t lightbarRed; + uint8_t lightbarGreen; + uint8_t lightbarBlue; + uint8_t bluetoothSpecific[24]; + uint32_t crc32; +} JOYDualSenseOutput; + + +typedef union { + JOYSwitchPacket switchPacket; + JOYDualShock3Output ds3Output; + JOYDualSenseOutput dualsenseOutput; +} JOYVendorSpecificOutput; + +@implementation JOYController +{ + IOHIDDeviceRef _device; + NSMutableDictionary *_buttons; + NSMutableDictionary *_axes; + NSMutableDictionary *_axes2D; + NSMutableDictionary *_axes3D; + NSMutableDictionary *_hats; + NSMutableDictionary *_fullReportElements; + NSMutableDictionary *> *_multiElements; + JOYAxes3D *_lastAxes3D; + + // Button emulation + NSMutableDictionary *_axisEmulatedButtons; + NSMutableDictionary *> *_axes2DEmulatedButtons; + NSMutableDictionary *> *_hatEmulatedButtons; + + JOYElement *_rumbleElement; + JOYElement *_connectedElement; + NSMutableDictionary *_iokitToJOY; + NSString *_serialSuffix; + bool _isSwitch; // Does this controller use the Switch protocol? + bool _isDualShock3; // Does this controller use DS3 outputs? + bool _isSony; // Is this a DS4 or newer Sony controller? + bool _isDualSense; + bool _isUSBDualSense; + + JOYVendorSpecificOutput _lastVendorSpecificOutput; + volatile double _rumbleAmplitude; + bool _physicallyConnected; + bool _logicallyConnected; + + NSDictionary *_hacks; + NSMutableData *_lastReport; + + // Used when creating inputs + JOYElement *_previousAxisElement; + + uint8_t _playerLEDs; + double _sentRumbleAmp; + unsigned _rumbleCounter; + bool _deviceCantSendReports; + dispatch_queue_t _rumbleQueue; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks +{ + return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks]; +} + +-(void)createOutputForElement:(JOYElement *)element +{ + uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue]; + uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue]; + + if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { + if (_hacks[JOYRumbleMin]) { + element.min = [_hacks[JOYRumbleMin] unsignedIntValue]; + } + if (_hacks[JOYRumbleMax]) { + element.max = [_hacks[JOYRumbleMax] unsignedIntValue]; + } + _rumbleElement = element; + } +} + +-(void)createInputForElement:(JOYElement *)element +{ + uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue]; + uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue]; + + if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { + _connectedElement = element; + _logicallyConnected = element.value != element.min; + return; + } + + NSDictionary *axisGroups = @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(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 || + (element.usagePage == kHIDPage_Consumer && (element.usage == kHIDUsage_Csmr_ACHome || + element.usage == kHIDUsage_Csmr_ACBack))) { + button: { + JOYButton *button = [[JOYButton alloc] initWithElement: element]; + [_buttons setObject:button forKey:element]; + NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil; + if (replacementUsage) { + button.usage = [replacementUsage unsignedIntValue]; + } + return; + } + } + else if (element.usagePage == kHIDPage_Simulation) { + switch (element.usage) { + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: + case kHIDUsage_Sim_Rudder: + case kHIDUsage_Sim_Throttle: + goto single; + } + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + switch (element.usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: { + + JOYElement *other = _previousAxisElement; + _previousAxisElement = element; + if (!other) goto single; + if (other.usage >= element.usage) goto single; + if (other.reportID != element.reportID) goto single; + if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; + if (other.parentID != element.parentID) goto single; + + JOYAxes2D *axes = nil; + if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) { + axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; + } + else { + axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; + } + NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)]; + if (replacementUsage) { + axes.usage = [replacementUsage unsignedIntValue]; + } + + [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; + [_axes removeObjectForKey:other]; + _previousAxisElement = nil; + _axes2D[other] = axes; + _axes2D[element] = axes; + + if (axes2DEmulateButtons) { + _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ + [[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], + ]; + } + break; + } + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: + { single: { + JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; + [_axes setObject:axis forKey:element]; + + NSNumber *replacementUsage = element.usagePage == kHIDPage_GenericDesktop? _hacks[JOYAxisUsageMapping][@(axis.usage)] : nil; + if (replacementUsage) { + axis.usage = [replacementUsage unsignedIntValue]; + } + + if ([_hacks[JOYEmulateAxisButtons] boolValue]) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage type:JOYButtonTypeAxisEmulated uniqueID:axis.uniqueID]; + } + + + break; + }} + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + case kHIDUsage_GD_SystemMainMenu: + goto button; + + case kHIDUsage_GD_Hatswitch: { + JOYHat *hat = [[JOYHat alloc] initWithElement: element]; + [_hats setObject:hat forKey:element]; + if (hatsEmulateButtons) { + _hatEmulatedButtons[@(hat.uniqueID)] = @[ + [[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; + } + } + } +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks +{ + self = [super init]; + if (!self) return self; + + _physicallyConnected = true; + _logicallyConnected = true; + _device = (IOHIDDeviceRef)CFRetain(device); + _serialSuffix = suffix; + _playerLEDs = -1; + + IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); + IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone)); + _buttons = [NSMutableDictionary dictionary]; + _axes = [NSMutableDictionary dictionary]; + _axes2D = [NSMutableDictionary dictionary]; + _axes3D = [NSMutableDictionary dictionary]; + _hats = [NSMutableDictionary dictionary]; + _axisEmulatedButtons = [NSMutableDictionary dictionary]; + _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; + _hatEmulatedButtons = [NSMutableDictionary dictionary]; + _iokitToJOY = [NSMutableDictionary dictionary]; + + + _hacks = hacks; + _isSwitch = [_hacks[JOYIsSwitch] boolValue]; + _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; + _isSony = [_hacks[JOYIsSony] boolValue]; + + NSDictionary *customReports = hacks[JOYCustomReports]; + _lastReport = [NSMutableData dataWithLength:MAX( + MAX( + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] + ), + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] + )]; + IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + + if (hacks[JOYCustomReports]) { + _multiElements = [NSMutableDictionary dictionary]; + _fullReportElements = [NSMutableDictionary dictionary]; + + + for (NSNumber *_reportID in customReports) { + signed reportID = [_reportID intValue]; + bool isOutput = false; + if (reportID < 0) { + isOutput = true; + reportID = -reportID; + } + + JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID]; + NSMutableArray *elements = [NSMutableArray array]; + for (NSDictionary *subElementDef in customReports[_reportID]) { + if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue; + JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element + size:subElementDef[@"size"].unsignedLongValue + offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID + usagePage:subElementDef[@"usagePage"].unsignedLongValue + usage:subElementDef[@"usage"].unsignedLongValue + min:subElementDef[@"min"].unsignedIntValue + max:subElementDef[@"max"].unsignedIntValue]; + [elements addObject:subElement]; + if (isOutput) { + [self createOutputForElement:subElement]; + } + else { + [self createInputForElement:subElement]; + } + } + _multiElements[element] = elements; + if (!isOutput) { + _fullReportElements[@(reportID)] = element; + } + } + } + + id previous = nil; + NSSet *ignoredReports = nil; + if (hacks[JOYIgnoredReports]) { + ignoredReports = [NSSet setWithArray:hacks[JOYIgnoredReports]]; + } + + for (id _element in array) { + if (_element == previous) continue; // Some elements are reported twice for some reason + previous = _element; + JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; + + bool isOutput = false; + if (filter && ![filter containsObject:@(element.reportID)]) continue; + + switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { + /* Handled */ + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + break; + case kIOHIDElementTypeOutput: + isOutput = true; + break; + /* Ignored */ + default: + case kIOHIDElementTypeInput_ScanCodes: + case kIOHIDElementTypeInput_NULL: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + continue; + } + if ((!isOutput && [ignoredReports containsObject:@(element.reportID)]) || + (isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue; + + + if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; + + if (isOutput) { + [self createOutputForElement:element]; + } + else { + [self createInputForElement:element]; + } + + _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; + } + + [exposedControllers addObject:self]; + if (_logicallyConnected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:self]; + } + } + } + + if (_hacks[JOYActivationReport]) { + [self sendReport:hacks[JOYActivationReport]]; + } + + 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) { + _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){ + .reportID = 1, + .led = { + {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, + {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, + } + }; + } + if (_isSony) { + _isDualSense = [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue] == 0xCE6; + } + + if (_isDualSense) { + _isUSBDualSense = [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]; + _lastVendorSpecificOutput.dualsenseOutput = (JOYDualSenseOutput){ + .reportID = 0x31, + .tag = 0x10, + .flags = 0x1403, // Rumble, lightbar and player LEDs + .flags2 = 2, + .lightbarSetup = 2, + .lightbarBlue = 255, + }; + if (_isUSBDualSense) { + _lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB = 1; + _lastVendorSpecificOutput.dualsenseOutput.lightbarBlue = 0; + _lastVendorSpecificOutput.dualsenseOutput.lightbarGreen = 96; + _lastVendorSpecificOutput.dualsenseOutput.lightbarRed = 255; + + } + // Send a report to switch the controller to a more capable mode + [self sendDualSenseOutput]; + _lastVendorSpecificOutput.dualsenseOutput.flags2 = 0; + _lastVendorSpecificOutput.dualsenseOutput.lightbarSetup = 0; + } + + _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String, + NULL); + + return self; +} + +- (NSString *)deviceName +{ + if (!_device) return nil; + return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey)); +} + +- (NSString *)uniqueID +{ + if (!_device) return nil; + NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey)); + if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) { + serial = [NSString stringWithFormat:@"%04x%04x%08x", + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]]; + } + if (_serialSuffix) { + return [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix]; + } + return serial; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID]; +} + +- (NSArray *)buttons +{ + NSMutableArray *ret = [[_buttons allValues] mutableCopy]; + [ret addObjectsFromArray:_axisEmulatedButtons.allValues]; + for (NSArray *array in _axes2DEmulatedButtons.allValues) { + [ret addObjectsFromArray:array]; + } + for (NSArray *array in _hatEmulatedButtons.allValues) { + [ret addObjectsFromArray:array]; + } + return ret; +} + +- (NSArray *)axes +{ + return [_axes allValues]; +} + +- (NSArray *)axes2D +{ + return [[NSSet setWithArray:[_axes2D allValues]] allObjects]; +} + +- (NSArray *)axes3D +{ + return [[NSSet setWithArray:[_axes3D allValues]] allObjects]; +} + +- (NSArray *)hats +{ + return [_hats allValues]; +} + +- (void)gotReport:(NSData *)report +{ + JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; + if (element) { + [element updateValue:report]; + + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } + } + } + dispatch_async(_rumbleQueue, ^{ + [self updateRumble]; + }); +} + +- (void)elementChanged:(IOHIDElementRef)element +{ + JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))]; + if (_element) { + [self _elementChanged:_element]; + } + else { + //NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element)); + } +} + +- (void)_elementChanged:(JOYElement *)element +{ + if (element == _connectedElement) { + bool old = self.connected; + _logicallyConnected = _connectedElement.value != _connectedElement.min; + if (!old && self.connected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:self]; + } + } + } + else if (old && !self.connected) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:self]; + } + } + } + } + + if (!self.connected) return; + { + JOYButton *button = _buttons[element]; + if (button) { + if ([button updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + return; + } + } + + + { + JOYAxis *axis = _axes[element]; + if (axis) { + if ([axis updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxis:)]) { + [listener controller:self movedAxis:axis]; + } + } + JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)]; + if ([button updateStateFromAxis:axis]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + return; + } + } + + { + JOYAxes2D *axes = _axes2D[element]; + if (axes) { + if ([axes updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) { + [listener controller:self movedAxes2D:axes]; + } + } + NSArray *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)]; + for (JOYEmulatedButton *button in buttons) { + if ([button updateStateFromAxes2D:axes]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + } + return; + } + } + + { + 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) { + if ([hat updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedHat:)]) { + [listener controller:self movedHat:hat]; + } + } + + NSArray *buttons = _hatEmulatedButtons[@(hat.uniqueID)]; + for (JOYEmulatedButton *button in buttons) { + if ([button updateStateFromHat:hat]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { + [listener controller:self buttonChangedState:button]; + } + } + } + } + } + return; + } + } +} + +- (void)disconnected +{ + if (_logicallyConnected && [exposedControllers containsObject:self]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:self]; + } + } + } + _physicallyConnected = false; + [exposedControllers removeObject:self]; + [self setRumbleAmplitude:0]; + dispatch_sync(_rumbleQueue, ^{ + [self updateRumble]; + }); + _device = nil; +} + +- (void)sendReport:(NSData *)report +{ + if (!report.length) return; + if (!_device) return; + if (_deviceCantSendReports) return; + /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after + freezing for 5 seconds. Stop sending reports if that's the case. */ + if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) { + _deviceCantSendReports = true; + NSLog(@"This Mac appears to be incapable of sending output reports to %@", self); + } +} + +- (void) sendDualSenseOutput +{ + if (_isUSBDualSense) { + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB length:_lastVendorSpecificOutput.dualsenseOutput.bluetoothSpecific - &_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB]]; + return; + } + _lastVendorSpecificOutput.dualsenseOutput.sequence += 0x10; + static const uint32_t table[] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + const uint8_t *byte = (void *)&_lastVendorSpecificOutput.dualsenseOutput; + uint32_t size = sizeof(_lastVendorSpecificOutput.dualsenseOutput) - 4; + uint32_t ret = 0xFFFFFFFF; + ret = table[(ret ^ 0xA2) & 0xFF] ^ (ret >> 8); + + while (size--) { + ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8); + } + + _lastVendorSpecificOutput.dualsenseOutput.crc32 = ~ret; + + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput length:sizeof(_lastVendorSpecificOutput.dualsenseOutput)]]; +} + +- (uint8_t)LEDMaskForPlayer:(unsigned)player +{ + if (_isDualShock3) { + return 2 << player; + } + if (_isDualSense) { + switch (player) { + case 0: return 0x04; + case 1: return 0x0A; + case 2: return 0x15; + case 3: return 0x1B; + default: return 0; + } + } + return 1 << player; +} + +- (void)setPlayerLEDs:(uint8_t)mask +{ + if (mask == _playerLEDs) { + return; + } + _playerLEDs = mask; + if (_isSwitch) { + _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED + _lastVendorSpecificOutput.switchPacket.commandData[0] = mask & 0xF; + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.ledsEnabled = (mask & 0x1F); + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; + } + else if (_isDualSense) { + _lastVendorSpecificOutput.dualsenseOutput.playerLEDs = mask & 0x1F; + [self sendDualSenseOutput]; + } +} + +- (void)updateRumble +{ + if (!self.connected) { + return; + } + if (!_rumbleElement && !_isSwitch && !_isDualShock3 && !_isDualSense) { + return; + } + if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { + double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); + if (ampToSend != _sentRumbleAmp) { + [_rumbleElement setValue:ampToSend]; + _sentRumbleAmp = ampToSend; + } + _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION); + if (_rumbleCounter >= PWM_RESOLUTION) { + _rumbleCounter -= PWM_RESOLUTION; + } + } + else { + if (_rumbleAmplitude == _sentRumbleAmp) { + return; + } + _sentRumbleAmp = _rumbleAmplitude; + if (_isSwitch) { + double frequency = 144; + double amp = _rumbleAmplitude; + + uint8_t highAmp = amp * 0x64; + uint8_t lowAmp = amp * 0x32 + 0x40; + if (frequency < 0) frequency = 0; + if (frequency > 1252) frequency = 1252; + uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); + + uint16_t highFreq = (encodedFrequency - 0x60) * 4; + uint8_t lowFreq = encodedFrequency - 0x40; + + //if (frequency < 82 || frequency > 312) { + if (amp) { + highAmp = 0; + } + + if (frequency < 40 || frequency > 626) { + lowAmp = 0; + } + + _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; + _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); + _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; + _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; + + + _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0; // LED + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + } + else if (_isDualShock3) { + _lastVendorSpecificOutput.ds3Output.reportID = 1; + _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xFF : 0; + _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xFF); + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; + } + else if (_isDualSense) { + _lastVendorSpecificOutput.dualsenseOutput.rumbleLeftStrength = round(_rumbleAmplitude * _rumbleAmplitude * 0xFF); + _lastVendorSpecificOutput.dualsenseOutput.rumbleRightStrength = _rumbleAmplitude > 0.25 ? round(pow(_rumbleAmplitude - 0.25, 2) * 0xFF) : 0; + [self sendDualSenseOutput]; + } + else { + [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; + } + } +} + +- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ +{ + if (amp < 0) amp = 0; + if (amp > 1) amp = 1; + _rumbleAmplitude = amp; +} + +- (bool)isConnected +{ + return _logicallyConnected && _physicallyConnected; +} + ++ (void)controllerAdded:(IOHIDDeviceRef) device +{ + NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + NSDictionary *hacks = hacksByName[name]; + if (!hacks) { + hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; + } + NSArray *filters = hacks[JOYReportIDFilters]; + JOYController *controller = nil; + if (filters) { + controller = [[JOYMultiplayerController alloc] initWithDevice:device + reportIDFilters:filters + hacks:hacks]; + } + else { + controller = [[JOYController alloc] initWithDevice:device hacks:hacks]; + } + + [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; + + +} + ++ (void)controllerRemoved:(IOHIDDeviceRef) device +{ + [[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected]; + [controllers removeObjectForKey:[NSValue valueWithPointer:device]]; +} + ++ (NSArray *)allControllers +{ + return exposedControllers; +} + ++ (void)load +{ +#include "ControllerConfiguration.inc" +} + ++(void)registerListener:(id)listener +{ + [listeners addObject:listener]; +} + ++(void)unregisterListener:(id)listener +{ + [listeners removeObject:listener]; +} + ++ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options +{ + axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue]; + hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue]; + + controllers = [NSMutableDictionary dictionary]; + exposedControllers = [NSMutableArray array]; + NSArray *array = @[ + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), + CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController), + @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)}, + ]; + + listeners = [NSMutableSet set]; + static IOHIDManagerRef manager = nil; + if (manager) { + CFRelease(manager); // Stop the previous session + } + manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + if (!manager) return; + if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) { + CFRelease(manager); + return; + } + + IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array); + IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL); + IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode); +} + +- (void)dealloc +{ + if (_device) { + CFRelease(_device); + _device = NULL; + } +} +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYElement.h b/thirdparty/SameBoy-old/JoyKit/JOYElement.h new file mode 100644 index 000000000..0e917dd08 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYElement.h @@ -0,0 +1,20 @@ +#import +#include + +@interface JOYElement : NSObject +- (instancetype)initWithElement:(IOHIDElementRef)element; +- (int32_t)value; +- (NSData *)dataValue; +- (IOReturn)setValue:(uint32_t)value; +- (IOReturn)setDataValue:(NSData *)value; +@property (readonly) uint16_t usage; +@property (readonly) uint16_t usagePage; +@property (readonly) uint32_t uniqueID; +@property int32_t min; +@property int32_t max; +@property (readonly) int32_t reportID; +@property (readonly) int32_t parentID; + +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYElement.m b/thirdparty/SameBoy-old/JoyKit/JOYElement.m new file mode 100644 index 000000000..2432002a3 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYElement.m @@ -0,0 +1,133 @@ +#import "JOYElement.h" +#include +#include + +@implementation JOYElement +{ + id _element; + IOHIDDeviceRef _device; + int32_t _min, _max; +} + +- (int32_t)min +{ + return MIN(_min, _max); +} + +- (int32_t)max +{ + return MAX(_max, _min); +} + +-(void)setMin:(int32_t)min +{ + _min = min; +} + +- (void)setMax:(int32_t)max +{ + _max = max; +} + +/* Ugly hack because IOHIDDeviceCopyMatchingElements is slow */ ++ (NSArray *) cookiesToSkipForDevice:(IOHIDDeviceRef)device +{ + id _device = (__bridge id)device; + NSMutableArray *ret = objc_getAssociatedObject(_device, _cmd); + if (ret) return ret; + + ret = [NSMutableArray array]; + NSArray *nones = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, + (__bridge CFDictionaryRef)@{@(kIOHIDElementTypeKey): @(kIOHIDElementTypeInput_NULL)}, + 0)); + for (id none in nones) { + [ret addObject:@(IOHIDElementGetCookie((__bridge IOHIDElementRef)none))]; + } + objc_setAssociatedObject(_device, _cmd, ret, OBJC_ASSOCIATION_RETAIN); + return ret; +} + +- (instancetype)initWithElement:(IOHIDElementRef)element +{ + if ((self = [super init])) { + _element = (__bridge id)element; + _usage = IOHIDElementGetUsage(element); + _usagePage = IOHIDElementGetUsagePage(element); + _uniqueID = (uint32_t)IOHIDElementGetCookie(element); + _min = (int32_t) IOHIDElementGetLogicalMin(element); + _max = (int32_t) IOHIDElementGetLogicalMax(element); + _reportID = IOHIDElementGetReportID(element); + IOHIDElementRef parent = IOHIDElementGetParent(element); + _parentID = parent? (uint32_t)IOHIDElementGetCookie(parent) : -1; + _device = IOHIDElementGetDevice(element); + + /* Catalina added a new input type in a way that breaks cookie consistency across macOS versions, + we shall adjust our cookies to to compensate */ + unsigned cookieShift = 0, parentCookieShift = 0; + + for (NSNumber *none in [JOYElement cookiesToSkipForDevice:_device]) { + if (none.unsignedIntValue < _uniqueID) { + cookieShift++; + } + if (none.unsignedIntValue < (int32_t)_parentID) { + parentCookieShift++; + } + } + + _uniqueID -= cookieShift; + _parentID -= parentCookieShift; + } + return self; +} + +- (int32_t)value +{ + IOHIDValueRef value = NULL; + IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value); + if (!value) return 0; + CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks. + return (int32_t)IOHIDValueGetIntegerValue(value); +} + +- (NSData *)dataValue +{ + IOHIDValueRef value = NULL; + IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value); + if (!value) return 0; + CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks. + return [NSData dataWithBytes:IOHIDValueGetBytePtr(value) length:IOHIDValueGetLength(value)]; +} + +- (IOReturn)setValue:(uint32_t)value +{ + IOHIDValueRef ivalue = IOHIDValueCreateWithIntegerValue(NULL, (__bridge IOHIDElementRef)_element, 0, value); + IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + CFRelease(ivalue); + return ret; +} + +- (IOReturn)setDataValue:(NSData *)value +{ + IOHIDValueRef ivalue = IOHIDValueCreateWithBytes(NULL, (__bridge IOHIDElementRef)_element, 0, value.bytes, value.length); + IOReturn ret = IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue); + CFRelease(ivalue); + return ret; +} + +/* For use as a dictionary key */ + +- (NSUInteger)hash +{ + return self.uniqueID; +} + +- (BOOL)isEqual:(id)object +{ + return self->_element == object; +} + +- (id)copyWithZone:(nullable NSZone *)zone; +{ + return self; +} +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.h b/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.h new file mode 100644 index 000000000..05ccde829 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.h @@ -0,0 +1,11 @@ +#import "JOYButton.h" +#import "JOYAxis.h" +#import "JOYAxes2D.h" +#import "JOYHat.h" + +@interface JOYEmulatedButton : JOYButton +- (instancetype)initWithUsage:(JOYButtonUsage)usage type:(JOYButtonType)type uniqueID:(uint64_t)uniqueID; +- (bool)updateStateFromAxis:(JOYAxis *)axis; +- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes; +- (bool)updateStateFromHat:(JOYHat *)hat; +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.m b/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.m new file mode 100644 index 000000000..5e6d1b3cc --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYEmulatedButton.m @@ -0,0 +1,99 @@ +#import "JOYEmulatedButton.h" +#import + +@interface JOYButton () +{ + @public bool _state; +} +@end + +@implementation JOYEmulatedButton +{ + uint64_t _uniqueID; + JOYButtonType _type; +} + +- (instancetype)initWithUsage:(JOYButtonUsage)usage type:(JOYButtonType)type uniqueID:(uint64_t)uniqueID; +{ + self = [super init]; + self.usage = usage; + _uniqueID = uniqueID; + _type = type; + + return self; +} + +- (uint64_t)uniqueID +{ + return _uniqueID; +} + +- (bool)updateStateFromAxis:(JOYAxis *)axis +{ + bool old = _state; + _state = [axis value] > 0.8; + return _state != old; +} + +- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes +{ + bool old = _state; + if (axes.distance < 0.5) { + _state = false; + } + else { + unsigned direction = ((unsigned)round(axes.angle / 360 * 8)) & 7; + switch (self.usage) { + case JOYButtonUsageDPadLeft: + _state = direction >= 3 && direction <= 5; + break; + case JOYButtonUsageDPadRight: + _state = direction <= 1 || direction == 7; + break; + case JOYButtonUsageDPadUp: + _state = direction >= 5; + break; + case JOYButtonUsageDPadDown: + _state = direction <= 3 && direction >= 1; + break; + default: + break; + } + } + return _state != old; +} + +- (bool)updateStateFromHat:(JOYHat *)hat +{ + bool old = _state; + if (!hat.pressed) { + _state = false; + } + else { + unsigned direction = ((unsigned)round(hat.angle / 360 * 8)) & 7; + switch (self.usage) { + case JOYButtonUsageDPadLeft: + _state = direction >= 3 && direction <= 5; + break; + case JOYButtonUsageDPadRight: + _state = direction <= 1 || direction == 7; + break; + case JOYButtonUsageDPadUp: + _state = direction >= 5; + break; + case JOYButtonUsageDPadDown: + _state = direction <= 3 && direction >= 1; + break; + default: + break; + } + } + return _state != old; +} + +- (JOYButtonType)type +{ + return _type; +} + +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.h b/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.h new file mode 100644 index 000000000..808644e76 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.h @@ -0,0 +1,10 @@ +#import +#include +#include "JOYElement.h" + +@interface JOYFullReportElement : JOYElement +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID; +- (void)updateValue:(NSData *)value; +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.m b/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.m new file mode 100644 index 000000000..a19a53007 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYFullReportElement.m @@ -0,0 +1,74 @@ +#import "JOYFullReportElement.h" +#include + +@implementation JOYFullReportElement +{ + IOHIDDeviceRef _device; + NSData *_data; + unsigned _reportID; + size_t _capacity; +} + +- (uint32_t)uniqueID +{ + return _reportID ^ 0xFFFF; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID +{ + if ((self = [super init])) { + _data = [[NSMutableData alloc] initWithLength:[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]]; + *(uint8_t *)(((NSMutableData *)_data).mutableBytes) = reportID; + _reportID = reportID; + _device = device; + } + return self; +} + +- (int32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return 0; +} + +- (NSData *)dataValue +{ + return _data; +} + +- (IOReturn)setValue:(uint32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return -1; +} + +- (IOReturn)setDataValue:(NSData *)value +{ + + [self updateValue:value]; + return IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, _reportID, [_data bytes], [_data length]);; +} + +- (void)updateValue:(NSData *)value +{ + _data = [value copy]; +} + +/* For use as a dictionary key */ + +- (NSUInteger)hash +{ + return self.uniqueID; +} + +- (BOOL)isEqual:(JOYFullReportElement *)object +{ + if ([object isKindOfClass:self.class]) return false; + return self.uniqueID == object.uniqueID; +} + +- (id)copyWithZone:(nullable NSZone *)zone; +{ + return self; +} +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYHat.h b/thirdparty/SameBoy-old/JoyKit/JOYHat.h new file mode 100644 index 000000000..05a582927 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYHat.h @@ -0,0 +1,11 @@ +#import + +@interface JOYHat : NSObject +- (uint64_t)uniqueID; +- (double)angle; +- (unsigned)resolution; +@property (readonly, getter=isPressed) bool pressed; + +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYHat.m b/thirdparty/SameBoy-old/JoyKit/JOYHat.m new file mode 100644 index 000000000..b5a18f0b4 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYHat.m @@ -0,0 +1,62 @@ +#import "JOYHat.h" +#import "JOYElement.h" +#import + +@implementation JOYHat +{ + JOYElement *_element; + double _state; +} + +- (uint64_t)uniqueID +{ + return _element.uniqueID; +} + +- (NSString *)description +{ + if (self.isPressed) { + return [NSString stringWithFormat:@"<%@: %p (%llu); State: %f degrees>", self.className, self, self.uniqueID, self.angle]; + } + return [NSString stringWithFormat:@"<%@: %p (%llu); State: released>", self.className, self, self.uniqueID]; + +} + +- (instancetype)initWithElement:(JOYElement *)element +{ + self = [super init]; + if (!self) return self; + + _element = element; + _state = -1; + + return self; +} + +- (bool)isPressed +{ + return _state >= 0 && _state < 360; +} + +- (double)angle +{ + if (self.isPressed) return fmod((_state + 270), 360); + return -1; +} + +- (unsigned)resolution +{ + return _element.max - _element.min + 1; +} + +- (bool)updateState +{ + unsigned state = ([_element value] - _element.min) * 360.0 / self.resolution; + if (_state != state) { + _state = state; + return true; + } + return false; +} + +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.h b/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.h new file mode 100644 index 000000000..44d742191 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.h @@ -0,0 +1,8 @@ +#import "JOYController.h" +#include + +@interface JOYMultiplayerController : JOYController +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:hacks; +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.m b/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.m new file mode 100644 index 000000000..a31ae9212 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYMultiplayerController.m @@ -0,0 +1,50 @@ +#import "JOYMultiplayerController.h" + +@interface JOYController () +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks; +- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value; +- (void)disconnected; +- (void)sendReport:(NSData *)report; +@end + +@implementation JOYMultiplayerController +{ + NSMutableArray *_children; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:(NSDictionary *)hacks; +{ + self = [super init]; + if (!self) return self; + + _children = [NSMutableArray array]; + + unsigned index = 1; + for (NSArray *filter in reportIDFilters) { + JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index] hacks:hacks]; + [_children addObject:controller]; + index++; + } + return self; +} + +- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value +{ + for (JOYController *child in _children) { + [child elementChanged:element toValue:value]; + } +} + +- (void)disconnected +{ + for (JOYController *child in _children) { + [child disconnected]; + } +} + +- (void)sendReport:(NSData *)report +{ + [[_children firstObject] sendReport:report]; +} + +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JOYSubElement.h b/thirdparty/SameBoy-old/JoyKit/JOYSubElement.h new file mode 100644 index 000000000..a13b5c768 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYSubElement.h @@ -0,0 +1,14 @@ +#import "JOYElement.h" + +@interface JOYSubElement : JOYElement +- (instancetype)initWithRealElement:(JOYElement *)element + size:(size_t) size // in bits + offset:(size_t) offset // in bits + usagePage:(uint16_t)usagePage + usage:(uint16_t)usage + min:(int32_t)min + max:(int32_t)max; + +@end + + diff --git a/thirdparty/SameBoy-old/JoyKit/JOYSubElement.m b/thirdparty/SameBoy-old/JoyKit/JOYSubElement.m new file mode 100644 index 000000000..186caf9ee --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JOYSubElement.m @@ -0,0 +1,106 @@ +#import "JOYSubElement.h" + +@interface JOYElement () +{ + @public uint16_t _usage; + @public uint16_t _usagePage; + @public uint32_t _uniqueID; + @public int32_t _min; + @public int32_t _max; + @public int32_t _reportID; + @public int32_t _parentID; +} +@end + +@implementation JOYSubElement +{ + JOYElement *_parent; + size_t _size; // in bits + size_t _offset; // in bits +} + +- (instancetype)initWithRealElement:(JOYElement *)element + size:(size_t) size // in bits + offset:(size_t) offset // in bits + usagePage:(uint16_t)usagePage + usage:(uint16_t)usage + min:(int32_t)min + max:(int32_t)max +{ + if ((self = [super init])) { + _parent = element; + _size = size; + _offset = offset; + _usage = usage; + _usagePage = usagePage; + _uniqueID = (uint32_t)((_parent.uniqueID << 16) | offset); + _min = min; + _max = max; + _reportID = _parent.reportID; + _parentID = _parent.parentID; + } + return self; +} + +- (int32_t)value +{ + NSData *parentValue = [_parent dataValue]; + if (!parentValue) return 0; + if (_size > 32) return 0; + if (_size + (_offset % 8) > 32) return 0; + size_t parentLength = parentValue.length; + if (_size > parentLength * 8) return 0; + if (_size + _offset >= parentLength * 8) return 0; + const uint8_t *bytes = parentValue.bytes; + + uint8_t temp[4] = {0,}; + 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; + } + + return ret; +} + +- (IOReturn)setValue: (uint32_t) value +{ + NSMutableData *dataValue = [[_parent dataValue] mutableCopy]; + if (!dataValue) return -1; + if (_size > 32) return -1; + if (_size + (_offset % 8) > 32) return -1; + size_t parentLength = dataValue.length; + if (_size > parentLength * 8) return -1; + if (_size + _offset >= parentLength * 8) return -1; + uint8_t *bytes = dataValue.mutableBytes; + + uint8_t temp[4] = {0,}; + memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1); + (*(uint32_t *)temp) &= ~((1 << (_size - 1)) << (_offset % 8)); + (*(uint32_t *)temp) |= (value) << (_offset % 8); + memcpy(bytes + _offset / 8, temp, (_offset + _size - 1) / 8 - _offset / 8 + 1); + return [_parent setDataValue:dataValue]; +} + +- (NSData *)dataValue +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (IOReturn)setDataValue:(NSData *)data +{ + [self doesNotRecognizeSelector:_cmd]; + return -1; +} + + +@end diff --git a/thirdparty/SameBoy-old/JoyKit/JoyKit.h b/thirdparty/SameBoy-old/JoyKit/JoyKit.h new file mode 100644 index 000000000..d56b50515 --- /dev/null +++ b/thirdparty/SameBoy-old/JoyKit/JoyKit.h @@ -0,0 +1,6 @@ +#ifndef JoyKit_h +#define JoyKit_h + +#include "JOYController.h" + +#endif diff --git a/thirdparty/SameBoy-old/LICENSE b/thirdparty/SameBoy-old/LICENSE new file mode 100644 index 000000000..94f34689c --- /dev/null +++ b/thirdparty/SameBoy-old/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-2022 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 +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. \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Makefile b/thirdparty/SameBoy-old/Makefile new file mode 100644 index 000000000..334e21d14 --- /dev/null +++ b/thirdparty/SameBoy-old/Makefile @@ -0,0 +1,540 @@ +# Make hacks +.INTERMEDIATE: + +# Set target, configuration, version and destination folders + +PLATFORM := $(shell uname -s) +ifneq ($(findstring MINGW,$(PLATFORM)),) +PLATFORM := windows32 +USE_WINDRES := true +endif + +ifneq ($(findstring MSYS,$(PLATFORM)),) +PLATFORM := windows32 +endif + +ifeq ($(PLATFORM),windows32) +_ := $(shell chcp 65001) +EXESUFFIX:=.exe +NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows +SDL_AUDIO_DRIVERS ?= xaudio2 xaudio2_7 sdl +else +EXESUFFIX:= +NATIVE_CC := cc +SDL_AUDIO_DRIVERS ?= sdl +endif + +PB12_COMPRESS := build/pb12$(EXESUFFIX) + +ifeq ($(PLATFORM),Darwin) +DEFAULT := cocoa +ENABLE_OPENAL ?= 1 +else +DEFAULT := sdl +endif + +ifneq ($(shell which xdg-open)$(FREEDESKTOP),) +# Running on an FreeDesktop environment, configure for (optional) installation +DESTDIR ?= +PREFIX ?= /usr/local +DATA_DIR ?= $(PREFIX)/share/sameboy/ +FREEDESKTOP ?= true +endif + +default: $(DEFAULT) + +ifeq ($(MAKECMDGOALS),) +MAKECMDGOALS := $(DEFAULT) +endif + +include version.mk +export VERSION +CONF ?= debug + +BIN := build/bin +OBJ := build/obj +BOOTROMS_DIR ?= $(BIN)/BootROMs + +ifdef DATA_DIR +CFLAGS += -DDATA_DIR="\"$(DATA_DIR)\"" +endif + +# Set tools + +# Use clang if it's available. +ifeq ($(origin CC),default) +ifneq (, $(shell which clang)) +CC := clang +endif +endif + +# Find libraries with pkg-config if available. +ifneq (, $(shell which pkg-config)) +# But not on macOS, it's annoying +ifneq ($(PLATFORM),Darwin) +PKG_CONFIG := pkg-config +endif +endif + +ifeq ($(PLATFORM),windows32) +# To force use of the Unix version instead of the Windows version +MKDIR := $(shell which mkdir) +else +MKDIR := mkdir +endif + +ifeq ($(CONF),native_release) +override CONF := release +LDFLAGS += -march=native -mtune=native +CFLAGS += -march=native -mtune=native +endif + +ifeq ($(CONF),fat_release) +override CONF := release +FAT_FLAGS += -arch x86_64 -arch arm64 +endif + + + +# Set compilation and linkage flags based on target, platform and configuration + +OPEN_DIALOG = OpenDialog/gtk.c +NULL := /dev/null + +ifeq ($(PLATFORM),windows32) +OPEN_DIALOG = OpenDialog/windows.c +NULL := NUL +endif + +ifeq ($(PLATFORM),Darwin) +OPEN_DIALOG = OpenDialog/cocoa.m +endif + +# These must come before the -Wno- flags +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 +ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) +WARNINGS += -Wpartial-availability +endif + +# GCC's implementation of this warning has false positives, so we skip it +ifneq ($(shell $(CC) --version 2>&1 | grep "gcc"), ) +WARNINGS += -Wno-maybe-uninitialized +endif + +CFLAGS += $(WARNINGS) + +CFLAGS += -std=gnu11 -D_GNU_SOURCE -DGB_VERSION='"$(VERSION)"' -I. -D_USE_MATH_DEFINES +ifneq (,$(UPDATE_SUPPORT)) +CFLAGS += -DUPDATE_SUPPORT +endif + +ifeq (,$(PKG_CONFIG)) +SDL_CFLAGS := $(shell sdl2-config --cflags) +SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread + +# We cannot detect the presence of OpenAL dev headers, +# so we must do this manually +ifeq ($(ENABLE_OPENAL),1) +SDL_CFLAGS += -DENABLE_OPENAL +ifeq ($(PLATFORM),Darwin) +SDL_LDFLAGS += -framework OpenAL +else +SDL_LDFLAGS += -lopenal +endif +SDL_AUDIO_DRIVERS += openal +endif +else +SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) +SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread + +# Allow OpenAL to be disabled even if the development libraries are available +ifneq ($(ENABLE_OPENAL),0) +ifeq ($(shell $(PKG_CONFIG) --exists openal && echo 0),0) +SDL_CFLAGS += $(shell $(PKG_CONFIG) --cflags openal) -DENABLE_OPENAL +SDL_LDFLAGS += $(shell $(PKG_CONFIG) --libs openal) +SDL_AUDIO_DRIVERS += openal +endif +endif +endif + +ifeq (,$(PKG_CONFIG)) +GL_LDFLAGS := -lGL +else +GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) +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 -lole32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows +SDL_LDFLAGS := -lSDL2 +GL_LDFLAGS := -lopengl32 +else +LDFLAGS += -lc -lm -ldl +endif + +ifeq ($(PLATFORM),Darwin) +SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> $(NULL)) +ifeq ($(SYSROOT),) +SYSROOT := /Library/Developer/CommandLineTools/SDKs/$(shell ls /Library/Developer/CommandLineTools/SDKs/ | grep 10 | tail -n 1) +endif +ifeq ($(SYSROOT),/Library/Developer/CommandLineTools/SDKs/) +$(error Could not find a macOS SDK) +endif + +CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 -isysroot $(SYSROOT) +OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) +LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -framework Security -framework WebKit -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -isysroot $(SYSROOT) +GL_LDFLAGS := -framework OpenGL +endif +CFLAGS += -Wno-deprecated-declarations +ifeq ($(PLATFORM),windows32) +CFLAGS += -Wno-deprecated-declarations # Seems like Microsoft deprecated every single LIBC function +LDFLAGS += -Wl,/NODEFAULTLIB:libcmt.lib +endif + +ifeq ($(CONF),debug) +CFLAGS += -g +else ifeq ($(CONF), release) +CFLAGS += -O3 -DNDEBUG +STRIP := strip +CODESIGN := true +ifeq ($(PLATFORM),Darwin) +LDFLAGS += -Wl,-exported_symbols_list,$(NULL) +STRIP := strip -x +CODESIGN := codesign -fs - +endif +ifeq ($(PLATFORM),windows32) +LDFLAGS += -fuse-ld=lld +endif +LDFLAGS += -flto +CFLAGS += -flto +LDFLAGS += -Wno-lto-type-mismatch # For GCC's LTO + +else +$(error Invalid value for CONF: $(CONF). Use "debug", "release" or "native_release") +endif + +# Define our targets + +ifeq ($(PLATFORM),windows32) +SDL_TARGET := $(BIN)/SDL/sameboy.exe $(BIN)/SDL/sameboy_debugger.exe $(BIN)/SDL/SDL2.dll +TESTER_TARGET := $(BIN)/tester/sameboy_tester.exe +else +SDL_TARGET := $(BIN)/SDL/sameboy +TESTER_TARGET := $(BIN)/tester/sameboy_tester +endif + +cocoa: $(BIN)/SameBoy.app +quicklook: $(BIN)/SameBoy.qlgenerator +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 $(BIN)/SDL/Palettes +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/mgb_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 + +CORE_SOURCES := $(shell ls Core/*.c) +SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS)) +TESTER_SOURCES := $(shell ls Tester/*.c) + +ifeq ($(PLATFORM),Darwin) +COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m) +QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c) +endif + +ifeq ($(PLATFORM),windows32) +CORE_SOURCES += $(shell ls Windows/*.c) +endif + +CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES)) +COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES)) +QUICKLOOK_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(QUICKLOOK_SOURCES)) +SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) +TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES)) + +# Automatic dependency generation + +ifneq ($(filter-out clean bootroms libretro %.bin, $(MAKECMDGOALS)),) +-include $(CORE_OBJECTS:.o=.dep) +ifneq ($(filter $(MAKECMDGOALS),sdl),) +-include $(SDL_OBJECTS:.o=.dep) +endif +ifneq ($(filter $(MAKECMDGOALS),tester),) +-include $(TESTER_OBJECTS:.o=.dep) +endif +ifneq ($(filter $(MAKECMDGOALS),cocoa),) +-include $(COCOA_OBJECTS:.o=.dep) +endif +endif + +$(OBJ)/SDL/%.dep: SDL/% + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + +$(OBJ)/OpenDialog/%.dep: OpenDialog/% + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + +$(OBJ)/%.dep: % + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + +# Compilation rules + +$(OBJ)/Core/%.c.o: Core/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) -DGB_INTERNAL -c $< -o $@ + +$(OBJ)/SDL/%.c.o: SDL/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ + +$(OBJ)/OpenDialog/%.c.o: OpenDialog/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ + + +$(OBJ)/%.c.o: %.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) -c $< -o $@ + +# HexFiend requires more flags +$(OBJ)/HexFiend/%.m.o: HexFiend/%.m + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch + +$(OBJ)/%.m.o: %.m + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ + +# Cocoa Port + +$(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ + $(shell ls Cocoa/*.icns Cocoa/*.png) \ + Cocoa/License.html \ + 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/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 \ + $(BIN)/SameBoy.app/Contents/Resources/sgb2_boot.bin \ + $(patsubst %.xib,%.nib,$(addprefix $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/,$(shell cd Cocoa;ls *.xib))) \ + $(BIN)/SameBoy.qlgenerator \ + Shaders + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources + cp Cocoa/*.icns Cocoa/*.png Misc/registers.sym $(BIN)/SameBoy.app/Contents/Resources/ + sed s/@VERSION/$(VERSION)/ < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist + cp Cocoa/License.html $(BIN)/SameBoy.app/Contents/Resources/Credits.html + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources/Shaders + cp Shaders/*.fsh Shaders/*.metal $(BIN)/SameBoy.app/Contents/Resources/Shaders + $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Library/QuickLook/ + cp -rf $(BIN)/SameBoy.qlgenerator $(BIN)/SameBoy.app/Contents/Library/QuickLook/ +ifeq ($(CONF), release) + $(CODESIGN) $@ +endif + +$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit +ifeq ($(CONF), release) + $(STRIP) $@ +endif + +$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib + ibtool --compile $@ $^ 2>&1 | cat - + +# Quick Look generator + +$(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL \ + $(shell ls QuickLook/*.png) \ + QuickLook/Info.plist \ + $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin + $(MKDIR) -p $(BIN)/SameBoy.qlgenerator/Contents/Resources + cp QuickLook/*.png $(BIN)/SameBoy.qlgenerator/Contents/Resources/ + sed s/@VERSION/$(VERSION)/ < QuickLook/Info.plist > $(BIN)/SameBoy.qlgenerator/Contents/Info.plist +ifeq ($(CONF), release) + $(CODESIGN) $@ +endif + +# Currently, SameBoy.app includes two "copies" of each Core .o file once in the app itself and +# once in the QL Generator. It should probably become a dylib instead. +$(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook +ifeq ($(CONF), release) + $(STRIP) $@ +endif + +# cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided +# boot ROM directory. +$(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs/cgb_boot_fast.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +# SDL Port + +# Unix versions build only one binary +$(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) +ifeq ($(CONF), release) + $(STRIP) $@ + $(CODESIGN) $@ +endif + +# Windows version builds two, one with a conole and one without it +$(BIN)/SDL/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) -Wl,/subsystem:windows + +$(BIN)/SDL/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) -Wl,/subsystem:console + +ifneq ($(USE_WINDRES),) +$(OBJ)/%.o: %.rc + -@$(MKDIR) -p $(dir $@) + windres --preprocessor cpp -DVERSION=\"$(VERSION)\" $^ $@ +else +$(OBJ)/%.res: %.rc + -@$(MKDIR) -p $(dir $@) + rc /fo $@ /dVERSION=\"$(VERSION)\" $^ + +%.o: %.res + cvtres /OUT:"$@" $^ +endif + +# We must provide SDL2.dll with the Windows port. +$(BIN)/SDL/SDL2.dll: + @$(eval MATCH := $(shell where $$LIB:SDL2.dll)) + cp "$(MATCH)" $@ + +# Tester + +$(BIN)/tester/sameboy_tester: $(CORE_OBJECTS) $(TESTER_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) +ifeq ($(CONF), release) + $(STRIP) $@ + $(CODESIGN) $@ +endif + +$(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) -Wl,/subsystem:console + +$(BIN)/SDL/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/SDL/LICENSE: LICENSE + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/SDL/registers.sym: Misc/registers.sym + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/SDL/background.bmp: SDL/background.bmp + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(BIN)/SDL/Shaders: Shaders + -@$(MKDIR) -p $@ + cp -rf Shaders/*.fsh $@ + +$(BIN)/SDL/Palettes: Misc/Palettes + -@$(MKDIR) -p $@ + cp -rf Misc/Palettes/*.sbp $@ + +# Boot ROMs + +$(OBJ)/%.2bpp: %.png + -@$(MKDIR) -p $(dir $@) + rgbgfx -h -u -o $@ $< + +$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS) + $(realpath $(PB12_COMPRESS)) < $< > $@ + +$(PB12_COMPRESS): BootROMs/pb12.c + $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ + +$(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 + +$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12 + -@$(MKDIR) -p $(dir $@) + rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $< + rgblink -o $@.tmp2 $@.tmp + dd if=$@.tmp2 of=$@ count=1 bs=$(if $(findstring dmg,$@)$(findstring sgb,$@),256,2304) 2> $(NULL) + @rm $@.tmp $@.tmp2 + +# Libretro Core (uses its own build system) +libretro: + CFLAGS="$(WARNINGS)" $(MAKE) -C libretro + +# install for Linux/FreeDesktop/etc. +# 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 ($(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)) +install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop + -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) + mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ + cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ + 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 + -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 + -@$(MKDIR) -p $(DESTDIR)$(PREFIX)/share/applications/ + 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 $@) + 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/mime/packages/sameboy.xml: FreeDesktop/sameboy.xml + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ +endif + +# Clean +clean: + rm -rf build + +.PHONY: libretro tester diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Desert.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Desert.sbp new file mode 100644 index 000000000..28625ad3b Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Desert.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Evening.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Evening.sbp new file mode 100644 index 000000000..e11998ab1 --- /dev/null +++ b/thirdparty/SameBoy-old/Misc/Palettes/Evening.sbp @@ -0,0 +1 @@ +LPBS&6UiS䦻}^LH+ \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Fog.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Fog.sbp new file mode 100644 index 000000000..a79fe00fd Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Fog.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Magic Eggplant.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Magic Eggplant.sbp new file mode 100644 index 000000000..6bd59291e Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Magic Eggplant.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Radioactive Pea.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Radioactive Pea.sbp new file mode 100644 index 000000000..57f9d6a31 Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Radioactive Pea.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Seaweed.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Seaweed.sbp new file mode 100644 index 000000000..3718efd74 Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Seaweed.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/Palettes/Twilight.sbp b/thirdparty/SameBoy-old/Misc/Palettes/Twilight.sbp new file mode 100644 index 000000000..a5decc103 Binary files /dev/null and b/thirdparty/SameBoy-old/Misc/Palettes/Twilight.sbp differ diff --git a/thirdparty/SameBoy-old/Misc/registers.sym b/thirdparty/SameBoy-old/Misc/registers.sym new file mode 100644 index 000000000..67c3266d3 --- /dev/null +++ b/thirdparty/SameBoy-old/Misc/registers.sym @@ -0,0 +1,134 @@ +00:FF00 IO_JOYP +00:FF00 rJOYP +00:FF01 IO_SB +00:FF01 rSB +00:FF02 IO_SC +00:FF02 rSC +00:FF04 IO_DIV +00:FF04 rDIV +00:FF05 IO_TIMA +00:FF05 rTIMA +00:FF06 IO_TMA +00:FF06 rTMA +00:FF07 IO_TAC +00:FF07 rTAC +00:FF0F IO_IF +00:FF0F rIF +00:FF10 IO_NR10 +00:FF10 rNR10 +00:FF11 IO_NR11 +00:FF11 rNR11 +00:FF12 IO_NR12 +00:FF12 rNR12 +00:FF13 IO_NR13 +00:FF13 rNR13 +00:FF14 IO_NR14 +00:FF14 rNR14 +00:FF16 IO_NR21 +00:FF16 rNR21 +00:FF17 IO_NR22 +00:FF17 rNR22 +00:FF18 IO_NR23 +00:FF18 rNR23 +00:FF19 IO_NR24 +00:FF19 rNR24 +00:FF1A IO_NR30 +00:FF1A rNR30 +00:FF1B IO_NR31 +00:FF1B rNR31 +00:FF1C IO_NR32 +00:FF1C rNR32 +00:FF1D IO_NR33 +00:FF1D rNR33 +00:FF1E IO_NR34 +00:FF1E rNR34 +00:FF20 IO_NR41 +00:FF20 rNR41 +00:FF21 IO_NR42 +00:FF21 rNR42 +00:FF22 IO_NR43 +00:FF22 rNR43 +00:FF23 IO_NR44 +00:FF23 rNR44 +00:FF24 IO_NR50 +00:FF24 rNR50 +00:FF25 IO_NR51 +00:FF25 rNR51 +00:FF26 IO_NR52 +00:FF26 rNR52 +00:FF30 IO_WAV_START +00:FF30 rWAV_START +00:FF3F IO_WAV_END +00:FF3F rWAV_END +00:FF40 IO_LCDC +00:FF40 rLCDC +00:FF41 IO_STAT +00:FF41 rSTAT +00:FF42 IO_SCY +00:FF42 rSCY +00:FF43 IO_SCX +00:FF43 rSCX +00:FF44 IO_LY +00:FF44 rLY +00:FF45 IO_LYC +00:FF45 rLYC +00:FF46 IO_DMA +00:FF46 rDMA +00:FF47 IO_BGP +00:FF47 rBGP +00:FF48 IO_OBP0 +00:FF48 rOBP0 +00:FF49 IO_OBP1 +00:FF49 rOBP1 +00:FF4A IO_WY +00:FF4A rWY +00:FF4B IO_WX +00:FF4B rWX +00:FF4C IO_KEY0 +00:FF4C rKEY0 +00:FF4D IO_KEY1 +00:FF4D rKEY1 +00:FF4F IO_VBK +00:FF4F rVBK +00:FF50 IO_BANK +00:FF50 rBANK +00:FF51 IO_HDMA1 +00:FF51 rHDMA1 +00:FF52 IO_HDMA2 +00:FF52 rHDMA2 +00:FF53 IO_HDMA3 +00:FF53 rHDMA3 +00:FF54 IO_HDMA4 +00:FF54 rHDMA4 +00:FF55 IO_HDMA5 +00:FF55 rHDMA5 +00:FF56 IO_RP +00:FF56 rRP +00:FF68 IO_BGPI +00:FF68 rBGPI +00:FF69 IO_BGPD +00:FF69 rBGPD +00:FF6A IO_OBPI +00:FF6A rOBPI +00:FF6B IO_OBPD +00:FF6B rOBPD +00:FF6C IO_OPRI +00:FF6C rOPRI +00:FF70 IO_SVBK +00:FF70 rSVBK +00:FF71 IO_PSM +00:FF71 rPSM +00:FF72 IO_PSWX +00:FF72 rPSWX +00:FF73 IO_PSWY +00:FF73 rPSWY +00:FF74 IO_PSW +00:FF74 rPSW +00:FF75 IO_UNKNOWN5 +00:FF75 rUNKNOWN5 +00:FF76 IO_PCM12 +00:FF76 rPCM12 +00:FF77 IO_PCM34 +00:FF77 rPCM34 +00:FFFF IO_IE +00:FFFF rIE diff --git a/thirdparty/SameBoy-old/OpenDialog/cocoa.m b/thirdparty/SameBoy-old/OpenDialog/cocoa.m new file mode 100644 index 000000000..fd9af3ca6 --- /dev/null +++ b/thirdparty/SameBoy-old/OpenDialog/cocoa.m @@ -0,0 +1,66 @@ +#import +#include "open_dialog.h" + + +char *do_open_rom_dialog(void) +{ + @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); + NSWindow *key = [NSApp keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + dialog.title = @"Open ROM"; + dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb", @"isx"]; + if ([dialog runModal] != NSModalResponseOK) return nil; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[[dialog URLs] firstObject] path]; + dup2(stderr_fd, STDERR_FILENO); + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} + +char *do_open_folder_dialog(void) +{ + @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); + NSWindow *key = [NSApp keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + dialog.title = @"Select Boot ROMs Folder"; + dialog.canChooseDirectories = true; + dialog.canChooseFiles = false; + if ([dialog runModal] != NSModalResponseOK) return nil; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[[dialog URLs] firstObject] path]; + dup2(stderr_fd, STDERR_FILENO); + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} + +/* The Cocoa variant of this function isn't as fully featured as the GTK and Windows ones, as Mac users would use + the fully featured Cocoa port of SameBoy anyway*/ +char *do_save_recording_dialog(unsigned frequency) +{ + @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); + NSWindow *key = [NSApp keyWindow]; + NSSavePanel *dialog = [NSSavePanel savePanel]; + dialog.title = @"Audio recording save location"; + dialog.allowedFileTypes = @[@"aiff", @"aif", @"aifc", @"wav", @"raw", @"pcm"]; + if ([dialog runModal] != NSModalResponseOK) return nil; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[dialog URL] path]; + dup2(stderr_fd, STDERR_FILENO); + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} diff --git a/thirdparty/SameBoy-old/OpenDialog/gtk.c b/thirdparty/SameBoy-old/OpenDialog/gtk.c new file mode 100644 index 000000000..d3c0faa81 --- /dev/null +++ b/thirdparty/SameBoy-old/OpenDialog/gtk.c @@ -0,0 +1,370 @@ +#include "open_dialog.h" +#include +#include +#include +#include +#include +#include + +#define GTK_FILE_CHOOSER_ACTION_OPEN 0 +#define GTK_FILE_CHOOSER_ACTION_SAVE 1 +#define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2 +#define GTK_RESPONSE_ACCEPT -3 +#define GTK_RESPONSE_CANCEL -6 + + +void *_gtk_file_chooser_dialog_new (const char *title, + void *parent, + int action, + const char *first_button_text, + ...); +bool _gtk_init_check (int *argc, char ***argv); +int _gtk_dialog_run(void *); +void _g_free(void *); +void _gtk_widget_destroy(void *); +char *_gtk_file_chooser_get_filename(void *); +void _g_log_set_default_handler (void *function, void *data); +void *_gtk_file_filter_new(void); +void _gtk_file_filter_add_pattern(void *filter, const char *pattern); +void _gtk_file_filter_set_name(void *filter, const char *name); +void _gtk_file_chooser_add_filter(void *dialog, void *filter); +void _gtk_main_iteration(void); +bool _gtk_events_pending(void); +unsigned long _g_signal_connect_data(void *instance, + const char *detailed_signal, + void *c_handler, + void *data, + void *destroy_data, + unsigned connect_flags); +void _gtk_file_chooser_set_current_name(void *dialog, + const char *name); +void *_gtk_file_chooser_get_filter(void *dialog); +const char *_gtk_file_filter_get_name (void *dialog); +#define g_signal_connect(instance, detailed_signal, c_handler, data) \ +g_signal_connect_data((instance), (detailed_signal), (c_handler), (data), NULL, 0) + + + +#define LAZY(symbol) static typeof(_##symbol) *symbol = NULL;\ +if (symbol == NULL) symbol = dlsym(handle, #symbol);\ +if (symbol == NULL) goto lazy_error +#define TRY_DLOPEN(name) handle = handle? handle : dlopen(name, RTLD_NOW) + +void nop(){} + +static void wait_mouse_up(void) +{ + while (true) { + if (!(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT))) break; + SDL_Event event; + SDL_PollEvent(&event); + } +} + +char *do_open_rom_dialog(void) +{ + wait_mouse_up(); + 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("Open ROM", + 0, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + + + void *filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.gb"); + gtk_file_filter_add_pattern(filter, "*.gbc"); + gtk_file_filter_add_pattern(filter, "*.sgb"); + gtk_file_filter_add_pattern(filter, "*.isx"); + gtk_file_filter_set_name(filter, "Game Boy ROMs"); + gtk_file_chooser_add_filter(dialog, filter); + + 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; +} + +char *do_open_folder_dialog(void) +{ + wait_mouse_up(); + 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; +} + +static void filter_changed(void *dialog, + void *unused, + void *unused2) +{ + 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_file_chooser_get_filename); + LAZY(gtk_file_chooser_set_current_name); + LAZY(g_free); + LAZY(gtk_file_chooser_get_filter); + LAZY(gtk_file_filter_get_name); + + char *filename = gtk_file_chooser_get_filename(dialog); + if (!filename) return; + char *temp = filename + strlen(filename); + char *basename = filename; + bool deleted_extension = false; + while (temp != filename) { + temp--; + if (*temp == '.' && !deleted_extension) { + *temp = 0; + deleted_extension = true; + } + else if (*temp == '/') { + basename = temp + 1; + break; + } + } + + char *new_filename = NULL; + + switch (gtk_file_filter_get_name(gtk_file_chooser_get_filter(dialog))[1]) { + case 'p': + default: + asprintf(&new_filename, "%s.aiff", basename); + break; + case 'I': + asprintf(&new_filename, "%s.wav", basename); + break; + case 'a': + asprintf(&new_filename, "%s.raw", basename); + break; + } + + + gtk_file_chooser_set_current_name(dialog, new_filename); + free(new_filename); + g_free(filename); + return; + +lazy_error: + fprintf(stderr, "Failed updating the file extension\n"); +} + + +char *do_save_recording_dialog(unsigned frequency) +{ + wait_mouse_up(); + 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); + LAZY(g_signal_connect_data); + LAZY(gtk_file_chooser_set_current_name); + + /* Shut up GTK */ + g_log_set_default_handler(nop, NULL); + + gtk_init_check(0, 0); + + + void *dialog = gtk_file_chooser_dialog_new("Audio recording save location", + 0, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL ); + + + void *filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.aiff"); + gtk_file_filter_add_pattern(filter, "*.aif"); + gtk_file_filter_add_pattern(filter, "*.aifc"); + gtk_file_filter_set_name(filter, "Apple AIFF"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.wav"); + gtk_file_filter_set_name(filter, "RIFF WAVE"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.raw"); + gtk_file_filter_add_pattern(filter, "*.pcm"); + static char raw_name[40]; +#ifdef GB_BIG_ENDIAN + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit BE)", frequency); +#else + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit LE)", frequency); +#endif + gtk_file_filter_set_name(filter, raw_name); + gtk_file_chooser_add_filter(dialog, filter); + + g_signal_connect(dialog, "notify::filter", filter_changed, NULL); + gtk_file_chooser_set_current_name(dialog, "Untitled.aiff"); + + 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/thirdparty/SameBoy-old/OpenDialog/open_dialog.h b/thirdparty/SameBoy-old/OpenDialog/open_dialog.h new file mode 100644 index 000000000..35484108d --- /dev/null +++ b/thirdparty/SameBoy-old/OpenDialog/open_dialog.h @@ -0,0 +1,7 @@ +#ifndef open_rom_h +#define open_rom_h + +char *do_open_rom_dialog(void); +char *do_open_folder_dialog(void); +char *do_save_recording_dialog(unsigned frequency); +#endif /* open_rom_h */ diff --git a/thirdparty/SameBoy-old/OpenDialog/windows.c b/thirdparty/SameBoy-old/OpenDialog/windows.c new file mode 100644 index 000000000..5a0af0e32 --- /dev/null +++ b/thirdparty/SameBoy-old/OpenDialog/windows.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include "open_dialog.h" + +static char *wc_to_utf8_alloc(const wchar_t *wide) +{ + unsigned int cb = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL); + if (cb) { + char *buffer = (char*) malloc(cb); + if (buffer) { + WideCharToMultiByte(CP_UTF8, 0, wide, -1, buffer, cb, NULL, NULL); + return buffer; + } + } + return NULL; +} + +char *do_open_rom_dialog(void) +{ + OPENFILENAMEW dialog; + wchar_t filename[MAX_PATH]; + + filename[0] = '\0'; + memset(&dialog, 0, sizeof(dialog)); + dialog.lStructSize = sizeof(dialog); + dialog.lpstrFile = filename; + dialog.nMaxFile = MAX_PATH; + dialog.lpstrFilter = L"Game Boy ROMs\0*.gb;*.gbc;*.sgb;*.isx\0All files\0*.*\0\0"; + dialog.nFilterIndex = 1; + dialog.lpstrFileTitle = NULL; + dialog.nMaxFileTitle = 0; + dialog.lpstrInitialDir = NULL; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + + if (GetOpenFileNameW(&dialog)) { + return wc_to_utf8_alloc(filename); + } + + return NULL; +} + +char *do_open_folder_dialog(void) +{ + char *ret = NULL; + BROWSEINFOW dialog; + + memset(&dialog, 0, sizeof(dialog)); + dialog.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + dialog.lpszTitle = L"Select Boot ROMs Folder"; + + HRESULT hrOleInit = OleInitialize(NULL); + LPITEMIDLIST list = SHBrowseForFolderW(&dialog); + if (list) { + wchar_t filename[MAX_PATH]; + if (SHGetPathFromIDListW(list, filename)) { + ret = wc_to_utf8_alloc(filename); + } + CoTaskMemFree(list); + } + + if (SUCCEEDED(hrOleInit)) OleUninitialize(); + return ret; +} + +char *do_save_recording_dialog(unsigned frequency) +{ + OPENFILENAMEW dialog; + wchar_t filename[MAX_PATH + 5] = L"recording.wav"; + static wchar_t filter[] = L"RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo _______Hz, 16-bit LE)\0*.raw;*.pcm;\0All files\0*.*\0\0"; + + memset(&dialog, 0, sizeof(dialog)); + dialog.lStructSize = sizeof(dialog); + dialog.lpstrFile = filename; + dialog.nMaxFile = MAX_PATH; + dialog.lpstrFilter = filter; + swprintf(filter + sizeof("RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo ") - 1, + sizeof("_______Hz, 16-bit LE)"), + L"%dHz, 16-bit LE) ", + frequency); + + dialog.nFilterIndex = 1; + dialog.lpstrInitialDir = NULL; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; + + if (GetSaveFileNameW(&dialog)) { + if (dialog.nFileExtension == 0) { + switch (dialog.nFilterIndex) { + case 1: + wcscat(filename, L".wav"); + break; + case 2: + wcscat(filename, L".aiff"); + break; + case 3: + wcscat(filename, L".raw"); + break; + } + } + return wc_to_utf8_alloc(filename); + } + + return NULL; +} diff --git a/thirdparty/SameBoy-old/QuickLook/CartridgeTemplate.png b/thirdparty/SameBoy-old/QuickLook/CartridgeTemplate.png new file mode 100644 index 000000000..3d6d600ee Binary files /dev/null and b/thirdparty/SameBoy-old/QuickLook/CartridgeTemplate.png differ diff --git a/thirdparty/SameBoy-old/QuickLook/ColorCartridgeTemplate.png b/thirdparty/SameBoy-old/QuickLook/ColorCartridgeTemplate.png new file mode 100644 index 000000000..356e45d29 Binary files /dev/null and b/thirdparty/SameBoy-old/QuickLook/ColorCartridgeTemplate.png differ diff --git a/thirdparty/SameBoy-old/QuickLook/Info.plist b/thirdparty/SameBoy-old/QuickLook/Info.plist new file mode 100644 index 000000000..dffbee8e5 --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/Info.plist @@ -0,0 +1,63 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDocumentTypes + + + CFBundleTypeRole + QLGenerator + LSItemContentTypes + + com.github.liji32.sameboy.gb + com.github.liji32.sameboy.gbc + com.github.liji32.sameboy.isx + + + + CFBundleExecutable + SameBoyQL + CFBundleIdentifier + com.github.liji32.sameboy.previewer + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SameBoy + CFBundleShortVersionString + Version @VERSION + CFBundleSignature + ???? + CFPlugInDynamicRegisterFunction + + CFPlugInDynamicRegistration + NO + CFPlugInFactories + + 48BC750C-2BB9-49B1-AE80-786E22B3DEB4 + QuickLookGeneratorPluginFactory + + CFPlugInTypes + + 5E2D9680-5022-40FA-B806-43349622E5B9 + + 48BC750C-2BB9-49B1-AE80-786E22B3DEB4 + + + CFPlugInUnloadFunction + + NSHumanReadableCopyright + Copyright © 2015-2022 Lior Halphon + QLNeedsToBeRunInMainThread + + QLPreviewHeight + 144 + QLPreviewWidth + 160 + QLSupportsConcurrentRequests + + QLThumbnailMinimumSize + 64 + + diff --git a/thirdparty/SameBoy-old/QuickLook/UniversalCartridgeTemplate.png b/thirdparty/SameBoy-old/QuickLook/UniversalCartridgeTemplate.png new file mode 100644 index 000000000..7f251f4a0 Binary files /dev/null and b/thirdparty/SameBoy-old/QuickLook/UniversalCartridgeTemplate.png differ diff --git a/thirdparty/SameBoy-old/QuickLook/exports.sym b/thirdparty/SameBoy-old/QuickLook/exports.sym new file mode 100644 index 000000000..2e7fddebf --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/exports.sym @@ -0,0 +1 @@ +_QuickLookGeneratorPluginFactory diff --git a/thirdparty/SameBoy-old/QuickLook/generator.m b/thirdparty/SameBoy-old/QuickLook/generator.m new file mode 100644 index 000000000..f2651d289 --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/generator.m @@ -0,0 +1,119 @@ +#include +#include +#include "get_image_for_rom.h" + +static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder) +{ + /* Load the template NSImages when generating the first thumbnail */ + static NSImage *template = nil; + static NSImage *templateUniversal = nil; + static NSImage *templateColor = nil; + static NSBundle *bundle = nil; + static dispatch_once_t onceToken; + if (showBorder) { + dispatch_once(&onceToken, ^{ + bundle = [NSBundle bundleWithIdentifier:@"com.github.liji32.sameboy.previewer"]; + template = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CartridgeTemplate" ofType:@"png"]]; + templateUniversal = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"UniversalCartridgeTemplate" ofType:@"png"]]; + templateColor = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"ColorCartridgeTemplate" ofType:@"png"]]; + }); + } + uint32_t bitmap[160*144]; + uint8_t cgbFlag = 0; + + /* The cgb_boot_fast boot ROM skips the boot animation */ + if (get_image_for_rom([[(__bridge NSURL *)url path] UTF8String], + [[bundle pathForResource:@"cgb_boot_fast" ofType:@"bin"] UTF8String], + bitmap, &cgbFlag)) { + return -1; + } + + /* Convert the screenshot to a CGImageRef */ + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmap, sizeof(bitmap), NULL); + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; + CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; + + CGImageRef iref = CGImageCreate(160, + 144, + 8, + 32, + 4 * 160, + colorSpaceRef, + bitmapInfo, + provider, + NULL, + true, + renderingIntent); + CGContextSetInterpolationQuality(cgContext, kCGInterpolationNone); + NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:false]; + [NSGraphicsContext setCurrentContext:context]; + + + /* Convert the screenshot to a magnified NSImage */ + NSImage *screenshot = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(160, 144)]; + /* Draw the screenshot */ + if (showBorder) { + [screenshot drawInRect:NSMakeRect(192, 150, 640, 576)]; + } + else { + [screenshot drawInRect:NSMakeRect(0, 0, 640, 576)]; + } + + if (showBorder) { + /* Use the CGB flag to determine the cartridge "look": + - DMG cartridges are grey + - CGB cartridges are transparent + - CGB cartridges that support DMG systems are black + */ + NSImage *effectiveTemplate = nil; + switch (cgbFlag) { + case 0xC0: + effectiveTemplate = templateColor; + break; + case 0x80: + effectiveTemplate = templateUniversal; + break; + default: + effectiveTemplate = template; + } + + /* Mask it with the template (The middle part of the template image is transparent) */ + [effectiveTemplate drawInRect:(NSRect){{0, 0}, template.size}]; + } + + CGColorSpaceRelease(colorSpaceRef); + CGDataProviderRelease(provider); + CGImageRelease(iref); + + return noErr; +} + +OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) +{ + @autoreleasepool { + CGContextRef cgContext = QLPreviewRequestCreateContext(preview, ((NSSize){640, 576}), true, nil); + if (render(cgContext, url, false) == noErr) { + QLPreviewRequestFlushContext(preview, cgContext); + CGContextRelease(cgContext); + return noErr; + } + CGContextRelease(cgContext); + return -1; + } +} + +OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) +{ + extern NSString *kQLThumbnailPropertyIconFlavorKey; + @autoreleasepool { + CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, ((NSSize){1024, 1024}), true, (__bridge CFDictionaryRef)(@{kQLThumbnailPropertyIconFlavorKey : @(0)})); + if (render(cgContext, url, true) == noErr) { + QLThumbnailRequestFlushContext(thumbnail, cgContext); + CGContextRelease(cgContext); + return noErr; + } + CGContextRelease(cgContext); + return -1; + } +} diff --git a/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.c b/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.c new file mode 100644 index 000000000..6c9ac9198 --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include + +#include "get_image_for_rom.h" + +#define LENGTH 60 * 10 + +struct local_data { + unsigned long frames; + bool running; +}; + +static char *async_input_callback(GB_gameboy_t *gb) +{ + return NULL; +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + +} + + +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + + struct local_data *local_data = (struct local_data *)GB_get_user_data(gb); + + if (local_data->frames == LENGTH) { + local_data->running = false; + } + else if (local_data->frames == LENGTH - 1) { + GB_set_rendering_disabled(gb, false); + } + + local_data->frames++; +} + +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return (b << 16) | (g << 8) | (r) | 0xFF000000; +} + +int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *output, uint8_t *cgb_flag) +{ + GB_gameboy_t gb; + GB_init(&gb, GB_MODEL_CGB_E); + if (GB_load_boot_rom(&gb, boot_path)) { + GB_free(&gb); + return 1; + } + + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_pixels_output(&gb, output); + GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_async_input_callback(&gb, async_input_callback); + GB_set_log_callback(&gb, log_callback); + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED); + + size_t length = strlen(filename); + char extension[4] = {0,}; + if (length > 4) { + if (filename[length - 4] == '.') { + extension[0] = tolower(filename[length - 3]); + extension[1] = tolower(filename[length - 2]); + extension[2] = tolower(filename[length - 1]); + } + } + if (strcmp(extension, "isx") == 0) { + if (GB_load_isx(&gb, filename)) { + GB_free(&gb); + return 1; + } + } + else if (GB_load_rom(&gb, filename)) { + GB_free(&gb); + return 1; + } + + /* Run emulation */ + struct local_data local_data = {0,}; + GB_set_user_data(&gb, &local_data); + local_data.running = true; + local_data.frames = 0; + GB_set_rendering_disabled(&gb, true); + GB_set_turbo_mode(&gb, true, true); + + *cgb_flag = GB_read_memory(&gb, 0x143) & 0xC0; + + + while (local_data.running) { + GB_run(&gb); + } + + + GB_free(&gb); + return 0; +} + diff --git a/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.h b/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.h new file mode 100644 index 000000000..598486a5f --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/get_image_for_rom.h @@ -0,0 +1,10 @@ +#ifndef get_image_for_rom_h +#define get_image_for_rom_h +#include + +typedef bool (*cancel_callback_t)(void*); + +int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *output, uint8_t *cgb_flag); + + +#endif diff --git a/thirdparty/SameBoy-old/QuickLook/main.c b/thirdparty/SameBoy-old/QuickLook/main.c new file mode 100644 index 000000000..4e45313ba --- /dev/null +++ b/thirdparty/SameBoy-old/QuickLook/main.c @@ -0,0 +1,201 @@ +/* This is an Apple-provided "bootstrap" code for Quick Look generators, nothing intersting here. */ + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// constants +// ----------------------------------------------------------------------------- + +// Don't modify this line +#define PLUGIN_ID "48BC750C-2BB9-49B1-AE80-786E22B3DEB4" + +// +// Below is the generic glue code for all plug-ins. +// +// You should not have to modify this code aside from changing +// names if you decide to change the names defined in the Info.plist +// + + +// ----------------------------------------------------------------------------- +// typedefs +// ----------------------------------------------------------------------------- + +OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); +OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); + +// The layout for an instance of QuickLookGeneratorPlugIn +typedef struct __QuickLookGeneratorPluginType +{ + void *conduitInterface; + CFUUIDRef factoryID; + UInt32 refCount; +} QuickLookGeneratorPluginType; + +// ----------------------------------------------------------------------------- +// prototypes +// ----------------------------------------------------------------------------- +// Forward declaration for the IUnknown implementation. +// + +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 +// ----------------------------------------------------------------------------- +// The QLGeneratorInterfaceStruct function table. +// +static QLGeneratorInterfaceStruct myInterfaceFtbl = { + NULL, + QuickLookGeneratorQueryInterface, + QuickLookGeneratorPluginAddRef, + QuickLookGeneratorPluginRelease, + NULL, + NULL, + NULL, + NULL +}; + + +// ----------------------------------------------------------------------------- +// AllocQuickLookGeneratorPluginType +// ----------------------------------------------------------------------------- +// Utility function that allocates a new instance. +// You can do some initial setup for the generator here if you wish +// like allocating globals etc... +// +QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID) +{ + QuickLookGeneratorPluginType *theNewInstance; + + theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); + memset(theNewInstance, 0, sizeof(QuickLookGeneratorPluginType)); + + /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ + theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); + memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl, sizeof(QLGeneratorInterfaceStruct)); + + /* Retain and keep an open instance refcount for each factory. */ + theNewInstance->factoryID = CFRetain(inFactoryID); + CFPlugInAddInstanceForFactory(inFactoryID); + + /* This function returns the IUnknown interface so set the refCount to one. */ + theNewInstance->refCount = 1; + return theNewInstance; +} + +// ----------------------------------------------------------------------------- +// DeallocQuickLookGeneratorPluginType +// ----------------------------------------------------------------------------- +// Utility function that deallocates the instance when +// the refCount goes to zero. +// In the current implementation generator interfaces are never deallocated +// but implement this as this might change in the future +// +void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance) +{ + CFUUIDRef theFactoryID; + + theFactoryID = thisInstance->factoryID; + /* Free the conduitInterface table up */ + free(thisInstance->conduitInterface); + + /* Free the instance structure */ + free(thisInstance); + if (theFactoryID) { + CFPlugInRemoveInstanceForFactory(theFactoryID); + CFRelease(theFactoryID); + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorQueryInterface +// ----------------------------------------------------------------------------- +// Implementation of the IUnknown QueryInterface function. +// +HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv) +{ + CFUUIDRef interfaceID; + + interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, iid); + + if (CFEqual(interfaceID, kQLGeneratorCallbacksInterfaceID)) { + /* If the Right interface was requested, bump the ref count, + * set the ppv parameter equal to the instance, and + * return good status. + */ + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance); + *ppv = thisInstance; + CFRelease(interfaceID); + return S_OK; + } + else { + /* Requested interface unknown, bail with error. */ + *ppv = NULL; + CFRelease(interfaceID); + return E_NOINTERFACE; + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginAddRef +// ----------------------------------------------------------------------------- +// Implementation of reference counting for this type. Whenever an interface +// is requested, bump the refCount for the instance. NOTE: returning the +// refcount is a convention but is not required so don't rely on it. +// +ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) +{ + ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1; + return ((QuickLookGeneratorPluginType*) thisInstance)->refCount; +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginRelease +// ----------------------------------------------------------------------------- +// When an interface is released, decrement the refCount. +// If the refCount goes to zero, deallocate the instance. +// +ULONG QuickLookGeneratorPluginRelease(void *thisInstance) +{ + ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1; + if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0) { + DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance ); + return 0; + } + else { + return ((QuickLookGeneratorPluginType*) thisInstance )->refCount; + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginFactory +// ----------------------------------------------------------------------------- +void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) +{ + QuickLookGeneratorPluginType *result; + CFUUIDRef uuid; + + /* If correct type is being requested, allocate an + * instance of kQLGeneratorTypeID and return the IUnknown interface. + */ + if (CFEqual(typeID, kQLGeneratorTypeID)) { + uuid = CFUUIDCreateFromString(kCFAllocatorDefault, CFSTR(PLUGIN_ID)); + result = AllocQuickLookGeneratorPluginType(uuid); + CFRelease(uuid); + return result; + } + /* If the requested type is incorrect, return NULL. */ + return NULL; +} + diff --git a/thirdparty/SameBoy-old/README.md b/thirdparty/SameBoy-old/README.md new file mode 100644 index 000000000..d06ef4d20 --- /dev/null +++ b/thirdparty/SameBoy-old/README.md @@ -0,0 +1,58 @@ +# SameBoy + +SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for macOS, an SDL frontend for other operating systems, and a libretro core. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/). + +## Features +Features common to both Cocoa and SDL versions: + * Supports Game Boy (DMG) and Game Boy Color (CGB) emulation + * Lets you choose the model you want to emulate regardless of ROM + * High quality 96KHz audio + * Battery save support + * Save states + * Includes open source DMG and CGB boot ROMs: + * Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Game Boy games on a Game Boy Color + * Supports manual palette selection with key combinations, with 4 additional new palettes (A + B + direction) + * Supports palette selection in a CGB game, forcing it to run in 'paletted' DMG mode, if ROM allows doing so. + * Support for games with a non-Nintendo logo in the header + * No long animation in the DMG boot + * Advanced text-based debugger with an expression evaluator, disassembler, conditional breakpoints, conditional watchpoints, backtracing and other features + * Extremely high accuracy + * Emulates [PCM_12 and PCM_34 registers](https://github.com/LIJI32/GBVisualizer) + * T-cycle accurate emulation of LCD timing effects, supporting the Demotronic trick, Prehistorik Man, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos + * Real time clock emulation + * Retina/High DPI display support, allowing a wider range of scaling factors without artifacts + * Optional frame blending (Requires OpenGL 3.2 or later) + * Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x; Requires OpenGL 3.2 or later or Metal) + +Features currently supported only with the Cocoa version: + * Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars + * Game Boy Camera support + +[Read more](https://sameboy.github.io/features/). + +## 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 (Recommended; required for macOS) or GCC + * make + * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) + * SDL port: libsdl2 + * [rgbds](https://github.com/gbdev/rgbds/releases/), for boot ROM compilation + +On Windows, SameBoy also requires: + * Visual Studio (For headers, etc.) + * [GnuWin](http://gnuwin32.sourceforge.net/) + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (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. + +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 + +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. diff --git a/thirdparty/SameBoy-old/SDL/audio.c b/thirdparty/SameBoy-old/SDL/audio.c new file mode 100644 index 000000000..29b3eb0f6 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "audio/audio.h" +#include "configuration.h" + +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +static const GB_audio_driver_t *driver = NULL; + +bool GB_audio_init(void) +{ + const GB_audio_driver_t *drivers[] = { +#ifdef _WIN32 + GB_AUDIO_DRIVER_REF(XAudio2), + GB_AUDIO_DRIVER_REF(XAudio2_7), +#endif + GB_AUDIO_DRIVER_REF(SDL), +#ifdef ENABLE_OPENAL + GB_AUDIO_DRIVER_REF(OpenAL), +#endif + }; + + // First try the preferred driver + for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) { + driver = drivers[i]; + if (strcmp(driver->name, configuration.audio_driver) != 0) { + continue; + } + if (driver->audio_init()) { + if (driver->audio_deinit) { + atexit(driver->audio_deinit); + } + return true; + } + } + + // Else go by priority + for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) { + driver = drivers[i]; + if (driver->audio_init()) { + atexit(driver->audio_deinit); + return true; + } + } + + driver = NULL; + return false; +} + +bool GB_audio_is_playing(void) +{ + if (unlikely(!driver)) return false; + return driver->audio_is_playing(); +} + +void GB_audio_set_paused(bool paused) +{ + if (unlikely(!driver)) return; + return driver->audio_set_paused(paused); +} + +void GB_audio_clear_queue(void) +{ + if (unlikely(!driver)) return; + return driver->audio_clear_queue(); +} + +unsigned GB_audio_get_frequency(void) +{ + if (unlikely(!driver)) return 0; + return driver->audio_get_frequency(); +} + +size_t GB_audio_get_queue_length(void) +{ + if (unlikely(!driver)) return 0; + return driver->audio_get_queue_length(); +} + +void GB_audio_queue_sample(GB_sample_t *sample) +{ + if (unlikely(!driver)) return; + return driver->audio_queue_sample(sample); +} + +const char *GB_audio_driver_name(void) +{ + if (unlikely(!driver)) return "None"; + return driver->name; +} + +const char *GB_audio_driver_name_at_index(unsigned index) +{ + const GB_audio_driver_t *drivers[] = { +#ifdef _WIN32 + GB_AUDIO_DRIVER_REF(XAudio2), + GB_AUDIO_DRIVER_REF(XAudio2_7), +#endif + GB_AUDIO_DRIVER_REF(SDL), +#ifdef ENABLE_OPENAL + GB_AUDIO_DRIVER_REF(OpenAL), +#endif + }; + if (index >= sizeof(drivers) / sizeof(drivers[0])) { + return ""; + } + return drivers[index]->name; +} diff --git a/thirdparty/SameBoy-old/SDL/audio/audio.h b/thirdparty/SameBoy-old/SDL/audio/audio.h new file mode 100644 index 000000000..0de574679 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/audio.h @@ -0,0 +1,45 @@ +#ifndef sdl_audio_h +#define sdl_audio_h + +#include +#include +#include + +bool GB_audio_is_playing(void); +void GB_audio_set_paused(bool paused); +void GB_audio_clear_queue(void); +unsigned GB_audio_get_frequency(void); +size_t GB_audio_get_queue_length(void); +void GB_audio_queue_sample(GB_sample_t *sample); +bool GB_audio_init(void); +void GB_audio_deinit(void); +const char *GB_audio_driver_name(void); +const char *GB_audio_driver_name_at_index(unsigned index); + +typedef struct { + typeof(GB_audio_is_playing) *audio_is_playing; + typeof(GB_audio_set_paused) *audio_set_paused; + typeof(GB_audio_clear_queue) *audio_clear_queue; + typeof(GB_audio_get_frequency) *audio_get_frequency; + typeof(GB_audio_get_queue_length) *audio_get_queue_length; + typeof(GB_audio_queue_sample) *audio_queue_sample; + typeof(GB_audio_init) *audio_init; + typeof(GB_audio_deinit) *audio_deinit; + const char *name; +} GB_audio_driver_t; + +#define GB_AUDIO_DRIVER(_name) const GB_audio_driver_t _name##driver = { \ + .audio_is_playing = _audio_is_playing, \ + .audio_set_paused = _audio_set_paused, \ + .audio_clear_queue = _audio_clear_queue, \ + .audio_get_frequency = _audio_get_frequency, \ + .audio_get_queue_length = _audio_get_queue_length, \ + .audio_queue_sample = _audio_queue_sample, \ + .audio_init = _audio_init, \ + .audio_deinit = _audio_deinit, \ + .name = #_name, \ +} + +#define GB_AUDIO_DRIVER_REF(name) ({extern const GB_audio_driver_t name##driver; &name##driver;}) + +#endif /* sdl_audio_h */ diff --git a/thirdparty/SameBoy-old/SDL/audio/openal.c b/thirdparty/SameBoy-old/SDL/audio/openal.c new file mode 100644 index 000000000..fdcaeadea --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/openal.c @@ -0,0 +1,317 @@ +#include "audio.h" +#if defined(__APPLE__) +#include +#include + +#else +#include +#include +#endif +#include +#include +#include + +#define BUFFER_LEN_MS 5 + +static ALCdevice *al_device = NULL; +static ALCcontext *al_context = NULL; +static GB_sample_t *audio_buffer = NULL; +static ALuint al_source = 0; +static ALCint sample_rate = 0; +static unsigned buffer_size = 0; +static unsigned buffer_pos = 0; +static bool is_paused = false; + +#define AL_ERR_STRINGIFY(x) #x +#define AL_ERR_TOSTRING(x) AL_ERR_STRINGIFY(x) +#define AL_ERROR(msg) check_al_error(msg, AL_ERR_TOSTRING(__LINE__)) + +// Check if the previous OpenAL call returned an error. +// If an error occurred a message will be logged to stderr. +static bool check_al_error(const char *user_msg, const char *line) +{ + ALCenum error = alGetError(); + const char *description = ""; + + switch (error) { + case AL_NO_ERROR: + return false; + case AL_INVALID_NAME: + description = "A bad name (ID) was passed to an OpenAL function"; + break; + case AL_INVALID_ENUM: + description = "An invalid enum value was passed to an OpenAL function"; + break; + case AL_INVALID_VALUE: + description = "An invalid value was passed to an OpenAL function"; + break; + case AL_INVALID_OPERATION: + description = "The requested operation is not valid"; + break; + case AL_OUT_OF_MEMORY: + description = "The requested operation resulted in OpenAL running out of memory"; + break; + } + + if (user_msg != NULL) { + fprintf(stderr, "[OpenAL:%s] %s: %s\n", line, user_msg, description); + } + else { + fprintf(stderr, "[OpenAL:%s] %s\n", line, description); + } + + return true; +} + +static void _audio_deinit(void) +{ + // Stop the source (this should mark all queued buffers as processed) + alSourceStop(al_source); + + // Check if there are buffers that can be freed + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (!AL_ERROR("Failed to query number of processed buffers")) { + // Try to free the buffers, we do not care about potential errors here + while (processed--) { + ALuint buffer; + alSourceUnqueueBuffers(al_source, 1, &buffer); + alDeleteBuffers(1, &buffer); + } + } + + alDeleteSources(1, &al_source); + if (al_context) { + alcDestroyContext(al_context); + al_context = NULL; + } + + if (al_device) { + alcCloseDevice(al_device); + al_device = NULL; + } + + if (audio_buffer) { + free(audio_buffer); + audio_buffer = NULL; + } +} + +static void free_processed_buffers(void) +{ + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (AL_ERROR("Failed to query number of processed buffers")) { + return; + } + + while (processed--) { + ALuint buffer; + + alSourceUnqueueBuffers(al_source, 1, &buffer); + if (AL_ERROR("Failed to unqueue buffer")) { + return; + } + + alDeleteBuffers(1, &buffer); + /* Due to a limitation in Apple's OpenAL implementation, this function + can fail once in a few times. If it does, ignore the warning, and let + this buffer be freed in a later call to free_processed_buffers. */ +#if defined(__APPLE__) + if (alGetError()) return; +#else + if (AL_ERROR("Failed to delete buffer")) { + return; + } +#endif + } +} + +static bool _audio_is_playing(void) +{ + ALenum state; + alGetSourcei(al_source, AL_SOURCE_STATE, &state); + if (AL_ERROR("Failed to query source state")) { + return false; + } + + return state == AL_PLAYING; +} + +static void _audio_set_paused(bool paused) +{ + is_paused = paused; + if (paused) { + alSourcePause(al_source); + } + else { + alSourcePlay(al_source); + } +} + +static void _audio_clear_queue(void) +{ + bool is_playing = _audio_is_playing(); + + // Stopping a source clears its queue + alSourceStop(al_source); + if (AL_ERROR(NULL)) { + return; + } + + free_processed_buffers(); + buffer_pos = 0; + + if (is_playing) { + _audio_set_paused(false); + } +} + +static unsigned _audio_get_frequency(void) +{ + return sample_rate; +} + +static size_t _audio_get_queue_length(void) +{ + // Get the number of all attached buffers + ALint buffers; + alGetSourcei(al_source, AL_BUFFERS_QUEUED, &buffers); + if (AL_ERROR("Failed to query number of queued buffers")) { + buffers = 0; + } + + // Get the number of all processed buffers (ready to be detached) + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (AL_ERROR("Failed to query number of processed buffers")) { + processed = 0; + } + + return (buffers - processed) * buffer_size + buffer_pos; +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (is_paused) return; + audio_buffer[buffer_pos++] = *sample; + + if (buffer_pos == buffer_size) { + buffer_pos = 0; + + ALuint al_buffer; + alGenBuffers(1, &al_buffer); + if (AL_ERROR("Failed to create audio buffer")) { + return; + } + + alBufferData(al_buffer, AL_FORMAT_STEREO16, audio_buffer, buffer_size * sizeof(GB_sample_t), sample_rate); + if (AL_ERROR("Failed to buffer data")) { + return; + } + + alSourceQueueBuffers(al_source, 1, &al_buffer); + if (AL_ERROR("Failed to queue buffer")) { + return; + } + + // In case of an audio underrun, the source might + // have finished playing all attached buffers + // which means its status will be "AL_STOPPED". + if (!_audio_is_playing()) { + alSourcePlay(al_source); + } + + free_processed_buffers(); + } +} + +static bool _audio_init(void) +{ + // Open the default device + al_device = alcOpenDevice(NULL); + if (!al_device) { + AL_ERROR("Failed to open device"); + return false; + } + + // Create a new audio context without special attributes + al_context = alcCreateContext(al_device, NULL); + if (al_context == NULL) { + AL_ERROR("Failed to create context"); + _audio_deinit(); + return false; + } + + // Enable our audio context + if (!alcMakeContextCurrent(al_context)) { + AL_ERROR("Failed to set context"); + _audio_deinit(); + return false; + } + + // Query the sample rate of the playback device + alcGetIntegerv(al_device, ALC_FREQUENCY, 1, &sample_rate); + if (AL_ERROR("Failed to query sample rate")) { + _audio_deinit(); + return false; + } + if (sample_rate == 0) { + sample_rate = 48000; + } + + // Allocate our working buffer + buffer_size = (sample_rate * BUFFER_LEN_MS) / 1000; + audio_buffer = malloc(buffer_size * sizeof(GB_sample_t)); + if (audio_buffer == NULL) { + fprintf(stderr, "Failed to allocate audio buffer\n"); + _audio_deinit(); + return false; + } + + // Create our playback source + alGenSources(1, &al_source); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Keep the pitch as is + alSourcef(al_source, AL_PITCH, 1); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Keep the volume as is + alSourcef(al_source, AL_GAIN, 1); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Position our source at the center of the 3D space + alSource3f(al_source, AL_POSITION, 0, 0, 0); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Our source is fixed in space + alSource3f(al_source, AL_VELOCITY, 0, 0, 0); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Our source does not loop + alSourcei(al_source, AL_LOOPING, AL_FALSE); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + return true; +} + +GB_AUDIO_DRIVER(OpenAL); diff --git a/thirdparty/SameBoy-old/SDL/audio/sdl.c b/thirdparty/SameBoy-old/SDL/audio/sdl.c new file mode 100644 index 000000000..9c0cd98bb --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/sdl.c @@ -0,0 +1,111 @@ +#include "audio.h" +#include + +#ifndef _WIN32 +#define AUDIO_FREQUENCY 96000 +#include +#else +#include +/* Windows (well, at least my VM) can't handle 96KHz sound well :( */ + +/* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq. + we can get is 48000. 96000 also works, but always has some faint crackling in + the audio, no matter how high or low I set the buffer length... + Not quite satisfied with that solution, because acc. to SDL2 docs, + 96k + WASAPI *should* work. */ + +#define AUDIO_FREQUENCY 48000 +#endif + +/* Compatibility with older SDL versions */ +#ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0 +#endif + +static SDL_AudioDeviceID device_id; +static SDL_AudioSpec want_aspec, have_aspec; + +#define AUDIO_BUFFER_SIZE 512 +static unsigned buffer_pos = 0; +static GB_sample_t audio_buffer[AUDIO_BUFFER_SIZE]; + +static bool _audio_is_playing(void) +{ + return SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; +} + +static void _audio_clear_queue(void) +{ + SDL_ClearQueuedAudio(device_id); +} + +static void _audio_set_paused(bool paused) +{ + _audio_clear_queue(); + SDL_PauseAudioDevice(device_id, paused); +} + +static unsigned _audio_get_frequency(void) +{ + return have_aspec.freq; +} + +static size_t _audio_get_queue_length(void) +{ + return SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t); +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + audio_buffer[buffer_pos++] = *sample; + + if (buffer_pos == AUDIO_BUFFER_SIZE) { + buffer_pos = 0; + SDL_QueueAudio(device_id, (const void *)audio_buffer, sizeof(audio_buffer)); + } +} + +static bool _audio_init(void) +{ + if (SDL_Init(SDL_INIT_AUDIO) != 0) { + printf("Failed to initialize SDL audio: %s", SDL_GetError()); + return false; + } + + /* Configure Audio */ + memset(&want_aspec, 0, sizeof(want_aspec)); + want_aspec.freq = AUDIO_FREQUENCY; + want_aspec.format = AUDIO_S16SYS; + want_aspec.channels = 2; + want_aspec.samples = 512; + + SDL_version _sdl_version; + SDL_GetVersion(&_sdl_version); + unsigned sdl_version = _sdl_version.major * 1000 + _sdl_version.minor * 100 + _sdl_version.patch; + +#ifndef _WIN32 + /* SDL 2.0.5 on macOS and Linux introduced a bug where certain combinations of buffer lengths and frequencies + fail to produce audio correctly. */ + if (sdl_version >= 2005) { + want_aspec.samples = 2048; + } +#else + if (sdl_version < 2006) { + /* Since WASAPI audio was introduced in SDL 2.0.6, we have to lower the audio frequency + to 44100 because otherwise we would get garbled audio output.*/ + want_aspec.freq = 44100; + } +#endif + + device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + + return true; +} + +static void _audio_deinit(void) +{ + _audio_set_paused(true); + SDL_CloseAudioDevice(device_id); +} + +GB_AUDIO_DRIVER(SDL); diff --git a/thirdparty/SameBoy-old/SDL/audio/xaudio2.c b/thirdparty/SameBoy-old/SDL/audio/xaudio2.c new file mode 100644 index 000000000..e2ca68b8f --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/xaudio2.c @@ -0,0 +1,159 @@ +#define COBJMACROS +#include "audio.h" +#include +#include +#include + +static unsigned audio_frequency = 48000; +static IXAudio2 *xaudio2 = NULL; +static IXAudio2MasteringVoice *master_voice = NULL; +static IXAudio2SourceVoice *source_voice = NULL; +static bool playing = false; +static GB_sample_t sample_pool[0x2000]; +static unsigned pos = 0; + +#define BATCH_SIZE 256 + +static WAVEFORMATEX wave_format = { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 2, + .nBlockAlign = 4, + .wBitsPerSample = 16, + .cbSize = 0 +}; + +static bool _audio_is_playing(void) +{ + return playing; +} + +static void _audio_clear_queue(void) +{ + pos = 0; + IXAudio2SourceVoice_FlushSourceBuffers(source_voice); +} + +static void _audio_set_paused(bool paused) +{ + if (paused) { + playing = false; + IXAudio2SourceVoice_Stop(source_voice, 0, XAUDIO2_COMMIT_NOW); + _audio_clear_queue(); + } + else { + playing = true; + IXAudio2SourceVoice_Start(source_voice, 0, XAUDIO2_COMMIT_NOW); + } + +} + + +#define _DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) static const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +_DEFINE_PROPERTYKEY(_PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0); + + +static void update_frequency(void) +{ + HRESULT hr; + IMMDevice *device = NULL; + IMMDeviceEnumerator *enumerator = NULL; + IPropertyStore *store = NULL; + PWAVEFORMATEX deviceFormatProperties; + PROPVARIANT prop; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); + if (FAILED(hr)) return; + + // get default audio endpoint + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, eRender, eMultimedia, &device); + if (FAILED(hr)) return; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &store); + if (FAILED(hr)) return; + + hr = IPropertyStore_GetValue(store, &_PKEY_AudioEngine_DeviceFormat, &prop); + if (FAILED(hr)) return; + + deviceFormatProperties = (PWAVEFORMATEX)prop.blob.pBlobData; + audio_frequency = deviceFormatProperties->nSamplesPerSec; + if (audio_frequency < 8000 || audio_frequency > 192000) { + // Bogus value, revert to 48KHz + audio_frequency = 48000; + } +} + +static unsigned _audio_get_frequency(void) +{ + return audio_frequency; +} + +static size_t _audio_get_queue_length(void) +{ + static XAUDIO2_VOICE_STATE state; + IXAudio2SourceVoice_GetState(source_voice, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED); + + return state.BuffersQueued * BATCH_SIZE + (pos & (BATCH_SIZE - 1)); +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (!playing) return; + + static XAUDIO2_BUFFER buffer = {.AudioBytes = sizeof(*sample) * BATCH_SIZE, }; + sample_pool[pos] = *sample; + buffer.pAudioData = (void *)&sample_pool[pos & ~(BATCH_SIZE - 1)]; + pos++; + pos &= 0x1fff; + if ((pos & (BATCH_SIZE - 1)) == 0) { + IXAudio2SourceVoice_SubmitSourceBuffer(source_voice, &buffer, NULL); + } +} + +static bool _audio_init(void) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + fprintf(stderr, "CoInitializeEx failed: %lx\n", hr); + return false; + } + + hr = XAudio2Create(&xaudio2, 0, XAUDIO2_DEFAULT_PROCESSOR); + if (FAILED(hr)) { + fprintf(stderr, "XAudio2Create failed: %lx\n", hr); + return false; + } + + update_frequency(); + + hr = IXAudio2_CreateMasteringVoice(xaudio2, &master_voice, + 2, // 2 channels + audio_frequency, + 0, // Flags + 0, // Device index + NULL, // Effect chain + AudioCategory_GameMedia // Category + ); + if (FAILED(hr)) { + fprintf(stderr, "CreateMasteringVoice failed: %lx\n", hr); + return false; + } + + wave_format.nSamplesPerSec = audio_frequency; + wave_format.nAvgBytesPerSec = audio_frequency * 4; + hr = IXAudio2_CreateSourceVoice(xaudio2, &source_voice, &wave_format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); + + if (FAILED(hr)) { + fprintf(stderr, "CreateSourceVoice failed: %lx\n", hr); + return false; + } + + return true; +} + +static void _audio_deinit(void) +{ + _audio_set_paused(true); +} + +GB_AUDIO_DRIVER(XAudio2); diff --git a/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.c b/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.c new file mode 100644 index 000000000..b8ed544af --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.c @@ -0,0 +1,179 @@ +#define COBJMACROS +#include "xaudio2_7.h" +#include "audio.h" +#include + + +static unsigned audio_frequency = 48000; +static IXAudio2 *xaudio2 = NULL; +static IXAudio2MasteringVoice *master_voice = NULL; +static IXAudio2SourceVoice *source_voice = NULL; +static bool playing = false; +static GB_sample_t sample_pool[0x2000]; +static unsigned pos = 0; + +#define BATCH_SIZE 256 + + +static WAVEFORMATEX wave_format = { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 2, + .nBlockAlign = 4, + .wBitsPerSample = 16, + .cbSize = 0 +}; + +static inline HRESULT XAudio2Create(IXAudio2 **out, + UINT32 Flags, + XAUDIO2_PROCESSOR XAudio2Processor) +{ + IXAudio2 *xaudio2; + LoadLibraryEx("xaudio2_7.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + HRESULT hr = CoCreateInstance(&CLSID_XAudio2, NULL, CLSCTX_INPROC_SERVER, &IID_IXAudio2, (void **)&xaudio2); + if (SUCCEEDED(hr)) { + hr = xaudio2->lpVtbl->Initialize(xaudio2, Flags, XAudio2Processor); + } + + if (SUCCEEDED(hr)) { + *out = xaudio2; + } + else if (xaudio2) { + xaudio2->lpVtbl->Release(xaudio2); + } + return hr; +} + +static bool _audio_is_playing(void) +{ + return playing; +} + +static void _audio_clear_queue(void) +{ + pos = 0; + IXAudio2SourceVoice_FlushSourceBuffers(source_voice); +} + +static void _audio_set_paused(bool paused) +{ + if (paused) { + playing = false; + IXAudio2SourceVoice_Stop(source_voice, 0, XAUDIO2_COMMIT_NOW); + _audio_clear_queue(); + } + else { + playing = true; + IXAudio2SourceVoice_Start(source_voice, 0, XAUDIO2_COMMIT_NOW); + } + +} + +#define _DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) static const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +_DEFINE_PROPERTYKEY(_PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0); + + +static void update_frequency(void) +{ + HRESULT hr; + IMMDevice *device = NULL; + IMMDeviceEnumerator *enumerator = NULL; + IPropertyStore *store = NULL; + PWAVEFORMATEX deviceFormatProperties; + PROPVARIANT prop; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); + if (FAILED(hr)) return; + + // get default audio endpoint + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, eRender, eMultimedia, &device); + if (FAILED(hr)) return; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &store); + if (FAILED(hr)) return; + + hr = IPropertyStore_GetValue(store, &_PKEY_AudioEngine_DeviceFormat, &prop); + if (FAILED(hr)) return; + + deviceFormatProperties = (PWAVEFORMATEX)prop.blob.pBlobData; + audio_frequency = deviceFormatProperties->nSamplesPerSec; + if (audio_frequency < 8000 || audio_frequency > 192000) { + // Bogus value, revert to 48KHz + audio_frequency = 48000; + } +} + +static unsigned _audio_get_frequency(void) +{ + return audio_frequency; +} + +static size_t _audio_get_queue_length(void) +{ + static XAUDIO2_VOICE_STATE state; + IXAudio2SourceVoice_GetState(source_voice, &state); + + return state.BuffersQueued * BATCH_SIZE + (pos & (BATCH_SIZE - 1)); +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (!playing) return; + + static XAUDIO2_BUFFER buffer = {.AudioBytes = sizeof(*sample) * BATCH_SIZE, }; + sample_pool[pos] = *sample; + buffer.pAudioData = (void *)&sample_pool[pos & ~(BATCH_SIZE - 1)]; + pos++; + pos &= 0x1fff; + if ((pos & (BATCH_SIZE - 1)) == 0) { + IXAudio2SourceVoice_SubmitSourceBuffer(source_voice, &buffer, NULL); + } +} + +static bool _audio_init(void) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + fprintf(stderr, "CoInitializeEx failed: %lx\n", hr); + return false; + } + + hr = XAudio2Create(&xaudio2, 0, XAUDIO2_DEFAULT_PROCESSOR); + if (FAILED(hr)) { + fprintf(stderr, "XAudio2Create failed: %lx\n", hr); + return false; + } + + update_frequency(); + + hr = IXAudio2_CreateMasteringVoice(xaudio2, &master_voice, + 2, // 2 channels + audio_frequency, + 0, // Flags + 0, // Device index + NULL // Effect chain + ); + if (FAILED(hr)) { + fprintf(stderr, "CreateMasteringVoice failed: %lx\n", hr); + return false; + } + + wave_format.nSamplesPerSec = audio_frequency; + wave_format.nAvgBytesPerSec = audio_frequency * 4; + hr = IXAudio2_CreateSourceVoice(xaudio2, &source_voice, &wave_format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); + + if (FAILED(hr)) { + fprintf(stderr, "CreateSourceVoice failed: %lx\n", hr); + return false; + } + + return true; +} + +static void _audio_deinit(void) +{ + _audio_set_paused(true); +} + +GB_AUDIO_DRIVER(XAudio2_7); diff --git a/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.h b/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.h new file mode 100644 index 000000000..298715650 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/audio/xaudio2_7.h @@ -0,0 +1,108 @@ +#define INITGUID +#include + +/* Minimal definitions for XAudio2.7 */ +typedef UINT32 XAUDIO2_PROCESSOR; + +typedef struct XAUDIO2_BUFFER { + UINT32 Flags; + UINT32 AudioBytes; + const BYTE *pAudioData; + UINT32 PlayBegin; + UINT32 PlayLength; + UINT32 LoopBegin; + UINT32 LoopLength; + UINT32 LoopCount; + void *pContext; +} XAUDIO2_BUFFER; + +typedef struct XAUDIO2_VOICE_STATE { + void *pCurrentBufferContext; + UINT32 BuffersQueued; + UINT64 SamplesPlayed; +} XAUDIO2_VOICE_STATE; + +typedef struct IXAudio2SourceVoice { + struct IXAudio2SourceVoiceVtbl *lpVtbl; +} IXAudio2SourceVoice; + +typedef struct IXAudio2SourceVoiceVtbl IXAudio2SourceVoiceVtbl; + +#undef INTERFACE +#define INTERFACE IXAudio2SourceVoice + +struct IXAudio2SourceVoiceVtbl { + void *voiceMethods[19]; // Unused inherited methods + STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet) PURE; + STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet) PURE; + STDMETHOD(SubmitSourceBuffer) (THIS_ __in const XAUDIO2_BUFFER *pBuffer, __in_opt const void *pBufferWMA) PURE; + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + STDMETHOD(Discontinuity) (THIS) PURE; + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet) PURE; + STDMETHOD_(void, GetState) (THIS_ __out XAUDIO2_VOICE_STATE *pVoiceState) PURE; +}; + +typedef struct IXAudio2 { + struct IXAudio2Vtbl *lpVtbl; +} IXAudio2; + +typedef struct IXAudio2Vtbl IXAudio2Vtbl; +typedef void *IXAudio2MasteringVoice; + +#undef INTERFACE +#define INTERFACE IXAudio2 + +struct IXAudio2Vtbl { + void *QueryInterface; + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + STDMETHOD_(ULONG, Release) (THIS) PURE; + void *GetDeviceCount; + void *GetDeviceDetails; + STDMETHOD(Initialize) (THIS_ UINT32 Flags, + XAUDIO2_PROCESSOR XAudio2Processor) PURE; + + void *RegisterForCallbacks; + void *UnregisterForCallbacks; + + STDMETHOD(CreateSourceVoice) (THIS_ __deref_out IXAudio2SourceVoice **ppSourceVoice, + __in const WAVEFORMATEX *pSourceFormat, + UINT32 Flags, + float MaxFrequencyRatio, + __in_opt void *pCallback, + __in_opt const void *pSendList, + __in_opt const void *pEffectChain) PURE; + + void *CreateSubmixVoice; + + STDMETHOD(CreateMasteringVoice) (THIS_ __deref_out IXAudio2MasteringVoice **ppMasteringVoice, + UINT32 InputChannels, + UINT32 InputSampleRate, + UINT32 Flags, UINT32 DeviceIndex, + __in_opt const void *pEffectChain) PURE; +}; + +#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ +DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ +DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +DEFINE_CLSID(XAudio2, 5a508685, a254, 4fba, 9b, 82, 9a, 24, b0, 03, 06, af); +DEFINE_IID(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + + +#define IXAudio2SourceVoice_Start(This,Flags,OperationSet) ((This)->lpVtbl->Start(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_Stop(This,Flags,OperationSet) ((This)->lpVtbl->Stop(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_SubmitSourceBuffer(This,pBuffer,pBufferWMA) ((This)->lpVtbl->SubmitSourceBuffer(This,pBuffer,pBufferWMA)) +#define IXAudio2SourceVoice_FlushSourceBuffers(This) ((This)->lpVtbl->FlushSourceBuffers(This)) +#define IXAudio2SourceVoice_GetState(This,pVoiceState) ((This)->lpVtbl->GetState(This,pVoiceState)) +#define IXAudio2_CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain) ((This)->lpVtbl->CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain)) +#define IXAudio2_CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain) ((This)->lpVtbl->CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain)) + +#define XAUDIO2_COMMIT_NOW 0 +#define XAUDIO2_DEFAULT_PROCESSOR 0xffffffff +#define XAUDIO2_DEFAULT_FREQ_RATIO 2.0f + +// WASAPI extras. This is a hack, but Windows itself is a hack so I don't care +DEFINE_CLSID(MMDeviceEnumerator, bcde0395, e52f, 467c, 8e, 3d, c4, 57, 92, 91, 69, 2e); +DEFINE_IID(IMMDeviceEnumerator, a95664d2, 9614, 4f35, a7, 46, de, 8d, b6, 36, 17, e6); diff --git a/thirdparty/SameBoy-old/SDL/background.bmp b/thirdparty/SameBoy-old/SDL/background.bmp new file mode 100644 index 000000000..d356d24e5 Binary files /dev/null and b/thirdparty/SameBoy-old/SDL/background.bmp differ diff --git a/thirdparty/SameBoy-old/SDL/configuration.c b/thirdparty/SameBoy-old/SDL/configuration.c new file mode 100644 index 000000000..35ad29952 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/configuration.c @@ -0,0 +1,54 @@ +#include "configuration.h" + +configuration_t configuration = +{ + .keys = { + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_X, + SDL_SCANCODE_Z, + SDL_SCANCODE_BACKSPACE, + SDL_SCANCODE_RETURN, + SDL_SCANCODE_SPACE + }, + .keys_2 = { + SDL_SCANCODE_TAB, + SDL_SCANCODE_LSHIFT, + }, + .joypad_configuration = { + 13, + 14, + 11, + 12, + 0, + 1, + 9, + 8, + 10, + 4, + -1, + 5, + // The rest are unmapped by default + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }, + .joypad_axises = { + 0, + 1, + }, + .color_correction_mode = GB_COLOR_CORRECTION_MODERN_BALANCED, + .highpass_mode = GB_HIGHPASS_ACCURATE, + .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, + .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, + .rewind_length = 60 * 2, + .model = MODEL_CGB, + .volume = 100, + .rumble_mode = GB_RUMBLE_ALL_GAMES, + .default_scale = 2, + .color_temperature = 10, + .cgb_revision = GB_MODEL_CGB_E - GB_MODEL_CGB_0, + .dmg_palette = 1, // Replacing the old default (0) as of 0.15.2 + .agb_revision = GB_MODEL_AGB_A, +}; diff --git a/thirdparty/SameBoy-old/SDL/configuration.h b/thirdparty/SameBoy-old/SDL/configuration.h new file mode 100644 index 000000000..c328e3c9c --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/configuration.h @@ -0,0 +1,140 @@ +#ifndef configuration_h +#define configuration_h + +#include +#include +#include "shader.h" + +enum scaling_mode { + GB_SDL_SCALING_ENTIRE_WINDOW, + GB_SDL_SCALING_KEEP_RATIO, + GB_SDL_SCALING_INTEGER_FACTOR, + GB_SDL_SCALING_MAX, +}; + +typedef enum { + JOYPAD_BUTTON_RIGHT, + JOYPAD_BUTTON_LEFT, + JOYPAD_BUTTON_UP, + JOYPAD_BUTTON_DOWN, + JOYPAD_BUTTON_A, + JOYPAD_BUTTON_B, + JOYPAD_BUTTON_SELECT, + JOYPAD_BUTTON_START, + JOYPAD_BUTTON_MENU, + JOYPAD_BUTTON_TURBO, + JOYPAD_BUTTON_REWIND, + JOYPAD_BUTTON_SLOW_MOTION, + JOYPAD_BUTTON_HOTKEY_1, + JOYPAD_BUTTON_HOTKEY_2, + JOYPAD_BUTTONS_MAX +} joypad_button_t; + +typedef enum { + JOYPAD_AXISES_X, + JOYPAD_AXISES_Y, + JOYPAD_AXISES_MAX +} joypad_axis_t; + +typedef enum { + HOTKEY_NONE, + HOTKEY_PAUSE, + HOTKEY_MUTE, + HOTKEY_RESET, + HOTKEY_QUIT, + HOTKEY_SAVE_STATE_1, + HOTKEY_LOAD_STATE_1, + HOTKEY_SAVE_STATE_2, + HOTKEY_LOAD_STATE_2, + HOTKEY_SAVE_STATE_3, + HOTKEY_LOAD_STATE_3, + HOTKEY_SAVE_STATE_4, + HOTKEY_LOAD_STATE_4, + HOTKEY_SAVE_STATE_5, + HOTKEY_LOAD_STATE_5, + HOTKEY_SAVE_STATE_6, + HOTKEY_LOAD_STATE_6, + HOTKEY_SAVE_STATE_7, + HOTKEY_LOAD_STATE_7, + HOTKEY_SAVE_STATE_8, + HOTKEY_LOAD_STATE_8, + HOTKEY_SAVE_STATE_9, + HOTKEY_LOAD_STATE_9, + HOTKEY_SAVE_STATE_10, + HOTKEY_LOAD_STATE_10, + HOTKEY_MAX = HOTKEY_LOAD_STATE_10, +} hotkey_action_t; + +typedef struct { + SDL_Scancode keys[9]; + GB_color_correction_mode_t color_correction_mode; + enum scaling_mode scaling_mode; + uint8_t blending_mode; + + GB_highpass_mode_t highpass_mode; + + bool _deprecated_div_joystick; + bool _deprecated_flip_joystick_bit_1; + bool _deprecated_swap_joysticks_bits_1_and_2; + + char filter[32]; + enum { + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_SGB, + MODEL_MGB, + MODEL_MAX, + } model; + + /* v0.11 */ + uint32_t rewind_length; + SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */ + uint8_t joypad_configuration[32]; /* 14 Keys + padding for the future*/; + uint8_t joypad_axises[JOYPAD_AXISES_MAX]; + + /* v0.12 */ + enum { + SGB_NTSC, + SGB_PAL, + SGB_2, + SGB_MAX + } sgb_revision; + + /* v0.13 */ + uint8_t dmg_palette; + GB_border_mode_t border_mode; + uint8_t volume; + GB_rumble_mode_t rumble_mode; + + uint8_t default_scale; + + /* v0.14 */ + unsigned padding; + uint8_t color_temperature; + char bootrom_path[4096]; + uint8_t interference_volume; + GB_rtc_mode_t rtc_mode; + + /* v0.14.4 */ + bool osd; + + struct __attribute__((packed, aligned(4))) { + + /* v0.15 */ + bool allow_mouse_controls; + uint8_t cgb_revision; + /* v0.15.1 */ + char audio_driver[16]; + /* v0.15.2 */ + bool allow_background_controllers; + bool gui_pallete_enabled; // Change the GUI palette only once the user changed the DMG palette + char dmg_palette_name[25]; + hotkey_action_t hotkey_actions[2]; + uint16_t agb_revision; + }; +} configuration_t; + +extern configuration_t configuration; + +#endif diff --git a/thirdparty/SameBoy-old/SDL/console.c b/thirdparty/SameBoy-old/SDL/console.c new file mode 100644 index 000000000..295de96ca --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/console.c @@ -0,0 +1,1020 @@ +#include "console.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ESC(x) "\x1B" x +#define CSI(x) ESC("[" x) +#define SGR(x) CSI(x "m") + +static bool initialized = false; +typedef struct listent_s listent_t; + +struct listent_s { + listent_t *prev; + listent_t *next; + char content[]; +}; + +typedef struct { + listent_t *first; + listent_t *last; +} fifo_t; + +static fifo_t lines; +static fifo_t history; + +static void remove_entry(fifo_t *fifo, listent_t *entry) +{ + if (fifo->last == entry) { + fifo->last = entry->prev; + } + if (fifo->first == entry) { + fifo->first = entry->next; + } + if (entry->next) { + entry->next->prev = entry->prev; + } + if (entry->prev) { + entry->prev->next = entry->next; + } + free(entry); +} + +static void add_entry(fifo_t *fifo, const char *content) +{ + size_t length = strlen(content); + listent_t *entry = malloc(sizeof(*entry) + length + 1); + entry->next = NULL; + entry->prev = fifo->last; + memcpy(entry->content, content, length); + entry->content[length] = 0; + if (fifo->last) { + fifo->last->next = entry; + } + fifo->last = entry; + if (!fifo->first) { + fifo->first = entry; + } +} + +static listent_t *reverse_find(listent_t *entry, const char *string, bool exact) +{ + while (entry) { + if (exact && strcmp(entry->content, string) == 0) { + return entry; + } + if (!exact && strstr(entry->content, string)) { + return entry; + } + entry = entry->prev; + } + return NULL; +} + +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 +} + +static unsigned width, height; + +static char raw_getc(void) +{ +#ifdef _WIN32 + char c; + unsigned long ret; + ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL); +#else + ssize_t ret; + char c; + + do { + ret = read(STDIN_FILENO, &c, 1); + } while (ret == -1 && errno == EINTR); +#endif + return ret == 1? c : EOF; +} + +#ifdef _WIN32 +#pragma clang diagnostic ignored "-Wmacro-redefined" +#include + +static void update_size(void) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + width = csbi.srWindow.Right - csbi.srWindow.Left + 1; + height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +} + +static unsigned long input_mode, output_mode; + +static void cleanup(void) +{ + printf(CSI("!p")); // reset + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode); + fflush(stdout); +} + +static bool initialize(void) +{ + if (!is_term()) return false; + update_size(); + if (width == 0 || height == 0) { + return false; + } + + static bool once = false; + if (!once) { + atexit(cleanup); + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode); + once = true; + } + 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); + + printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height); + + fflush(stdout); + initialized = true; + return true; +} +#else +#include + +static void update_size(void) +{ + struct winsize winsize; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize); + width = winsize.ws_col; + height = winsize.ws_row; +} + +static void terminal_resized(int ignored) +{ + update_size(); +} + +#include +static struct termios terminal; + + +static void cleanup(void) +{ + printf(CSI("!p")); // reset + tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal); + fflush(stdout); +} + +static bool initialize(void) +{ + if (!is_term()) return false; + update_size(); + if (width == 0 || height == 0) { + return false; + } + + static bool once = false; + if (!once) { + atexit(cleanup); + signal(SIGWINCH, terminal_resized); + tcgetattr(STDIN_FILENO, &terminal); +#ifdef _WIN32 + _setmode(STDIN_FILENO, _O_TEXT); +#endif + once = true; + } + struct termios raw_terminal; + raw_terminal = terminal; + raw_terminal.c_lflag = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal); + + printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height); + + fflush(stdout); + initialized = true; + return true; +} +#endif + +static struct { + char *content; + size_t allocation_size; + size_t length; + size_t position; + size_t scroll; + bool reverse_search; + listent_t *search_line; +} line; + +#define CTL(x) ((x) - 'A' + 1) + +static const char *prompt = ""; +static size_t prompt_length = 0; +static bool repeat_empty = false; + +static bool redraw_prompt(bool force) +{ + if (line.reverse_search) { + if (!force) return false; + if (line.length == 0) { + printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1); + return true; + } + if (!line.search_line) { + printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content); + return true; + } + const char *loc = strstr(line.search_line->content, line.content); + printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"), + prompt, + (int)(loc - line.search_line->content), + line.search_line->content, + line.content, + loc + line.length, + (unsigned)(loc - line.search_line->content + line.length + prompt_length + 1)); + return true; + } + + size_t max = width - 1 - prompt_length; + + if (line.scroll && line.length <= max) { + line.scroll = 0; + force = true; + } + + if (line.scroll > line.length - max) { + line.scroll = line.length - max; + force = true; + } + + if (line.position < line.scroll + 1 && line.position) { + line.scroll = line.position - 1; + force = true; + } + + if (line.position == 0 && line.scroll) { + line.scroll = 0; + force = true; + } + + if (line.position > line.scroll + max) { + line.scroll = line.position - max; + force = true; + } + + if (!force && line.length <= max) { + return false; + } + + if (line.length <= max) { + printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1)); + return true; + } + + size_t left = max; + const char *string = line.content + line.scroll; + printf("\r" CSI("K") "%s", prompt); + if (line.scroll) { + printf(SGR("2") "%c" SGR("0"), *string); + string++; + left--; + } + if (line.scroll + max == line.length) { + printf("%s", string); + } + else { + printf("%.*s", (int)(left - 1), string); + string += left; + left = 1; + printf(SGR("2") "%c" SGR("0"), *string); + } + printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1)); + + return true; +} + +static void set_position(size_t position) +{ + if (position > line.length) { + printf("\a"); + return; + } + line.position = position; + if (!redraw_prompt(false)) { + printf(CSI("%uG"), (unsigned)(position + prompt_length + 1)); + } +} + +static void set_line(const char *content) +{ + line.length = strlen(content); + if (line.length + 1 > line.allocation_size) { + line.content = realloc(line.content, line.length + 1); + line.allocation_size = line.length + 1; + } + else if (line.allocation_size > 256 && line.length < 128) { + line.content = realloc(line.content, line.length + 1); + line.allocation_size = line.length + 1; + } + line.position = line.length; + strcpy(line.content, content); + redraw_prompt(true); +} + +static void insert(const char *string) +{ + size_t insertion_length = strlen(string); + size_t new_length = insertion_length + line.length; + bool need_realloc = false; + while (line.allocation_size < new_length + 1) { + line.allocation_size *= 2; + need_realloc = true; + } + if (need_realloc) { + line.content = realloc(line.content, line.allocation_size); + } + memmove(line.content + line.position + insertion_length, + line.content + line.position, + line.length - line.position); + memcpy(line.content + line.position, string, insertion_length); + line.position += insertion_length; + line.content[new_length] = 0; + line.length = new_length; + if (!redraw_prompt(line.position != line.length)) { + printf("%s", string); + } +} + +static void delete(size_t size, bool forward) +{ + if (line.length < size) { + printf("\a"); + return; + } + if (forward) { + if (line.position > line.length - size) { + printf("\a"); + return; + } + else { + line.position += size; + } + } + else if (line.position < size) { + printf("\a"); + return; + } + memmove(line.content + line.position - size, + line.content + line.position, + line.length - line.position); + line.length -= size; + line.content[line.length] = 0; + line.position -= size; + + if (!redraw_prompt(line.position != line.length)) { + printf(CSI("%uG") CSI("K"), + (unsigned)(line.position + prompt_length + 1)); + } +} + +static void move_word(bool forward) +{ + signed offset = forward? 1 : -1; + size_t end = forward? line.length : 0; + signed check_offset = forward? 0 : -1; + if (line.position == end) { + printf("\a"); + return; + } + line.position += offset; + while (line.position != end && isalnum(line.content[line.position + check_offset])) { + line.position += offset; + } + if (!redraw_prompt(false)) { + printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1)); + } +} + +static void delete_word(bool forward) +{ + size_t original_pos = line.position; + signed offset = forward? 1 : -1; + size_t end = forward? line.length : 0; + signed check_offset = forward? 0 : -1; + if (line.position == end) { + printf("\a"); + return; + } + line.position += offset; + while (line.position != end && isalnum(line.content[line.position + check_offset])) { + line.position += offset; + } + if (forward) { + delete(line.position - original_pos, false); + } + else { + delete(original_pos - line.position, true); + } +} + +#define MOD_ALT(x) (0x100 | x) +#define MOD_SHIFT(x) (0x200 | x) +#define MOD_CTRL(x) (0x400 | x) +#define MOD_SPECIAL(x) (0x800 | x) + +static unsigned get_extended_key(void) +{ + unsigned modifiers = 0; + char c = 0; +restart: + c = raw_getc(); + if (c == 0x1B) { + modifiers = MOD_SHIFT(MOD_ALT(0)); + goto restart; + } + else if (c != '[' && c != 'O') { + return MOD_ALT(c); + } + unsigned ret = 0; + while (true) { + c = raw_getc(); + if (c >= '0' && c <= '9') { + ret = ret * 10 + c - '0'; + } + else if (c == ';') { + if (ret == 1) { + modifiers |= MOD_ALT(0); + } + else if (ret == 2) { + modifiers |= MOD_SHIFT(0); + } + else if (ret == 5) { + modifiers |= MOD_CTRL(0); + } + ret = 0; + } + else if (c == '~') { + return MOD_SPECIAL(ret) | modifiers; + } + else { + if (ret == 1) { + modifiers |= MOD_ALT(0); + } + else if (ret == 2) { + modifiers |= MOD_SHIFT(0); + } + else if (ret == 5) { + modifiers |= MOD_CTRL(0); + } + return c | modifiers; + } + } +} + +#define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0) +static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER; + +static char reverse_search_mainloop(void) +{ + while (true) { + char c = raw_getc(); + pthread_mutex_lock(&terminal_lock); + + switch (c) { + case CTL('C'): + line.search_line = NULL; + set_line(""); + pthread_mutex_unlock(&terminal_lock); + return CTL('A'); + case CTL('R'): + line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false); + if (!line.search_line) { + printf("\a"); + } + redraw_prompt(true); + break; + case CTL('W'): + delete_word(false); + redraw_prompt(true); + break; +#ifndef _WIN32 + case CTL('Z'): + set_line(""); + raise(SIGSTOP); + initialize(); // Reinitialize + redraw_prompt(true); + break; +#endif + case CTL('H'): + case 0x7F: // Backspace + delete(1, false); + redraw_prompt(true); + break; + default: + if (c >= ' ') { + char string[2] = {c, 0}; + insert(string); + line.search_line = reverse_find(line.search_line?: history.last, line.content, false); + if (!line.search_line) { + printf("\a"); + } + redraw_prompt(true); + } + else { + pthread_mutex_unlock(&terminal_lock); + return c; + } + break; + } + pthread_mutex_unlock(&terminal_lock); + fflush(stdout); + } + +} + + +static +#ifdef _WIN32 +int __stdcall +#else +void * +#endif +mainloop(char *(*completer)(const char *substring, uintptr_t *context)) +{ + listent_t *history_line = NULL; + uintptr_t complete_context = 0; + size_t completion_length = 0; + while (true) { + char c; + if (line.reverse_search) { + c = reverse_search_mainloop(); + line.reverse_search = false; + if (line.search_line) { + size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length; + set_line(line.search_line->content); + line.search_line = NULL; + set_position(pos); + } + else { + redraw_prompt(true); + } + } + else { + c = raw_getc(); + } + pthread_mutex_lock(&terminal_lock); + + switch (c) { + case CTL('A'): + set_position(0); + complete_context = completion_length = 0; + break; + case CTL('B'): + set_position(line.position - 1); + complete_context = completion_length = 0; + break; + case CTL('C'): + if (line.length) { + set_line(""); + history_line = NULL; + complete_context = completion_length = 0; + } + else { +#ifdef _WIN32 + raise(SIGINT); +#else + kill(getpid(), SIGINT); +#endif + } + break; + case CTL('D'): + if (line.length) { + delete(1, true); + complete_context = completion_length = 0; + } + else { + pthread_mutex_lock(&lines_lock); + add_entry(&lines, CON_EOF); + pthread_cond_signal(&lines_cond); + pthread_mutex_unlock(&lines_lock); + } + break; + case CTL('E'): + set_position(line.length); + complete_context = completion_length = 0; + break; + case CTL('F'): + set_position(line.position + 1); + complete_context = completion_length = 0; + break; + case CTL('K'): + printf(CSI("K")); + if (!redraw_prompt(false)) { + line.length = line.position; + line.content[line.length] = 0; + } + complete_context = completion_length = 0; + break; + case CTL('R'): + complete_context = completion_length = 0; + line.reverse_search = true; + set_line(""); + + break; + case CTL('T'): + if (line.length < 2) { + printf("\a"); + break; + } + if (line.position && line.position == line.length) { + line.position--; + } + if (line.position == 0) { + printf("\a"); + break; + } + SWAP(line.content + line.position, + line.content + line.position - 1); + line.position++; + redraw_prompt(true); + complete_context = completion_length = 0; + break; + case CTL('W'): + delete_word(false); + complete_context = completion_length = 0; + break; +#ifndef _WIN32 + case CTL('Z'): + set_line(""); + complete_context = completion_length = 0; + raise(SIGSTOP); + initialize(); // Reinitialize + break; +#endif + case '\r': + case '\n': + pthread_mutex_lock(&lines_lock); + if (line.length == 0 && repeat_empty && history.last) { + add_entry(&lines, history.last->content); + } + else { + add_entry(&lines, line.content); + } + pthread_cond_signal(&lines_cond); + pthread_mutex_unlock(&lines_lock); + if (line.length) { + listent_t *dup = reverse_find(history.last, line.content, true); + if (dup) { + remove_entry(&history, dup); + } + add_entry(&history, line.content); + set_line(""); + history_line = NULL; + } + complete_context = completion_length = 0; + break; + case CTL('H'): + case 0x7F: // Backspace + delete(1, false); + complete_context = completion_length = 0; + break; + case 0x1B: + switch (get_extended_key()) { + case MOD_SPECIAL(1): // Home + case MOD_SPECIAL(7): + case 'H': + set_position(0); + complete_context = completion_length = 0; + break; + case MOD_SPECIAL(8): // End + case 'F': + set_position(line.length); + complete_context = completion_length = 0; + break; + case MOD_SPECIAL(3): // Delete + delete(1, true); + complete_context = completion_length = 0; + break; + case 'A': // Up + if (!history_line) { + history_line = history.last; + } + else { + history_line = history_line->prev; + } + if (history_line) { + set_line(history_line->content); + complete_context = completion_length = 0; + } + else { + history_line = history.first; + printf("\a"); + } + + break; + case 'B': // Down + if (!history_line) { + printf("\a"); + break; + } + history_line = history_line->next; + if (history_line) { + set_line(history_line->content); + complete_context = completion_length = 0; + } + else { + set_line(""); + complete_context = completion_length = 0; + } + break; + case 'C': // Right + set_position(line.position + 1); + complete_context = completion_length = 0; + break; + case 'D': // Left + set_position(line.position - 1); + complete_context = completion_length = 0; + break; + case MOD_ALT('b'): + case MOD_ALT('D'): + move_word(false); + complete_context = completion_length = 0; + break; + case MOD_ALT('f'): + case MOD_ALT('C'): + move_word(true); + complete_context = completion_length = 0; + break; + case MOD_ALT(0x7F): // ALT+Backspace + delete_word(false); + complete_context = completion_length = 0; + break; + case MOD_ALT('('): // ALT+Delete + delete_word(true); + complete_context = completion_length = 0; + break; + default: + printf("\a"); + break; + } + break; + case '\t': { + char temp = line.content[line.position - completion_length]; + line.content[line.position - completion_length] = 0; + char *completion = completer? completer(line.content, &complete_context) : NULL; + line.content[line.position - completion_length] = temp; + if (completion) { + if (completion_length) { + delete(completion_length, false); + } + insert(completion); + completion_length = strlen(completion); + free(completion); + } + else { + printf("\a"); + } + break; + } + default: + if (c >= ' ') { + char string[2] = {c, 0}; + insert(string); + complete_context = completion_length = 0; + } + else { + printf("\a"); + } + break; + } + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + } + return 0; +} + +char *CON_readline(const char *new_prompt) +{ + pthread_mutex_lock(&terminal_lock); + const char *old_prompt = prompt; + prompt = new_prompt; + prompt_length = strlen(prompt); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + + pthread_mutex_lock(&lines_lock); + while (!lines.first) { + pthread_cond_wait(&lines_cond, &lines_lock); + } + char *ret = strdup(lines.first->content); + remove_entry(&lines, lines.first); + pthread_mutex_unlock(&lines_lock); + + pthread_mutex_lock(&terminal_lock); + prompt = old_prompt; + prompt_length = strlen(prompt); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + return ret; +} + +char *CON_readline_async(void) +{ + char *ret = NULL; + pthread_mutex_lock(&lines_lock); + if (lines.first) { + ret = strdup(lines.first->content); + remove_entry(&lines, lines.first); + } + pthread_mutex_unlock(&lines_lock); + return ret; +} + +bool CON_start(char *(*completer)(const char *substring, uintptr_t *context)) +{ + if (!initialize()) { + return false; + } + set_line(""); + pthread_t thread; + return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0; +} + +void CON_attributed_print(const char *string, CON_attributes_t *attributes) +{ + if (!initialized) { + printf("%s", string); + return; + } + static bool pending_newline = false; + pthread_mutex_lock(&terminal_lock); + printf(ESC("8")); + bool needs_reset = false; + if (attributes) { + if (attributes->color) { + if (attributes->color >= 0x10) { + printf(SGR("%d"), attributes->color - 0x11 + 90); + } + else { + printf(SGR("%d"), attributes->color - 1 + 30); + } + needs_reset = true; + } + if (attributes->background) { + if (attributes->background >= 0x10) { + printf(SGR("%d"), attributes->background - 0x11 + 100); + } + else { + printf(SGR("%d"), attributes->background - 1 + 40); + } + needs_reset = true; + } + if (attributes->bold) { + printf(SGR("1")); + needs_reset = true; + } + if (attributes->italic) { + printf(SGR("3")); + needs_reset = true; + } + if (attributes->underline) { + printf(SGR("4")); + needs_reset = true; + } + } + const char *it = string; + bool need_redraw_prompt = false; + while (*it) { + if (pending_newline) { + need_redraw_prompt = true; + printf("\n" CSI("K") "\n" CSI("A")); + pending_newline = false; + continue; + } + if (*it == '\n') { + printf("%.*s", (int)(it - string), string); + string = it + 1; + pending_newline = true; + } + it++; + } + if (*string) { + printf("%s", string); + } + if (needs_reset) { + printf(SGR("0")); + } + printf(ESC("7") CSI("B")); + if (need_redraw_prompt) { + redraw_prompt(true); + } + else { + set_position(line.position); + } + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); +} + +void CON_print(const char *string) +{ + CON_attributed_print(string, NULL); +} + +void CON_vprintf(const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + CON_attributed_print(string, NULL); + free(string); +} + +void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + CON_attributed_print(string, attributes); + free(string); +} + +void CON_printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + CON_vprintf(fmt, args); + va_end(args); +} + + +void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) +{ + va_list args; + va_start(args, attributes); + CON_attributed_vprintf(fmt, attributes, args); + va_end(args); +} + +void CON_set_async_prompt(const char *string) +{ + pthread_mutex_lock(&terminal_lock); + prompt = string; + prompt_length = strlen(string); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); +} + +void CON_set_repeat_empty(bool repeat) +{ + repeat_empty = repeat; +} diff --git a/thirdparty/SameBoy-old/SDL/console.h b/thirdparty/SameBoy-old/SDL/console.h new file mode 100644 index 000000000..d15898888 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/console.h @@ -0,0 +1,49 @@ +#include +#include +#include + +#define CON_EOF "\x04" +bool CON_start(char *(*completer)(const char *substring, uintptr_t *context)); +char *CON_readline(const char *prompt); +char *CON_readline_async(void); + +typedef struct { + enum { + CON_COLOR_NONE = 0, + CON_COLOR_BLACK, + CON_COLOR_RED, + CON_COLOR_GREEN, + CON_COLOR_YELLOW, + CON_COLOR_BLUE, + CON_COLOR_MAGENTA, + CON_COLOR_CYAN, + CON_COLOR_LIGHT_GREY, + + CON_COLOR_DARK_GREY = CON_COLOR_BLACK + 0x10, + CON_COLOR_BRIGHT_RED = CON_COLOR_RED + 0x10, + CON_COLOR_BRIGHT_GREEN = CON_COLOR_GREEN + 0x10, + CON_COLOR_BRIGHT_YELLOW = CON_COLOR_YELLOW + 0x10, + CON_COLOR_BRIGHT_BLUE = CON_COLOR_BLUE + 0x10, + CON_COLOR_BRIGHT_MAGENTA = CON_COLOR_MAGENTA + 0x10, + CON_COLOR_BRIGHT_CYAN = CON_COLOR_CYAN + 0x10, + CON_COLOR_WHITE = CON_COLOR_LIGHT_GREY + 0x10, + } color:8, background:8; + bool bold; + bool italic; + bool underline; +} CON_attributes_t; + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void CON_print(const char *string); +void CON_attributed_print(const char *string, CON_attributes_t *attributes); +void CON_vprintf(const char *fmt, va_list args); +void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args); +void CON_printf(const char *fmt, ...) __printflike(1, 2); +void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) __printflike(1, 3); +void CON_set_async_prompt(const char *string); +void CON_set_repeat_empty(bool repeat); diff --git a/thirdparty/SameBoy-old/SDL/font.c b/thirdparty/SameBoy-old/SDL/font.c new file mode 100644 index 000000000..ea2c590cb --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/font.c @@ -0,0 +1,1124 @@ +#include "font.h" + +#define _ 0 +#define X 1 + +uint8_t font[] = { + /* */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* ! */ + _, _, 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, _, _, _, 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, X, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* . */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* / */ + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + + /* 0 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 1 */ + _, _, X, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* 2 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, X, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* 3 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 4 */ + _, _, _, X, _, _, + _, _, X, X, _, _, + _, X, _, X, _, _, + X, _, _, X, _, _, + X, X, X, X, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, + + /* 5 */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 6 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 7 */ + X, X, X, X, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* 8 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 9 */ + _, 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, _, _, _, + _, _, _, _, _, _, + + /* @ */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, + + /* A */ + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* B */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* C */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* D */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* E */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* F */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, + + /* G */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* H */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* I */ + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* J */ + _, _, X, X, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* K */ + X, _, _, _, X, _, + X, _, _, X, _, _, + X, _, X, _, _, _, + X, X, _, _, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* L */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* M */ + X, _, _, _, X, _, + X, X, _, X, X, _, + X, X, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* N */ + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* O */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* P */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, + + /* Q */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + _, _, _, _, X, X, + + /* R */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* S */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* T */ + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* U */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* V */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* W */ + 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, _, + _, _, _, _, _, _, + + /* Y */ + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* Z */ + 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, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* a */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, _, _, _, + _, _, _, X, _, _, + _, X, X, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + _, _, _, _, _, _, + + /* b */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* c */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* d */ + _, _, _, _, X, _, + _, _, _, _, X, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* e */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* f */ + _, _, X, X, _, _, + _, X, _, _, _, _, + X, X, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + /* g */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, X, _, _, + _, X, X, _, _, _, + _, _, _, X, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + + /* h */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* i */ + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* j */ + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, _, _, _, _, + + /* k */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, X, _, _, + X, X, _, _, _, _, + X, _, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* l */ + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, X, _, + _, _, _, _, _, _, + + /* m */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, _, _, _, _, _, + + /* n */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* o */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* p */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, X, _, _, + X, _, _, _, _, _, + + /* q */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, + + /* r */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, _, X, _, _, + _, X, X, _, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + /* s */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* t */ + _, _, _, _, _, _, + _, X, _, _, _, _, + X, X, X, X, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, X, X, _, + _, _, _, _, _, _, + + /* u */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, _, _, + + /* v */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* w */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, + + /* x */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* y */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, + _, X, X, X, _, _, + + /* z */ + _, _, _, _, _, _, + _, _, _, _, _, _, + 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, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* Custom characters */ + /* Selection */ + _, X, _, _, _, _, + _, X, X, _, _, _, + _, X, X, X, _, _, + _, X, X, X, X, _, + _, X, X, X, _, _, + _, X, X, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + + /* CTRL symbol */ + _, 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, + _, _, _, _, _, _, + + /* Shift symbol */ + _, _, 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, _, + _, _, _, _, _, _, + + /* Cmd symbol */ + + _, _, _, _, _, 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, _, + + /* Left Arrow */ + _, _, _, _, X, _, + _, _, _, X, X, _, + _, _, X, X, X, _, + _, X, X, X, X, _, + _, _, X, X, X, _, + _, _, _, 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, _, + + /* 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/thirdparty/SameBoy-old/SDL/font.h b/thirdparty/SameBoy-old/SDL/font.h new file mode 100644 index 000000000..e7f8e2a0c --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/font.h @@ -0,0 +1,20 @@ +#ifndef font_h +#define font_h + +#include +extern uint8_t font[]; +extern const uint8_t font_max; +#define GLYPH_HEIGHT 8 +#define GLYPH_WIDTH 6 +#define LEFT_ARROW_STRING "\x86" +#define RIGHT_ARROW_STRING "\x7F" +#define SELECTION_STRING RIGHT_ARROW_STRING +#define CTRL_STRING "\x80\x81\x82" +#define SHIFT_STRING "\x83" +#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/thirdparty/SameBoy-old/SDL/gui.c b/thirdparty/SameBoy-old/SDL/gui.c new file mode 100644 index 000000000..befea12ca --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/gui.c @@ -0,0 +1,2152 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "gui.h" +#include "font.h" +#include "audio/audio.h" + +static const SDL_Color gui_palette[4] = {{8, 24, 16,}, {57, 97, 57,}, {132, 165, 99}, {198, 222, 140}}; +static uint32_t gui_palette_native[4]; + +SDL_Window *window = NULL; +SDL_Renderer *renderer = NULL; +SDL_Texture *texture = NULL; +SDL_PixelFormat *pixel_format = NULL; +enum pending_command pending_command; +unsigned command_parameter; +char *dropped_state_file = NULL; + +static char **custom_palettes; +static unsigned n_custom_palettes; + + +#ifdef __APPLE__ +#define MODIFIER_NAME " " CMD_STRING +#else +#define MODIFIER_NAME CTRL_STRING +#endif + +shader_t shader; +static SDL_Rect rect; +static unsigned factor; + +static SDL_Surface *converted_background = NULL; + +void render_texture(void *pixels, void *previous) +{ + if (renderer) { + if (pixels) { + SDL_UpdateTexture(texture, NULL, pixels, GB_get_screen_width(&gb) * sizeof (uint32_t)); + } + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + else { + static void *_pixels = NULL; + if (pixels) { + _pixels = pixels; + } + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + GB_frame_blending_mode_t mode = configuration.blending_mode; + if (!previous) { + mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (GB_is_sgb(&gb)) { + mode = GB_FRAME_BLENDING_MODE_SIMPLE; + } + else { + mode = GB_is_odd_frame(&gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + } + render_bitmap_with_shader(&shader, _pixels, previous, + GB_get_screen_width(&gb), GB_get_screen_height(&gb), + rect.x, rect.y, rect.w, rect.h, + mode); + SDL_GL_SwapWindow(window); + } +} + +static const char *help[] = { +"Drop a ROM to play.\n" +"\n" +"Keyboard Shortcuts:\n" +" Open Menu: Escape\n" +" Open ROM: " MODIFIER_NAME "+O\n" +" Reset: " MODIFIER_NAME "+R\n" +" Pause: " MODIFIER_NAME "+P\n" +" Save state: " MODIFIER_NAME "+(0-9)\n" +" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" +" Toggle Fullscreen " MODIFIER_NAME "+F\n" +#ifdef __APPLE__ +" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n" +#else +" Mute/Unmute: " MODIFIER_NAME "+M\n" +#endif +" Break Debugger: " CTRL_STRING "+C" +}; + +void update_viewport(void) +{ + int win_width, win_height; + SDL_GL_GetDrawableSize(window, &win_width, &win_height); + int logical_width, logical_height; + SDL_GetWindowSize(window, &logical_width, &logical_height); + factor = win_width / logical_width; + + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); + + if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { + x_factor = (unsigned)(x_factor); + y_factor = (unsigned)(y_factor); + } + + if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { + if (x_factor > y_factor) { + x_factor = y_factor; + } + else { + y_factor = x_factor; + } + } + + unsigned new_width = x_factor * GB_get_screen_width(&gb); + unsigned new_height = y_factor * GB_get_screen_height(&gb); + + rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, + new_width, new_height}; + + if (renderer) { + SDL_RenderSetViewport(renderer, &rect); + } + else { + glViewport(rect.x, rect.y, rect.w, rect.h); + } +} + +static void rescale_window(void) +{ + SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale); +} + +static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color, uint32_t *mask_top, uint32_t *mask_bottom) +{ + if (ch < ' ' || ch > font_max) { + ch = '?'; + } + + uint8_t *data = &font[(ch - ' ') * GLYPH_WIDTH * GLYPH_HEIGHT]; + + for (unsigned y = GLYPH_HEIGHT; y--;) { + for (unsigned x = GLYPH_WIDTH; x--;) { + if (*(data++) && buffer >= mask_top && buffer < mask_bottom) { + (*buffer) = color; + } + buffer++; + } + buffer += width - GLYPH_WIDTH; + } +} + +static signed scroll = 0; +static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, bool is_osd) +{ + if (!is_osd) { + y -= scroll; + } + unsigned orig_x = x; + unsigned y_offset = is_osd? 0 : (GB_get_screen_height(&gb) - 144) / 2; + while (*string) { + if (*string == '\n') { + x = orig_x; + y += GLYPH_HEIGHT + 4; + string++; + continue; + } + + if (x > width - GLYPH_WIDTH) { + break; + } + + draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (is_osd? GB_get_screen_height(&gb) : y_offset + 144)]); + x += GLYPH_WIDTH; + string++; + } +} + +void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border, bool is_osd) +{ + draw_unbordered_text(buffer, width, height, x - 1, y, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x + 1, y, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y - 1, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y + 1, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y, string, color, is_osd); +} + +const char *osd_text = NULL; +unsigned osd_countdown = 0; +unsigned osd_text_lines = 1; + +void show_osd_text(const char *text) +{ + osd_text_lines = 1; + osd_text = text; + osd_countdown = 30; + while (*text++) { + if (*text == '\n') { + osd_text_lines++; + osd_countdown += 30; + } + } +} + + +enum decoration { + DECORATION_NONE, + DECORATION_SELECTION, + DECORATION_ARROWS, +}; + +static void draw_text_centered(uint32_t *buffer, unsigned width, unsigned height, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration) +{ + unsigned x = width / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; + draw_text(buffer, width, height, x, y, string, color, border, false); + switch (decoration) { + case DECORATION_SELECTION: + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border, false); + break; + case DECORATION_ARROWS: + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border, false); + draw_text(buffer, width, height, width - x, y, RIGHT_ARROW_STRING, color, border, false); + break; + + case DECORATION_NONE: + break; + } +} + +struct menu_item { + const char *string; + void (*handler)(unsigned); + const char *(*value_getter)(unsigned); + void (*backwards_handler)(unsigned); +}; +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 { + SHOWING_DROP_MESSAGE, + SHOWING_MENU, + SHOWING_HELP, + WAITING_FOR_KEY, + WAITING_FOR_JBUTTON, +} gui_state; + +static unsigned joypad_configuration_progress = 0; +static uint8_t joypad_axis_temp; + +static void item_exit(unsigned index) +{ + pending_command = GB_SDL_QUIT_COMMAND; +} + +static unsigned current_help_page = 0; +static void item_help(unsigned index) +{ + current_help_page = 0; + gui_state = SHOWING_HELP; +} + +static void enter_emulation_menu(unsigned index); +static void enter_graphics_menu(unsigned index); +static void enter_keyboard_menu(unsigned index); +static void enter_joypad_menu(unsigned index); +static void enter_audio_menu(unsigned index); +static void enter_controls_menu(unsigned index); +static void toggle_audio_recording(unsigned index); + +extern void set_filename(const char *new_filename, typeof(free) *new_free_function); +static void open_rom(unsigned index) +{ + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } +} + +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 char audio_recording_menu_item[] = "Start Audio Recording"; + +static const struct menu_item paused_menu[] = { + {"Resume", NULL}, + {"Open ROM", open_rom}, + {"Emulation Options", enter_emulation_menu}, + {"Graphic Options", enter_graphics_menu}, + {"Audio Options", enter_audio_menu}, + {"Control Options", enter_controls_menu}, + {audio_recording_menu_item, toggle_audio_recording}, + {"Help", item_help}, + {"Quit SameBoy", item_exit}, + {NULL,} +}; + +static const struct menu_item *const nonpaused_menu = &paused_menu[1]; + +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) +{ + + configuration.model++; + if (configuration.model == MODEL_MAX) { + configuration.model = 0; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_model_backwards(unsigned index) +{ + if (configuration.model == 0) { + configuration.model = MODEL_MAX; + } + configuration.model--; + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_model_string(unsigned index) +{ + return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy", "Game Boy Pocket"} + [configuration.model]; +} + +static void cycle_cgb_revision(unsigned index) +{ + + if (configuration.cgb_revision == GB_MODEL_CGB_E - GB_MODEL_CGB_0) { + configuration.cgb_revision = 0; + } + else { + configuration.cgb_revision++; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_cgb_revision_backwards(unsigned index) +{ + if (configuration.cgb_revision == 0) { + configuration.cgb_revision = GB_MODEL_CGB_E - GB_MODEL_CGB_0; + } + else { + configuration.cgb_revision--; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_cgb_revision_string(unsigned index) +{ + return (const char *[]){ + "CPU CGB 0 (Exp.)", + "CPU CGB A (Exp.)", + "CPU CGB B (Exp.)", + "CPU CGB C (Exp.)", + "CPU CGB D", + "CPU CGB E", + } + [configuration.cgb_revision]; +} + +static void cycle_sgb_revision(unsigned index) +{ + + configuration.sgb_revision++; + if (configuration.sgb_revision == SGB_MAX) { + configuration.sgb_revision = 0; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_sgb_revision_backwards(unsigned index) +{ + if (configuration.sgb_revision == 0) { + configuration.sgb_revision = SGB_MAX; + } + configuration.sgb_revision--; + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_sgb_revision_string(unsigned index) +{ + return (const char *[]){"Super Game Boy NTSC", "Super Game Boy PAL", "Super Game Boy 2"} + [configuration.sgb_revision]; +} + +static const uint32_t rewind_lengths[] = {0, 10, 30, 60, 60 * 2, 60 * 5, 60 * 10}; +static const char *rewind_strings[] = {"Disabled", + "10 Seconds", + "30 Seconds", + "1 Minute", + "2 Minutes", + "5 Minutes", + "10 Minutes", +}; + +static void cycle_rewind(unsigned index) +{ + for (unsigned i = 0; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]) - 1; i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + configuration.rewind_length = rewind_lengths[i + 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); + return; + } + } + configuration.rewind_length = rewind_lengths[0]; + GB_set_rewind_length(&gb, configuration.rewind_length); +} + +static void cycle_rewind_backwards(unsigned index) +{ + for (unsigned i = 1; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]); i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + configuration.rewind_length = rewind_lengths[i - 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); + return; + } + } + configuration.rewind_length = rewind_lengths[sizeof(rewind_lengths) / sizeof(rewind_lengths[0]) - 1]; + GB_set_rewind_length(&gb, configuration.rewind_length); +} + +static const char *current_rewind_string(unsigned index) +{ + for (unsigned i = 0; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]); i++) { + if (configuration.rewind_length == rewind_lengths[i]) { + return rewind_strings[i]; + } + } + return "Custom"; +} + +static 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 void toggle_rtc_mode(unsigned index) +{ + configuration.rtc_mode = !configuration.rtc_mode; +} + +static 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 void cycle_agb_revision(unsigned index) +{ + + configuration.agb_revision ^= GB_MODEL_GBP_BIT; + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_agb_revision_string(unsigned index) +{ + if (configuration.agb_revision == GB_MODEL_GBP_A) { + return "CPU AGB A (GBP)"; + } + return "CPU AGB A (AGB)"; +} + +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}, + {"GBC Revision:", cycle_cgb_revision, current_cgb_revision_string, cycle_cgb_revision_backwards}, + {"GBA Revision:", cycle_agb_revision, current_agb_revision_string, cycle_agb_revision}, + {"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,} +}; + +static void enter_emulation_menu(unsigned index) +{ + current_menu = emulation_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static const char *current_scaling_mode(unsigned index) +{ + return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"} + [configuration.scaling_mode]; +} + +static const char *current_default_scale(unsigned index) +{ + return (const char *[]){"1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x"} + [configuration.default_scale - 1]; +} + +const char *current_color_correction_mode(unsigned index) +{ + return (const char *[]){"Disabled", "Correct Color Curves", "Modern - Balanced", "Modern - Boost Contrast", "Reduce Contrast", "Harsh Reality", "Modern - Accurate"} + [configuration.color_correction_mode]; +} + +const char *current_color_temperature(unsigned index) +{ + static char ret[22]; + strcpy(ret, SLIDER_STRING); + ret[configuration.color_temperature] = SELECTED_SLIDER_STRING[configuration.color_temperature]; + return ret; +} + + +const char *current_palette(unsigned index) +{ + if (configuration.dmg_palette == 4) { + return configuration.dmg_palette_name; + } + return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"} + [configuration.dmg_palette]; +} + +const char *current_border_mode(unsigned index) +{ + return (const char *[]){"SGB Only", "Never", "Always"} + [configuration.border_mode]; +} + +static void cycle_scaling(unsigned index) +{ + configuration.scaling_mode++; + if (configuration.scaling_mode == GB_SDL_SCALING_MAX) { + configuration.scaling_mode = 0; + } + update_viewport(); + render_texture(NULL, NULL); +} + +static void cycle_scaling_backwards(unsigned index) +{ + if (configuration.scaling_mode == 0) { + configuration.scaling_mode = GB_SDL_SCALING_MAX - 1; + } + else { + configuration.scaling_mode--; + } + update_viewport(); + render_texture(NULL, NULL); +} + +static void cycle_default_scale(unsigned index) +{ + if (configuration.default_scale == GB_SDL_DEFAULT_SCALE_MAX) { + configuration.default_scale = 1; + } + else { + configuration.default_scale++; + } + + rescale_window(); + update_viewport(); +} + +static void cycle_default_scale_backwards(unsigned index) +{ + if (configuration.default_scale == 1) { + configuration.default_scale = GB_SDL_DEFAULT_SCALE_MAX; + } + else { + configuration.default_scale--; + } + + rescale_window(); + update_viewport(); +} + +static void cycle_color_correction(unsigned index) +{ + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_BALANCED) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_ACCURATE; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_ACCURATE) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST; + } + else { + configuration.color_correction_mode++; + } +} + +static void cycle_color_correction_backwards(unsigned index) +{ + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_LOW_CONTRAST; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_ACCURATE) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_BALANCED; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_ACCURATE; + } + else { + configuration.color_correction_mode--; + } +} + +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--; + } +} + +const GB_palette_t *current_dmg_palette(void) +{ + typedef struct __attribute__ ((packed)) { + uint32_t magic; + uint8_t flags; + struct GB_color_s colors[5]; + int32_t brightness_bias; + uint32_t hue_bias; + uint32_t hue_bias_strength; + } theme_t; + + static theme_t theme; + + if (configuration.dmg_palette == 4) { + char *path = resource_path("Palettes"); + sprintf(path + strlen(path), "/%s.sbp", configuration.dmg_palette_name); + FILE *file = fopen(path, "rb"); + if (!file) return &GB_PALETTE_GREY; + memset(&theme, 0, sizeof(theme)); + fread(&theme, sizeof(theme), 1, file); + fclose(file); +#ifdef GB_BIG_ENDIAN + theme.magic = __builtin_bswap32(theme.magic); +#endif + if (theme.magic != 'SBPL') return &GB_PALETTE_GREY; + return (GB_palette_t *)&theme.colors; + } + + switch (configuration.dmg_palette) { + case 1: return &GB_PALETTE_DMG; + case 2: return &GB_PALETTE_MGB; + case 3: return &GB_PALETTE_GBL; + default: return &GB_PALETTE_GREY; + } +} + +static void update_gui_palette(void) +{ + const GB_palette_t *palette = current_dmg_palette(); + + SDL_Color colors[4]; + for (unsigned i = 4; i--; ) { + gui_palette_native[i] = SDL_MapRGB(pixel_format, palette->colors[i].r, palette->colors[i].g, palette->colors[i].b); + colors[i].r = palette->colors[i].r; + colors[i].g = palette->colors[i].g; + colors[i].b = palette->colors[i].b; + } + + SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); + + /* Create a blank background if background.bmp could not be loaded */ + if (!background) { + background = SDL_CreateRGBSurface(0, 160, 144, 8, 0, 0, 0, 0); + } + SDL_SetPaletteColors(background->format->palette, colors, 0, 4); + converted_background = SDL_ConvertSurface(background, pixel_format, 0); + SDL_FreeSurface(background); +} + +static void cycle_palette(unsigned index) +{ + if (configuration.dmg_palette == 3) { + if (n_custom_palettes == 0) { + configuration.dmg_palette = 0; + } + else { + configuration.dmg_palette = 4; + strcpy(configuration.dmg_palette_name, custom_palettes[0]); + } + } + else if (configuration.dmg_palette == 4) { + for (unsigned i = 0; i < n_custom_palettes; i++) { + if (strcmp(custom_palettes[i], configuration.dmg_palette_name) == 0) { + if (i == n_custom_palettes - 1) { + configuration.dmg_palette = 0; + } + else { + strcpy(configuration.dmg_palette_name, custom_palettes[i + 1]); + } + break; + } + } + } + else { + configuration.dmg_palette++; + } + configuration.gui_pallete_enabled = true; + update_gui_palette(); +} + +static void cycle_palette_backwards(unsigned index) +{ + if (configuration.dmg_palette == 0) { + if (n_custom_palettes == 0) { + configuration.dmg_palette = 3; + } + else { + configuration.dmg_palette = 4; + strcpy(configuration.dmg_palette_name, custom_palettes[n_custom_palettes - 1]); + } + } + else if (configuration.dmg_palette == 4) { + for (unsigned i = 0; i < n_custom_palettes; i++) { + if (strcmp(custom_palettes[i], configuration.dmg_palette_name) == 0) { + if (i == 0) { + configuration.dmg_palette = 3; + } + else { + strcpy(configuration.dmg_palette_name, custom_palettes[i - 1]); + } + break; + } + } + } + else { + configuration.dmg_palette--; + } + configuration.gui_pallete_enabled = true; + update_gui_palette(); +} + +static void cycle_border_mode(unsigned index) +{ + if (configuration.border_mode == GB_BORDER_ALWAYS) { + configuration.border_mode = GB_BORDER_SGB; + } + else { + configuration.border_mode++; + } +} + +static void cycle_border_mode_backwards(unsigned index) +{ + if (configuration.border_mode == GB_BORDER_SGB) { + configuration.border_mode = GB_BORDER_ALWAYS; + } + else { + configuration.border_mode--; + } +} + +extern bool uses_gl(void); +struct shader_name { + const char *file_name; + const char *display_name; +} shaders[] = +{ + {"NearestNeighbor", "Nearest Neighbor"}, + {"Bilinear", "Bilinear"}, + {"SmoothBilinear", "Smooth Bilinear"}, + {"MonoLCD", "Monochrome LCD"}, + {"LCD", "LCD Display"}, + {"CRT", "CRT Display"}, + {"Scale2x", "Scale2x"}, + {"Scale4x", "Scale4x"}, + {"AAScale2x", "Anti-aliased Scale2x"}, + {"AAScale4x", "Anti-aliased Scale4x"}, + {"HQ2x", "HQ2x"}, + {"OmniScale", "OmniScale"}, + {"OmniScaleLegacy", "OmniScale Legacy"}, + {"AAOmniScaleLegacy", "AA OmniScale Legacy"}, +}; + +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) { + break; + } + } + + + i += 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i -= sizeof(shaders) / sizeof(shaders[0]); + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } +} + +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) { + break; + } + } + + i -= 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i = sizeof(shaders) / sizeof(shaders[0]) - 1; + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + +} +static 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) { + break; + } + } + + if (i == sizeof(shaders) / sizeof(shaders[0])) { + i = 0; + } + + return shaders[i].display_name; +} + +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; + } + else { + configuration.blending_mode++; + } +} + +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; + } + else { + configuration.blending_mode--; + } +} + +static const char *blending_mode_string(unsigned index) +{ + if (!uses_gl()) return "Requires OpenGL 3.2+"; + return (const char *[]){"Disabled", "Simple", "Accurate"} + [configuration.blending_mode]; +} + +static void toggle_osd(unsigned index) +{ + osd_countdown = 0; + configuration.osd = !configuration.osd; +} + +static const char *current_osd_mode(unsigned index) +{ + return configuration.osd? "Enabled" : "Disabled"; +} + +static const struct menu_item graphics_menu[] = { + {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, + {"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards}, + {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, + {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, + {"Ambient Light Temp.:", decrease_color_temperature, current_color_temperature, increase_color_temperature}, + {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, + {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, + {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, + {"On-Screen Display:", toggle_osd, current_osd_mode, toggle_osd}, + + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_graphics_menu(unsigned index) +{ + current_menu = graphics_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static const char *highpass_filter_string(unsigned index) +{ + return (const char *[]){"None (Keep DC Offset)", "Accurate", "Preserve Waveform"} + [configuration.highpass_mode]; +} + +static void cycle_highpass_filter(unsigned index) +{ + configuration.highpass_mode++; + if (configuration.highpass_mode == GB_HIGHPASS_MAX) { + configuration.highpass_mode = 0; + } +} + +static void cycle_highpass_filter_backwards(unsigned index) +{ + if (configuration.highpass_mode == 0) { + configuration.highpass_mode = GB_HIGHPASS_MAX - 1; + } + else { + configuration.highpass_mode--; + } +} + +static const char *volume_string(unsigned index) +{ + static char ret[5]; + sprintf(ret, "%d%%", configuration.volume); + return ret; +} + +static void increase_volume(unsigned index) +{ + configuration.volume += 5; + if (configuration.volume > 100) { + configuration.volume = 100; + } +} + +static void decrease_volume(unsigned index) +{ + configuration.volume -= 5; + if (configuration.volume > 100) { + configuration.volume = 0; + } +} + +static const char *interference_volume_string(unsigned index) +{ + static char ret[5]; + sprintf(ret, "%d%%", configuration.interference_volume); + return ret; +} + +static void increase_interference_volume(unsigned index) +{ + configuration.interference_volume += 5; + if (configuration.interference_volume > 100) { + configuration.interference_volume = 100; + } +} + +static void decrease_interference_volume(unsigned index) +{ + configuration.interference_volume -= 5; + if (configuration.interference_volume > 100) { + configuration.interference_volume = 0; + } +} + +static const char *audio_driver_string(unsigned index) +{ + return GB_audio_driver_name(); +} + +static const char *preferred_audio_driver_string(unsigned index) +{ + if (configuration.audio_driver[0] == 0) { + return "Auto"; + } + return configuration.audio_driver; +} + +static void audio_driver_changed(void); + +static void cycle_prefrered_audio_driver(unsigned index) +{ + audio_driver_changed(); + if (configuration.audio_driver[0] == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(0)); + return; + } + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { // Not a supported driver? Switch to auto + configuration.audio_driver[0] = 0; + return; + } + if (strcmp(configuration.audio_driver, name) == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i + 1)); + return; + } + i++; + } +} + +static void cycle_preferred_audio_driver_backwards(unsigned index) +{ + audio_driver_changed(); + if (configuration.audio_driver[0] == 0) { + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i - 1)); + return; + } + i++; + } + return; + } + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { // Not a supported driver? Switch to auto + configuration.audio_driver[0] = 0; + return; + } + if (strcmp(configuration.audio_driver, name) == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i - 1)); + return; + } + i++; + } +} + +static void nop(unsigned index){} + +static 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}, + {"Preferred Audio Driver:", cycle_prefrered_audio_driver, preferred_audio_driver_string, cycle_preferred_audio_driver_backwards}, + {"Active Driver:", nop, audio_driver_string}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void audio_driver_changed(void) +{ + audio_menu[4].value_getter = NULL; + audio_menu[4].string = "Relaunch to apply"; +} + +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) +{ + gui_state = WAITING_FOR_KEY; +} + +static const char *key_name(unsigned index); + +static const struct menu_item keyboard_menu[] = { + {"Right:", modify_key, key_name,}, + {"Left:", modify_key, key_name,}, + {"Up:", modify_key, key_name,}, + {"Down:", modify_key, key_name,}, + {"A:", modify_key, key_name,}, + {"B:", modify_key, key_name,}, + {"Select:", modify_key, key_name,}, + {"Start:", modify_key, key_name,}, + {"Turbo:", modify_key, key_name,}, + {"Rewind:", modify_key, key_name,}, + {"Slow-Motion:", modify_key, key_name,}, + {"Back", enter_controls_menu}, + {NULL,} +}; + +static const char *key_name(unsigned index) +{ + if (index > 8) { + return SDL_GetScancodeName(configuration.keys_2[index - 9]); + } + return SDL_GetScancodeName(configuration.keys[index]); +} + +static void enter_keyboard_menu(unsigned index) +{ + current_menu = keyboard_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static unsigned joypad_index = 0; +static SDL_Joystick *joystick = NULL; +static SDL_GameController *controller = NULL; +SDL_Haptic *haptic = NULL; + +static const char *current_joypad_name(unsigned index) +{ + static char name[23] = {0,}; + const char *orig_name = joystick? SDL_JoystickName(joystick) : NULL; + if (!orig_name) return "Not Found"; + unsigned i = 0; + + // 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 > 0? *orig_name : MOJIBAKE_STRING[0]; + } + orig_name++; + } + if (i && name[i - 1] == ' ') { + i--; + } + name[i] = 0; + + return name; +} + +static void cycle_joypads(unsigned index) +{ + joypad_index++; + if (joypad_index >= SDL_NumJoysticks()) { + joypad_index = 0; + } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; + } + else if (joystick) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + if ((controller = SDL_GameControllerOpen(joypad_index))) { + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(joypad_index); + } + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} + +static void cycle_joypads_backwards(unsigned index) +{ + joypad_index--; + if (joypad_index >= SDL_NumJoysticks()) { + joypad_index = SDL_NumJoysticks() - 1; + } + + if (haptic) { + SDL_HapticClose(haptic); + haptic = NULL; + } + + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; + } + else if (joystick) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + if ((controller = SDL_GameControllerOpen(joypad_index))) { + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(joypad_index); + } + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + }} + +static void detect_joypad_layout(unsigned index) +{ + gui_state = WAITING_FOR_JBUTTON; + joypad_configuration_progress = 0; + joypad_axis_temp = -1; +} + +static void cycle_rumble_mode(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_ALL_GAMES) { + configuration.rumble_mode = GB_RUMBLE_DISABLED; + } + else { + configuration.rumble_mode++; + } +} + +static void cycle_rumble_mode_backwards(unsigned index) +{ + if (configuration.rumble_mode == GB_RUMBLE_DISABLED) { + configuration.rumble_mode = GB_RUMBLE_ALL_GAMES; + } + else { + configuration.rumble_mode--; + } +} + +static const char *current_rumble_mode(unsigned index) +{ + return (const char *[]){"Disabled", "Rumble Game Paks Only", "All Games"} + [configuration.rumble_mode]; +} + +static void toggle_allow_background_controllers(unsigned index) +{ + configuration.allow_background_controllers ^= true; + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + configuration.allow_background_controllers? "1" : "0"); +} + +static const char *current_background_control_mode(unsigned index) +{ + return configuration.allow_background_controllers? "Always" : "During Window Focus Only"; +} + +static void cycle_hotkey(unsigned index) +{ + if (configuration.hotkey_actions[index - 2] == HOTKEY_MAX) { + configuration.hotkey_actions[index - 2] = 0; + } + else { + configuration.hotkey_actions[index - 2]++; + } +} + +static void cycle_hotkey_backwards(unsigned index) +{ + if (configuration.hotkey_actions[index - 2] == 0) { + configuration.hotkey_actions[index - 2] = HOTKEY_MAX; + } + else { + configuration.hotkey_actions[index - 2]--; + } +} + +static const char *current_hotkey(unsigned index) +{ + return (const char *[]){ + "None", + "Toggle Pause", + "Toggle Mute", + "Reset", + "Quit SameBoy", + "Save State Slot 1", + "Load State Slot 1", + "Save State Slot 2", + "Load State Slot 2", + "Save State Slot 3", + "Load State Slot 3", + "Save State Slot 4", + "Load State Slot 4", + "Save State Slot 5", + "Load State Slot 5", + "Save State Slot 6", + "Load State Slot 6", + "Save State Slot 7", + "Load State Slot 7", + "Save State Slot 8", + "Load State Slot 8", + "Save State Slot 9", + "Load State Slot 9", + "Save State Slot 10", + "Load State Slot 10", + } + [configuration.hotkey_actions[index - 2]]; +} + +static const struct menu_item joypad_menu[] = { + {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, + {"Configure layout", detect_joypad_layout}, + {"Hotkey 1 Action:", cycle_hotkey, current_hotkey, cycle_hotkey_backwards}, + {"Hotkey 2 Action:", cycle_hotkey, current_hotkey, cycle_hotkey_backwards}, + {"Rumble Mode:", cycle_rumble_mode, current_rumble_mode, cycle_rumble_mode_backwards}, + {"Enable Control:", toggle_allow_background_controllers, current_background_control_mode, toggle_allow_background_controllers}, + {"Back", enter_controls_menu}, + {NULL,} +}; + +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) +{ + for (unsigned i = 0; i < JOYPAD_BUTTONS_MAX; i++) { + if (configuration.joypad_configuration[i] == physical_button) { + return i; + } + } + return JOYPAD_BUTTONS_MAX; +} + +joypad_axis_t get_joypad_axis(uint8_t physical_axis) +{ + for (unsigned i = 0; i < JOYPAD_AXISES_MAX; i++) { + if (configuration.joypad_axises[i] == physical_axis) { + return i; + } + } + return JOYPAD_AXISES_MAX; +} + + +void connect_joypad(void) +{ + if (joystick && !SDL_NumJoysticks()) { + if (controller) { + SDL_GameControllerClose(controller); + controller = NULL; + joystick = NULL; + } + else { + SDL_JoystickClose(joystick); + joystick = NULL; + } + } + else if (!joystick && SDL_NumJoysticks()) { + if ((controller = SDL_GameControllerOpen(0))) { + joystick = SDL_GameControllerGetJoystick(controller); + } + else { + joystick = SDL_JoystickOpen(0); + } + } + if (joystick) { + haptic = SDL_HapticOpenFromJoystick(joystick); + } +} + +static void toggle_mouse_control(unsigned index) +{ + configuration.allow_mouse_controls = !configuration.allow_mouse_controls; +} + +static const char *mouse_control_string(unsigned index) +{ + return configuration.allow_mouse_controls? "Allow mouse control" : "Disallow mouse control"; +} + +static const struct menu_item controls_menu[] = { + {"Keyboard Options", enter_keyboard_menu}, + {"Joypad Options", enter_joypad_menu}, + {"Motion-controlled games:", toggle_mouse_control, mouse_control_string, toggle_mouse_control}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_controls_menu(unsigned index) +{ + current_menu = controls_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static void toggle_audio_recording(unsigned index) +{ + if (!GB_is_inited(&gb)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Cannot start audio recording, open a ROM file first.", window); + return; + } + static bool is_recording = false; + if (is_recording) { + is_recording = false; + show_osd_text("Audio recording ended"); + int error = GB_stop_audio_recording(&gb); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + } + static const char item_string[] = "Start Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + return; + } + char *filename = do_save_recording_dialog(GB_get_sample_rate(&gb)); + + /* Drop events as it SDL seems to catch several in-dialog events */ + SDL_Event event; + while (SDL_PollEvent(&event)); + + if (filename) { + GB_audio_format_t format = GB_AUDIO_FORMAT_RAW; + size_t length = strlen(filename); + if (length >= 5) { + if (strcasecmp(".aiff", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".aifc", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (length >= 4) { + if (strcasecmp(".aif", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".wav", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_WAV; + } + } + } + + int error = GB_start_audio_recording(&gb, filename, format); + free(filename); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + return; + } + + is_recording = true; + static const char item_string[] = "Stop Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + show_osd_text("Audio recording started"); + } +} + +void convert_mouse_coordinates(signed *x, signed *y) +{ + signed width = GB_get_screen_width(&gb); + signed height = GB_get_screen_height(&gb); + signed x_offset = (width - 160) / 2; + signed y_offset = (height - 144) / 2; + + *x = (signed)(*x - rect.x / factor) * width / (signed)(rect.w / factor) - x_offset; + *y = (signed)(*y - rect.y / factor) * height / (signed)(rect.h / factor) - y_offset; + + if (strcmp("CRT", configuration.filter) == 0) { + *y = *y * 8 / 7; + *y -= 144 / 16; + } +} + +void run_gui(bool is_running) +{ + SDL_ShowCursor(SDL_ENABLE); + connect_joypad(); + + /* Draw the background screen */ + if (!converted_background) { + if (configuration.gui_pallete_enabled) { + update_gui_palette(); + } + else { + SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); + + /* Create a blank background if background.bmp could not be loaded */ + if (!background) { + background = SDL_CreateRGBSurface(0, 160, 144, 8, 0, 0, 0, 0); + } + SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); + converted_background = SDL_ConvertSurface(background, pixel_format, 0); + SDL_FreeSurface(background); + + for (unsigned i = 4; i--; ) { + gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b); + } + } + } + + unsigned width = GB_get_screen_width(&gb); + unsigned height = GB_get_screen_height(&gb); + unsigned x_offset = (width - 160) / 2; + unsigned y_offset = (height - 144) / 2; + uint32_t pixels[width * height]; + + if (width != 160 || height != 144) { + for (unsigned i = 0; i < width * height; i++) { + pixels[i] = gui_palette_native[0]; + } + } + + SDL_Event event = {0,}; + 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; + + bool scrollbar_drag = false; + signed scroll_mouse_start = 0; + signed scroll_start = 0; + while (true) { + SDL_WaitEvent(&event); + /* Convert Joypad and mouse events (We only generate down events) */ + if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { + switch (event.type) { + case SDL_KEYDOWN: + if (gui_state == WAITING_FOR_KEY) break; + if (event.key.keysym.mod != 0) break; + switch (event.key.keysym.scancode) { + // Do not remap these keys to prevent deadlocking + case SDL_SCANCODE_ESCAPE: + case SDL_SCANCODE_RETURN: + case SDL_SCANCODE_RIGHT: + case SDL_SCANCODE_LEFT: + case SDL_SCANCODE_UP: + case SDL_SCANCODE_DOWN: + break; + + default: + if (event.key.keysym.scancode == configuration.keys[GB_KEY_RIGHT]) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_LEFT]) event.key.keysym.scancode = SDL_SCANCODE_LEFT; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_UP]) event.key.keysym.scancode = SDL_SCANCODE_UP; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_DOWN]) event.key.keysym.scancode = SDL_SCANCODE_DOWN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_A]) event.key.keysym.scancode = SDL_SCANCODE_RETURN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_START]) event.key.keysym.scancode = SDL_SCANCODE_RETURN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_B]) event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + break; + } + break; + + case SDL_WINDOWEVENT: + should_render = true; + break; + case SDL_MOUSEBUTTONUP: + scrollbar_drag = false; + break; + case SDL_MOUSEBUTTONDOWN: + if (gui_state == SHOWING_HELP) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_RETURN; + } + else if (gui_state == SHOWING_DROP_MESSAGE) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + } + else if (gui_state == SHOWING_MENU) { + signed x = event.button.x; + signed y = event.button.y; + convert_mouse_coordinates(&x, &y); + if (x >= 160 - 6 && x < 160 && menu_height > 144) { + unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144); + if (scrollbar_offset + scrollbar_size > 140) { + scrollbar_offset = 140 - scrollbar_size; + } + + if (y < scrollbar_offset || y > scrollbar_offset + scrollbar_size) { + scroll = (menu_height - 144) * y / 143; + should_render = true; + } + + scrollbar_drag = true; + mouse_scroling = true; + scroll_mouse_start = y; + scroll_start = scroll; + break; + } + y += scroll; + + if (x < 0 || x >= 160 || y < 24) { + continue; + } + + unsigned item_y = 24; + unsigned index = 0; + for (const struct menu_item *item = current_menu; item->string; item++, index++) { + if (!item->backwards_handler) { + if (y >= item_y && y < item_y + 12) { + break; + } + item_y += 12; + } + else { + if (y >= item_y && y < item_y + 24) { + break; + } + item_y += 24; + } + } + + if (!current_menu[index].string) continue; + + current_selection = index; + event.type = SDL_KEYDOWN; + if (current_menu[index].backwards_handler) { + event.key.keysym.scancode = x < 80? SDL_SCANCODE_LEFT : SDL_SCANCODE_RIGHT; + } + else { + event.key.keysym.scancode = SDL_SCANCODE_RETURN; + } + + } + break; + case SDL_JOYBUTTONDOWN: + event.type = SDL_KEYDOWN; + joypad_button_t button = get_joypad_button(event.jbutton.button); + if (button == JOYPAD_BUTTON_A) { + event.key.keysym.scancode = SDL_SCANCODE_RETURN; + } + else if (button == JOYPAD_BUTTON_MENU) { + event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + } + else if (button == JOYPAD_BUTTON_UP) event.key.keysym.scancode = SDL_SCANCODE_UP; + else if (button == JOYPAD_BUTTON_DOWN) event.key.keysym.scancode = SDL_SCANCODE_DOWN; + else if (button == JOYPAD_BUTTON_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT; + else if (button == JOYPAD_BUTTON_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + break; + + case SDL_JOYHATMOTION: { + uint8_t value = event.jhat.value; + if (value != 0) { + uint32_t scancode = + value == SDL_HAT_UP ? SDL_SCANCODE_UP + : value == SDL_HAT_DOWN ? SDL_SCANCODE_DOWN + : value == SDL_HAT_LEFT ? SDL_SCANCODE_LEFT + : value == SDL_HAT_RIGHT ? SDL_SCANCODE_RIGHT + : 0; + + if (scancode != 0) { + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = scancode; + } + } + break; + } + + case SDL_JOYAXISMOTION: { + static bool axis_active[2] = {false, false}; + joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); + if (axis == JOYPAD_AXISES_X) { + if (!axis_active[0] && event.jaxis.value > JOYSTICK_HIGH) { + axis_active[0] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + } + else if (!axis_active[0] && event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[0] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_LEFT; + + } + else if (axis_active[0] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[0] = false; + } + } + else if (axis == JOYPAD_AXISES_Y) { + if (!axis_active[1] && event.jaxis.value > JOYSTICK_HIGH) { + axis_active[1] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_DOWN; + } + else if (!axis_active[1] && event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[1] = true; + event.type = SDL_KEYDOWN; + event.key.keysym.scancode = SDL_SCANCODE_UP; + } + else if (axis_active[1] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[1] = false; + } + } + } + } + } + switch (event.type) { + case SDL_QUIT: { + if (!is_running) { + exit(0); + } + else { + pending_command = GB_SDL_QUIT_COMMAND; + return; + } + + } + case SDL_WINDOWEVENT: { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + update_viewport(); + render_texture(NULL, NULL); + } + break; + } + case SDL_DROPFILE: { + 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; + } + else { + SDL_free(event.drop.file); + } + break; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } + } + case SDL_JOYBUTTONDOWN: { + if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { + should_render = true; + configuration.joypad_configuration[joypad_configuration_progress++] = event.jbutton.button; + } + break; + } + case SDL_JOYHATMOTION: { + if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress == JOYPAD_BUTTON_RIGHT) { + should_render = true; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + } + break; + } + + case SDL_JOYAXISMOTION: { + if (gui_state == WAITING_FOR_JBUTTON && + joypad_configuration_progress == JOYPAD_BUTTONS_MAX && + abs(event.jaxis.value) >= 0x4000) { + if (joypad_axis_temp == (uint8_t)-1) { + joypad_axis_temp = event.jaxis.axis; + } + else if (joypad_axis_temp != event.jaxis.axis) { + if (joypad_axis_temp < event.jaxis.axis) { + configuration.joypad_axises[JOYPAD_AXISES_X] = joypad_axis_temp; + configuration.joypad_axises[JOYPAD_AXISES_Y] = event.jaxis.axis; + } + else { + configuration.joypad_axises[JOYPAD_AXISES_Y] = joypad_axis_temp; + configuration.joypad_axises[JOYPAD_AXISES_X] = event.jaxis.axis; + } + + gui_state = SHOWING_MENU; + should_render = true; + } + } + 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_MOUSEMOTION: { + if (scrollbar_drag && scrollbar_size < 140 && scrollbar_size > 0) { + signed x = event.motion.x; + signed y = event.motion.y; + convert_mouse_coordinates(&x, &y); + signed delta = scroll_mouse_start - y; + scroll = scroll_start - delta * (signed)(menu_height - 144) / (signed)(140 - scrollbar_size); + if (scroll < 0) { + scroll = 0; + } + if (scroll >= menu_height - 144) { + scroll = menu_height - 144; + } + + should_render = true; + } + break; + } + + + case SDL_KEYDOWN: + scrollbar_drag = false; + if (gui_state == WAITING_FOR_KEY) { + if (current_selection > 8) { + 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); + } + else { + SDL_SetWindowFullscreen(window, 0); + } + update_viewport(); + } + else if (event_hotkey_code(&event) == SDL_SCANCODE_O) { + if (event.key.keysym.mod & MODIFIER) { + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } + } + } + else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && gui_state == WAITING_FOR_JBUTTON) { + should_render = true; + if (joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { + configuration.joypad_configuration[joypad_configuration_progress] = -1; + } + else { + configuration.joypad_axises[0] = -1; + configuration.joypad_axises[1] = -1; + } + joypad_configuration_progress++; + + if (joypad_configuration_progress > JOYPAD_BUTTONS_MAX) { + gui_state = SHOWING_MENU; + } + } + else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { + if (gui_state == SHOWING_MENU && current_menu != root_menu) { + for (const struct menu_item *item = current_menu; item->string; item++) { + if (strcmp(item->string, "Back") == 0) { + item->handler(0); + break; + } + } + should_render = true; + } + else if (is_running) { + return; + } + else { + if (gui_state == SHOWING_DROP_MESSAGE) { + gui_state = SHOWING_MENU; + } + else if (gui_state == SHOWING_MENU) { + 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) { + if (current_menu[current_selection].handler) { + current_menu[current_selection].handler(current_selection); + if (pending_command == GB_SDL_RESET_COMMAND && !is_running) { + pending_command = GB_SDL_NO_COMMAND; + } + if (pending_command) { + if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) { + exit(0); + } + return; + } + should_render = true; + } + else { + return; + } + } + else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) { + current_menu[current_selection].handler(current_selection); + should_render = true; + } + else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) { + current_menu[current_selection].backwards_handler(current_selection); + should_render = true; + } + } + else if (gui_state == SHOWING_HELP) { + current_help_page++; + if (current_help_page == sizeof(help) / sizeof(help[0])) { + gui_state = SHOWING_MENU; + } + should_render = true; + } + break; + } + + if (should_render) { + should_render = false; + rerender: + SDL_LockSurface(converted_background); + if (width == 160 && height == 144) { + memcpy(pixels, converted_background->pixels, sizeof(pixels)); + } + else { + for (unsigned y = 0; y < 144; y++) { + memcpy(pixels + x_offset + width * (y + y_offset), ((uint32_t *)converted_background->pixels) + 160 * y, 160 * 4); + } + } + SDL_UnlockSurface(converted_background); + + switch (gui_state) { + case SHOWING_DROP_MESSAGE: + draw_text_centered(pixels, width, height, 8 + y_offset, "Press ESC for menu", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 116 + y_offset, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false); + draw_text_centered(pixels, width, height, 128 + y_offset, "file to play", gui_palette_native[3], gui_palette_native[0], false); + break; + case SHOWING_MENU: + 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 && !mouse_scroling) { + if (i == 0) { + if (y < scroll) { + scroll = (y - 4) / 12 * 12; + goto rerender; + } + } + else { + if (y < scroll + 24) { + scroll = (y - 24) / 12 * 12; + goto rerender; + } + } + } + if (i == current_selection && i == 0 && scroll != 0 && !mouse_scroling) { + scroll = 0; + goto rerender; + } + if (item->value_getter && !item->backwards_handler) { + char line[25]; + snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (unsigned)strlen(item->string), item->value_getter(i)); + draw_text_centered(pixels, width, height, y + y_offset, line, gui_palette_native[3], gui_palette_native[0], + i == current_selection ? DECORATION_SELECTION : DECORATION_NONE); + y += 12; + + } + else { + draw_text_centered(pixels, width, height, y + y_offset, item->string, gui_palette_native[3], gui_palette_native[0], + 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 - 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 && !mouse_scroling) { + if (y > scroll + 144) { + scroll = (y - 144) / 12 * 12; + if (scroll > menu_height - 144) { + scroll = menu_height - 144; + } + goto rerender; + } + } + + } + if (scrollbar_size) { + unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144); + if (scrollbar_offset + scrollbar_size > 140) { + scrollbar_offset = 140 - scrollbar_size; + } + for (unsigned y = 0; y < 140; y++) { + uint32_t *pixel = pixels + x_offset + 156 + width * (y + y_offset + 2); + if (y >= scrollbar_offset && y < scrollbar_offset + scrollbar_size) { + pixel[0] = pixel[1] = gui_palette_native[2]; + } + else { + pixel[0] = pixel[1] = gui_palette_native[1]; + } + + } + } + break; + case SHOWING_HELP: + draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0], false); + break; + case WAITING_FOR_KEY: + draw_text_centered(pixels, width, height, 68 + y_offset, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + break; + case WAITING_FOR_JBUTTON: + draw_text_centered(pixels, width, height, 68 + y_offset, + joypad_configuration_progress != JOYPAD_BUTTONS_MAX ? "Press button for" : "Move the Analog Stick", + gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, width, height, 80 + y_offset, + (const char *[]) + { + "Right", + "Left", + "Up", + "Down", + "A", + "B", + "Select", + "Start", + "Open Menu", + "Turbo", + "Rewind", + "Slow-Motion", + "Hotkey 1", + "Hotkey 2", + "", + } [joypad_configuration_progress], + gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + draw_text_centered(pixels, width, height, 104 + y_offset, "Press Enter to skip", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); + break; + } + + render_texture(pixels, NULL); +#ifdef _WIN32 + /* Required for some Windows 10 machines, god knows why */ + render_texture(pixels, NULL); +#endif + } + } +} + +static void __attribute__ ((constructor)) list_custom_palettes(void) +{ + char *path = resource_path("Palettes"); + if (!path) return; + if (strlen(path) > 1024 - 30) { + // path too long to safely concat filenames + return; + } + DIR *dir = opendir(path); + if (!dir) return; + + struct dirent *ent; + + while ((ent = readdir(dir))) { + unsigned length = strlen(ent->d_name); + if (length < 5 || length > 28) { + continue; + } + if (strcmp(ent->d_name + length - 4, ".sbp")) continue; + ent->d_name[length - 4] = 0; + custom_palettes = realloc(custom_palettes, + sizeof(custom_palettes[0]) * (n_custom_palettes + 1)); + custom_palettes[n_custom_palettes++] = strdup(ent->d_name); + } + + closedir(dir); +} diff --git a/thirdparty/SameBoy-old/SDL/gui.h b/thirdparty/SameBoy-old/SDL/gui.h new file mode 100644 index 000000000..835794a24 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/gui.h @@ -0,0 +1,69 @@ +#ifndef gui_h +#define gui_h + +#include +#include +#include +#include "shader.h" +#include "configuration.h" + +#define JOYSTICK_HIGH 0x4000 +#define JOYSTICK_LOW 0x3800 + +#ifdef __APPLE__ +#define MODIFIER KMOD_GUI +#else +#define MODIFIER KMOD_CTRL +#endif + +extern GB_gameboy_t gb; + +extern SDL_Window *window; +extern SDL_Renderer *renderer; +extern SDL_Texture *texture; +extern SDL_PixelFormat *pixel_format; +extern SDL_Haptic *haptic; +extern shader_t shader; + +enum pending_command { + GB_SDL_NO_COMMAND, + GB_SDL_SAVE_STATE_COMMAND, + GB_SDL_LOAD_STATE_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; + +void update_viewport(void); +void run_gui(bool is_running); +void render_texture(void *pixels, void *previous); +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; +} + +void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border, bool is_osd); +void show_osd_text(const char *text); +extern const char *osd_text; +extern unsigned osd_countdown; +extern unsigned osd_text_lines; +void convert_mouse_coordinates(signed *x, signed *y); +const GB_palette_t *current_dmg_palette(void); + +#endif diff --git a/thirdparty/SameBoy-old/SDL/main.c b/thirdparty/SameBoy-old/SDL/main.c new file mode 100644 index 000000000..c3f95942c --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/main.c @@ -0,0 +1,992 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "gui.h" +#include "shader.h" +#include "audio/audio.h" +#include "console.h" + +#ifndef _WIN32 +#include +#else +#include +#endif + +static bool stop_on_start = false; +GB_gameboy_t gb; +static bool paused = false; +static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; +static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2; +static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false; +static double clock_mutliplier = 1.0; + +static char *filename = NULL; +static typeof(free) *free_function = NULL; +static char *battery_save_path_ptr = NULL; +static SDL_GLContext gl_context = NULL; +static bool console_supported = false; + +bool uses_gl(void) +{ + return gl_context; +} + +void set_filename(const char *new_filename, typeof(free) *new_free_function) +{ + if (filename && free_function) { + free_function(filename); + } + filename = (char *) new_filename; + free_function = new_free_function; +} + +static char *completer(const char *substring, uintptr_t *context) +{ + if (!GB_is_inited(&gb)) return NULL; + char *temp = strdup(substring); + char *ret = GB_debugger_complete_substring(&gb, temp, context); + free(temp); + return ret; +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + CON_attributes_t con_attributes = {0,}; + con_attributes.bold = attributes & GB_LOG_BOLD; + con_attributes.underline = attributes & GB_LOG_UNDERLINE; + if (attributes & GB_LOG_DASHED_UNDERLINE) { + while (*string) { + con_attributes.underline ^= true; + CON_attributed_printf("%c", &con_attributes, *string); + string++; + } + } + else { + CON_attributed_print(string, &con_attributes); + } +} + +static void handle_eof(void) +{ + CON_set_async_prompt(""); + char *line = CON_readline("Quit? [y]/n > "); + if (line[0] == 'n' || line[0] == 'N') { + free(line); + CON_set_async_prompt("> "); + } + else { + exit(0); + } +} + +static char *input_callback(GB_gameboy_t *gb) +{ +retry: { + char *ret = CON_readline("Stopped> "); + if (strcmp(ret, CON_EOF) == 0) { + handle_eof(); + free(ret); + goto retry; + } + else { + CON_attributes_t attributes = {.bold = true}; + CON_attributed_printf("> %s\n", &attributes, ret); + } + return ret; +} +} + +static char *asyc_input_callback(GB_gameboy_t *gb) +{ +retry: { + char *ret = CON_readline_async(); + if (ret && strcmp(ret, CON_EOF) == 0) { + handle_eof(); + free(ret); + goto retry; + } + else if (ret) { + CON_attributes_t attributes = {.bold = true}; + CON_attributed_printf("> %s\n", &attributes, ret); + } + return ret; +} +} + + +static char *captured_log = NULL; + +static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + size_t current_len = strlen(captured_log); + size_t len_to_add = strlen(string); + captured_log = realloc(captured_log, current_len + len_to_add + 1); + memcpy(captured_log + current_len, string, len_to_add); + captured_log[current_len + len_to_add] = 0; +} + +static void start_capturing_logs(void) +{ + if (captured_log != NULL) { + free(captured_log); + } + captured_log = malloc(1); + captured_log[0] = 0; + GB_set_log_callback(&gb, log_capture_callback); +} + +static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title) +{ + GB_set_log_callback(&gb, console_supported? log_callback : NULL); + if (captured_log[0] == 0) { + free(captured_log); + captured_log = NULL; + } + else { + if (show_popup) { + SDL_ShowSimpleMessageBox(popup_flags, title, captured_log, window); + } + if (should_exit) { + exit(1); + } + } + return captured_log; +} + +static void update_palette(void) +{ + GB_set_palette(&gb, current_dmg_palette()); +} + +static void screen_size_changed(void) +{ + SDL_DestroyTexture(texture); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, + GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); + + update_viewport(); +} + +static void open_menu(void) +{ + bool audio_playing = GB_audio_is_playing(); + if (audio_playing) { + GB_audio_set_paused(true); + } + size_t previous_width = GB_get_screen_width(&gb); + run_gui(true); + SDL_ShowCursor(SDL_DISABLE); + if (audio_playing) { + GB_audio_set_paused(false); + } + GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0); + GB_set_interference_volume(&gb, configuration.interference_volume / 100.0); + GB_set_border_mode(&gb, configuration.border_mode); + update_palette(); + GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); + 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(); + } +} + +static void handle_events(GB_gameboy_t *gb) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + pending_command = GB_SDL_QUIT_COMMAND; + break; + + case SDL_DROPFILE: { + if (GB_is_save_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; + } + + case SDL_WINDOWEVENT: { + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + update_viewport(); + } + break; + } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) { + GB_set_key_state(gb, GB_KEY_A, event.type == SDL_MOUSEBUTTONDOWN); + } + break; + } + + case SDL_MOUSEMOTION: { + if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) { + signed x = event.motion.x; + signed y = event.motion.y; + convert_mouse_coordinates(&x, &y); + x = SDL_max(SDL_min(x, 160), 0); + y = SDL_max(SDL_min(y, 144), 0); + GB_set_accelerometer_values(gb, (x - 80) / -80.0, (y - 72) / -72.0); + } + break; + } + + case SDL_JOYBUTTONUP: + case SDL_JOYBUTTONDOWN: { + joypad_button_t button = get_joypad_button(event.jbutton.button); + if ((GB_key_t) button < GB_KEY_MAX) { + GB_set_key_state(gb, (GB_key_t) button, event.type == SDL_JOYBUTTONDOWN); + } + else if (button == JOYPAD_BUTTON_TURBO) { + GB_audio_clear_queue(); + turbo_down = event.type == SDL_JOYBUTTONDOWN; + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (button == JOYPAD_BUTTON_SLOW_MOTION) { + underclock_down = event.type == SDL_JOYBUTTONDOWN; + } + else if (button == JOYPAD_BUTTON_REWIND) { + rewind_down = event.type == SDL_JOYBUTTONDOWN; + if (event.type == SDL_JOYBUTTONUP) { + rewind_paused = false; + } + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) { + open_menu(); + } + else if ((button == JOYPAD_BUTTON_HOTKEY_1 || button == JOYPAD_BUTTON_HOTKEY_2) && event.type == SDL_JOYBUTTONDOWN) { + hotkey_action_t action = configuration.hotkey_actions[button - JOYPAD_BUTTON_HOTKEY_1]; + switch (action) { + case HOTKEY_NONE: + break; + case HOTKEY_PAUSE: + paused = !paused; + break; + case HOTKEY_MUTE: + GB_audio_set_paused(GB_audio_is_playing()); + break; + case HOTKEY_RESET: + pending_command = GB_SDL_RESET_COMMAND; + break; + case HOTKEY_QUIT: + pending_command = GB_SDL_QUIT_COMMAND; + break; + default: + command_parameter = (action - HOTKEY_SAVE_STATE_1) / 2 + 1; + pending_command = ((action - HOTKEY_SAVE_STATE_1) % 2)? GB_SDL_LOAD_STATE_COMMAND:GB_SDL_SAVE_STATE_COMMAND; + break; + case HOTKEY_SAVE_STATE_10: + command_parameter = 0; + pending_command = GB_SDL_SAVE_STATE_COMMAND; + break; + case HOTKEY_LOAD_STATE_10: + command_parameter = 0; + pending_command = GB_SDL_LOAD_STATE_COMMAND; + break; + } + } + } + break; + + case SDL_JOYAXISMOTION: { + static bool axis_active[2] = {false, false}; + static double accel_values[2] = {0, 0}; + joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); + if (axis == JOYPAD_AXISES_X) { + if (GB_has_accelerometer(gb)) { + accel_values[0] = event.jaxis.value / (double)32768.0; + GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]); + } + else if (event.jaxis.value > JOYSTICK_HIGH) { + axis_active[0] = true; + GB_set_key_state(gb, GB_KEY_RIGHT, true); + GB_set_key_state(gb, GB_KEY_LEFT, false); + } + else if (event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[0] = true; + GB_set_key_state(gb, GB_KEY_RIGHT, false); + GB_set_key_state(gb, GB_KEY_LEFT, true); + } + else if (axis_active[0] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[0] = false; + GB_set_key_state(gb, GB_KEY_RIGHT, false); + GB_set_key_state(gb, GB_KEY_LEFT, false); + } + } + else if (axis == JOYPAD_AXISES_Y) { + if (GB_has_accelerometer(gb)) { + accel_values[1] = event.jaxis.value / (double)32768.0; + GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]); + } + else if (event.jaxis.value > JOYSTICK_HIGH) { + axis_active[1] = true; + GB_set_key_state(gb, GB_KEY_DOWN, true); + GB_set_key_state(gb, GB_KEY_UP, false); + } + else if (event.jaxis.value < -JOYSTICK_HIGH) { + axis_active[1] = true; + GB_set_key_state(gb, GB_KEY_DOWN, false); + GB_set_key_state(gb, GB_KEY_UP, true); + } + else if (axis_active[1] && event.jaxis.value < JOYSTICK_LOW && event.jaxis.value > -JOYSTICK_LOW) { + axis_active[1] = false; + GB_set_key_state(gb, GB_KEY_DOWN, false); + GB_set_key_state(gb, GB_KEY_UP, false); + } + } + } + break; + + case SDL_JOYHATMOTION: { + uint8_t value = event.jhat.value; + int8_t updown = + value == SDL_HAT_LEFTUP || value == SDL_HAT_UP || value == SDL_HAT_RIGHTUP ? -1 : (value == SDL_HAT_LEFTDOWN || value == SDL_HAT_DOWN || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + int8_t leftright = + value == SDL_HAT_LEFTUP || value == SDL_HAT_LEFT || value == SDL_HAT_LEFTDOWN ? -1 : (value == SDL_HAT_RIGHTUP || value == SDL_HAT_RIGHT || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + + GB_set_key_state(gb, GB_KEY_LEFT, leftright == -1); + GB_set_key_state(gb, GB_KEY_RIGHT, leftright == 1); + GB_set_key_state(gb, GB_KEY_UP, updown == -1); + GB_set_key_state(gb, GB_KEY_DOWN, updown == 1); + break; + }; + + case SDL_KEYDOWN: + switch (event_hotkey_code(&event)) { + case SDL_SCANCODE_ESCAPE: { + open_menu(); + break; + } + case SDL_SCANCODE_C: + if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { + CON_print("^C\a\n"); + GB_debugger_break(gb); + } + break; + + case SDL_SCANCODE_R: + if (event.key.keysym.mod & MODIFIER) { + pending_command = GB_SDL_RESET_COMMAND; + } + break; + + case SDL_SCANCODE_O: { + if (event.key.keysym.mod & MODIFIER) { + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } + } + break; + } + + case SDL_SCANCODE_P: + if (event.key.keysym.mod & MODIFIER) { + paused = !paused; + } + break; + case SDL_SCANCODE_M: + if (event.key.keysym.mod & MODIFIER) { +#ifdef __APPLE__ + // Can't override CMD+M (Minimize) in SDL + if (!(event.key.keysym.mod & KMOD_SHIFT)) { + break; + } +#endif + GB_audio_set_paused(GB_audio_is_playing()); + } + break; + + case SDL_SCANCODE_F: + if (event.key.keysym.mod & MODIFIER) { + if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else { + SDL_SetWindowFullscreen(window, 0); + } + update_viewport(); + } + break; + + default: + /* Save states */ + if (event.key.keysym.scancode >= SDL_SCANCODE_1 && event.key.keysym.scancode <= SDL_SCANCODE_0) { + if (event.key.keysym.mod & MODIFIER) { + command_parameter = (event.key.keysym.scancode - SDL_SCANCODE_1 + 1) % 10; + + if (event.key.keysym.mod & KMOD_SHIFT) { + pending_command = GB_SDL_LOAD_STATE_COMMAND; + } + else { + pending_command = GB_SDL_SAVE_STATE_COMMAND; + } + } + } + break; + } + case SDL_KEYUP: // Fallthrough + if (event.key.keysym.scancode == configuration.keys[8]) { + turbo_down = event.type == SDL_KEYDOWN; + GB_audio_clear_queue(); + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (event.key.keysym.scancode == configuration.keys_2[0]) { + rewind_down = event.type == SDL_KEYDOWN; + if (event.type == SDL_KEYUP) { + rewind_paused = false; + } + GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); + } + else if (event.key.keysym.scancode == configuration.keys_2[1]) { + underclock_down = event.type == SDL_KEYDOWN; + } + else { + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + if (event.key.keysym.scancode == configuration.keys[i]) { + GB_set_key_state(gb, i, event.type == SDL_KEYDOWN); + } + } + } + break; + default: + break; + } + } +} + +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return SDL_MapRGB(pixel_format, r, g, b); +} + +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + if (underclock_down && clock_mutliplier > 0.5) { + clock_mutliplier -= 1.0/16; + GB_set_clock_multiplier(gb, clock_mutliplier); + } + else if (!underclock_down && clock_mutliplier < 1.0) { + clock_mutliplier += 1.0/16; + GB_set_clock_multiplier(gb, clock_mutliplier); + } + + if (turbo_down) { + show_osd_text("Fast forward..."); + } + else if (underclock_down) { + show_osd_text("Slow motion..."); + } + else if (rewind_down) { + show_osd_text("Rewinding..."); + } + + if (osd_countdown && configuration.osd) { + unsigned width = GB_get_screen_width(gb); + unsigned height = GB_get_screen_height(gb); + draw_text(active_pixel_buffer, + width, height, 8, height - 8 - osd_text_lines * 12, osd_text, + rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0), + true); + osd_countdown--; + } + if (type != GB_VBLANK_TYPE_REPEAT) { + if (configuration.blending_mode) { + render_texture(active_pixel_buffer, previous_pixel_buffer); + uint32_t *temp = active_pixel_buffer; + active_pixel_buffer = previous_pixel_buffer; + previous_pixel_buffer = temp; + GB_set_pixels_output(gb, active_pixel_buffer); + } + else { + render_texture(active_pixel_buffer, NULL); + } + } + do_rewind = rewind_down; + handle_events(gb); +} + +static void rumble(GB_gameboy_t *gb, double amp) +{ + SDL_HapticRumblePlay(haptic, amp, 250); +} + +static void debugger_interrupt(int ignore) +{ + if (!GB_is_inited(&gb)) exit(0); + /* ^C twice to exit */ + if (GB_debugger_is_stopped(&gb)) { + GB_save_battery(&gb, battery_save_path_ptr); + exit(0); + } + if (console_supported) { + CON_print("^C\n"); + } + GB_debugger_break(&gb); +} + +static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) +{ + if (turbo_down) { + static unsigned skip = 0; + skip++; + if (skip == GB_audio_get_frequency() / 8) { + skip = 0; + } + if (skip > GB_audio_get_frequency() / 16) { + return; + } + } + + if (GB_audio_get_queue_length() > GB_audio_get_frequency() / 8) { // Maximum lag of 0.125s + return; + } + + if (configuration.volume != 100) { + sample->left = sample->left * configuration.volume / 100; + sample->right = sample->right * configuration.volume / 100; + } + + GB_audio_queue_sample(sample); + +} + +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) + 5]; + char save_extension[] = ".s0"; + save_extension[2] += command_parameter; + replace_extension(filename, strlen(filename), save_path, save_extension); + + start_capturing_logs(); + bool success; + if (pending_command == GB_SDL_LOAD_STATE_COMMAND) { + 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, + success? "Notice" : "Error"); + if (success) { + show_osd_text(pending_command == GB_SDL_LOAD_STATE_COMMAND? "State loaded" : "State saved"); + } + 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, + success? "Notice" : "Error"); + SDL_free(dropped_state_file); + if (success) { + show_osd_text("State loaded"); + } + return false; + + case GB_SDL_NO_COMMAND: + return false; + + case GB_SDL_RESET_COMMAND: + case GB_SDL_NEW_FILE_COMMAND: + GB_save_battery(&gb, battery_save_path_ptr); + return true; + + case GB_SDL_QUIT_COMMAND: + GB_save_battery(&gb, battery_save_path_ptr); + exit(0); + } + return false; +} + +static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) +{ + static const char *const names[] = { + [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_CGB_0] = "cgb0_boot.bin", + [GB_BOOT_ROM_CGB] = "cgb_boot.bin", + [GB_BOOT_ROM_AGB] = "agb_boot.bin", + }; + 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, SDL_MESSAGEBOX_ERROR, "Error"); + } +} + +static bool is_path_writeable(const char *path) +{ + if (!access(path, W_OK)) return true; + int fd = creat(path, 0644); + if (fd == -1) return false; + close(fd); + unlink(path); + return true; +} + +static void run(void) +{ + SDL_ShowCursor(SDL_DISABLE); + GB_model_t model; + pending_command = GB_SDL_NO_COMMAND; +restart: + model = (GB_model_t []) + { + [MODEL_DMG] = GB_MODEL_DMG_B, + [MODEL_CGB] = GB_MODEL_CGB_0 + configuration.cgb_revision, + [MODEL_AGB] = configuration.agb_revision, + [MODEL_MGB] = GB_MODEL_MGB, + [MODEL_SGB] = (GB_model_t []) + { + [SGB_NTSC] = GB_MODEL_SGB_NTSC, + [SGB_PAL] = GB_MODEL_SGB_PAL, + [SGB_2] = GB_MODEL_SGB2, + }[configuration.sgb_revision], + }[configuration.model]; + + if (GB_is_inited(&gb)) { + GB_switch_model_and_reset(&gb, model); + } + else { + GB_init(&gb, model); + + GB_set_boot_rom_load_callback(&gb, load_boot_rom); + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_pixels_output(&gb, active_pixel_buffer); + GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_rumble_callback(&gb, rumble); + GB_set_rumble_mode(&gb, configuration.rumble_mode); + GB_set_sample_rate(&gb, GB_audio_get_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); + } + 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); + + if (console_supported) { + CON_set_async_prompt("> "); + GB_set_log_callback(&gb, log_callback); + GB_set_input_callback(&gb, input_callback); + GB_set_async_input_callback(&gb, asyc_input_callback); + } + } + if (stop_on_start) { + stop_on_start = false; + GB_debugger_break(&gb); + } + + bool error = false; + GB_debugger_clear_symbols(&gb); + start_capturing_logs(); + size_t path_length = strlen(filename); + char extension[4] = {0,}; + if (path_length > 4) { + if (filename[path_length - 4] == '.') { + 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) { + error = GB_load_isx(&gb, filename); + /* Configure battery */ + char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ + replace_extension(filename, path_length, battery_save_path, ".ram"); + battery_save_path_ptr = battery_save_path; + GB_load_battery(&gb, battery_save_path); + } + else { + GB_load_rom(&gb, filename); + } + + /* Configure battery */ + char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ + replace_extension(filename, path_length, battery_save_path, ".sav"); + battery_save_path_ptr = battery_save_path; + GB_load_battery(&gb, battery_save_path); + if (GB_save_battery_size(&gb)) { + if (!is_path_writeable(battery_save_path)) { + GB_log(&gb, "The save path for this ROM is not writeable, progress will not be saved.\n"); + } + } + + end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning"); + + static char start_text[64]; + static char title[17]; + GB_get_rom_title(&gb, title); + sprintf(start_text, "SameBoy v" GB_VERSION "\n%s\n%08X", title, GB_get_rom_crc32(&gb)); + show_osd_text(start_text); + + /* Configure symbols */ + GB_debugger_load_symbol_file(&gb, resource_path("registers.sym")); + + char symbols_path[path_length + 5]; + replace_extension(filename, path_length, symbols_path, ".sym"); + GB_debugger_load_symbol_file(&gb, symbols_path); + + screen_size_changed(); + + /* Run emulation */ + while (true) { + if (paused || rewind_paused) { + SDL_WaitEvent(NULL); + handle_events(&gb); + } + else { + if (do_rewind) { + GB_rewind_pop(&gb); + if (turbo_down) { + GB_rewind_pop(&gb); + } + if (!GB_rewind_pop(&gb)) { + rewind_paused = true; + } + do_rewind = false; + } + GB_run(&gb); + } + + /* These commands can't run in the handle_event function, because they're not safe in a vblank context. */ + if (handle_pending_command()) { + pending_command = GB_SDL_NO_COMMAND; + goto restart; + } + pending_command = GB_SDL_NO_COMMAND; + } +} + +static char prefs_path[1024] = {0, }; + +static void save_configuration(void) +{ + FILE *prefs_file = fopen(prefs_path, "wb"); + if (prefs_file) { + fwrite(&configuration, 1, sizeof(configuration), prefs_file); + fclose(prefs_file); + } +} + +static void stop_recording(void) +{ + GB_stop_audio_recording(&gb); +} + +static bool get_arg_flag(const char *flag, int *argc, char **argv) +{ + for (unsigned i = 1; i < *argc; i++) { + if (strcmp(argv[i], flag) == 0) { + (*argc)--; + argv[i] = argv[*argc]; + return true; + } + } + return false; +} + +#ifdef __APPLE__ +#include +static void enable_smooth_scrolling(void) +{ + CFPreferencesSetAppValue(CFSTR("AppleMomentumScrollSupported"), kCFBooleanTrue, kCFPreferencesCurrentApplication); +} +#endif + +int main(int argc, char **argv) +{ +#ifdef _WIN32 + SetProcessDPIAware(); +#endif +#ifdef __APPLE__ + enable_smooth_scrolling(); +#endif + + bool fullscreen = get_arg_flag("--fullscreen", &argc, argv) || get_arg_flag("-f", &argc, argv); + bool nogl = get_arg_flag("--nogl", &argc, argv); + stop_on_start = get_arg_flag("--stop-debugger", &argc, argv) || get_arg_flag("-s", &argc, argv); + + if (argc > 2 || (argc == 2 && argv[1][0] == '-')) { + fprintf(stderr, "SameBoy v" GB_VERSION "\n"); + fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [rom]\n", argv[0]); + exit(1); + } + + if (argc == 2) { + filename = argv[1]; + } + + signal(SIGINT, debugger_interrupt); + + SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO); + if ((console_supported = CON_start(completer))) { + CON_set_repeat_empty(true); + CON_printf("SameBoy v" GB_VERSION "\n"); + } + else { + fprintf(stderr, "SameBoy v" GB_VERSION "\n"); + } + + strcpy(prefs_path, resource_path("prefs.bin")); + if (access(prefs_path, R_OK | W_OK) != 0) { + char *prefs_dir = SDL_GetPrefPath("", "SameBoy"); + snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir); + SDL_free(prefs_dir); + } + + FILE *prefs_file = fopen(prefs_path, "rb"); + if (prefs_file) { + fread(&configuration, 1, sizeof(configuration), prefs_file); + fclose(prefs_file); + + /* Sanitize for stability */ + configuration.color_correction_mode %= GB_COLOR_CORRECTION_MODERN_ACCURATE + 1; + configuration.scaling_mode %= GB_SDL_SCALING_MAX; + configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1; + configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1; + configuration.highpass_mode %= GB_HIGHPASS_MAX; + configuration.model %= MODEL_MAX; + configuration.sgb_revision %= SGB_MAX; + configuration.dmg_palette %= 5; + if (configuration.dmg_palette) { + configuration.gui_pallete_enabled = true; + } + 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; + configuration.cgb_revision %= GB_MODEL_CGB_E - GB_MODEL_CGB_0 + 1; + configuration.audio_driver[15] = 0; + configuration.dmg_palette_name[24] = 0; + // Fix broken defaults, should keys 12-31 should be unmapped by default + if (configuration.joypad_configuration[31] == 0) { + memset(configuration.joypad_configuration + 12 , -1, 32 - 12); + } + if ((configuration.agb_revision & ~GB_MODEL_GBP_BIT) != GB_MODEL_AGB_A) { + configuration.agb_revision = GB_MODEL_AGB_A; + } + } + + if (configuration.model >= MODEL_MAX) { + configuration.model = MODEL_CGB; + } + + if (configuration.default_scale == 0) { + configuration.default_scale = 2; + } + + atexit(save_configuration); + atexit(stop_recording); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + configuration.allow_background_controllers? "1" : "0"); + + window = SDL_CreateWindow("SameBoy v" GB_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) { + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + + gl_context = nogl? NULL : SDL_GL_CreateContext(window); + + GLint major = 0, minor = 0; + if (gl_context) { + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + } + + if (gl_context && major * 0x100 + minor < 0x302) { + SDL_GL_DeleteContext(gl_context); + gl_context = NULL; + } + + if (gl_context == NULL) { + renderer = SDL_CreateRenderer(window, -1, 0); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144); + pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window)); + } + else { + pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); + } + + GB_audio_init(); + + SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + update_viewport(); + + if (filename == NULL) { + stop_on_start = false; + run_gui(false); + } + else { + connect_joypad(); + } + GB_audio_set_paused(false); + run(); // Never returns + return 0; +} diff --git a/thirdparty/SameBoy-old/SDL/opengl_compat.c b/thirdparty/SameBoy-old/SDL/opengl_compat.c new file mode 100644 index 000000000..af7ce6d77 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/opengl_compat.c @@ -0,0 +1,34 @@ +#define GL_GLEXT_PROTOTYPES +#include + +#ifndef __APPLE__ +#define GL_COMPAT_NAME(func) gl_compat_##func +#define GL_COMPAT_VAR(func) typeof(func) *GL_COMPAT_NAME(func) + +GL_COMPAT_VAR(glCreateShader); +GL_COMPAT_VAR(glGetAttribLocation); +GL_COMPAT_VAR(glGetUniformLocation); +GL_COMPAT_VAR(glUseProgram); +GL_COMPAT_VAR(glGenVertexArrays); +GL_COMPAT_VAR(glBindVertexArray); +GL_COMPAT_VAR(glGenBuffers); +GL_COMPAT_VAR(glBindBuffer); +GL_COMPAT_VAR(glBufferData); +GL_COMPAT_VAR(glEnableVertexAttribArray); +GL_COMPAT_VAR(glVertexAttribPointer); +GL_COMPAT_VAR(glCreateProgram); +GL_COMPAT_VAR(glAttachShader); +GL_COMPAT_VAR(glLinkProgram); +GL_COMPAT_VAR(glGetProgramiv); +GL_COMPAT_VAR(glGetProgramInfoLog); +GL_COMPAT_VAR(glDeleteShader); +GL_COMPAT_VAR(glUniform2f); +GL_COMPAT_VAR(glActiveTexture); +GL_COMPAT_VAR(glUniform1i); +GL_COMPAT_VAR(glBindFragDataLocation); +GL_COMPAT_VAR(glDeleteProgram); +GL_COMPAT_VAR(glShaderSource); +GL_COMPAT_VAR(glCompileShader); +GL_COMPAT_VAR(glGetShaderiv); +GL_COMPAT_VAR(glGetShaderInfoLog); +#endif diff --git a/thirdparty/SameBoy-old/SDL/opengl_compat.h b/thirdparty/SameBoy-old/SDL/opengl_compat.h new file mode 100644 index 000000000..4b79b0c7c --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/opengl_compat.h @@ -0,0 +1,45 @@ +#ifndef opengl_compat_h +#define opengl_compat_h + +#define GL_GLEXT_PROTOTYPES +#include +#include + +#ifndef __APPLE__ +#define GL_COMPAT_NAME(func) gl_compat_##func + +#define GL_COMPAT_WRAPPER(func) \ +({ extern typeof(func) *GL_COMPAT_NAME(func); \ +if (!GL_COMPAT_NAME(func)) GL_COMPAT_NAME(func) = SDL_GL_GetProcAddress(#func); \ + GL_COMPAT_NAME(func); \ +}) + +#define glCreateShader GL_COMPAT_WRAPPER(glCreateShader) +#define glGetAttribLocation GL_COMPAT_WRAPPER(glGetAttribLocation) +#define glGetUniformLocation GL_COMPAT_WRAPPER(glGetUniformLocation) +#define glUseProgram GL_COMPAT_WRAPPER(glUseProgram) +#define glGenVertexArrays GL_COMPAT_WRAPPER(glGenVertexArrays) +#define glBindVertexArray GL_COMPAT_WRAPPER(glBindVertexArray) +#define glGenBuffers GL_COMPAT_WRAPPER(glGenBuffers) +#define glBindBuffer GL_COMPAT_WRAPPER(glBindBuffer) +#define glBufferData GL_COMPAT_WRAPPER(glBufferData) +#define glEnableVertexAttribArray GL_COMPAT_WRAPPER(glEnableVertexAttribArray) +#define glVertexAttribPointer GL_COMPAT_WRAPPER(glVertexAttribPointer) +#define glCreateProgram GL_COMPAT_WRAPPER(glCreateProgram) +#define glAttachShader GL_COMPAT_WRAPPER(glAttachShader) +#define glLinkProgram GL_COMPAT_WRAPPER(glLinkProgram) +#define glGetProgramiv GL_COMPAT_WRAPPER(glGetProgramiv) +#define glGetProgramInfoLog GL_COMPAT_WRAPPER(glGetProgramInfoLog) +#define glDeleteShader GL_COMPAT_WRAPPER(glDeleteShader) +#define glUniform2f GL_COMPAT_WRAPPER(glUniform2f) +#define glActiveTexture GL_COMPAT_WRAPPER(glActiveTexture) +#define glUniform1i GL_COMPAT_WRAPPER(glUniform1i) +#define glBindFragDataLocation GL_COMPAT_WRAPPER(glBindFragDataLocation) +#define glDeleteProgram GL_COMPAT_WRAPPER(glDeleteProgram) +#define glShaderSource GL_COMPAT_WRAPPER(glShaderSource) +#define glCompileShader GL_COMPAT_WRAPPER(glCompileShader) +#define glGetShaderiv GL_COMPAT_WRAPPER(glGetShaderiv) +#define glGetShaderInfoLog GL_COMPAT_WRAPPER(glGetShaderInfoLog) +#endif + +#endif /* opengl_compat_h */ diff --git a/thirdparty/SameBoy-old/SDL/shader.c b/thirdparty/SameBoy-old/SDL/shader.c new file mode 100644 index 000000000..44de29044 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/shader.c @@ -0,0 +1,206 @@ +#include +#include +#include "shader.h" +#include "utils.h" + +static const char *vertex_shader = "\n\ +#version 150 \n\ +in vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +static GLuint create_shader(const char *source, GLenum type) +{ + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Shader Error: %s", messages); + } + return shader; +} + +static GLuint create_program(const char *vsh, const char *fsh) +{ + // Build shaders + GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER); + GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER); + + // Create program + GLuint program = glCreateProgram(); + + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Program Error: %s", messages); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + 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); + + if (major * 0x100 + minor < 0x302) { + return false; + } + + static char master_shader_code[0x801] = {0,}; + static char shader_code[0x10001] = {0,}; + static char final_shader_code[0x10801] = {0,}; + static ssize_t filter_token_location = 0; + + if (!master_shader_code[0]) { + FILE *master_shader_f = fopen(resource_path("Shaders/MasterShader.fsh"), "r"); + if (!master_shader_f) return false; + fread(master_shader_code, 1, sizeof(master_shader_code) - 1, master_shader_f); + fclose(master_shader_f); + filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; + if (filter_token_location < 0) { + master_shader_code[0] = 0; + return false; + } + } + + char shader_path[1024]; + sprintf(shader_path, "Shaders/%s.fsh", name); + + FILE *shader_f = fopen(resource_path(shader_path), "r"); + if (!shader_f) return false; + memset(shader_code, 0, sizeof(shader_code)); + fread(shader_code, 1, sizeof(shader_code) - 1, shader_f); + fclose(shader_f); + + memset(final_shader_code, 0, sizeof(final_shader_code)); + memcpy(final_shader_code, master_shader_code, filter_token_location); + strcpy(final_shader_code + filter_token_location, shader_code); + strcat(final_shader_code + filter_token_location, + master_shader_code + filter_token_location + sizeof("{filter}") - 1); + + shader->program = create_program(vertex_shader, final_shader_code); + + // Attributes + shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); + // Uniforms + shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "origin"); + + glGenTextures(1, &shader->texture); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->texture_uniform = glGetUniformLocation(shader->program, "image"); + + glGenTextures(1, &shader->previous_texture); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); + + shader->blending_mode_uniform = glGetUniformLocation(shader->program, "frame_blending_mode"); + + // Program + + glUseProgram(shader->program); + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint vbo; + glGenBuffers(1, &vbo); + + // Attributes + + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, + }; + + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(shader->position_attribute); + glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); + + return true; +} + +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode) +{ + glUseProgram(shader->program); + glUniform2f(shader->origin_uniform, x, y); + glUniform2f(shader->resolution_uniform, w, h); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(shader->texture_uniform, 0); + glUniform1i(shader->blending_mode_uniform, previous? blending_mode : GB_FRAME_BLENDING_MODE_DISABLED); + if (previous) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(shader->previous_texture_uniform, 1); + } + glBindFragDataLocation(shader->program, 0, "frag_color"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +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); + + if (major * 0x100 + minor < 0x302) { + return; + } + + glDeleteProgram(shader->program); + glDeleteTextures(1, &shader->texture); + glDeleteTextures(1, &shader->previous_texture); + +} diff --git a/thirdparty/SameBoy-old/SDL/shader.h b/thirdparty/SameBoy-old/SDL/shader.h new file mode 100644 index 000000000..149958d50 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/shader.h @@ -0,0 +1,34 @@ +#ifndef shader_h +#define shader_h +#include "opengl_compat.h" +#include + +typedef struct shader_s { + GLuint resolution_uniform; + GLuint origin_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint blending_mode_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} shader_t; + +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_t; + +bool init_shader_with_name(shader_t *shader, const char *name); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode); +void free_shader(struct shader_s *shader); + +#endif /* shader_h */ diff --git a/thirdparty/SameBoy-old/SDL/utils.c b/thirdparty/SameBoy-old/SDL/utils.c new file mode 100644 index 000000000..603e34a83 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/utils.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include "utils.h" + +static const char *resource_folder(void) +{ + static const char *ret = NULL; + if (!ret) { + ret = SDL_GetBasePath(); + if (!ret) { + ret = "./"; + } + } + return ret; +} + +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; +} + + +void replace_extension(const char *src, size_t length, char *dest, const char *ext) +{ + memcpy(dest, src, length); + dest[length] = 0; + + /* Remove extension */ + for (size_t i = length; i--;) { + if (dest[i] == '/') break; + if (dest[i] == '.') { + dest[i] = 0; + break; + } + } + + /* Add new extension */ + strcat(dest, ext); +} diff --git a/thirdparty/SameBoy-old/SDL/utils.h b/thirdparty/SameBoy-old/SDL/utils.h new file mode 100644 index 000000000..5c0383d38 --- /dev/null +++ b/thirdparty/SameBoy-old/SDL/utils.h @@ -0,0 +1,8 @@ +#ifndef utils_h +#define utils_h +#include + +char *resource_path(const char *filename); +void replace_extension(const char *src, size_t length, char *dest, const char *ext); + +#endif /* utils_h */ diff --git a/thirdparty/SameBoy-old/Shaders/AAOmniScaleLegacy.fsh b/thirdparty/SameBoy-old/Shaders/AAOmniScaleLegacy.fsh new file mode 100644 index 000000000..b84e2cedd --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/AAOmniScaleLegacy.fsh @@ -0,0 +1,118 @@ + +STATIC float quickDistance(vec4 a, vec4 b) +{ + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); +} + +STATIC vec4 omniScale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + vec2 pos = fract(pixel); + + /* Special handling for diaonals */ + bool hasDownDiagonal = false; + bool hasUpDiagonal = false; + if (equal(q12, q21) && inequal(q11, q22)) hasUpDiagonal = true; + else if (inequal(q12, q21) && equal(q11, q22)) hasDownDiagonal = true; + else if (equal(q12, q21) && equal(q11, q22)) { + if (equal(q11, q12)) return q11; + int diagonalBias = 0; + for (float y = -1.0; y < 3.0; y++) { + for (float x = -1.0; x < 3.0; x++) { + vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); + if (equal(color, q11)) diagonalBias++; + if (equal(color, q12)) diagonalBias--; + } + } + if (diagonalBias <= 0) { + hasDownDiagonal = true; + } + if (diagonalBias >= 0) { + hasUpDiagonal = true; + } + } + + if (hasUpDiagonal || hasDownDiagonal) { + vec4 downDiagonalResult, upDiagonalResult; + + if (hasUpDiagonal) { + float diagonalPos = pos.x + pos.y; + + if (diagonalPos < 0.5) { + upDiagonalResult = q11; + } + else if (diagonalPos > 1.5) { + upDiagonalResult = q22; + } + else { + upDiagonalResult = q12; + } + } + + if (hasDownDiagonal) { + float diagonalPos = 1.0 - pos.x + pos.y; + + if (diagonalPos < 0.5) { + downDiagonalResult = q21; + } + else if (diagonalPos > 1.5) { + downDiagonalResult = q12; + } + else { + downDiagonalResult = q11; + } + } + + if (!hasUpDiagonal) return downDiagonalResult; + if (!hasDownDiagonal) return upDiagonalResult; + return mix(downDiagonalResult, upDiagonalResult, 0.5); + } + + vec4 r1 = mix(q11, q21, fract(pos.x)); + vec4 r2 = mix(q12, q22, fract(pos.x)); + + vec4 unqunatized = mix(r1, r2, fract(pos.y)); + + float q11d = quickDistance(unqunatized, q11); + float q21d = quickDistance(unqunatized, q21); + float q12d = quickDistance(unqunatized, q12); + float q22d = quickDistance(unqunatized, q22); + + float best = min(q11d, + min(q21d, + min(q12d, + q22d))); + + if (equal(q11d, best)) { + return q11; + } + + if (equal(q21d, best)) { + return q21; + } + + if (equal(q12d, best)) { + return q12; + } + + return q22; +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = vec2(1.0, 1.0) / output_resolution; + // 4-pixel super sampling + + vec4 q11 = omniScale(image, position + pixel * vec2(-0.25, -0.25), input_resolution, output_resolution); + vec4 q21 = omniScale(image, position + pixel * vec2(+0.25, -0.25), input_resolution, output_resolution); + vec4 q12 = omniScale(image, position + pixel * vec2(-0.25, +0.25), input_resolution, output_resolution); + vec4 q22 = omniScale(image, position + pixel * vec2(+0.25, +0.25), input_resolution, output_resolution); + + return (q11 + q21 + q12 + q22) / 4.0; +} diff --git a/thirdparty/SameBoy-old/Shaders/AAScale2x.fsh b/thirdparty/SameBoy-old/Shaders/AAScale2x.fsh new file mode 100644 index 000000000..b1b35cefe --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/AAScale2x.fsh @@ -0,0 +1,38 @@ +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + // vec4 A = texture(image, position + vec2( -o.x, o.y)); + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); + vec2 p = position * input_resolution; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); +} diff --git a/thirdparty/SameBoy-old/Shaders/AAScale4x.fsh b/thirdparty/SameBoy-old/Shaders/AAScale4x.fsh new file mode 100644 index 000000000..f8237c791 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/AAScale4x.fsh @@ -0,0 +1,75 @@ +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); + vec2 p = position * input_resolution; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } +} +STATIC vec4 scale2x_wrapper(sampler2D t, vec2 pos, vec2 offset, vec2 input_resolution, vec2 output_resolution) +{ + vec2 origin = (floor(pos * input_resolution * 2)) + vec2(0.5, 0.5); + return scale2x(t, (origin + offset) / input_resolution / 2, input_resolution, output_resolution); +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + vec4 B = scale2x_wrapper(image, position, vec2( 0, 1), input_resolution, output_resolution); + vec4 D = scale2x_wrapper(image, position, vec2( -1, 0), input_resolution, output_resolution); + vec4 E = scale2x_wrapper(image, position, vec2( 0, 0), input_resolution, output_resolution); + vec4 F = scale2x_wrapper(image, position, vec2( 1, 0), input_resolution, output_resolution); + vec4 H = scale2x_wrapper(image, position, vec2( 0, -1), input_resolution, output_resolution); + + vec4 R; + vec2 p = position * input_resolution * 2.; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + R = equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + R = equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + R = equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + R = equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } + + return mix(R, E, 0.5); +} diff --git a/thirdparty/SameBoy-old/Shaders/Bilinear.fsh b/thirdparty/SameBoy-old/Shaders/Bilinear.fsh new file mode 100644 index 000000000..e68e1d19f --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/Bilinear.fsh @@ -0,0 +1,14 @@ +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + vec4 r1 = mix(q11, q21, fract(pixel.x)); + vec4 r2 = mix(q12, q22, fract(pixel.x)); + + return mix (r1, r2, fract(pixel.y)); +} diff --git a/thirdparty/SameBoy-old/Shaders/CRT.fsh b/thirdparty/SameBoy-old/Shaders/CRT.fsh new file mode 100644 index 000000000..25e5b37f4 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/CRT.fsh @@ -0,0 +1,162 @@ +#define COLOR_LOW 0.7 +#define COLOR_HIGH 1.0 +#define VERTICAL_BORDER_DEPTH 0.6 +#define SCANLINE_DEPTH 0.3 +#define CURVENESS 0.3 + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + /* Curve and pixel ratio */ + float y_curve = cos(position.x - 0.5) * CURVENESS + (1 - CURVENESS); + float y_multiplier = 8.0 / 7.0 / y_curve; + position.y *= y_multiplier; + position.y -= (y_multiplier - 1) / 2; + if (position.y < 0.0) return vec4(0,0,0,0); + if (position.y > 1.0) return vec4(0,0,0,0); + + float x_curve = cos(position.y - 0.5) * CURVENESS + (1 - CURVENESS); + float x_multiplier = 1/x_curve; + position.x *= x_multiplier; + position.x -= (x_multiplier - 1) / 2; + if (position.x < 0.0) return vec4(0,0,0,0); + if (position.x > 1.0) return vec4(0,0,0,0); + + /* Setting up common vars */ + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); + + vec4 center = texture_relative(image, position, vec2(0, 0)); + vec4 left = texture_relative(image, position, vec2(-1, 0)); + vec4 right = texture_relative(image, position, vec2(1, 0)); + + /* Vertical blurring */ + if (pos.y < 1.0 / 6.0) { + center = mix(center, texture_relative(image, position, vec2( 0, -1)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, -1)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, -1)), 0.5 - sub_pos.y / 2.0); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, texture_relative(image, position, vec2( 0, 1)), sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, 1)), sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, 1)), sub_pos.y / 2.0); + } + + /* Scanlines */ + float scanline_multiplier; + if (pos.y < 0.5) { + scanline_multiplier = (pos.y * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else { + scanline_multiplier = ((1 - pos.y) * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + center *= scanline_multiplier; + left *= scanline_multiplier; + right *= scanline_multiplier; + + /* Vertical seperator for shadow masks */ + bool odd = bool(int((position * input_resolution).x) & 1); + if (odd) { + pos.y += 0.5; + pos.y = fract(pos.y); + } + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + + /* Blur the edges of the separators of adjacent columns */ + if (pos.x < 1.0 / 6.0 || pos.x > 5.0 / 6.0) { + pos.y += 0.5; + pos.y = fract(pos.y); + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + } + + + /* Subpixel blurring, like LCD filter*/ + + vec4 midleft = mix(left, center, 0.5); + vec4 midright = mix(right, center, 0.5); + + vec4 ret; + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + /* Anti alias the curve */ + vec2 pixel_position = position * output_resolution; + if (pixel_position.x < 1) { + ret *= pixel_position.x; + } + else if (pixel_position.x > output_resolution.x - 1) { + ret *= output_resolution.x - pixel_position.x; + } + if (pixel_position.y < 1) { + ret *= pixel_position.y; + } + else if (pixel_position.y > output_resolution.y - 1) { + ret *= output_resolution.y - pixel_position.y; + } + + return ret; +} diff --git a/thirdparty/SameBoy-old/Shaders/HQ2x.fsh b/thirdparty/SameBoy-old/Shaders/HQ2x.fsh new file mode 100644 index 000000000..0baf9e146 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/HQ2x.fsh @@ -0,0 +1,113 @@ +/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ + +/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is + also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ +STATIC vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + +STATIC bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +STATIC vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +{ + return (c1 * w1 + c2 * w2) / (w1 + w2); +} + +STATIC vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +{ + return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1); + + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) o.x = -o.x; + if (p.y > 0.5) o.y = -o.y; + + vec4 w0 = texture_relative(image, position, vec2( -o.x, -o.y)); + vec4 w1 = texture_relative(image, position, vec2( 0, -o.y)); + vec4 w2 = texture_relative(image, position, vec2( o.x, -o.y)); + vec4 w3 = texture_relative(image, position, vec2( -o.x, 0)); + vec4 w4 = texture_relative(image, position, vec2( 0, 0)); + vec4 w5 = texture_relative(image, position, vec2( o.x, 0)); + vec4 w6 = texture_relative(image, position, vec2( -o.x, o.y)); + vec4 w7 = texture_relative(image, position, vec2( 0, o.y)); + vec4 w8 = texture_relative(image, position, vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1; + if (is_different(w1, w4)) pattern |= 2; + if (is_different(w2, w4)) pattern |= 4; + if (is_different(w3, w4)) pattern |= 8; + if (is_different(w5, w4)) pattern |= 16; + if (is_different(w6, w4)) pattern |= 32; + if (is_different(w7, w4)) pattern |= 64; + if (is_different(w8, w4)) pattern |= 128; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0B,0x08)) { + return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); + } + if (P(0x0B,0x02)) { + return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); + } + if (P(0x2F,0x2F)) { + return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); + } + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) { + return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + } + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + } + + return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); +} diff --git a/thirdparty/SameBoy-old/Shaders/LCD.fsh b/thirdparty/SameBoy-old/Shaders/LCD.fsh new file mode 100644 index 000000000..d49ac57ed --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/LCD.fsh @@ -0,0 +1,68 @@ +#define COLOR_LOW 0.8 +#define COLOR_HIGH 1.0 +#define SCANLINE_DEPTH 0.1 + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); + + vec4 center = texture_relative(image, position, vec2(0, 0)); + vec4 left = texture_relative(image, position, vec2(-1, 0)); + vec4 right = texture_relative(image, position, vec2(1, 0)); + + if (pos.y < 1.0 / 6.0) { + center = mix(center, texture_relative(image, position, vec2( 0, -1)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, -1)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, -1)), 0.5 - sub_pos.y / 2.0); + center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, texture_relative(image, position, vec2( 0, 1)), sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, 1)), sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, 1)), sub_pos.y / 2.0); + center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + + vec4 midleft = mix(left, center, 0.5); + vec4 midright = mix(right, center, 0.5); + + vec4 ret; + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + return ret; +} diff --git a/thirdparty/SameBoy-old/Shaders/MasterShader.fsh b/thirdparty/SameBoy-old/Shaders/MasterShader.fsh new file mode 100644 index 000000000..24bba3cbe --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/MasterShader.fsh @@ -0,0 +1,78 @@ +#version 150 +uniform sampler2D image; +uniform sampler2D previous_image; +uniform int frame_blending_mode; + +uniform vec2 output_resolution; +uniform vec2 origin; + +#define equal(x, y) ((x) == (y)) +#define inequal(x, y) ((x) != (y)) +#define STATIC +#define GAMMA (2.2) + +out vec4 frag_color; + +vec4 _texture(sampler2D t, vec2 pos) +{ + return pow(texture(t, pos), vec4(GAMMA)); +} + +vec4 texture_relative(sampler2D t, vec2 pos, vec2 offset) +{ + vec2 input_resolution = textureSize(t, 0); + return _texture(t, (floor(pos * input_resolution) + offset + vec2(0.5, 0.5)) / input_resolution); +} + +#define texture _texture + +#line 1 +{filter} + + +#define BLEND_BIAS (2.0/5.0) + +#define DISABLED 0 +#define SIMPLE 1 +#define ACCURATE 2 +#define ACCURATE_EVEN ACCURATE +#define ACCURATE_ODD 3 + +void main() +{ + vec2 position = gl_FragCoord.xy - origin; + position /= output_resolution; + position.y = 1 - position.y; + vec2 input_resolution = textureSize(image, 0); + + float ratio; + switch (frame_blending_mode) { + default: + case DISABLED: + frag_color = pow(scale(image, position, input_resolution, output_resolution), vec4(1.0 / GAMMA)); + return; + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; + } + + frag_color = pow(mix(scale(image, position, input_resolution, output_resolution), + scale(previous_image, position, input_resolution, output_resolution), ratio), vec4(1.0 / GAMMA)); + +} diff --git a/thirdparty/SameBoy-old/Shaders/MasterShader.metal b/thirdparty/SameBoy-old/Shaders/MasterShader.metal new file mode 100644 index 000000000..7ba04dda6 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/MasterShader.metal @@ -0,0 +1,101 @@ +#include +#include +#include + +using namespace metal; + +/* For GLSL compatibility */ +typedef float2 vec2; +typedef float3 vec3; +typedef float4 vec4; +typedef texture2d sampler2D; +#define equal(x, y) all((x) == (y)) +#define inequal(x, y) any((x) != (y)) +#define STATIC static +#define GAMMA (2.2) + +typedef struct { + float4 position [[position]]; + float2 texcoords; +} rasterizer_data; + +// Vertex Function +vertex rasterizer_data vertex_shader(uint index [[ vertex_id ]], + constant vector_float2 *vertices [[ buffer(0) ]]) +{ + rasterizer_data out; + + out.position.xy = vertices[index].xy; + out.position.z = 0.0; + out.position.w = 1.0; + out.texcoords = (vertices[index].xy + float2(1, 1)) / 2.0; + + return out; +} + + +static inline float4 texture(texture2d texture, float2 pos) +{ + constexpr sampler texture_sampler; + return pow(float4(texture.sample(texture_sampler, pos)), GAMMA); +} + +__attribute__((unused)) static inline float4 texture_relative(texture2d t, float2 pos, float2 offset) +{ + float2 input_resolution = float2(t.get_width(), t.get_height());; + float2 origin = (floor(pos * input_resolution)) + float2(0.5, 0.5); + return texture(t, (origin + offset) / input_resolution); +} + +#line 1 +{filter} + +#define BLEND_BIAS (2.0/5.0) + +enum frame_blending_mode { + DISABLED, + SIMPLE, + ACCURATE, + ACCURATE_EVEN = ACCURATE, + ACCURATE_ODD, +}; + +fragment float4 fragment_shader(rasterizer_data in [[stage_in]], + texture2d image [[ texture(0) ]], + texture2d previous_image [[ texture(1) ]], + constant enum frame_blending_mode *frame_blending_mode [[ buffer(0) ]], + constant float2 *output_resolution [[ buffer(1) ]]) +{ + float2 input_resolution = float2(image.get_width(), image.get_height()); + + in.texcoords.y = 1 - in.texcoords.y; + float ratio; + switch (*frame_blending_mode) { + default: + case DISABLED: + return pow(scale(image, in.texcoords, input_resolution, *output_resolution), 1 / GAMMA); + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; + } + + return pow(mix(scale(image, in.texcoords, input_resolution, *output_resolution), + scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio), 1 / GAMMA); +} + diff --git a/thirdparty/SameBoy-old/Shaders/MonoLCD.fsh b/thirdparty/SameBoy-old/Shaders/MonoLCD.fsh new file mode 100644 index 000000000..009e1db17 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/MonoLCD.fsh @@ -0,0 +1,50 @@ +#define SCANLINE_DEPTH 0.25 +#define BLOOM 0.4 + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + vec2 s = smoothstep(0., 1., fract(pixel)); + + vec4 r1 = mix(q11, q21, s.x); + vec4 r2 = mix(q12, q22, s.x); + + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); + + float multiplier = 1.0; + + if (pos.y < 1.0 / 6.0) { + multiplier *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.y > 5.0 / 6.0) { + multiplier *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + if (pos.x < 1.0 / 6.0) { + multiplier *= sub_pos.x * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.x > 5.0 / 6.0) { + multiplier *= (1.0 - sub_pos.x) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + vec4 pre_shadow = mix(texture(image, position) * multiplier, mix(r1, r2, s.y), BLOOM); + pixel += vec2(-0.6, -0.8); + + q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + r1 = mix(q11, q21, fract(pixel.x)); + r2 = mix(q12, q22, fract(pixel.x)); + + vec4 shadow = mix(r1, r2, fract(pixel.y)); + return mix(min(shadow, pre_shadow), pre_shadow, 0.75); +} diff --git a/thirdparty/SameBoy-old/Shaders/NearestNeighbor.fsh b/thirdparty/SameBoy-old/Shaders/NearestNeighbor.fsh new file mode 100644 index 000000000..7f37024c0 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/NearestNeighbor.fsh @@ -0,0 +1,4 @@ +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + return texture(image, position); +} diff --git a/thirdparty/SameBoy-old/Shaders/OmniScale.fsh b/thirdparty/SameBoy-old/Shaders/OmniScale.fsh new file mode 100644 index 000000000..28ce3211d --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/OmniScale.fsh @@ -0,0 +1,262 @@ +/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences: + - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows + scaling to any factor. + - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + */ + +/* We use the same colorspace as the HQ algorithms. */ +STATIC vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + + +STATIC bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1); + + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) { + o.x = -o.x; + p.x = 1.0 - p.x; + } + if (p.y > 0.5) { + o.y = -o.y; + p.y = 1.0 - p.y; + } + + vec4 w0 = texture_relative(image, position, vec2( -o.x, -o.y)); + vec4 w1 = texture_relative(image, position, vec2( 0, -o.y)); + vec4 w2 = texture_relative(image, position, vec2( o.x, -o.y)); + vec4 w3 = texture_relative(image, position, vec2( -o.x, 0)); + vec4 w4 = texture_relative(image, position, vec2( 0, 0)); + vec4 w5 = texture_relative(image, position, vec2( o.x, 0)); + vec4 w6 = texture_relative(image, position, vec2( -o.x, o.y)); + vec4 w7 = texture_relative(image, position, vec2( 0, o.y)); + vec4 w8 = texture_relative(image, position, vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1 << 0; + if (is_different(w1, w4)) pattern |= 1 << 1; + if (is_different(w2, w4)) pattern |= 1 << 2; + if (is_different(w3, w4)) pattern |= 1 << 3; + if (is_different(w5, w4)) pattern |= 1 << 4; + if (is_different(w6, w4)) pattern |= 1 << 5; + if (is_different(w7, w4)) pattern |= 1 << 6; + if (is_different(w8, w4)) pattern |= 1 << 7; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return mix(w4, w3, 0.5 - p.x); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return mix(w4, w1, 0.5 - p.y); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); + } + if (P(0x0B,0x08)) { + return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); + } + if (P(0x0B,0x02)) { + return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + } + if (P(0x2F,0x2F)) { + float dist = length(p - vec2(0.5)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + if (dist < 0.5 - pixel_size / 2) { + return w4; + } + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist > 0.5 + pixel_size / 2) { + return r; + } + return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + float dist = p.x - 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (dist > pixel_size / 2) { + return w1; + } + vec4 r = mix(w3, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w1, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + float dist = p.y - 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (p.y - 2.0 * p.x > pixel_size / 2) { + return w3; + } + vec4 r = mix(w1, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w3, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x8F) || P(0x7E,0x0E)) { + float dist = p.x + 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (dist > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x7E,0x2A) || P(0xEF,0xAB)) { + float dist = p.y + 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return mix(w4, w3, 0.5 - p.x); + } + + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return mix(w4, w1, 0.5 - p.y); + } + + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + } + + if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 0.5 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + if (P(0x0B,0x01)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + } + + if (P(0x0B,0x00)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + } + + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + /* We need more samples to "solve" this diagonal */ + vec4 x0 = texture_relative(image, position, vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture_relative(image, position, vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture_relative(image, position, vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture_relative(image, position, vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture_relative(image, position, vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture_relative(image, position, vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture_relative(image, position, vec2( -o.x * 2.0, o.y )); + + if (is_different(x0, w4)) pattern |= 1 << 8; + if (is_different(x1, w4)) pattern |= 1 << 9; + if (is_different(x2, w4)) pattern |= 1 << 10; + if (is_different(x3, w4)) pattern |= 1 << 11; + if (is_different(x4, w4)) pattern |= 1 << 12; + if (is_different(x5, w4)) pattern |= 1 << 13; + if (is_different(x6, w4)) pattern |= 1 << 14; + + int diagonal_bias = -7; + while (pattern != 0) { + diagonal_bias += pattern & 1; + pattern >>= 1; + } + + if (diagonal_bias <= 0) { + vec4 r = mix(w1, w3, p.y - p.x + 0.5); + if (dist < 0.5 - pixel_size / 2) { + return r; + } + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + return w4; +} diff --git a/thirdparty/SameBoy-old/Shaders/OmniScaleLegacy.fsh b/thirdparty/SameBoy-old/Shaders/OmniScaleLegacy.fsh new file mode 100644 index 000000000..06849fdd0 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/OmniScaleLegacy.fsh @@ -0,0 +1,104 @@ +STATIC float quickDistance(vec4 a, vec4 b) +{ + return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z); +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + vec2 pos = fract(pixel); + + /* Special handling for diaonals */ + bool hasDownDiagonal = false; + bool hasUpDiagonal = false; + if (equal(q12, q21) && inequal(q11, q22)) hasUpDiagonal = true; + else if (inequal(q12, q21) && equal(q11, q22)) hasDownDiagonal = true; + else if (equal(q12, q21) && equal(q11, q22)) { + if (equal(q11, q12)) return q11; + int diagonalBias = 0; + for (float y = -1.0; y < 3.0; y++) { + for (float x = -1.0; x < 3.0; x++) { + vec4 color = texture(image, (pixel + vec2(x, y)) / input_resolution); + if (equal(color, q11)) diagonalBias++; + if (equal(color, q12)) diagonalBias--; + } + } + if (diagonalBias <= 0) { + hasDownDiagonal = true; + } + if (diagonalBias >= 0) { + hasUpDiagonal = true; + } + } + + if (hasUpDiagonal || hasDownDiagonal) { + vec4 downDiagonalResult, upDiagonalResult; + + if (hasUpDiagonal) { + float diagonalPos = pos.x + pos.y; + + if (diagonalPos < 0.5) { + upDiagonalResult = q11; + } + else if (diagonalPos > 1.5) { + upDiagonalResult = q22; + } + else { + upDiagonalResult = q12; + } + } + + if (hasDownDiagonal) { + float diagonalPos = 1.0 - pos.x + pos.y; + + if (diagonalPos < 0.5) { + downDiagonalResult = q21; + } + else if (diagonalPos > 1.5) { + downDiagonalResult = q12; + } + else { + downDiagonalResult = q11; + } + } + + if (!hasUpDiagonal) return downDiagonalResult; + if (!hasDownDiagonal) return upDiagonalResult; + return mix(downDiagonalResult, upDiagonalResult, 0.5); + } + + vec4 r1 = mix(q11, q21, fract(pos.x)); + vec4 r2 = mix(q12, q22, fract(pos.x)); + + vec4 unqunatized = mix(r1, r2, fract(pos.y)); + + float q11d = quickDistance(unqunatized, q11); + float q21d = quickDistance(unqunatized, q21); + float q12d = quickDistance(unqunatized, q12); + float q22d = quickDistance(unqunatized, q22); + + float best = min(q11d, + min(q21d, + min(q12d, + q22d))); + + if (equal(q11d, best)) { + return q11; + } + + if (equal(q21d, best)) { + return q21; + } + + if (equal(q12d, best)) { + return q12; + } + + return q22; +} diff --git a/thirdparty/SameBoy-old/Shaders/Scale2x.fsh b/thirdparty/SameBoy-old/Shaders/Scale2x.fsh new file mode 100644 index 000000000..44bcfc4d6 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/Scale2x.fsh @@ -0,0 +1,35 @@ +/* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */ + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); + vec2 p = position * input_resolution; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } +} diff --git a/thirdparty/SameBoy-old/Shaders/Scale4x.fsh b/thirdparty/SameBoy-old/Shaders/Scale4x.fsh new file mode 100644 index 000000000..93c4b2159 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/Scale4x.fsh @@ -0,0 +1,72 @@ +STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); + vec2 p = position * input_resolution; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } +} + +STATIC vec4 scale2x_wrapper(sampler2D t, vec2 pos, vec2 offset, vec2 input_resolution, vec2 output_resolution) +{ + vec2 origin = (floor(pos * input_resolution * 2)) + vec2(0.5, 0.5); + return scale2x(t, (origin + offset) / input_resolution / 2, input_resolution, output_resolution); +} + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // texel arrangement + // A B C + // D E F + // G H I + vec4 B = scale2x_wrapper(image, position, vec2( 0, 1), input_resolution, output_resolution); + vec4 D = scale2x_wrapper(image, position, vec2( -1, 0), input_resolution, output_resolution); + vec4 E = scale2x_wrapper(image, position, vec2( 0, 0), input_resolution, output_resolution); + vec4 F = scale2x_wrapper(image, position, vec2( 1, 0), input_resolution, output_resolution); + vec4 H = scale2x_wrapper(image, position, vec2( 0, -1), input_resolution, output_resolution); + + vec2 p = position * input_resolution * 2.; + // p = the position within a pixel [0...1] + p = fract(p); + if (p.x > .5) { + if (p.y > .5) { + // Top Right + return equal(B, F) && inequal(B, D) && inequal(F, H) ? F : E; + } else { + // Bottom Right + return equal(H, F) && inequal(D, H) && inequal(B, F) ? F : E; + } + } else { + if (p.y > .5) { + // Top Left + return equal(D, B) && inequal(B, F) && inequal(D, H) ? D : E; + } else { + // Bottom Left + return equal(D, H) && inequal(D, B) && inequal(H, F) ? D : E; + } + } +} diff --git a/thirdparty/SameBoy-old/Shaders/SmoothBilinear.fsh b/thirdparty/SameBoy-old/Shaders/SmoothBilinear.fsh new file mode 100644 index 000000000..d50827723 --- /dev/null +++ b/thirdparty/SameBoy-old/Shaders/SmoothBilinear.fsh @@ -0,0 +1,16 @@ +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + vec2 pixel = position * input_resolution - vec2(0.5, 0.5); + + vec4 q11 = texture(image, (floor(pixel) + 0.5) / input_resolution); + vec4 q12 = texture(image, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / input_resolution); + vec4 q21 = texture(image, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / input_resolution); + vec4 q22 = texture(image, (ceil(pixel) + 0.5) / input_resolution); + + vec2 s = smoothstep(0., 1., fract(pixel)); + + vec4 r1 = mix(q11, q21, s.x); + vec4 r2 = mix(q12, q22, s.x); + + return mix (r1, r2, s.y); +} diff --git a/thirdparty/SameBoy-old/Tester/main.c b/thirdparty/SameBoy-old/Tester/main.c new file mode 100644 index 000000000..a1c89a52f --- /dev/null +++ b/thirdparty/SameBoy-old/Tester/main.c @@ -0,0 +1,537 @@ +// The tester requires low-level access to the GB struct to detect failures +#define GB_INTERNAL + +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define snprintf _snprintf +#else +#include +#endif + +#include +#include + +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, + do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right, + semi_random, limit_start, pointer_control, unsafe_speed_switch; +static unsigned int test_length = 60 * 40; +GB_gameboy_t gb; + +static unsigned int frames = 0; +static bool use_tga = false; +static 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 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[256*224]; + +static char *async_input_callback(GB_gameboy_t *gb) +{ + return NULL; +} + +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. */ + if (push_start_a && (frames < test_length - 120 || do_not_stop)) { + unsigned combo_length = 40; + if (start_is_not_first || push_a_twice) combo_length = 60; /* The start item in the menu is not the first, so also push down */ + else if (a_is_bad || start_is_bad) combo_length = 20; /* Pressing A has a negative effect (when trying to start the game). */ + + if (semi_random) { + if (frames % 10 == 0) { + unsigned key = (((frames / 20) * 0x1337cafe) >> 29) & 7; + gb->keys[0][key] = (frames % 20) == 0; + } + } + else { + switch ((push_faster ? frames * 2 : + push_slower ? frames / 2 : + push_a_twice? frames / 4: + frames) % combo_length + (start_is_bad? 20 : 0) ) { + case 0: + if (!limit_start || frames < 20 * 60) { + GB_set_key_state(gb, push_right? GB_KEY_RIGHT: GB_KEY_START, true); + } + if (pointer_control) { + GB_set_key_state(gb, GB_KEY_LEFT, true); + GB_set_key_state(gb, GB_KEY_UP, true); + } + + break; + case 10: + GB_set_key_state(gb, push_right? GB_KEY_RIGHT: GB_KEY_START, false); + if (pointer_control) { + GB_set_key_state(gb, GB_KEY_LEFT, false); + GB_set_key_state(gb, GB_KEY_UP, false); + } + break; + case 20: + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, true); + break; + case 30: + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, false); + break; + case 40: + if (push_a_twice) { + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, true); + } + else if (gb->boot_rom_finished) { + GB_set_key_state(gb, GB_KEY_DOWN, true); + } + break; + case 50: + GB_set_key_state(gb, b_is_confirm? GB_KEY_B: GB_KEY_A, false); + GB_set_key_state(gb, GB_KEY_DOWN, false); + break; + } + } + } + +} + +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + /* Detect common crashes and stop the test early */ + if (frames < test_length - 1) { + if (gb->backtrace_size >= 0x200 + (large_stack? 0x80: 0) || (!allow_weird_sp_values && (gb->registers[GB_REGISTER_SP] >= 0xfe00 && gb->registers[GB_REGISTER_SP] < 0xff80))) { + GB_log(gb, "A stack overflow has probably occurred. (SP = $%04x; backtrace size = %d) \n", + gb->registers[GB_REGISTER_SP], gb->backtrace_size); + frames = test_length - 1; + } + if (gb->halted && !gb->interrupt_enable && gb->speed_switch_halt_countdown == 0) { + GB_log(gb, "The game is deadlocked.\n"); + frames = test_length - 1; + } + } + + if (frames >= test_length && !gb->disable_rendering) { + bool is_screen_blank = true; + if (!gb->sgb) { + for (unsigned i = 160 * 144; i--;) { + if (bitmap[i] != bitmap[0]) { + is_screen_blank = false; + break; + } + } + } + else { + if (gb->sgb->mask_mode == 0) { + for (unsigned i = 160 * 144; i--;) { + if (gb->sgb->screen_buffer[i] != gb->sgb->screen_buffer[0]) { + is_screen_blank = false; + break; + } + } + } + } + + /* 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"); + if (use_tga) { + tga_header[0xC] = GB_get_screen_width(gb); + tga_header[0xD] = GB_get_screen_width(gb) >> 8; + tga_header[0xE] = GB_get_screen_height(gb); + tga_header[0xF] = GB_get_screen_height(gb) >> 8; + fwrite(&tga_header, 1, sizeof(tga_header), f); + } + else { + (*(uint32_t *)&bmp_header[0x2]) = sizeof(bmp_header) + sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2; + (*(uint32_t *)&bmp_header[0x12]) = GB_get_screen_width(gb); + (*(int32_t *)&bmp_header[0x16]) = -GB_get_screen_height(gb); + (*(uint32_t *)&bmp_header[0x22]) = sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2; + fwrite(&bmp_header, 1, sizeof(bmp_header), f); + } + fwrite(&bitmap, 1, sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb), f); + fclose(f); + if (!gb->boot_rom_finished) { + GB_log(gb, "Boot ROM did not finish.\n"); + } + 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; + } + } + else if (frames >= test_length - 1) { + gb->disable_rendering = false; + } +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + if (!log_file) log_file = fopen(log_filename, "w"); + fprintf(log_file, "%s", string); +} + +#ifdef __APPLE__ +#include +#endif + +static const char *executable_folder(void) +{ + static char path[1024] = {0,}; + if (path[0]) { + return path; + } + /* Ugly unportable code! :( */ +#ifdef __APPLE__ + uint32_t length = sizeof(path) - 1; + _NSGetExecutablePath(&path[0], &length); +#else +#ifdef __linux__ + size_t __attribute__((unused)) length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1); + assert(length != -1); +#else +#ifdef _WIN32 + HMODULE hModule = GetModuleHandle(NULL); + GetModuleFileName(hModule, path, sizeof(path) - 1); +#else + /* No OS-specific way, assume running from CWD */ + getcwd(&path[0], sizeof(path) - 1); + return path; +#endif +#endif +#endif + size_t pos = strlen(path); + while (pos) { + pos--; +#ifdef _WIN32 + if (path[pos] == '\\') { +#else + if (path[pos] == '/') { +#endif + path[pos] = 0; + break; + } + } + return path; +} + +static char *executable_relative_path(const char *filename) +{ + static char path[1024]; + snprintf(path, sizeof(path), "%s/%s", executable_folder(), filename); + return path; +} + +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) +{ + memcpy(dest, src, length); + dest[length] = 0; + + /* Remove extension */ + for (size_t i = length; i--;) { + if (dest[i] == '/') break; + if (dest[i] == '.') { + dest[i] = 0; + break; + } + } + + /* Add new extension */ + strcat(dest, ext); +} + + +int main(int argc, char **argv) +{ + fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n"); + + if (argc == 1) { + fprintf(stderr, "Usage: %s [--dmg] [--sgb] [--cgb] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" +#ifndef _WIN32 + " [--jobs number of tests to run simultaneously]" +#endif + " rom ...\n", argv[0]); + exit(1); + } + +#ifndef _WIN32 + unsigned int max_forks = 1; + unsigned int current_forks = 0; +#endif + + bool dmg = false; + bool sgb = false; + bool sav = false; + const char *boot_rom_path = NULL; + + GB_random_set_enabled(false); + + for (unsigned i = 1; i < argc; i++) { + if (strcmp(argv[i], "--dmg") == 0) { + fprintf(stderr, "Using DMG mode\n"); + dmg = true; + sgb = false; + continue; + } + + if (strcmp(argv[i], "--sgb") == 0) { + fprintf(stderr, "Using SGB mode\n"); + sgb = true; + dmg = false; + continue; + } + + if (strcmp(argv[i], "--cgb") == 0) { + fprintf(stderr, "Using CGB mode\n"); + dmg = false; + sgb = false; + 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"); + push_start_a = true; + continue; + } + + if (strcmp(argv[i], "--length") == 0 && i != argc - 1) { + test_length = atoi(argv[++i]) * 60; + fprintf(stderr, "Test length is %d seconds\n", test_length / 60); + continue; + } + + if (strcmp(argv[i], "--boot") == 0 && i != argc - 1) { + fprintf(stderr, "Using boot ROM %s\n", argv[i + 1]); + boot_rom_path = argv[++i]; + 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]); + /* Make sure wrong input doesn't blow anything up. */ + if (max_forks < 1) max_forks = 1; + if (max_forks > 16) max_forks = 16; + fprintf(stderr, "Running up to %d tests simultaneously\n", max_forks); + continue; + } + + if (max_forks > 1) { + while (current_forks >= max_forks) { + int wait_out; + while (wait(&wait_out) == -1); + current_forks--; + } + + current_forks++; + if (fork() != 0) continue; + } +#endif + filename = argv[i]; + 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, use_tga? ".tga" : ".bmp"); + bmp_filename = &bitmap_path[0]; + + char log_path[path_length + 5]; + 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) { + GB_init(&gb, GB_MODEL_DMG_B); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("dmg_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("dmg_boot.bin")); + exit(1); + } + } + else if (sgb) { + GB_init(&gb, GB_MODEL_SGB2); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("sgb2_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("sgb2_boot.bin")); + exit(1); + } + } + else { + GB_init(&gb, GB_MODEL_CGB_E); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("cgb_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("cgb_boot.bin")); + exit(1); + } + } + + GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank); + GB_set_pixels_output(&gb, &bitmap[0]); + GB_set_rgb_encode_callback(&gb, rgb_encode); + 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); + GB_set_emulate_joypad_bouncing(&gb, false); // Adds too much noise + + if (GB_load_rom(&gb, filename)) { + perror("Failed to load ROM"); + exit(1); + } + + /* Game specific hacks for start attempt automations */ + /* It's OK. No overflow is possible here. */ + start_is_not_first = strcmp((const char *)(gb.rom + 0x134), "NEKOJARA") == 0 || + strcmp((const char *)(gb.rom + 0x134), "GINGA") == 0; + a_is_bad = strcmp((const char *)(gb.rom + 0x134), "DESERT STRIKE") == 0 || + /* Restarting in Puzzle Boy/Kwirk (Start followed by A) leaks stack. */ + strcmp((const char *)(gb.rom + 0x134), "KWIRK") == 0 || + strcmp((const char *)(gb.rom + 0x134), "PUZZLE BOY") == 0; + start_is_bad = strcmp((const char *)(gb.rom + 0x134), "BLUESALPHA") == 0 || + 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), "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; + push_slower = strcmp((const char *)(gb.rom + 0x134), "BAKENOU") == 0; + do_not_stop = strcmp((const char *)(gb.rom + 0x134), "SPACE INVADERS") == 0; + push_right = memcmp((const char *)(gb.rom + 0x134), "BOB ET BOB", strlen("BOB ET BOB")) == 0 || + strcmp((const char *)(gb.rom + 0x134), "LITTLE MASTER") == 0 || + /* M&M's Minis Madness Demo (which has no menu but the same title as the full game) */ + (memcmp((const char *)(gb.rom + 0x134), "MINIMADNESSBMIE", strlen("MINIMADNESSBMIE")) == 0 && + gb.rom[0x14e] == 0x6c); + /* This game has some terrible menus. */ + semi_random = strcmp((const char *)(gb.rom + 0x134), "KUKU GAME") == 0; + + + + /* This game temporarily sets SP to OAM RAM */ + allow_weird_sp_values = strcmp((const char *)(gb.rom + 0x134), "WDL:TT") == 0 || + /* Some mooneye-gb tests abuse the stack */ + strcmp((const char *)(gb.rom + 0x134), "mooneye-gb test") == 0; + + /* This game uses some recursive algorithms and therefore requires quite a large call stack */ + large_stack = memcmp((const char *)(gb.rom + 0x134), "MICRO EPAK1BM", strlen("MICRO EPAK1BM")) == 0 || + strcmp((const char *)(gb.rom + 0x134), "TECMO BOWL") == 0; + /* High quality game that leaks stack whenever you open the menu (with start), + but requires pressing start to play it. */ + limit_start = strcmp((const char *)(gb.rom + 0x134), "DIVA STARS") == 0; + large_stack |= limit_start; + + /* Pressing start while in the map in Tsuri Sensei will leak an internal screen-stack which + will eventually overflow, override an array of jump-table indexes, jump to a random + address, execute an invalid opcode, and crash. Pressing A twice while slowing down + will prevent this scenario. */ + push_a_twice = strcmp((const char *)(gb.rom + 0x134), "TURI SENSEI V1") == 0; + + /* Yes, you should totally use a cursor point & click interface for the language select menu. */ + 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; + frames = 0; + unsigned cycles = 0; + while (running) { + cycles += GB_run(&gb); + if (cycles >= 139810) { /* Approximately 1/60 a second. Intentionally not the actual length of a frame. */ + handle_buttons(&gb); + cycles -= 139810; + frames++; + } + /* This early crash test must not run in vblank because PC might not point to the next instruction. */ + if (gb.pc == 0x38 && frames < test_length - 1 && GB_read_memory(&gb, 0x38) == 0xFF) { + GB_log(&gb, "The game is probably stuck in an FF loop.\n"); + frames = test_length - 1; + } + } + + + if (log_file) { + fclose(log_file); + log_file = NULL; + } + + GB_free(&gb); +#ifndef _WIN32 + if (max_forks > 1) { + exit(0); + } +#endif + } +#ifndef _WIN32 + int wait_out; + while (wait(&wait_out) != -1); +#endif + return 0; +} + diff --git a/thirdparty/SameBoy-old/Windows/dirent.c b/thirdparty/SameBoy-old/Windows/dirent.c new file mode 100644 index 000000000..f5fd8b3b2 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/dirent.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include "dirent.h" + +DIR *opendir(const char *filename) +{ + wchar_t w_filename[MAX_PATH + 3] = {0,}; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, MAX_PATH); + if (length) { + w_filename[length - 1] = L'/'; + w_filename[length] = L'*'; + w_filename[length + 1] = 0; + } + DIR *ret = malloc(sizeof(*ret)); + ret->handle = FindFirstFileW(w_filename, &ret->entry); + if (ret->handle == INVALID_HANDLE_VALUE) { + free(ret); + return NULL; + } + + return ret; +} + +int closedir(DIR *dir) +{ + if (dir->handle != INVALID_HANDLE_VALUE) { + FindClose(dir->handle); + } + free(dir); + return 0; +} + +struct dirent *readdir(DIR *dir) +{ + if (dir->handle == INVALID_HANDLE_VALUE) { + return NULL; + } + + WideCharToMultiByte(CP_UTF8, 0, dir->entry.cFileName, -1, + dir->out_entry.d_name, sizeof(dir->out_entry.d_name), + NULL, NULL); + + if (!FindNextFileW(dir->handle, &dir->entry)) { + FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + return &dir->out_entry; +} diff --git a/thirdparty/SameBoy-old/Windows/dirent.h b/thirdparty/SameBoy-old/Windows/dirent.h new file mode 100644 index 000000000..7102995b2 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/dirent.h @@ -0,0 +1,15 @@ +#include + +struct dirent { + char d_name[MAX_PATH + 1]; +}; + +typedef struct { + HANDLE handle; + WIN32_FIND_DATAW entry; + struct dirent out_entry; +} DIR; + +DIR *opendir(const char *filename); +int closedir(DIR *dir); +struct dirent *readdir(DIR *dir); diff --git a/thirdparty/SameBoy-old/Windows/inttypes.h b/thirdparty/SameBoy-old/Windows/inttypes.h new file mode 100644 index 000000000..9a6118bd8 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/inttypes.h @@ -0,0 +1 @@ +#include diff --git a/thirdparty/SameBoy-old/Windows/math.h b/thirdparty/SameBoy-old/Windows/math.h new file mode 100644 index 000000000..2b934f90f --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/math.h @@ -0,0 +1,9 @@ +#pragma once +#include_next +#ifndef __MINGW32__ +/* "Old" (Pre-2015) Windows headers/libc don't have round. */ +static inline double round(double f) +{ + return f >= 0? (int)(f + 0.5) : (int)(f - 0.5); +} +#endif \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Windows/pthread.h b/thirdparty/SameBoy-old/Windows/pthread.h new file mode 100644 index 000000000..7bf328bb0 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/pthread.h @@ -0,0 +1,86 @@ +/* Very minimal pthread implementation for Windows */ +#include +#include + +typedef HANDLE pthread_t; +typedef struct pthread_attr_s pthread_attr_t; + +static inline int pthread_create(pthread_t *pthread, + const pthread_attr_t *attrs, + LPTHREAD_START_ROUTINE function, + void *context) +{ + assert(!attrs); + *pthread = CreateThread(NULL, 0, function, + context, 0, NULL); + return *pthread? 0 : GetLastError(); +} + + +typedef struct { + unsigned status; + CRITICAL_SECTION cs; +} pthread_mutex_t; +#define PTHREAD_MUTEX_INITIALIZER {0,} + +static inline CRITICAL_SECTION *pthread_mutex_to_cs(pthread_mutex_t *mutex) +{ +retry: + if (mutex->status == 2) return &mutex->cs; + + unsigned expected = 0; + unsigned desired = 1; + if (__atomic_compare_exchange(&mutex->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + InitializeCriticalSection(&mutex->cs); + mutex->status = 2; + return &mutex->cs; + } + goto retry; +} + +static inline int pthread_mutex_lock(pthread_mutex_t *mutex) +{ + EnterCriticalSection(pthread_mutex_to_cs(mutex)); + return 0; +} + +static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + LeaveCriticalSection(pthread_mutex_to_cs(mutex)); + return 0; +} + +typedef struct { + unsigned status; + CONDITION_VARIABLE cond; +} pthread_cond_t; +#define PTHREAD_COND_INITIALIZER {0,} + +static inline CONDITION_VARIABLE *pthread_cond_to_win(pthread_cond_t *cond) +{ +retry: + if (cond->status == 2) return &cond->cond; + + unsigned expected = 0; + unsigned desired = 1; + if (__atomic_compare_exchange(&cond->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + InitializeConditionVariable(&cond->cond); + cond->status = 2; + return &cond->cond; + } + goto retry; +} + + +static inline int pthread_cond_signal(pthread_cond_t *cond) +{ + WakeConditionVariable(pthread_cond_to_win(cond)); + return 0; +} + +static inline int pthread_cond_wait(pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + // Mutex is locked therefore already initialized + return SleepConditionVariableCS(pthread_cond_to_win(cond), &mutex->cs, INFINITE); +} diff --git a/thirdparty/SameBoy-old/Windows/resources.rc b/thirdparty/SameBoy-old/Windows/resources.rc new file mode 100644 index 000000000..73c12139e Binary files /dev/null and b/thirdparty/SameBoy-old/Windows/resources.rc differ diff --git a/thirdparty/SameBoy-old/Windows/sameboy.ico b/thirdparty/SameBoy-old/Windows/sameboy.ico new file mode 100644 index 000000000..17f072c14 Binary files /dev/null and b/thirdparty/SameBoy-old/Windows/sameboy.ico differ diff --git a/thirdparty/SameBoy-old/Windows/stdint.h b/thirdparty/SameBoy-old/Windows/stdint.h new file mode 100644 index 000000000..cbe84d560 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/stdint.h @@ -0,0 +1,3 @@ +#pragma once +#include_next +typedef intptr_t ssize_t; \ No newline at end of file diff --git a/thirdparty/SameBoy-old/Windows/stdio.h b/thirdparty/SameBoy-old/Windows/stdio.h new file mode 100644 index 000000000..4a9279e3d --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/stdio.h @@ -0,0 +1,91 @@ +#pragma once +#include_next +#include +#include + +int access(const char *filename, int mode); +#define R_OK 4 +#define W_OK 2 + +#ifndef __MINGW32__ +#ifndef __LIBRETRO__ +static inline int vasprintf(char **str, const char *fmt, va_list args) +{ + size_t size = _vscprintf(fmt, args) + 1; + *str = (char*)malloc(size); + int ret = vsprintf(*str, fmt, args); + if (ret != size - 1) { + free(*str); + *str = NULL; + return -1; + } + return ret; +} + +static inline int asprintf(char **strp, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int r = vasprintf(strp, fmt, args); + va_end(args); + return r; +} + +#endif +#endif + +/* This code is public domain -- Will Hartung 4/9/09 */ +static inline size_t getline(char **lineptr, size_t *n, FILE *stream) +{ + char *bufptr = NULL; + char *p = bufptr; + size_t size; + int c; + + if (lineptr == NULL) { + return -1; + } + if (stream == NULL) { + return -1; + } + if (n == NULL) { + return -1; + } + bufptr = *lineptr; + size = *n; + + c = fgetc(stream); + if (c == EOF) { + return -1; + } + if (bufptr == NULL) { + bufptr = (char*)malloc(128); + if (bufptr == NULL) { + return -1; + } + size = 128; + } + p = bufptr; + while (c != EOF) { + if ((p - bufptr) > (size - 1)) { + size = size + 128; + bufptr = (char*)realloc(bufptr, size); + if (bufptr == NULL) { + return -1; + } + } + *p++ = c; + if (c == '\n') { + break; + } + c = fgetc(stream); + } + + *p++ = '\0'; + *lineptr = bufptr; + *n = size; + + return p - bufptr - 1; +} + +#define snprintf _snprintf diff --git a/thirdparty/SameBoy-old/Windows/string.h b/thirdparty/SameBoy-old/Windows/string.h new file mode 100644 index 000000000..f1cf6b1e9 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/string.h @@ -0,0 +1,4 @@ +#pragma once +#include_next +#define strdup _strdup +#define strcasecmp _stricmp diff --git a/thirdparty/SameBoy-old/Windows/unistd.h b/thirdparty/SameBoy-old/Windows/unistd.h new file mode 100644 index 000000000..c17587e45 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/unistd.h @@ -0,0 +1,10 @@ +#include +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define read(...) _read(__VA_ARGS__) +#define write(...) _write(__VA_ARGS__) +#define isatty(...) _isatty(__VA_ARGS__) +#define close(...) _close(__VA_ARGS__) +#define creat(...) _creat(__VA_ARGS__) diff --git a/thirdparty/SameBoy-old/Windows/utf8_compat.c b/thirdparty/SameBoy-old/Windows/utf8_compat.c new file mode 100644 index 000000000..9264e2e80 --- /dev/null +++ b/thirdparty/SameBoy-old/Windows/utf8_compat.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +FILE *fopen(const char *filename, const char *mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + + wchar_t w_mode[8] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, mode, -1, w_mode, sizeof(w_mode) / sizeof(w_mode[0])); + + return _wfopen(w_filename, w_mode); +} + +int access(const char *filename, int mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + + return _waccess(w_filename, mode); +} + +int _creat(const char *filename, int mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + return _wcreat(w_filename, mode & 0700); +} + diff --git a/thirdparty/SameBoy-old/libretro/Makefile b/thirdparty/SameBoy-old/libretro/Makefile new file mode 100644 index 000000000..ada200df2 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/Makefile @@ -0,0 +1,373 @@ +STATIC_LINKING := 0 +AR := ar + +CFLAGS := -Wall $(CFLAGS) + +GIT_VERSION ?= " $(shell git rev-parse --short HEAD || echo unknown)" +ifneq ($(GIT_VERSION)," unknown") + CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" +endif + +SPACE := +SPACE := $(SPACE) $(SPACE) +BACKSLASH := +BACKSLASH := \$(BACKSLASH) +filter_out1 = $(filter-out $(firstword $1),$1) +filter_out2 = $(call filter_out1,$(call filter_out1,$1)) +unixpath = $(subst \,/,$1) +unixcygpath = /$(subst :,,$(call unixpath,$1)) + +ifeq ($(platform),) +platform = unix +ifeq ($(shell uname -a),) + platform = win +else ifneq ($(findstring MINGW,$(shell uname -a)),) + platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + platform = osx +else ifneq ($(findstring win,$(shell uname -a)),) + platform = win +endif +endif + +# system platform +system_platform = unix +ifeq ($(shell uname -a),) + EXE_EXT = .exe + system_platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + system_platform = osx + arch = intel +ifeq ($(shell uname -p),powerpc) + arch = ppc +endif +else ifneq ($(findstring MINGW,$(shell uname -a)),) + system_platform = win +endif + +ifeq ($(platform), win) + INCFLAGS += -I Windows +endif + +CORE_DIR = ../ + +TARGET_NAME = sameboy +LIBM = -lm + +ifeq ($(ARCHFLAGS),) +ifeq ($(archs),ppc) + ARCHFLAGS = -arch ppc -arch ppc64 +else + ARCHFLAGS = -arch i386 -arch x86_64 +endif +endif + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS) + CXXFLAGS := -fsanitize=$(SANITIZER) $(CXXFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) -lasan +endif + +ifeq ($(platform), osx) +ifndef ($(NOUNIVERSAL)) + CFLAGS += $(ARCHFLAGS) + LFLAGS += $(ARCHFLAGS) +endif +endif + +ifeq ($(STATIC_LINKING), 1) +EXT := a +endif + +ifeq ($(platform), unix) + EXT ?= so + TARGET := $(TARGET_NAME)_libretro.$(EXT) + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined +else ifeq ($(platform), linux-portable) + TARGET := $(TARGET_NAME)_libretro.$(EXT) + fpic := -fPIC -nostdlib + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T + 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 + +########################### +# 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 +########################### + +####################################### +# Nintendo Switch (libtransistor) +else ifeq ($(platform), switch) + TARGET := $(TARGET_NAME)_libretro_$(platform).a + include $(LIBTRANSISTOR_HOME)/libtransistor.mk + CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls + STATIC_LINKING=1 +# Nintendo Switch (libnx) +else ifeq ($(platform), libnx) + include $(DEVKITPRO)/libnx/switch_rules + TARGET := $(TARGET_NAME)_libretro_$(platform).a + DEFINES += -DSWITCH=1 -D__SWITCH__ -DARM + CFLAGS += $(DEFINES) -fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec + CFLAGS += -march=armv8-a -mtune=cortex-a57 -mtp=soft -mcpu=cortex-a57+crc+fp+simd -ffast-math + CXXFLAGS := $(ASFLAGS) $(CFLAGS) + STATIC_LINKING = 1 +# Nintendo WiiU +else ifeq ($(platform), wiiu) + TARGET := $(TARGET_NAME)_libretro_$(platform).a + CC ?= $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + AR ?= $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + 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 +else ifneq (,$(findstring osx,$(platform))) + TARGET := $(TARGET_NAME)_libretro.dylib + fpic := -fPIC + SHARED := -dynamiclib +else ifneq (,$(findstring ios,$(platform))) + TARGET := $(TARGET_NAME)_libretro_ios.dylib + fpic := -fPIC + SHARED := -dynamiclib + +ifeq ($(IOSSDK),) + IOSSDK := $(shell xcodebuild -version -sdk iphoneos Path) +endif + + DEFINES := -DIOS +ifeq ($(platform),ios-arm64) + CC = cc -arch armv64 -isysroot $(IOSSDK) +else + CC = cc -arch armv7 -isysroot $(IOSSDK) +endif +ifeq ($(platform),$(filter $(platform),ios9 ios-arm64)) +CC += -miphoneos-version-min=8.0 +CFLAGS += -miphoneos-version-min=8.0 +else +CC += -miphoneos-version-min=5.0 +CFLAGS += -miphoneos-version-min=5.0 +endif +else ifneq (,$(findstring qnx,$(platform))) + TARGET := $(TARGET_NAME)_libretro_qnx.so + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined +else ifeq ($(platform), emscripten) + TARGET := $(TARGET_NAME)_libretro_emscripten.bc + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined +else ifeq ($(platform), vita) + 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 + STATIC_LINKING = 1 + +# Windows MSVC 2017 all architectures +else ifneq (,$(findstring windows_msvc2017,$(platform))) + + NO_GCC := 1 + CFLAGS += -DNOMINMAX + CXXFLAGS += -DNOMINMAX + WINDOWS_VERSION = 1 + + PlatformSuffix = $(subst windows_msvc2017_,,$(platform)) + ifneq (,$(findstring desktop,$(PlatformSuffix))) + WinPartition = desktop + MSVC2017CompileFlags = -DWINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP -FS + LDFLAGS += -MANIFEST -LTCG:incremental -NXCOMPAT -DYNAMICBASE -DEBUG -OPT:REF -INCREMENTAL:NO -SUBSYSTEM:WINDOWS -MANIFESTUAC:"level='asInvoker' uiAccess='false'" -OPT:ICF -ERRORREPORT:PROMPT -NOLOGO -TLBID:1 + LIBS += kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib + else ifneq (,$(findstring uwp,$(PlatformSuffix))) + WinPartition = uwp + MSVC2017CompileFlags = -DWINAPI_FAMILY=WINAPI_FAMILY_APP -D_WINDLL -D_UNICODE -DUNICODE -D__WRL_NO_DEFAULT_LIB__ -EHsc -FS + LDFLAGS += -APPCONTAINER -NXCOMPAT -DYNAMICBASE -MANIFEST:NO -LTCG -OPT:REF -SUBSYSTEM:CONSOLE -MANIFESTUAC:NO -OPT:ICF -ERRORREPORT:PROMPT -NOLOGO -TLBID:1 -DEBUG:FULL -WINMD:NO + LIBS += WindowsApp.lib + endif + + CFLAGS += $(MSVC2017CompileFlags) + CXXFLAGS += $(MSVC2017CompileFlags) + + TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) + + 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)%") + ProgramFiles86 := $(shell cygpath "$(ProgramFiles86w)") + + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_CURRENT_USER\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0) + WindowsSdkDir := $(WindowsSdkDir) + + WindowsSDKVersion ?= $(firstword $(foreach folder,$(subst $(subst \,/,$(WindowsSdkDir)Include/),,$(wildcard $(call fix_path,$(WindowsSdkDir)Include\*))),$(if $(wildcard $(call fix_path,$(WindowsSdkDir)Include/$(folder)/um/Windows.h)),$(folder),)))$(BACKSLASH) + WindowsSDKVersion := $(WindowsSDKVersion) + + VsInstallBuildTools = $(ProgramFiles86)/Microsoft Visual Studio/2017/BuildTools + VsInstallEnterprise = $(ProgramFiles86)/Microsoft Visual Studio/2017/Enterprise + VsInstallProfessional = $(ProgramFiles86)/Microsoft Visual Studio/2017/Professional + VsInstallCommunity = $(ProgramFiles86)/Microsoft Visual Studio/2017/Community + + VsInstallRoot ?= $(shell if [ -d "$(VsInstallBuildTools)" ]; then echo "$(VsInstallBuildTools)"; fi) + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallEnterprise)" ]; then echo "$(VsInstallEnterprise)"; fi) + endif + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallProfessional)" ]; then echo "$(VsInstallProfessional)"; fi) + endif + ifeq ($(VsInstallRoot), ) + VsInstallRoot = $(shell if [ -d "$(VsInstallCommunity)" ]; then echo "$(VsInstallCommunity)"; fi) + endif + VsInstallRoot := $(VsInstallRoot) + + VcCompilerToolsVer := $(shell cat "$(VsInstallRoot)/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt" | grep -o '[0-9\.]*') + VcCompilerToolsDir := $(VsInstallRoot)/VC/Tools/MSVC/$(VcCompilerToolsVer) + + WindowsSDKSharedIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\shared") + WindowsSDKUCRTIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\ucrt") + WindowsSDKUMIncludeDir := $(shell cygpath -w "$(WindowsSdkDir)\Include\$(WindowsSDKVersion)\um") + WindowsSDKUCRTLibDir := $(shell cygpath -w "$(WindowsSdkDir)\Lib\$(WindowsSDKVersion)\ucrt\$(TargetArchMoniker)") + WindowsSDKUMLibDir := $(shell cygpath -w "$(WindowsSdkDir)\Lib\$(WindowsSDKVersion)\um\$(TargetArchMoniker)") + + # For some reason the HostX86 compiler doesn't like compiling for x64 + # ("no such file" opening a shared library), and vice-versa. + # Work around it for now by using the strictly x86 compiler for x86, and x64 for x64. + # NOTE: What about ARM? + ifneq (,$(findstring x64,$(TargetArchMoniker))) + VCCompilerToolsBinDir := $(VcCompilerToolsDir)\bin\HostX64 + else + VCCompilerToolsBinDir := $(VcCompilerToolsDir)\bin\HostX86 + endif + + PATH := $(shell IFS=$$'\n'; cygpath "$(VCCompilerToolsBinDir)/$(TargetArchMoniker)"):$(PATH) + PATH := $(PATH):$(shell IFS=$$'\n'; cygpath "$(VsInstallRoot)/Common7/IDE") + INCLUDE := $(shell IFS=$$'\n'; cygpath -w "$(VcCompilerToolsDir)/include") + LIB := $(shell IFS=$$'\n'; cygpath -w "$(VcCompilerToolsDir)/lib/$(TargetArchMoniker)") + ifneq (,$(findstring uwp,$(PlatformSuffix))) + LIB := $(LIB);$(shell IFS=$$'\n'; cygpath -w "$(LIB)/store") + endif + + export INCLUDE := $(INCLUDE);$(WindowsSDKSharedIncludeDir);$(WindowsSDKUCRTIncludeDir);$(WindowsSDKUMIncludeDir) + export LIB := $(LIB);$(WindowsSDKUCRTLibDir);$(WindowsSDKUMLibDir) + TARGET := $(TARGET_NAME)_libretro.dll + PSS_STYLE :=2 + LDFLAGS += -DLL + +else + 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 + +TARGET := $(CORE_DIR)/build/bin/$(TARGET) + +# To force use of the Unix version instead of the Windows version +MKDIR := $(shell which mkdir) + +LDFLAGS += $(LIBM) + +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g +else + CFLAGS += -O2 -DNDEBUG +endif + +include Makefile.common + +CFLAGS += -DGB_VERSION=\"$(VERSION)\" + +OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) + +OBJOUT = -o +LINKOUT = -o + +ifneq (,$(findstring msvc,$(platform))) + OBJOUT = -Fo + LINKOUT = -out: +ifeq ($(STATIC_LINKING),1) + LD ?= lib.exe + STATIC_LINKING=0 +else + LD = link.exe +endif +else + LD = $(CC) +endif + +CFLAGS += -D__LIBRETRO__ $(fpic) $(INCFLAGS) -std=gnu11 -D_GNU_SOURCE -D_USE_MATH_DEFINES + +all: $(TARGET) + +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin + echo "/* AUTO-GENERATED */" > $@ + echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ +ifneq ($(findstring Haiku,$(shell uname -s)),) + # turns out od is posix, hexdump is not hence is less portable + # this is still rather ugly and could be done better I guess + od -A none -t x1 -v $< | sed -e 's/^\ /0x/' -e 's/\ /,\ 0x/g' -e 's/$$/,/g' | tr '\n' ' ' >> $@ +else + hexdump -v -e '/1 "0x%02x, "' $< >> $@ +endif + echo "};" >> $@ + echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ + +$(CORE_DIR)/build/bin/BootROMs/%_boot.bin: + $(MAKE) -C $(CORE_DIR) $(patsubst $(CORE_DIR)/%,%,$@) + +$(TARGET): $(OBJECTS) + -@$(MKDIR) -p $(dir $@) +ifeq ($(STATIC_LINKING), 1) + $(AR) rcs $@ $(OBJECTS) +else + $(LD) $(fpic) $(SHARED) $(INCFLAGS) $(LINKOUT)$@ $(OBJECTS) $(LDFLAGS) +endif + +$(CORE_DIR)/build/obj/%_libretro.c.o: $(CORE_DIR)/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) -c $(OBJOUT)$@ $< $(CFLAGS) $(fpic) -DGB_INTERNAL + +%.o: %.c + $(CC) $(CFLAGS) $(fpic) -c $(OBJOUT)$@ $< + +clean: + rm -f $(OBJECTS) $(TARGET) + +.PHONY: clean + diff --git a/thirdparty/SameBoy-old/libretro/Makefile.common b/thirdparty/SameBoy-old/libretro/Makefile.common new file mode 100644 index 000000000..fabe3ad4a --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/Makefile.common @@ -0,0 +1,29 @@ +include $(CORE_DIR)/version.mk + +INCFLAGS := -I$(CORE_DIR) + +SOURCES_C := $(CORE_DIR)/Core/gb.c \ + $(CORE_DIR)/Core/sgb.c \ + $(CORE_DIR)/Core/apu.c \ + $(CORE_DIR)/Core/memory.c \ + $(CORE_DIR)/Core/mbc.c \ + $(CORE_DIR)/Core/timing.c \ + $(CORE_DIR)/Core/display.c \ + $(CORE_DIR)/Core/symbol_hash.c \ + $(CORE_DIR)/Core/camera.c \ + $(CORE_DIR)/Core/sm83_cpu.c \ + $(CORE_DIR)/Core/joypad.c \ + $(CORE_DIR)/Core/save_state.c \ + $(CORE_DIR)/Core/random.c \ + $(CORE_DIR)/Core/rumble.c \ + $(CORE_DIR)/libretro/agb_boot.c \ + $(CORE_DIR)/libretro/cgb_boot.c \ + $(CORE_DIR)/libretro/dmg_boot.c \ + $(CORE_DIR)/libretro/sgb_boot.c \ + $(CORE_DIR)/libretro/sgb2_boot.c \ + $(CORE_DIR)/libretro/libretro.c + +CFLAGS += -DGB_DISABLE_TIMEKEEPING -DGB_DISABLE_REWIND -DGB_DISABLE_DEBUGGER -DGB_DISABLE_CHEATS + + +SOURCES_CXX := diff --git a/thirdparty/SameBoy-old/libretro/jni/Android.mk b/thirdparty/SameBoy-old/libretro/jni/Android.mk new file mode 100644 index 000000000..8ac1b3bab --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/jni/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH := $(call my-dir) + +CORE_DIR := $(LOCAL_PATH)/../.. + +CFLAGS := + +include $(CORE_DIR)/libretro/Makefile.common + +GENERATED_SOURCES := $(filter %_boot.c,$(SOURCES_C)) + +COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DGB_VERSION=\"$(VERSION)\" -Wno-multichar -DANDROID + +GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" +ifneq ($(GIT_VERSION)," unknown") + COREFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" +endif + +include $(CLEAR_VARS) +LOCAL_MODULE := retro +LOCAL_SRC_FILES := $(SOURCES_C) +LOCAL_CFLAGS := -std=c99 $(COREFLAGS) $(CFLAGS) +LOCAL_LDFLAGS := -Wl,-version-script=$(CORE_DIR)/libretro/link.T +include $(BUILD_SHARED_LIBRARY) + +$(CORE_DIR)/libretro/%_boot.c: $(CORE_DIR)/build/bin/BootROMs/%_boot.bin + echo "/* AUTO-GENERATED */" > $@ + echo "const unsigned char $(notdir $(@:%.c=%))[] = {" >> $@ + hexdump -v -e '/1 "0x%02x, "' $< >> $@ + echo "};" >> $@ + echo "const unsigned $(notdir $(@:%.c=%))_length = sizeof($(notdir $(@:%.c=%)));" >> $@ + +.INTERMEDIATE: $(GENERATED_SOURCES) diff --git a/thirdparty/SameBoy-old/libretro/jni/Application.mk b/thirdparty/SameBoy-old/libretro/jni/Application.mk new file mode 100644 index 000000000..a252a72d7 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/jni/Application.mk @@ -0,0 +1 @@ +APP_ABI := all diff --git a/thirdparty/SameBoy-old/libretro/libretro.c b/thirdparty/SameBoy-old/libretro/libretro.c new file mode 100644 index 000000000..eb5f2dd68 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/libretro.c @@ -0,0 +1,1817 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef WIIU +#define AUDIO_FREQUENCY 384000 +#else +/* Use the internal sample rate for the Wii U */ +#define AUDIO_FREQUENCY 48000 +#endif + +#ifdef _WIN32 +#include +#include +#define snprintf _snprintf +#endif + +#include +#include "libretro.h" +#include "libretro_core_options.inc" + +#ifdef _WIN32 +static const char slash = '\\'; +#else +static const char slash = '/'; +#endif + +#define MAX_VIDEO_WIDTH 256 +#define MAX_VIDEO_HEIGHT 224 +#define MAX_VIDEO_PIXELS (MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT) + +#define RETRO_MEMORY_GAMEBOY_1_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) +#define RETRO_MEMORY_GAMEBOY_2_SRAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_GAMEBOY_2_RTC ((3 << 8) | RETRO_MEMORY_RTC) + +#define RETRO_GAME_TYPE_GAMEBOY_LINK_2P 0x101 + +enum rom_type { + ROM_TYPE_INVALID, + ROM_TYPE_DMG, + ROM_TYPE_SGB, + ROM_TYPE_CGB +}; + +enum model { + MODEL_DMG_B, + MODEL_CGB_C, + MODEL_CGB_E, + MODEL_AGB, + MODEL_SGB_PAL, + MODEL_SGB_NTSC, + MODEL_SGB2, + MODEL_AUTO +}; + +static const GB_model_t libretro_to_internal_model[] = +{ + [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_A, + [MODEL_SGB_PAL] = GB_MODEL_SGB_PAL, + [MODEL_SGB_NTSC] = GB_MODEL_SGB_NTSC, + [MODEL_SGB2] = GB_MODEL_SGB2 +}; + +enum screen_layout { + LAYOUT_TOP_DOWN, + LAYOUT_LEFT_RIGHT +}; + +enum audio_out { + GB_1, + GB_2 +}; + +static enum model model[2] = { + MODEL_DMG_B, + MODEL_DMG_B +}; +static enum model auto_model[2] = { + MODEL_CGB_E, + MODEL_CGB_E +}; +static enum model auto_sgb_model[2] = { + MODEL_SGB_NTSC, + MODEL_SGB_NTSC +}; +static bool auto_sgb_enabled[2] = { + false, + false +}; + +static uint32_t *frame_buf = NULL; +static uint32_t *frame_buf_copy = NULL; +static uint32_t retained_frame_1[256 * 224]; +static uint32_t retained_frame_2[256 * 224]; +static struct retro_log_callback logging; +static retro_log_printf_t log_cb; + +static retro_video_refresh_t video_cb; +static retro_audio_sample_batch_t audio_batch_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; +static unsigned audio_out = 0; + +static bool geometry_updated = false; +static bool link_cable_emulation = false; +/*static bool infrared_emulation = false;*/ + +static struct { + int16_t *data; + int32_t size; + int32_t capacity; +} output_audio_buffer = {NULL, 0, 0}; + +char retro_system_directory[4096]; + +GB_gameboy_t gameboy[2]; + +extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[], sgb_boot[], sgb2_boot[]; +extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length; +bool vblank1_occurred = false, vblank2_occurred = false; + +static void fallback_log(enum retro_log_level level, const char *fmt, ...) +{ + (void)level; + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); +} + +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, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)); + GB_set_key_state_for_player(gb, GB_KEY_LEFT, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)); + GB_set_key_state_for_player(gb, GB_KEY_UP, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_UP)); + GB_set_key_state_for_player(gb, GB_KEY_DOWN, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)); + GB_set_key_state_for_player(gb, GB_KEY_A, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_A)); + GB_set_key_state_for_player(gb, GB_KEY_B, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_B)); + GB_set_key_state_for_player(gb, GB_KEY_SELECT, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT)); + GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0, + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_START)); + +} + +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); + } + else if (gb == &gameboy[1]) { + rumble.set_rumble_state(1, RETRO_RUMBLE_STRONG, 65535 * amplitude); + } +} + +static void ensure_output_audio_buffer_capacity(int32_t capacity) +{ + if (capacity <= output_audio_buffer.capacity) { + return; + } + output_audio_buffer.data = realloc( + output_audio_buffer.data, capacity * sizeof(*output_audio_buffer.data)); + output_audio_buffer.capacity = capacity; + log_cb(RETRO_LOG_DEBUG, "Output audio buffer capacity set to %d\n", capacity); +} + +static void init_output_audio_buffer(int32_t capacity) +{ + output_audio_buffer.data = NULL; + output_audio_buffer.size = 0; + output_audio_buffer.capacity = 0; + ensure_output_audio_buffer_capacity(capacity); +} + +static void free_output_audio_buffer() +{ + free(output_audio_buffer.data); + output_audio_buffer.data = NULL; + output_audio_buffer.size = 0; + output_audio_buffer.capacity = 0; +} + +static void upload_output_audio_buffer() +{ + int32_t remaining_frames = output_audio_buffer.size / 2; + int16_t *buf_pos = output_audio_buffer.data; + + while (remaining_frames > 0) { + size_t uploaded_frames = audio_batch_cb(buf_pos, remaining_frames); + buf_pos += uploaded_frames * 2; + remaining_frames -= uploaded_frames; + } + output_audio_buffer.size = 0; +} + +static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) +{ + if (!(audio_out == GB_1 && gb == &gameboy[0]) && + !(audio_out == GB_2 && gb == &gameboy[1])) { + return; + } + + if (output_audio_buffer.capacity - output_audio_buffer.size < 2) { + ensure_output_audio_buffer_capacity(output_audio_buffer.capacity * 1.5); + } + + output_audio_buffer.data[output_audio_buffer.size++] = sample->left; + output_audio_buffer.data[output_audio_buffer.size++] = sample->right; +} + +static void vblank1(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_1, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } + vblank1_occurred = true; +} + +static void vblank2(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_2, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } + vblank2_occurred = true; +} + +static void lcd_status_change_1(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_1, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + +static void lcd_status_change_2(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_2, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + +static bool bit_to_send1 = true, bit_to_send2 = true; + +static void serial_start1(GB_gameboy_t *gb, bool bit_received) +{ + bit_to_send1 = bit_received; +} + +static bool serial_end1(GB_gameboy_t *gb) +{ + bool ret = GB_serial_get_data_bit(&gameboy[1]); + GB_serial_set_data_bit(&gameboy[1], bit_to_send1); + return ret; +} + +static void serial_start2(GB_gameboy_t *gb, bool bit_received) +{ + bit_to_send2 = bit_received; +} + +static bool serial_end2(GB_gameboy_t *gb) +{ + bool ret = GB_serial_get_data_bit(&gameboy[0]); + GB_serial_set_data_bit(&gameboy[0], bit_to_send2); + 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; +} + +static retro_environment_t environ_cb; + +static void set_variable_visibility(void) +{ + struct retro_core_option_display option_display_singlecart; + struct retro_core_option_display option_display_dualcart; + + 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 + while (true) { + 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_auto_sgb_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_auto_sgb_model_1") == 0) || + (strcmp(key, "sameboy_model_2") == 0) || + (strcmp(key, "sameboy_auto_sgb_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 }, + { "rtc", RETRO_MEMORY_GAMEBOY_1_RTC }, +}; + +static const struct retro_subsystem_memory_info gb2_memory[] = { + { "srm", RETRO_MEMORY_GAMEBOY_2_SRAM }, + { "rtc", RETRO_MEMORY_GAMEBOY_2_RTC }, +}; + +static const struct retro_subsystem_rom_info gb_roms[] = { + { "GameBoy #1", "gb|gbc", false, false, true, gb1_memory, 1 }, + { "GameBoy #2", "gb|gbc", false, false, true, gb2_memory, 1 }, +}; + +static const struct retro_subsystem_info subsystems[] = { + { "2 Player Game Boy Link", "gb_link_2p", gb_roms, 2, RETRO_GAME_TYPE_GAMEBOY_LINK_2P }, + { NULL }, +}; + +static const struct retro_controller_description controllers[] = { + { "Nintendo Game Boy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, +}; + +static const struct retro_controller_description controllers_sgb[] = { + { "SNES/SFC Gamepad", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) }, +}; + +static struct retro_input_descriptor descriptors_1p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + +static struct retro_input_descriptor descriptors_2p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + +static struct retro_input_descriptor descriptors_4p[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0 }, +}; + + +static void set_link_cable_state(bool state) +{ + 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); + 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); + } +} + +static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) +{ + const char *model_name = (char *[]) { + [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_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_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_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_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_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]; + + 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); + log_cb(RETRO_LOG_INFO, "Loading boot image: %s\n", buf); + + if (GB_load_boot_rom(gb, buf)) { + GB_load_boot_rom_from_buffer(gb, boot_code, boot_length); + } +} + +static void retro_set_memory_maps(void) +{ + struct retro_memory_descriptor descs[11]; + size_t size; + uint16_t bank; + unsigned i; + + + /* todo: add netplay awareness for this so achievements can be granted on the respective client */ + i = 0; + memset(descs, 0, sizeof(descs)); + + descs[0].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IE, &size, &bank); + descs[0].start = 0xFFFF; + descs[0].len = 1; + + descs[1].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_HRAM, &size, &bank); + descs[1].start = 0xFF80; + descs[1].len = 0x0080; + + descs[2].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_RAM, &size, &bank); + descs[2].start = 0xC000; + descs[2].len = 0x1000; + + descs[3].ptr = descs[2].ptr + 0x1000; /* GB RAM/GBC RAM bank 1 */ + descs[3].start = 0xD000; + descs[3].len = 0x1000; + + descs[4].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + descs[4].start = 0xA000; + descs[4].len = 0x2000; + + descs[5].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_VRAM, &size, &bank); + descs[5].start = 0x8000; + descs[5].len = 0x2000; + + descs[6].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_ROM, &size, &bank); + descs[6].start = 0x0000; + descs[6].len = 0x4000; + descs[6].flags = RETRO_MEMDESC_CONST; + + descs[7].ptr = descs[6].ptr + (bank * 0x4000); + descs[7].start = 0x4000; + descs[7].len = 0x4000; + descs[7].flags = RETRO_MEMDESC_CONST; + + descs[8].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_OAM, &size, &bank); + descs[8].start = 0xFE00; + descs[8].len = 0x00A0; + descs[8].select = 0xFFFFFF00; + + descs[9].ptr = descs[2].ptr + 0x2000; /* GBC RAM bank 2 */ + descs[9].start = 0x10000; + descs[9].len = GB_is_cgb(&gameboy[i]) ? 0x6000 : 0; /* 0x1000 per bank (2-7), unmapped on GB */ + descs[9].select = 0xFFFF0000; + + descs[10].ptr = GB_get_direct_access(&gameboy[i], GB_DIRECT_ACCESS_IO, &size, &bank); + descs[10].start = 0xFF00; + descs[10].len = 0x0080; + descs[10].select = 0xFFFFFF00; + + struct retro_memory_map mmaps; + mmaps.descriptors = descs; + mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]); + environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); +} + +static void init_for_current_model(unsigned id) +{ + unsigned i = id; + enum model effective_model; + + effective_model = model[i]; + if (effective_model == MODEL_AUTO) { + effective_model = auto_model[i]; + } + + if (GB_is_inited(&gameboy[i])) { + GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]); + retro_set_memory_maps(); + } + else { + GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); + } + + GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load); + + /* When running multiple devices they are assumed to use the same resolution */ + + GB_set_pixels_output(&gameboy[i], + (uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i)); + GB_set_rgb_encode_callback(&gameboy[i], rgb_encode); + GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY); + GB_apu_set_sample_callback(&gameboy[i], audio_callback); + GB_set_rumble_callback(&gameboy[i], rumble_callback); + + /* todo: attempt to make these more generic */ + GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); + GB_set_lcd_status_callback(&gameboy[0], lcd_status_change_1); + if (emulated_devices == 2) { + GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); + GB_set_lcd_status_callback(&gameboy[2], lcd_status_change_2); + if (link_cable_emulation) { + set_link_cable_state(true); + } + } + + /* Let's be extremely nitpicky about how devices and descriptors are set */ + if (emulated_devices == 1 && (model[0] == MODEL_SGB_PAL || model[0] == MODEL_SGB_NTSC || model[0] == MODEL_SGB2)) { + static const struct retro_controller_info ports[] = { + { controllers_sgb, 1 }, + { controllers_sgb, 1 }, + { controllers_sgb, 1 }, + { controllers_sgb, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_4p); + } + else if (emulated_devices == 1) { + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); + } + else { + static const struct retro_controller_info ports[] = { + { controllers, 1 }, + { controllers, 1 }, + { NULL, 0 }, + }; + environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_2p); + } +} + +static void check_variables() +{ + struct retro_variable var = {0}; + if (emulated_devices == 1) { + + var.key = "sameboy_model"; + var.value = NULL; + + model[0] = MODEL_AUTO; + auto_sgb_enabled[0] = false; + + 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 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 if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = MODEL_AUTO; + auto_sgb_enabled[0] = true; + } + else { + new_model = MODEL_AUTO; + } + + model[0] = new_model; + } + + var.key = "sameboy_auto_sgb_model"; + var.value = NULL; + + auto_sgb_model[0] = MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = auto_sgb_model[0]; + 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_SGB_NTSC; + } + + auto_sgb_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 (strcmp(var.value, "off") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); + } + else if (strcmp(var.value, "correct curves") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + } + else if (strcmp(var.value, "emulate hardware") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BALANCED); + } + else if (strcmp(var.value, "preserve brightness") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); + } + 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } + } + + var.key = "sameboy_light_temperature"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_light_temperature(&gameboy[0], atof(var.value)); + } + + var.key = "sameboy_border"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); + } + else if (strcmp(var.value, "Super Game Boy only") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_SGB); + } + 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) { + GB_set_interference_volume(&gameboy[0], atoi(var.value) / 100.0); + } + + 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; + + model[0] = MODEL_AUTO; + auto_sgb_enabled[0] = false; + + 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 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 if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = MODEL_AUTO; + auto_sgb_enabled[0] = true; + } + else { + new_model = MODEL_AUTO; + } + + model[0] = new_model; + } + + var.key = "sameboy_auto_sgb_model_1"; + var.value = NULL; + + auto_sgb_model[0] = MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = auto_sgb_model[0]; + 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_SGB_NTSC; + } + + auto_sgb_model[0] = new_model; + } + + var.key = "sameboy_model_2"; + var.value = NULL; + + model[1] = MODEL_AUTO; + auto_sgb_enabled[1] = false; + + 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 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 if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = MODEL_AUTO; + auto_sgb_enabled[1] = true; + } + else { + new_model = MODEL_AUTO; + } + + model[1] = new_model; + } + + var.key = "sameboy_auto_sgb_model_2"; + var.value = NULL; + + auto_sgb_model[1] = MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = auto_sgb_model[1]; + 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_SGB_NTSC; + } + + auto_sgb_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 (strcmp(var.value, "off") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); + } + else if (strcmp(var.value, "correct curves") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); + } + else if (strcmp(var.value, "emulate hardware") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BALANCED); + } + else if (strcmp(var.value, "preserve brightness") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); + } + 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } + } + + var.key = "sameboy_color_correction_mode_2"; + var.value = NULL; + 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); + } + else if (strcmp(var.value, "correct curves") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_CORRECT_CURVES); + } + else if (strcmp(var.value, "emulate hardware") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_BALANCED); + } + else if (strcmp(var.value, "preserve brightness") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); + } + 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } + } + + var.key = "sameboy_light_temperature_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_light_temperature(&gameboy[0], atof(var.value)); + } + + var.key = "sameboy_light_temperature_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_light_temperature(&gameboy[1], atof(var.value)); + } + + 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) { + GB_set_interference_volume(&gameboy[0], atoi(var.value) / 100.0); + } + + var.key = "sameboy_audio_interference_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_interference_volume(&gameboy[1], atoi(var.value) / 100.0); + } + + var.key = "sameboy_rumble_1"; + 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); + } + } + + var.key = "sameboy_rumble_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_DISABLED); + } + else if (strcmp(var.value, "rumble-enabled games") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_CARTRIDGE_ONLY); + } + else if (strcmp(var.value, "all games") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_ALL_GAMES); + } + } + + } + set_variable_visibility(); +} + +void retro_init(void) +{ + const char *dir = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) { + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir); + } + else { + snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); + } + + if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) { + log_cb = logging.log; + } + else { + log_cb = fallback_log; + } + + if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) { + libretro_supports_bitmasks = true; + } + + init_output_audio_buffer(16384); +} + +void retro_deinit(void) +{ + free(frame_buf); + free(frame_buf_copy); + frame_buf = NULL; + frame_buf_copy = NULL; + + free_output_audio_buffer(); + + libretro_supports_bitmasks = false; +} + +unsigned retro_api_version(void) +{ + return RETRO_API_VERSION; +} + +void retro_set_controller_port_device(unsigned port, unsigned device) +{ + log_cb(RETRO_LOG_INFO, "Connecting device %u into port %u\n", device, port); +} + +void retro_get_system_info(struct retro_system_info *info) +{ + memset(info, 0, sizeof(*info)); + info->library_name = "SameBoy"; +#ifdef GIT_VERSION + info->library_version = GB_VERSION GIT_VERSION; +#else + info->library_version = GB_VERSION; +#endif + info->need_fullpath = false; + info->valid_extensions = "gb|gbc"; +} + +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 (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; + geom.aspect_ratio = (double)GB_get_screen_width(&gameboy[0]) / (emulated_devices * GB_get_screen_height(&gameboy[0])); + } + else if (screen_layout == LAYOUT_LEFT_RIGHT) { + geom.base_width = GB_get_screen_width(&gameboy[0]) * emulated_devices; + geom.base_height = GB_get_screen_height(&gameboy[0]); + geom.aspect_ratio = ((double)GB_get_screen_width(&gameboy[0]) * emulated_devices) / GB_get_screen_height(&gameboy[0]); + } + } + 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]); + } + + geom.max_width = MAX_VIDEO_WIDTH * emulated_devices; + geom.max_height = MAX_VIDEO_HEIGHT * emulated_devices; + + info->geometry = geom; + info->timing = timing; +} + +void retro_set_environment(retro_environment_t cb) +{ + bool categories_supported; + + environ_cb = cb; + + libretro_set_core_options(environ_cb, &categories_supported); + + environ_cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); +} + +void retro_set_audio_sample(retro_audio_sample_t cb) +{ + (void)cb; +} + +void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) +{ + audio_batch_cb = cb; +} + +void retro_set_input_poll(retro_input_poll_t cb) +{ + input_poll_cb = cb; +} + +void retro_set_input_state(retro_input_state_t cb) +{ + input_state_cb = cb; +} + +void retro_set_video_refresh(retro_video_refresh_t cb) +{ + video_cb = 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) +{ + + bool updated = false; + + if (!initialized) { + geometry_updated = false; + } + + if (geometry_updated) { + struct retro_system_av_info info; + retro_get_system_av_info(&info); + environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &info.geometry); + geometry_updated = false; + } + + if (!frame_buf) { + return; + } + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { + check_variables(); + } + + 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_PAL || model[0] == MODEL_SGB_NTSC || model[0] == MODEL_SGB2)) { + for (unsigned i = 0; i < 4; i++) { + GB_update_keys_status(&gameboy[0], i); + } + } + else { + GB_update_keys_status(&gameboy[0], 0); + } + + vblank1_occurred = vblank2_occurred = false; + signed delta = 0; + if (emulated_devices == 2) { + while (!vblank1_occurred || !vblank2_occurred) { + if (delta >= 0) { + delta -= GB_run(&gameboy[0]); + } + else { + delta += GB_run(&gameboy[1]); + } + } + } + else { + GB_run_frame(&gameboy[0]); + } + + if (emulated_devices == 2) { + if (screen_layout == LAYOUT_TOP_DOWN) { + video_cb(frame_buf, + GB_get_screen_width(&gameboy[0]), + GB_get_screen_height(&gameboy[0]) * emulated_devices, + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); + } + else if (screen_layout == LAYOUT_LEFT_RIGHT) { + unsigned pitch = GB_get_screen_width(&gameboy[0]) * emulated_devices; + unsigned pixels_per_device = GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]); + for (int y = 0; y < GB_get_screen_height(&gameboy[0]); y++) { + for (unsigned i = 0; i < emulated_devices; i++) { + memcpy(frame_buf_copy + y * pitch + GB_get_screen_width(&gameboy[0]) * i, + frame_buf + pixels_per_device * i + y * GB_get_screen_width(&gameboy[0]), + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); + } + } + + video_cb(frame_buf_copy, GB_get_screen_width(&gameboy[0]) * emulated_devices, GB_get_screen_height(&gameboy[0]), GB_get_screen_width(&gameboy[0]) * emulated_devices * sizeof(uint32_t)); + } + } + else { + video_cb(frame_buf, + GB_get_screen_width(&gameboy[0]), + GB_get_screen_height(&gameboy[0]), + GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); + } + + upload_output_audio_buffer(); + initialized = true; +} + +static enum rom_type check_rom_header(const uint8_t *data, size_t size) +{ + enum rom_type type; + uint8_t cgb_flag; + uint8_t sgb_flag; + + if (!data || (size < 0x146 + 1)) { + return ROM_TYPE_INVALID; + } + + type = ROM_TYPE_DMG; + cgb_flag = data[0x143]; + sgb_flag = data[0x146]; + + if ((cgb_flag == 0x80) || (cgb_flag == 0xC0)) { + type = ROM_TYPE_CGB; + } + + if ((type == ROM_TYPE_DMG) && (sgb_flag == 0x03)) { + type = ROM_TYPE_SGB; + } + + return type; +} + +bool retro_load_game(const struct retro_game_info *info) +{ + enum rom_type content_type = ROM_TYPE_INVALID; + const uint8_t *content_data = NULL; + size_t content_size; + + if (info) { + content_data = (const uint8_t *)info->data; + content_size = info->size; + content_type = check_rom_header(content_data, content_size); + } + + check_variables(); + + switch (content_type) { + case ROM_TYPE_DMG: + auto_model[0] = MODEL_DMG_B; + auto_model[1] = MODEL_DMG_B; + break; + case ROM_TYPE_SGB: + auto_model[0] = auto_sgb_enabled[0] ? auto_sgb_model[0] : MODEL_DMG_B; + auto_model[1] = auto_sgb_enabled[1] ? auto_sgb_model[1] : MODEL_DMG_B; + break; + case ROM_TYPE_CGB: + auto_model[0] = MODEL_CGB_E; + auto_model[1] = MODEL_CGB_E; + break; + case ROM_TYPE_INVALID: + default: + log_cb(RETRO_LOG_ERROR, "Invalid content\n"); + return false; + } + + 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)) { + log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported\n"); + return false; + } + + for (int i = 0; i < emulated_devices; i++) { + init_for_current_model(i); + GB_load_rom_from_buffer(&gameboy[i], content_data, content_size); + } + + bool achievements = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); + + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) { + log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); + } + else { + log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); + } + + check_variables(); + + retro_set_memory_maps(); + + return true; +} + +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]); + } +} + +unsigned retro_get_region(void) +{ + return RETRO_REGION_NTSC; +} + +bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) +{ + if ((type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) && (num_info >= 2)) { + emulated_devices = 2; + } + else { + return false; /* all other types are unhandled for now */ + } + + check_variables(); + + frame_buf = (uint32_t*)malloc(emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); + frame_buf_copy = (uint32_t*)malloc(emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); + + memset(frame_buf, 0, emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); + 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)) { + log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported\n"); + return false; + } + + for (int i = 0; i < emulated_devices; i++) { + enum rom_type content_type = ROM_TYPE_INVALID; + const uint8_t *content_data = info[i].data; + size_t content_size = info[i].size; + + content_type = check_rom_header(content_data, content_size); + + switch (content_type) { + case ROM_TYPE_DMG: + auto_model[i] = MODEL_DMG_B; + break; + case ROM_TYPE_SGB: + auto_model[i] = auto_sgb_enabled[i] ? auto_sgb_model[i] : MODEL_DMG_B; + break; + case ROM_TYPE_CGB: + auto_model[i] = MODEL_CGB_E; + break; + case ROM_TYPE_INVALID: + default: + log_cb(RETRO_LOG_ERROR, "Invalid content\n"); + return false; + } + + init_for_current_model(i); + GB_load_rom_from_buffer(&gameboy[i], content_data, content_size); + } + + bool achievements = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); + + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble)) { + log_cb(RETRO_LOG_INFO, "Rumble environment supported\n"); + } + else { + log_cb(RETRO_LOG_INFO, "Rumble environment not supported\n"); + } + + check_variables(); + return true; +} + +size_t retro_serialize_size(void) +{ + static size_t maximum_save_size = 0; + 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; +} + +bool retro_serialize(void *data, size_t size) +{ + + if (!initialized || !data) { + return false; + } + + size_t offset = 0; + + 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; + } + + GB_save_state_to_buffer(&gameboy[i], ((uint8_t *) data) + offset); + offset += state_size; + size -= state_size; + } + + return true; +} + +bool retro_unserialize(const void *data, size_t size) +{ + 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; + } + + if (GB_load_state_from_buffer(&gameboy[i], data, state_size)) { + return false; + } + + size -= state_size; + data = ((uint8_t *)data) + state_size; + } + + return true; + +} + +void *retro_get_memory_data(unsigned type) +{ + void *data = NULL; + if (emulated_devices == 1) { + switch (type) { + case RETRO_MEMORY_SYSTEM_RAM: + data = gameboy[0].ram; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { + data = gameboy[0].mbc_ram; + } + else { + data = NULL; + } + break; + case RETRO_MEMORY_VIDEO_RAM: + data = gameboy[0].vram; + break; + case RETRO_MEMORY_RTC: + if (gameboy[0].cartridge_type->has_battery) { + data = GB_GET_SECTION(&gameboy[0], rtc); + } + else { + data = NULL; + } + break; + default: + break; + } + } + 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; + } + else { + data = NULL; + } + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) { + data = gameboy[1].mbc_ram; + } + else { + data = NULL; + } + break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if (gameboy[0].cartridge_type->has_battery) { + data = GB_GET_SECTION(&gameboy[0], rtc); + } + else { + data = NULL; + } + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if (gameboy[1].cartridge_type->has_battery) { + data = GB_GET_SECTION(&gameboy[1], rtc); + } + else { + data = NULL; + } + break; + default: + break; + } + } + + return data; +} + +size_t retro_get_memory_size(unsigned type) +{ + size_t size = 0; + if (emulated_devices == 1) { + switch (type) { + case RETRO_MEMORY_SYSTEM_RAM: + size = gameboy[0].ram_size; + break; + case RETRO_MEMORY_SAVE_RAM: + if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { + size = gameboy[0].mbc_ram_size; + } + else { + size = 0; + } + break; + case RETRO_MEMORY_VIDEO_RAM: + size = gameboy[0].vram_size; + break; + case RETRO_MEMORY_RTC: + if (gameboy[0].cartridge_type->has_battery) { + size = GB_SECTION_SIZE(rtc); + } + else { + size = 0; + } + break; + default: + break; + } + } + 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; + } + else { + size = 0; + } + break; + case RETRO_MEMORY_GAMEBOY_2_SRAM: + if (gameboy[1].cartridge_type->has_battery && gameboy[1].mbc_ram_size != 0) { + size = gameboy[1].mbc_ram_size; + } + else { + size = 0; + } + break; + case RETRO_MEMORY_GAMEBOY_1_RTC: + if (gameboy[0].cartridge_type->has_battery) { + size = GB_SECTION_SIZE(rtc); + } + break; + case RETRO_MEMORY_GAMEBOY_2_RTC: + if (gameboy[1].cartridge_type->has_battery) { + size = GB_SECTION_SIZE(rtc); + } + break; + default: + break; + } + } + + return size; +} + +void retro_cheat_reset(void) +{} + +void retro_cheat_set(unsigned index, bool enabled, const char *code) +{ + (void)index; + (void)enabled; + (void)code; +} diff --git a/thirdparty/SameBoy-old/libretro/libretro.h b/thirdparty/SameBoy-old/libretro/libretro.h new file mode 100644 index 000000000..4f4db1cf9 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/libretro.h @@ -0,0 +1,3638 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this libretro API header (libretro.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_H__ +#define LIBRETRO_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __cplusplus +#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 +#define true 1 +#define false 0 +#else +#include +#endif +#endif + +#ifndef RETRO_CALLCONV +# if defined(__GNUC__) && defined(__i386__) && !defined(__x86_64__) +# define RETRO_CALLCONV __attribute__((cdecl)) +# elif defined(_MSC_VER) && defined(_M_X86) && !defined(_M_X64) +# define RETRO_CALLCONV __cdecl +# else +# define RETRO_CALLCONV /* all other platforms only have one calling convention each */ +# endif +#endif + +#ifndef RETRO_API +# if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# ifdef RETRO_IMPORT_SYMBOLS +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllimport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllimport) +# endif +# else +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllexport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllexport) +# endif +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define RETRO_API RETRO_CALLCONV __attribute__((__visibility__("default"))) +# else +# define RETRO_API RETRO_CALLCONV +# endif +# endif +#endif + +/* Used for checking API/ABI mismatches that can break libretro + * implementations. + * It is not incremented for compatible changes to the API. + */ +#define RETRO_API_VERSION 1 + +/* + * 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 + * and a pointer. + * + * 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 + * correctly to arbitrary controller layouts. + */ + +#define RETRO_DEVICE_TYPE_SHIFT 8 +#define RETRO_DEVICE_MASK ((1 << RETRO_DEVICE_TYPE_SHIFT) - 1) +#define RETRO_DEVICE_SUBCLASS(base, id) (((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base) + +/* 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 + * 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 + * pointer is supposed to be on the screen. + * 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 + * pressed state. + * For event/text based keyboard input, see + * RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. + */ +#define RETRO_DEVICE_KEYBOARD 3 + +/* 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 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 + * screen a mouse (or something similar) is being placed. + * For a touch centric device, coordinates reported are the coordinates + * of the press. + * + * 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 + * 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 + * game image, etc. + * + * 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 + * 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 + * 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 + * index = 1, and so on. + * 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 + * Super Nintendo controller. + * 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 +#define RETRO_DEVICE_ID_JOYPAD_START 3 +#define RETRO_DEVICE_ID_JOYPAD_UP 4 +#define RETRO_DEVICE_ID_JOYPAD_DOWN 5 +#define RETRO_DEVICE_ID_JOYPAD_LEFT 6 +#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 +#define RETRO_DEVICE_ID_JOYPAD_A 8 +#define RETRO_DEVICE_ID_JOYPAD_X 9 +#define RETRO_DEVICE_ID_JOYPAD_L 10 +#define RETRO_DEVICE_ID_JOYPAD_R 11 +#define RETRO_DEVICE_ID_JOYPAD_L2 12 +#define RETRO_DEVICE_ID_JOYPAD_R2 13 +#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_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 +#define RETRO_DEVICE_ID_MOUSE_Y 1 +#define RETRO_DEVICE_ID_MOUSE_LEFT 2 +#define RETRO_DEVICE_ID_MOUSE_RIGHT 3 +#define RETRO_DEVICE_ID_MOUSE_WHEELUP 4 +#define RETRO_DEVICE_ID_MOUSE_WHEELDOWN 5 +#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. */ +#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 +#define RETRO_REGION_PAL 1 + +/* 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_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_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) */ + RETRO_LANGUAGE_DUMMY = INT_MAX +}; + +/* Passed to retro_get_memory_data/size(). + * If the memory type doesn't apply to the + * implementation NULL/0 can be returned. + */ +#define RETRO_MEMORY_MASK 0xff + +/* Regular save RAM. This RAM is usually found on a game cartridge, + * backed up by a battery. + * If save game data is too complex for a single memory buffer, + * the SAVE_DIRECTORY (preferably) or SYSTEM_DIRECTORY environment + * callback can be used. */ +#define RETRO_MEMORY_SAVE_RAM 0 + +/* Some games have a built-in clock to keep track of time. + * This memory is usually just a couple of bytes to keep track of time. + */ +#define RETRO_MEMORY_RTC 1 + +/* System ram lets a frontend peek into a game systems main RAM. */ +#define RETRO_MEMORY_SYSTEM_RAM 2 + +/* Video ram lets a frontend peek into a game systems video RAM (VRAM). */ +#define RETRO_MEMORY_VIDEO_RAM 3 + +/* Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. */ +enum retro_key +{ + RETROK_UNKNOWN = 0, + RETROK_FIRST = 0, + RETROK_BACKSPACE = 8, + RETROK_TAB = 9, + RETROK_CLEAR = 12, + RETROK_RETURN = 13, + RETROK_PAUSE = 19, + RETROK_ESCAPE = 27, + RETROK_SPACE = 32, + RETROK_EXCLAIM = 33, + RETROK_QUOTEDBL = 34, + RETROK_HASH = 35, + RETROK_DOLLAR = 36, + RETROK_AMPERSAND = 38, + RETROK_QUOTE = 39, + RETROK_LEFTPAREN = 40, + RETROK_RIGHTPAREN = 41, + RETROK_ASTERISK = 42, + RETROK_PLUS = 43, + RETROK_COMMA = 44, + RETROK_MINUS = 45, + RETROK_PERIOD = 46, + RETROK_SLASH = 47, + RETROK_0 = 48, + RETROK_1 = 49, + RETROK_2 = 50, + RETROK_3 = 51, + RETROK_4 = 52, + RETROK_5 = 53, + RETROK_6 = 54, + RETROK_7 = 55, + RETROK_8 = 56, + RETROK_9 = 57, + RETROK_COLON = 58, + RETROK_SEMICOLON = 59, + RETROK_LESS = 60, + RETROK_EQUALS = 61, + RETROK_GREATER = 62, + RETROK_QUESTION = 63, + RETROK_AT = 64, + RETROK_LEFTBRACKET = 91, + RETROK_BACKSLASH = 92, + RETROK_RIGHTBRACKET = 93, + RETROK_CARET = 94, + RETROK_UNDERSCORE = 95, + RETROK_BACKQUOTE = 96, + RETROK_a = 97, + RETROK_b = 98, + RETROK_c = 99, + RETROK_d = 100, + RETROK_e = 101, + RETROK_f = 102, + RETROK_g = 103, + RETROK_h = 104, + RETROK_i = 105, + RETROK_j = 106, + RETROK_k = 107, + RETROK_l = 108, + RETROK_m = 109, + RETROK_n = 110, + RETROK_o = 111, + RETROK_p = 112, + RETROK_q = 113, + RETROK_r = 114, + RETROK_s = 115, + RETROK_t = 116, + RETROK_u = 117, + RETROK_v = 118, + RETROK_w = 119, + 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, + RETROK_KP1 = 257, + RETROK_KP2 = 258, + RETROK_KP3 = 259, + RETROK_KP4 = 260, + RETROK_KP5 = 261, + RETROK_KP6 = 262, + RETROK_KP7 = 263, + RETROK_KP8 = 264, + RETROK_KP9 = 265, + RETROK_KP_PERIOD = 266, + RETROK_KP_DIVIDE = 267, + RETROK_KP_MULTIPLY = 268, + RETROK_KP_MINUS = 269, + RETROK_KP_PLUS = 270, + RETROK_KP_ENTER = 271, + RETROK_KP_EQUALS = 272, + + RETROK_UP = 273, + RETROK_DOWN = 274, + RETROK_RIGHT = 275, + RETROK_LEFT = 276, + RETROK_INSERT = 277, + RETROK_HOME = 278, + RETROK_END = 279, + RETROK_PAGEUP = 280, + RETROK_PAGEDOWN = 281, + + RETROK_F1 = 282, + RETROK_F2 = 283, + RETROK_F3 = 284, + RETROK_F4 = 285, + RETROK_F5 = 286, + RETROK_F6 = 287, + RETROK_F7 = 288, + RETROK_F8 = 289, + RETROK_F9 = 290, + RETROK_F10 = 291, + RETROK_F11 = 292, + RETROK_F12 = 293, + RETROK_F13 = 294, + RETROK_F14 = 295, + RETROK_F15 = 296, + + RETROK_NUMLOCK = 300, + RETROK_CAPSLOCK = 301, + RETROK_SCROLLOCK = 302, + RETROK_RSHIFT = 303, + RETROK_LSHIFT = 304, + RETROK_RCTRL = 305, + RETROK_LCTRL = 306, + RETROK_RALT = 307, + RETROK_LALT = 308, + RETROK_RMETA = 309, + RETROK_LMETA = 310, + RETROK_LSUPER = 311, + RETROK_RSUPER = 312, + RETROK_MODE = 313, + RETROK_COMPOSE = 314, + + RETROK_HELP = 315, + RETROK_PRINT = 316, + RETROK_SYSREQ = 317, + RETROK_BREAK = 318, + RETROK_MENU = 319, + RETROK_POWER = 320, + RETROK_EURO = 321, + RETROK_UNDO = 322, + RETROK_OEM_102 = 323, + + RETROK_LAST, + + RETROK_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +enum retro_mod +{ + RETROKMOD_NONE = 0x0000, + + RETROKMOD_SHIFT = 0x01, + RETROKMOD_CTRL = 0x02, + RETROKMOD_ALT = 0x04, + RETROKMOD_META = 0x08, + + RETROKMOD_NUMLOCK = 0x10, + RETROKMOD_CAPSLOCK = 0x20, + RETROKMOD_SCROLLOCK = 0x40, + + RETROKMOD_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +/* 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. */ +#define RETRO_ENVIRONMENT_PRIVATE 0x20000 + +/* Environment commands. */ +#define RETRO_ENVIRONMENT_SET_ROTATION 1 /* const unsigned * -- + * Sets screen rotation of graphics. + * 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 * -- + * 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 * -- + * Boolean value whether or not frontend supports frame duping, + * passing NULL to video frame callback. + */ + + /* 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 + * 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 + * fallback, stderr). + */ +#define RETRO_ENVIRONMENT_SHUTDOWN 7 /* N/A (NULL) -- + * Requests the frontend to shutdown. + * Should only be used if game has a specific + * way to shutdown the game from a menu item or similar. + */ +#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8 + /* const unsigned * -- + * Gives a hint to the frontend how demanding this implementation + * is on a system. E.g. reporting a level of 2 means + * this implementation should run decently on all frontends + * of level 2 and up. + * + * It can be used by the frontend to potentially warn + * about too demanding implementations. + * + * The levels are "floating". + * + * This function can be called on a per-game basis, + * as certain games an implementation can play might be + * particularly demanding. + * If called, it should be called in retro_load_game(). + */ +#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 + * 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 + * memory cards, etc, for lack of a better place to put it. + * This is now discouraged, and if possible, cores should try to + * use the new GET_SAVE_DIRECTORY. + */ +#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 + /* const enum retro_pixel_format * -- + * 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 + * format. + * + * 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 + * being set to NULL. + * 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 + /* const struct retro_keyboard_callback * -- + * Sets a callback function used to notify core about keyboard events. + */ +#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 + * disk images. + * 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 + * hardware acceleration. + * Should be called in retro_load_game(). + * 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 + * max_width/max_height provided in get_av_info(). + * 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 + * 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 + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * 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 + * terminated by a { NULL, NULL } element. + * 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 + * of expected values. + * + * 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. + * + * 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 + * values split up with '|'. + * + * 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 + /* bool * -- + * Result is set to true if some variables are updated by + * frontend since last call to RETRO_ENVIRONMENT_GET_VARIABLE. + * Variables should be queried with GET_VARIABLE. + */ +#define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 + /* const bool * -- + * 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 + * implementation was loaded. + * 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 + * be loaded without ugly hacks. + */ + + /* 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 + * being available for writing. + * 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 + * asynchronous and audio can be generated on the fly. + * This interface is not recommended for use with emulators which have + * highly synchronous audio. + * + * 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 + * 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 + * implement the "normal" audio interface. + * + * A libretro core using SET_AUDIO_CALLBACK should also make use of + * SET_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 + * 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 * -- + * 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 + * 0 in retro_input_state_t. + * Example bitmask: caps = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG). + * Should only be called in retro_run(). + */ +#define RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE (25 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* 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, + * enabling/disable it entirely, etc. + * 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 + * video camera. + * 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 + * will be delivered as a raw framebuffer, + * or as an OpenGL texture directly. + * + * 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 + * libretro GL core (SET_HW_RENDER). + * It is recommended to use a libretro GL core when + * using camera interface. + * + * 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 + * logging in a cross-platform way + * 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 + * 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 + * 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 + * location-based information from the host device, + * such as current latitude / longitude. + */ +#define RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY 30 /* Old name, kept for compatibility. */ +#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 + * core relies upon, such as art assets, + * input data, etc 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. + */ +#define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 + /* const char ** -- + * 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()). + * + * 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 + * within retro_run(). + * 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 + * 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 video and + * audio callbacks + * 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 + * 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 + * resolution changes in an emulator core if it's + * 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 + * of SET_SYSTEM_AV_INFO. + * + * 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 + * get_proc_address() interface. + * 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 + * **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 + * different kinds of games. + * 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 + * 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 + * subsystems are supported for use with retro_load_game_special(). + * 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 + * **MUST** be called from within retro_set_environment(). + */ +#define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 + /* const struct retro_controller_info * -- + * 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 specifcally + * provided by 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. + * + * 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 + * 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 + * anything else. + * 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 + * 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 + * 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 + * 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 + * constant time. + */ +#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 + * 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, + * a default username should be specified by the core. + */ +#define RETRO_ENVIRONMENT_GET_LANGUAGE 39 + /* unsigned * -- + * Returns the specified language of the frontend, if specified by the user. + * It can be used by the core for localization purposes. + */ +#define RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER (40 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_framebuffer * -- + * Returns a preallocated framebuffer which the core can use for rendering + * the frame into when not using SET_HW_RENDER. + * The framebuffer returned from this call must not be used + * after the current call to retro_run() returns. + * + * The goal of this call is to allow zero-copy behavior where a core + * can render directly into video memory, avoiding extra bandwidth cost by copying + * memory from core to video memory. + * + * If this call succeeds and the core renders into it, + * the framebuffer pointer and pitch can be passed to retro_video_refresh_t. + * If the buffer from GET_CURRENT_SOFTWARE_FRAMEBUFFER is to be used, + * the core must pass the exact + * same pointer as returned by GET_CURRENT_SOFTWARE_FRAMEBUFFER; + * i.e. passing a pointer which is offset from the + * buffer is undefined. The width, height and pitch parameters + * must also match exactly to the values obtained from GET_CURRENT_SOFTWARE_FRAMEBUFFER. + * + * It is possible for a frontend to return a different pixel format + * than the one used in SET_PIXEL_FORMAT. This can happen if the frontend + * needs to perform conversion. + * + * It is still valid for a core to render to a different buffer + * even if GET_CURRENT_SOFTWARE_FRAMEBUFFER succeeds. + * + * A frontend must make sure that the pointer obtained from this function is + * writeable (and readable). + */ +#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. + * Not all HW rendering APIs support or need this. + * The contents of the returned pointer is specific to the rendering API + * being used. See the various headers like libretro_vulkan.h, etc. + * + * GET_HW_RENDER_INTERFACE cannot be called before context_reset has been called. + * 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 + * either via memory descriptors set with RETRO_ENVIRONMENT_SET_MEMORY_MAPS + * or via retro_get_memory_data/retro_get_memory_size. + * + * 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_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). + * 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. + * + * 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_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). + * 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. + */ + +#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). + */ + +#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65 + /* const struct retro_system_content_info_override * -- + * Allows an implementation to override 'global' content + * info parameters reported by retro_get_system_info(). + * Overrides also affect subsystem content info parameters + * set via RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO. + * This function must be called inside retro_set_environment(). + * If callback returns false, content info overrides + * are unsupported by the frontend, and will be ignored. + * If callback returns true, extended game info may be + * retrieved by calling RETRO_ENVIRONMENT_GET_GAME_INFO_EXT + * in retro_load_game() or retro_load_game_special(). + * + * 'data' points to an array of retro_system_content_info_override + * structs terminated by a { NULL, false, false } element. + * If 'data' is NULL, no changes will be made to the frontend; + * a core may therefore pass NULL in order to test whether + * the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks are supported + * by the frontend. + * + * For struct member descriptions, see the definition of + * struct retro_system_content_info_override. + * + * Example: + * + * - struct retro_system_info: + * { + * "My Core", // library_name + * "v1.0", // library_version + * "m3u|md|cue|iso|chd|sms|gg|sg", // valid_extensions + * true, // need_fullpath + * false // block_extract + * } + * + * - Array of struct retro_system_content_info_override: + * { + * { + * "md|sms|gg", // extensions + * false, // need_fullpath + * true // persistent_data + * }, + * { + * "sg", // extensions + * false, // need_fullpath + * false // persistent_data + * }, + * { NULL, false, false } + * } + * + * Result: + * - Files of type m3u, cue, iso, chd will not be + * loaded by the frontend. Frontend will pass a + * valid path to the core, and core will handle + * loading internally + * - Files of type md, sms, gg will be loaded by + * the frontend. A valid memory buffer will be + * passed to the core. This memory buffer will + * remain valid until retro_deinit() returns + * - Files of type sg will be loaded by the frontend. + * A valid memory buffer will be passed to the core. + * This memory buffer will remain valid until + * retro_load_game() (or retro_load_game_special()) + * returns + * + * NOTE: If an extension is listed multiple times in + * an array of retro_system_content_info_override + * structs, only the first instance will be registered + */ + +#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66 + /* const struct retro_game_info_ext ** -- + * Allows an implementation to fetch extended game + * information, providing additional content path + * and memory buffer status details. + * This function may only be called inside + * retro_load_game() or retro_load_game_special(). + * If callback returns false, extended game information + * is unsupported by the frontend. In this case, only + * regular retro_game_info will be available. + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT is guaranteed + * to return true if RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE + * returns true. + * + * 'data' points to an array of retro_game_info_ext structs. + * + * For struct member descriptions, see the definition of + * struct retro_game_info_ext. + * + * - If function is called inside retro_load_game(), + * the retro_game_info_ext array is guaranteed to + * have a size of 1 - i.e. the returned pointer may + * be used to access directly the members of the + * first retro_game_info_ext struct, for example: + * + * struct retro_game_info_ext *game_info_ext; + * if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &game_info_ext)) + * printf("Content Directory: %s\n", game_info_ext->dir); + * + * - If the function is called inside retro_load_game_special(), + * the retro_game_info_ext array is guaranteed to have a + * size equal to the num_info argument passed to + * retro_load_game_special() + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67 + /* const struct retro_core_options_v2 * -- + * 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_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * 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. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * 'data' points to a retro_core_options_v2 struct, containing + * of two pointers: + * - retro_core_options_v2::categories is an array of + * retro_core_option_v2_category structs terminated by a + * { NULL, NULL, NULL } element. If retro_core_options_v2::categories + * is NULL, all core options will have no category and will be shown + * at the top level of the frontend core option interface. If frontend + * does not have core option category support, categories array will + * be ignored. + * - retro_core_options_v2::definitions is an array of + * retro_core_option_v2_definition structs terminated by a + * { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL } + * element. + * + * >> retro_core_option_v2_category notes: + * + * - retro_core_option_v2_category::key should contain string + * that uniquely identifies the core option category. Valid + * key characters are [a-z, A-Z, 0-9, _, -] + * Namespace collisions with other implementations' category + * keys are permitted. + * - retro_core_option_v2_category::desc should contain a human + * readable description of the category key. + * - retro_core_option_v2_category::info should contain any + * additional human readable information text that a typical + * user may need to understand the nature of the core option + * category. + * + * Example entry: + * { + * "advanced_settings", + * "Advanced", + * "Options affecting low-level emulation performance and accuracy." + * } + * + * >> retro_core_option_v2_definition notes: + * + * - retro_core_option_v2_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'. Valid key characters + * are [a-z, A-Z, 0-9, _, -]. + * - retro_core_option_v2_definition::desc should contain a human readable + * description of the key. Will be used when the frontend does not + * have core option category support. Examples: "Aspect Ratio" or + * "Video > Aspect Ratio". + * - retro_core_option_v2_definition::desc_categorized should contain a + * human readable description of the key, which will be used when + * frontend has core option category support. Example: "Aspect Ratio", + * where associated retro_core_option_v2_category::desc is "Video". + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::desc will be used instead. + * retro_core_option_v2_definition::desc_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_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_v2_definition::info_categorized should contain + * any additional human readable information text that a typical user + * may need to understand the functionality of the option, and will be + * used when frontend has core option category support. This is provided + * to accommodate the case where info text references an option by + * name/desc, and the desc/desc_categorized text for that option differ. + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::info will be used instead. + * retro_core_option_v2_definition::info_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::category_key should contain a + * category identifier (e.g. "video" or "audio") that will be + * assigned to the core option if frontend has core option category + * support. A categorized option will be shown in a subsection/ + * submenu of the frontend core option interface. If key is empty + * or NULL, or if key does not match one of the + * retro_core_option_v2_category::key values in the associated + * retro_core_option_v2_category array, option will have no category + * and will be shown at the top level of the frontend core option + * interface. + * - retro_core_option_v2_definition::values is an array of + * retro_core_option_value structs terminated by a { NULL, NULL } + * element. + * --> retro_core_option_v2_definition::values[index].value is an + * expected option value. + * --> retro_core_option_v2_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_v2_definition::default_value is the default + * core option setting. It must match one of the expected option + * values in the retro_core_option_v2_definition::values array. If + * it does not, or the default value is NULL, the first entry in the + * retro_core_option_v2_definition::values array is treated as the + * default. + * + * The number of possible option values 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. + * + * Example entries: + * + * - Uncategorized: + * + * { + * "foo_option", + * "Speed hack coprocessor X", + * NULL, + * "Provides increased performance at the expense of reduced accuracy.", + * NULL, + * NULL, + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * - Categorized: + * + * { + * "foo_option", + * "Advanced > Speed hack coprocessor X", + * "Speed hack coprocessor X", + * "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "advanced_settings", + * { + * { "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_V2_INTL 68 + /* const struct retro_core_options_v2_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_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2. + * 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. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_v2_intl struct. + * + * - retro_core_options_v2_intl::us is a pointer to a + * retro_core_options_v2 struct defining the US English + * core options implementation. It must point to a valid struct. + * + * - retro_core_options_v2_intl::local is a pointer to a + * retro_core_options_v2 struct defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_v2_intl::us is used by the frontend). Any items + * missing from this struct will be read from + * retro_core_options_v2_intl::us instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_v2_intl::us struct. Any default values in + * the retro_core_options_v2_intl::local struct will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69 + /* const struct retro_core_options_update_display_callback * -- + * Allows a frontend to signal that a core must update + * the visibility of any dynamically hidden core options, + * and enables the frontend to detect visibility changes. + * Used by the frontend to update the menu display status + * of core options without requiring a call of retro_run(). + * Must be called in retro_set_environment(). + */ + +#define RETRO_ENVIRONMENT_SET_VARIABLE 70 + /* const struct retro_variable * -- + * Allows an implementation to notify the frontend + * that a core option value has changed. + * + * retro_variable::key and retro_variable::value + * must match strings that have been set previously + * via one of the following: + * + * - RETRO_ENVIRONMENT_SET_VARIABLES + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL + * + * After changing a core option value via this + * callback, RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE + * will return true. + * + * If data is NULL, no changes will be registered + * and the callback will return true; an + * implementation may therefore pass NULL in order + * to test whether the callback is supported. + */ + +#define RETRO_ENVIRONMENT_GET_THROTTLE_STATE (71 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_throttle_state * -- + * Allows an implementation to get details on the actual rate + * the frontend is attempting to call retro_run(). + */ + +/* 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 +{ + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_DUMMY = INT_MAX +}; + +/* Base struct. All retro_hw_render_context_negotiation_interface_* types + * contain at least these fields. */ +struct retro_hw_render_context_negotiation_interface +{ + enum retro_hw_render_context_negotiation_interface_type interface_type; + unsigned interface_version; +}; + +/* 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 + * doing math on the pointer. + * + * 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 + * difference in 'offset' instead. + * + * 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 + * 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 + * 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. + * 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 + * 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 + * 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 + * by 'select' and 'disconnect'). */ + size_t len; + + /* 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_-, + * 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 + * 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 + * as empty. + * + * Address space names are case sensitive, but avoid lowercase if possible. + * The same pointer may exist in multiple address spaces. + * + * Examples: + * blank+blank - valid (multiple things may be mapped in the same namespace) + * 'Sp'+'Sp' - valid (multiple things may be mapped in the same namespace) + * 'A'+'B' - valid (neither is a prefix of each other) + * 'S'+blank - valid ('S' is not in 0-9A-F) + * '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 + * there is no namespace 'AR') + * 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 + * to append arbitrary data to an address, without a separator. */ + const char *addrspace; + + /* TODO: When finalizing this one, add a description field, which should be + * "WRAM" or something roughly equally long. */ + + /* TODO: When finalizing this one, replace 'select' with 'limit', which tells + * which bits can vary and still refer to the same address (limit = ~select). + * TODO: limit? range? vary? something else? */ + + /* TODO: When finalizing this one, if 'len' is above what 'select' (or + * 'limit') allows, it's bankswitched. Bankswitched data must have both 'len' + * and 'select' != 0, and the mappings don't tell how the system switches the + * banks. */ + + /* TODO: When finalizing this one, fix the 'len' bit removal order. + * For len=0x1800, pointer 0x1C00 should go to 0x1400, not 0x0C00. + * Algorithm: Take bits highest to lowest, but if it goes above len, clear + * 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 + * 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 + * 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 + * try to claim $7E0000, or at least $7E8000.) + * SNES SPC700 RAM: + * .addrspace="S", .len=0x10000 + * SNES WRAM mirrors: + * .flags=MIRROR, .start=0x000000, .select=0xC0E000, .len=0x2000 + * .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 + * the above two.) + * SNES LoROM (512KB, mirrored a couple of times): + * .flags=CONST, .start=0x008000, .select=0x408000, .disconnect=0x8000, .len=512*1024 + * .flags=CONST, .start=0x400000, .select=0x400000, .disconnect=0x8000, .len=512*1024 + * SNES HiROM (4MB): + * .flags=CONST, .start=0x400000, .select=0x400000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x008000, .select=0x408000, .len=4*1024*1024 + * SNES ExHiROM (8MB): + * .flags=CONST, .offset=0, .start=0xC00000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024, .start=0x400000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x808000, .select=0xC08000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024+0x8000, .start=0x008000, .select=0xC08000, .len=4*1024*1024 + * Clarify the size of the address space: + * .ptr=NULL, .select=0xFFFFFF + * .len can be implied by .select in many of them, but was included for clarity. + */ + +struct retro_memory_map +{ + const struct retro_memory_descriptor *descriptors; + unsigned num_descriptors; +}; + +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 + * 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 + * RETRO_DEVICE_SUBCLASS macro to create an ID. + * + * E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). */ + unsigned id; +}; + +struct retro_controller_info +{ + const struct retro_controller_description *types; + unsigned num_types; +}; + +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 + * libretro memory types. */ + unsigned type; +}; + +struct retro_subsystem_rom_info +{ + /* Describes what the content is (SGB BIOS, GB ROM, etc). */ + const char *desc; + + /* Same definition as retro_get_system_info(). */ + const char *valid_extensions; + + /* Same definition as retro_get_system_info(). */ + bool need_fullpath; + + /* Same definition as retro_get_system_info(). */ + bool block_extract; + + /* 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 + * memory types (retro_get_memory()). */ + const struct retro_subsystem_memory_info *memory; + unsigned num_memory; +}; + +struct retro_subsystem_info +{ + /* Human-readable string of the subsystem type, e.g. "Super GameBoy" */ + const char *desc; + + /* A computer friendly short string identifier for the subsystem type. + * This name must be [a-z]. + * E.g. if desc is "Super GameBoy", this can be "sgb". + * This identifier can be used for command-line interfaces, etc. + */ + const char *ident; + + /* 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, + * as it is the most "significant" content to a user. + * 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; +}; + +typedef void (RETRO_CALLCONV *retro_proc_address_t)(void); + +/* libretro API extension functions: + * (None here so far). + * + * Get a symbol from a libretro core. + * Cores should only return symbols which are actual + * extensions to the libretro API. + * + * 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, + * 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. + */ +typedef retro_proc_address_t (RETRO_CALLCONV *retro_get_proc_address_t)(const char *sym); + +struct retro_get_proc_address_interface +{ + retro_get_proc_address_t get_proc_address; +}; + +enum retro_log_level +{ + RETRO_LOG_DEBUG = 0, + RETRO_LOG_INFO, + RETRO_LOG_WARN, + RETRO_LOG_ERROR, + + RETRO_LOG_DUMMY = INT_MAX +}; + +/* Logging function. Takes log level argument as well. */ +typedef void (RETRO_CALLCONV *retro_log_printf_t)(enum retro_log_level level, + const char *fmt, ...); + +struct retro_log_callback +{ + retro_log_printf_t log; +}; + +/* Performance related functions */ + +/* ID values for SIMD CPU features */ +#define RETRO_SIMD_SSE (1 << 0) +#define RETRO_SIMD_SSE2 (1 << 1) +#define RETRO_SIMD_VMX (1 << 2) +#define RETRO_SIMD_VMX128 (1 << 3) +#define RETRO_SIMD_AVX (1 << 4) +#define RETRO_SIMD_NEON (1 << 5) +#define RETRO_SIMD_SSE3 (1 << 6) +#define RETRO_SIMD_SSSE3 (1 << 7) +#define RETRO_SIMD_MMX (1 << 8) +#define RETRO_SIMD_MMXEXT (1 << 9) +#define RETRO_SIMD_SSE4 (1 << 10) +#define RETRO_SIMD_SSE42 (1 << 11) +#define RETRO_SIMD_AVX2 (1 << 12) +#define RETRO_SIMD_VFPU (1 << 13) +#define RETRO_SIMD_PS (1 << 14) +#define RETRO_SIMD_AES (1 << 15) +#define RETRO_SIMD_VFPV3 (1 << 16) +#define RETRO_SIMD_VFPV4 (1 << 17) +#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; + +struct retro_perf_counter +{ + const char *ident; + retro_perf_tick_t start; + retro_perf_tick_t total; + retro_perf_tick_t call_cnt; + + bool registered; +}; + +/* Returns current time in microseconds. + * Tries to use the most accurate timer available. + */ +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 + * performance counter system). + * */ +typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void); + +/* Returns a bit-mask of detected CPU features (RETRO_SIMD_*). */ +typedef uint64_t (RETRO_CALLCONV *retro_get_cpu_features_t)(void); + +/* Asks frontend to log and/or display the state of performance counters. + * Performance counters can always be poked into manually as well. + */ +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 + * retro_perf_counter must be 0. + * 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); + +/* Starts a registered counter. */ +typedef void (RETRO_CALLCONV *retro_perf_start_t)(struct retro_perf_counter *counter); + +/* Stops a registered counter. */ +typedef void (RETRO_CALLCONV *retro_perf_stop_t)(struct retro_perf_counter *counter); + +/* For convenience it can be useful to wrap register, start and stop in macros. + * E.g.: + * #ifdef LOG_PERFORMANCE + * #define RETRO_PERFORMANCE_INIT(perf_cb, name) static struct retro_perf_counter name = {#name}; if (!name.registered) perf_cb.perf_register(&(name)) + * #define RETRO_PERFORMANCE_START(perf_cb, name) perf_cb.perf_start(&(name)) + * #define RETRO_PERFORMANCE_STOP(perf_cb, name) perf_cb.perf_stop(&(name)) + * #else + * ... Blank macros ... + * #endif + * + * These can then be used mid-functions around code snippets. + * + * extern struct retro_perf_callback perf_cb; * Somewhere in the core. + * + * void do_some_heavy_work(void) + * { + * RETRO_PERFORMANCE_INIT(cb, work_1; + * RETRO_PERFORMANCE_START(cb, work_1); + * heavy_work_1(); + * RETRO_PERFORMANCE_STOP(cb, work_1); + * + * RETRO_PERFORMANCE_INIT(cb, work_2); + * RETRO_PERFORMANCE_START(cb, work_2); + * heavy_work_2(); + * RETRO_PERFORMANCE_STOP(cb, work_2); + * } + * + * void retro_deinit(void) + * { + * perf_cb.perf_log(); * Log all perf counters here for example. + * } + */ + +struct retro_perf_callback +{ + retro_perf_get_time_usec_t get_time_usec; + retro_get_cpu_features_t get_cpu_features; + + retro_perf_get_counter_t get_perf_counter; + retro_perf_register_t perf_register; + retro_perf_start_t perf_start; + retro_perf_stop_t perf_stop; + retro_perf_log_t perf_log; +}; + +/* FIXME: Document the sensor API and work out behavior. + * It will be marked as experimental until then. + */ +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 +}; + +/* Id values for SENSOR types. */ +#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); + +typedef float (RETRO_CALLCONV *retro_sensor_get_input_t)(unsigned port, unsigned id); + +struct retro_sensor_interface +{ + retro_set_sensor_state_t set_sensor_state; + retro_sensor_get_input_t get_sensor_input; +}; + +enum retro_camera_buffer +{ + RETRO_CAMERA_BUFFER_OPENGL_TEXTURE = 0, + RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER, + + RETRO_CAMERA_BUFFER_DUMMY = INT_MAX +}; + +/* Starts the camera driver. Can only be called in retro_run(). */ +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 + * and/or deinitialized. + * retro_camera_start_t can be called in initialized callback. + */ +typedef void (RETRO_CALLCONV *retro_camera_lifetime_status_t)(void); + +/* A callback for raw framebuffer data. buffer points to an XRGB8888 buffer. + * 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, + 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 + * 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 + * more depending on extensions. + * + * 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 + * 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 + * the API definition. + */ +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. + * Example bitmask: caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE) | (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER). + */ + uint64_t caps; + + /* Desired resolution for camera. Is only used as a hint. */ + unsigned width; + unsigned height; + + /* Set by frontend. */ + retro_camera_start_t start; + retro_camera_stop_t stop; + + /* Set by libretro core if raw framebuffer callbacks will be used. */ + 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; + + /* 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 + * 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 + * location-based data. + * + * To ensure compatibility with all location-based implementations, + * values for both interval_ms and interval_distance should be provided. + * + * interval_ms is the interval expressed in milliseconds. + * interval_distance is the distance interval expressed in meters. + */ +typedef void (RETRO_CALLCONV *retro_location_set_interval_t)(unsigned interval_ms, + unsigned interval_distance); + +/* Start location services. The device will start listening for changes to the + * 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 + * to the current location. */ +typedef void (RETRO_CALLCONV *retro_location_stop_t)(void); + +/* 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 + * and/or deinitialized. + * retro_location_start_t can be called in initialized callback. + */ +typedef void (RETRO_CALLCONV *retro_location_lifetime_status_t)(void); + +struct retro_location_callback +{ + retro_location_start_t start; + retro_location_stop_t stop; + retro_location_get_position_t get_position; + retro_location_set_interval_t set_interval; + + retro_location_lifetime_status_t initialized; + retro_location_lifetime_status_t deinitialized; +}; + +enum retro_rumble_effect +{ + RETRO_RUMBLE_STRONG = 0, + RETRO_RUMBLE_WEAK = 1, + + RETRO_RUMBLE_DUMMY = INT_MAX +}; + +/* 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. + * Calling this before first retro_run() is likely to return false. */ +typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, + enum retro_rumble_effect effect, uint16_t strength); + +struct retro_rumble_interface +{ + retro_set_rumble_state_t set_rumble_state; +}; + +/* 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 + * 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 + * called with true. + * Initial state is false (inactive). + */ +typedef void (RETRO_CALLCONV *retro_audio_set_state_callback_t)(bool enabled); + +struct retro_audio_callback +{ + retro_audio_callback_t callback; + retro_audio_set_state_callback_t set_state; +}; + +/* 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 + * fast-forward, slow-motion and framestepping. + * + * In those scenarios the reference frame time value will be used. */ +typedef int64_t retro_usec_t; +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 + * rounding to ensure that framestepping, etc is exact. */ + 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. + * */ +#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) + +/* Invalidates the current HW context. + * Any GL state is lost, and must not be deinitialized explicitly. + * If explicit deinitialization is desired by the libretro core, + * 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, + * allowing libretro core to initialize resources. + */ +typedef void (RETRO_CALLCONV *retro_hw_context_reset_t)(void); + +/* Gets current framebuffer which is to be rendered to. + * Could change every frame potentially. + */ +typedef uintptr_t (RETRO_CALLCONV *retro_hw_get_current_framebuffer_t)(void); + +/* Get a symbol from HW context. */ +typedef retro_proc_address_t (RETRO_CALLCONV *retro_hw_get_proc_address_t)(const char *sym); + +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, + /* OpenGL ES 2.0. */ + RETRO_HW_CONTEXT_OPENGLES2 = 2, + /* Modern desktop core GL context. Use version_major/ + * version_minor fields to set GL version. */ + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + /* OpenGL ES 3.0 */ + RETRO_HW_CONTEXT_OPENGLES3 = 4, + /* OpenGL ES 3.1+. Set version_major/version_minor. For GLES2 and GLES3, + * use the corresponding enums directly. */ + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + + /* 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 +}; + +struct retro_hw_render_callback +{ + /* Which API to use. Set by libretro core. */ + enum retro_hw_context_type context_type; + + /* 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 + * implementation are guaranteed to be invalid. + * + * 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 + * without any attempt to "free" old resources. + */ + retro_hw_context_reset_t context_reset; + + /* Set by frontend. + * TODO: This is rather obsolete. The frontend should not + * be providing preallocated framebuffers. */ + retro_hw_get_current_framebuffer_t get_current_framebuffer; + + /* 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. + * TODO: Obsolete. */ + bool depth; + + /* Set if stencil buffers should be attached. + * TODO: Obsolete. */ + bool stencil; + + /* 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, + * 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 + * 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 + * such as if the context is lost beyond recovery. + * + * 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 + * 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 + * just be destroyed without any notification. + * + * 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 + * 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 + * resources in the subsequent context_reset. + */ + + /* Creates a debug context. */ + bool debug_context; +}; + +/* 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. + * character is the text character of the pressed key. (UTF-32). + * 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 + * 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 + * character, character should be 0. + */ +typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, + uint32_t character, uint16_t key_modifiers); + +struct retro_keyboard_callback +{ + retro_keyboard_event_t callback; +}; + +/* 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. + * + * 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 + * set_eject_state(true). + * Set the disk index with set_image_index(index). Insert the disk again + * with set_eject_state(false). + */ + +/* If ejected is true, "ejects" the virtual disk tray. + * When ejected, the disk image index can be set. + */ +typedef bool (RETRO_CALLCONV *retro_set_eject_state_t)(bool ejected); + +/* Gets current eject state. The initial state is 'not ejected'. */ +typedef bool (RETRO_CALLCONV *retro_get_eject_state_t)(void); + +/* Gets current disk index. First disk is index 0. + * If return value is >= get_num_images(), no disk is currently inserted. + */ +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 + * index >= get_num_images(). + */ +typedef bool (RETRO_CALLCONV *retro_set_image_index_t)(unsigned index); + +/* Gets total number of images which are available to use. */ +typedef unsigned (RETRO_CALLCONV *retro_get_num_images_t)(void); + +struct retro_game_info; + +/* Replaces the disk image associated with index. + * 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 + * 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() + * returned 4 before. + * Index 1 will be removed, and the new index is 3. + */ +typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, + const struct retro_game_info *info); + +/* 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 + * 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; + 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; +}; + +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. + * 0 bit must be set to 0. + * This pixel format is default for compatibility concerns only. + * If a 15/16-bit pixel format is desired, consider using RGB565. */ + RETRO_PIXEL_FORMAT_0RGB1555 = 0, + + /* XRGB8888, native endian. + * X bits are ignored. */ + RETRO_PIXEL_FORMAT_XRGB8888 = 1, + + /* 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 + * available on a wide range of low-power devices. + * + * It is also natively supported in APIs like OpenGL ES. */ + RETRO_PIXEL_FORMAT_RGB565 = 2, + + /* Ensure sizeof() == sizeof(int). */ + RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX +}; + +struct retro_message +{ + const char *msg; /* Message to be displayed. */ + 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. */ +struct retro_input_descriptor +{ + /* Associates given parameters with a description. */ + unsigned port; + unsigned device; + unsigned index; + unsigned id; + + /* Human readable description for parameters. + * The pointer must remain valid until + * retro_unload_game() is called. */ + const char *description; +}; + +struct retro_system_info +{ + /* All pointers are owned by libretro implementation, and pointers must + * remain valid until it is unloaded. */ + + 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 + * load, separated with pipe. + * I.e. "bin|rom|iso". + * Typically used for a GUI to filter + * out extensions. */ + + /* 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. + * + * Cores should strive for setting need_fullpath to false, + * as it allows the frontend to perform patching, etc. + * + * 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 + * loading the real content. + * Necessary for certain libretro implementations that load games + * from zipped archives. */ + bool block_extract; +}; + +struct retro_game_geometry +{ + unsigned base_width; /* Nominal video width of game. */ + unsigned base_height; /* Nominal video height of game. */ + unsigned max_width; /* Maximum possible width of game. */ + unsigned max_height; /* Maximum possible height of game. */ + + float aspect_ratio; /* Nominal aspect ratio of game. If + * aspect_ratio is <= 0.0, an aspect ratio + * of base_width / base_height is assumed. + * A frontend could override this setting, + * if desired. */ +}; + +struct retro_system_timing +{ + double fps; /* FPS of video content. */ + double sample_rate; /* Sampling rate of audio. */ +}; + +struct retro_system_av_info +{ + struct retro_game_geometry geometry; + struct retro_system_timing timing; +}; + +struct retro_variable +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * If NULL, obtains the complete environment string if more + * complex parsing is necessary. + * 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: 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 +{ + /* 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_core_option_v2_category +{ + /* Variable uniquely identifying the + * option category. Valid key characters + * are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable category description + * > Used as category menu label when + * frontend has core option category + * support */ + const char *desc; + + /* Human-readable category information + * > Used as category menu sublabel when + * frontend has core option category + * support + * > Optional (may be NULL or an empty + * string) */ + const char *info; +}; + +struct retro_core_option_v2_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * Valid key characters are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable core option description + * > Used as menu label when frontend does + * not have core option category support + * e.g. "Video > Aspect Ratio" */ + const char *desc; + + /* Human-readable core option description + * > Used as menu label when frontend has + * core option category support + * e.g. "Aspect Ratio", where associated + * retro_core_option_v2_category::desc + * is "Video" + * > If empty or NULL, the string specified by + * desc will be used as the menu label + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *desc_categorized; + + /* Human-readable core option information + * > Used as menu sublabel */ + const char *info; + + /* Human-readable core option information + * > Used as menu sublabel when frontend + * has core option category support + * (e.g. may be required when info text + * references an option by name/desc, + * and the desc/desc_categorized text + * for that option differ) + * > If empty or NULL, the string specified by + * info will be used as the menu sublabel + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *info_categorized; + + /* Variable specifying category (e.g. "video", + * "audio") that will be assigned to the option + * if frontend has core option category support. + * > Categorized options will be displayed in a + * subsection/submenu of the frontend core + * option interface + * > Specified string must match one of the + * retro_core_option_v2_category::key values + * in the associated retro_core_option_v2_category + * array; If no match is not found, specified + * string will be considered as NULL + * > If specified string is empty or NULL, option will + * have no category and will be shown at the top + * level of the frontend core option interface */ + const char *category_key; + + /* 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_v2 +{ + /* Array of retro_core_option_v2_category structs, + * terminated by NULL + * > If NULL, all entries in definitions array + * will have no category and will be shown at + * the top level of the frontend core option + * interface + * > Will be ignored if frontend does not have + * core option category support */ + struct retro_core_option_v2_category *categories; + + /* Array of retro_core_option_v2_definition structs, + * terminated by NULL */ + struct retro_core_option_v2_definition *definitions; +}; + +struct retro_core_options_v2_intl +{ + /* Pointer to a retro_core_options_v2 struct + * > US English implementation + * > Must point to a valid struct */ + struct retro_core_options_v2 *us; + + /* Pointer to a retro_core_options_v2 struct + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_options_v2 *local; +}; + +/* Used by the frontend to monitor changes in core option + * visibility. May be called each time any core option + * value is set via the frontend. + * - On each invocation, the core must update the visibility + * of any dynamically hidden options using the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY environment + * callback. + * - On the first invocation, returns 'true' if the visibility + * of any core option has changed since the last call of + * retro_load_game() or retro_load_game_special(). + * - On each subsequent invocation, returns 'true' if the + * visibility of any core option has changed since the last + * time the function was called. */ +typedef bool (RETRO_CALLCONV *retro_core_options_update_display_callback_t)(void); +struct retro_core_options_update_display_callback +{ + retro_core_options_update_display_callback_t callback; +}; + +struct retro_game_info +{ + const char *path; /* Path to game, UTF-8 encoded. + * 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 + * if need_fullpath was set. */ + size_t size; /* Size of memory buffer. */ + const char *meta; /* String of implementation specific meta-data. */ +}; + +#define RETRO_MEMORY_ACCESS_WRITE (1 << 0) + /* The core will write to the buffer provided by retro_framebuffer::data. */ +#define RETRO_MEMORY_ACCESS_READ (1 << 1) + /* The core will read from retro_framebuffer::data. */ +#define RETRO_MEMORY_TYPE_CACHED (1 << 0) + /* The memory in data is cached. + * If not cached, random writes and/or reading from the buffer is expected to be very slow. */ +struct retro_framebuffer +{ + void *data; /* The framebuffer which the core can render into. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. + The initial contents of data are unspecified. */ + unsigned width; /* The framebuffer width used by the core. Set by core. */ + unsigned height; /* The framebuffer height used by the core. Set by core. */ + size_t pitch; /* The number of bytes between the beginning of a scanline, + and beginning of the next scanline. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ + enum retro_pixel_format format; /* The pixel format the core must use to render into data. + This format could differ from the format used in + SET_PIXEL_FORMAT. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ + + unsigned access_flags; /* How the core will access the memory in the framebuffer. + RETRO_MEMORY_ACCESS_* flags. + Set by core. */ + unsigned memory_flags; /* Flags telling core how the memory has been mapped. + RETRO_MEMORY_TYPE_* flags. + 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 + * 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 + * 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 + * that is packed in memory, i.e. pitch == width * byte_per_pixel. + * 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 + * generates a single sample at a time. + * Format is signed 16-bit native endian. + */ +typedef void (RETRO_CALLCONV *retro_audio_sample_t)(int16_t left, int16_t right); + +/* Renders multiple audio frames in one go. + * + * One frame is defined as a sample of left and right channels, interleaved. + * I.e. int16_t buf[4] = { l, r, l, r }; would be 2 frames. + * Only one of the audio callbacks must ever be used. + */ +typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data, + size_t frames); + +/* Polls input. */ +typedef void (RETRO_CALLCONV *retro_input_poll_t)(void); + +/* Queries for input for player 'port'. device will be masked with + * RETRO_DEVICE_MASK. + * + * 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, + unsigned index, unsigned id); + +/* 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 + * 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); +RETRO_API void retro_set_audio_sample(retro_audio_sample_t); +RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t); +RETRO_API void retro_set_input_poll(retro_input_poll_t); +RETRO_API void retro_set_input_state(retro_input_state_t); + +/* Library global initialization/deinitialization. */ +RETRO_API void retro_init(void); +RETRO_API void retro_deinit(void); + +/* Must return RETRO_API_VERSION. Used to validate ABI compatibility + * when the API is revised. */ +RETRO_API unsigned retro_api_version(void); + +/* 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 + * variable if needed. + * 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 + * 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. + * + * 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. */ +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 + * 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 + * 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 + * value, to ensure that the frontend can allocate a save state buffer once. + */ +RETRO_API size_t retro_serialize_size(void); + +/* Serializes internal state. If failed, or size is lower than + * retro_serialize_size(), it should return false, true otherwise. */ +RETRO_API bool retro_serialize(void *data, size_t size); +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. + * 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, + * except in extreme cases. */ +RETRO_API bool retro_load_game_special( + unsigned game_type, + const struct retro_game_info *info, size_t num_info +); + +/* Unloads the currently loaded game. Called before retro_deinit(void). */ +RETRO_API void retro_unload_game(void); + +/* Gets region of game. */ +RETRO_API unsigned retro_get_region(void); + +/* Gets region of memory. */ +RETRO_API void *retro_get_memory_data(unsigned id); +RETRO_API size_t retro_get_memory_size(unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/SameBoy-old/libretro/libretro_core_options.inc b/thirdparty/SameBoy-old/libretro/libretro_core_options.inc new file mode 100644 index 000000000..94e147412 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/libretro_core_options.inc @@ -0,0 +1,946 @@ +#ifndef LIBRETRO_CORE_OPTIONS_H__ +#define LIBRETRO_CORE_OPTIONS_H__ + +#include +#include + +#include "libretro.h" +#include "retro_inline.h" + +/* + ******************************** + * VERSION: 2.0 + ******************************** + * + * - 2.0: Add support for core options v2 interface + * - 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_v2_category option_cats_us[] = { + { + "system", + "System", + "Configure base hardware selection." + }, + { + "video", + "Video", + "Configure display parameters: palette selection, colour correction, screen border." + }, + { + "audio", + "Audio", + "Configure audio emulation: highpass filter, electrical interference." + }, + { + "input", + "Input", + "Configure input parameters: rumble support." + }, + { NULL, NULL }, +}; + +struct retro_core_option_v2_definition option_defs_us[] = { + + /* Core options used in single cart mode */ + + { + "sameboy_model", + "System - Emulated Model (Requires Restart)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color 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_auto_sgb_model", + "System - Auto Detected SGB Model (Requires Restart)", + "Auto Detected SGB Model (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_rtc", + "System - Real Time Clock Emulation", + "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.", + NULL, + "system", + { + { "sync to system clock", "Sync to System Clock" }, + { "accurate", "Accurate" }, + { NULL, NULL }, + }, + "sync to system clock" + }, + { + "sameboy_mono_palette", + "Video - GB Mono Palette", + "GB Mono Palette", + "Selects the color palette that should be used when playing Game Boy games.", + NULL, + "video", + { + { "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", + "GBC Color Correction", + "Defines which type of color correction should be applied when playing Game Boy Color games.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_border", + "Video - Display Border", + "Display Border", + "Defines when to display an on-screen border around the content.", + NULL, + "video", + { + { "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", + "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.", + NULL, + "audio", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "Disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference", + "Audio - Interference Volume", + "Interference Volume", + "Controls the volume of the buzzing effect caused by the electrical interference between the Game Boy SoC and the system speakers.", + NULL, + "audio", + { + { "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", + "Rumble Mode", + "Defines which type of content should trigger rumble effects.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + + /* Core options used in dual cart mode */ + + { + "sameboy_link", + "System - Link Cable Emulation", + "Link Cable Emulation", + "Enables the emulation of the link cable, allowing communication and exchange of data between two Game Boy systems.", + NULL, + "system", + { + { "enabled", "Enabled" }, + { "disabled", "Disabled" }, + { NULL, NULL }, + }, + "enabled" + }, + { + "sameboy_screen_layout", + "System - Screen Layout", + "Screen Layout", + "When emulating two systems at once, this option defines the respective position of the two screens.", + NULL, + "system", + { + { "top-down", "Top-Down" }, + { "left-right", "Left-Right" }, + { NULL, NULL }, + }, + "top-down" + }, + { + "sameboy_audio_output", + "System - Audio Output", + "Audio Output", + "Selects which of the two emulated Game Boy systems should output audio.", + NULL, + "system", + { + { "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)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color 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_auto_sgb_model_1", + "System - Auto Detected SGB Model for Game Boy #1 (Requires Restart)", + "Auto Detected SGB Model for Game Boy #1 (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected for Game Boy #1. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_model_2", + "System - Emulated Model for Game Boy #2 (Requires Restart)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color 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_auto_sgb_model_2", + "System - Auto Detected SGB Model for Game Boy #2 (Requires Restart)", + "Auto Detected SGB Model for Game Boy #2 (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected for Game Boy #2. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_mono_palette_1", + "Video - GB Mono Palette for Game Boy #1", + "GB Mono Palette for Game Boy #1", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #1.", + NULL, + "video", + { + { "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", + "GB Mono Palette for Game Boy #2", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #2.", + NULL, + "video", + { + { "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", + "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.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_light_temperature_2", + "Video - Ambient Light Temperature for Game Boy #2", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_high_pass_filter_mode_1", + "Audio - Highpass Filter for Game Boy #1", + "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.", + NULL, + "audio", + { + { "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", + "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.", + NULL, + "audio", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference_1", + "Audio - Interference Volume for Game Boy #1", + "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.", + NULL, + "audio", + { + { "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", + "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.", + NULL, + "audio", + { + { "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", + "Rumble Mode for Game Boy #1", + "Defines which type of content should trigger rumble effects when played on Game Boy #1.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + { + "sameboy_rumble_2", + "Input - Rumble Mode for Game Boy #2", + "Rumble Mode for Game Boy #2", + "Defines which type of content should trigger rumble effects when played on Game Boy #2.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + + { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }, +}; + +struct retro_core_options_v2 options_us = { + option_cats_us, + option_defs_us +}; + +/* + ******************************** + * Language Mapping + ******************************** + */ + +#ifndef HAVE_NO_LANGEXTRA +struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST] = { + &options_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, + bool *categories_supported) +{ + unsigned version = 0; +#ifndef HAVE_NO_LANGEXTRA + unsigned language = 0; +#endif + + if (!environ_cb || !categories_supported) return; + + *categories_supported = false; + + if (!environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version)) version = 0; + + if (version >= 2) { +#ifndef HAVE_NO_LANGEXTRA + struct retro_core_options_v2_intl core_options_intl; + + core_options_intl.us = &options_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 = options_intl[language]; + + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL, + &core_options_intl); +#else + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + &options_us); +#endif + } + else { + size_t i, j; + size_t option_index = 0; + size_t num_options = 0; + struct retro_core_option_definition + *option_v1_defs_us = NULL; +#ifndef HAVE_NO_LANGEXTRA + size_t num_options_intl = 0; + struct retro_core_option_v2_definition + *option_defs_intl = NULL; + struct retro_core_option_definition + *option_v1_defs_intl = NULL; + struct retro_core_options_intl + core_options_v1_intl; +#endif + struct retro_variable *variables = NULL; + char **values_buf = NULL; + + /* Determine total number of options */ + while (true) { + if (option_defs_us[num_options].key) num_options++; + else break; + } + + if (version >= 1) { + /* Allocate US array */ + option_v1_defs_us = (struct retro_core_option_definition *) calloc(num_options + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_us array */ + for (i = 0; i < num_options; i++) { + struct retro_core_option_v2_definition *option_def_us = &option_defs_us[i]; + struct retro_core_option_value *option_values = option_def_us->values; + struct retro_core_option_definition *option_v1_def_us = &option_v1_defs_us[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_us->values; + + option_v1_def_us->key = option_def_us->key; + option_v1_def_us->desc = option_def_us->desc; + option_v1_def_us->info = option_def_us->info; + option_v1_def_us->default_value = option_def_us->default_value; + + /* Values must be copied individually... */ + while (option_values->value) { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + +#ifndef HAVE_NO_LANGEXTRA + if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && + (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) && + options_intl[language]) + option_defs_intl = options_intl[language]->definitions; + + if (option_defs_intl) { + /* Determine number of intl options */ + while (true) { + if (option_defs_intl[num_options_intl].key) num_options_intl++; + else break; + } + + /* Allocate intl array */ + option_v1_defs_intl = (struct retro_core_option_definition *) + calloc(num_options_intl + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_intl array */ + for (i = 0; i < num_options_intl; i++) { + struct retro_core_option_v2_definition *option_def_intl = &option_defs_intl[i]; + struct retro_core_option_value *option_values = option_def_intl->values; + struct retro_core_option_definition *option_v1_def_intl = &option_v1_defs_intl[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_intl->values; + + option_v1_def_intl->key = option_def_intl->key; + option_v1_def_intl->desc = option_def_intl->desc; + option_v1_def_intl->info = option_def_intl->info; + option_v1_def_intl->default_value = option_def_intl->default_value; + + /* Values must be copied individually... */ + while (option_values->value) { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + } + + core_options_v1_intl.us = option_v1_defs_us; + core_options_v1_intl.local = option_v1_defs_intl; + + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_v1_intl); +#else + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, option_v1_defs_us); +#endif + } + else { + /* 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 */ + while (true) { + if (values[num_values].value) { + /* 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++; + } + } + else break; + } + + /* Build values string */ + if (num_values > 0) { + 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[option_index].key = key; + variables[option_index].value = values_buf[i]; + option_index++; + } + + /* Set variables */ + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); + } + +error: + /* Clean up */ + + if (option_v1_defs_us) { + free(option_v1_defs_us); + option_v1_defs_us = NULL; + } + +#ifndef HAVE_NO_LANGEXTRA + if (option_v1_defs_intl) { + free(option_v1_defs_intl); + option_v1_defs_intl = NULL; + } +#endif + + 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/thirdparty/SameBoy-old/libretro/link.T b/thirdparty/SameBoy-old/libretro/link.T new file mode 100644 index 000000000..b0c262db9 --- /dev/null +++ b/thirdparty/SameBoy-old/libretro/link.T @@ -0,0 +1,5 @@ +{ + global: retro_*; + local: *; +}; + diff --git a/thirdparty/SameBoy-old/libretro/retro_inline.h b/thirdparty/SameBoy-old/libretro/retro_inline.h new file mode 100644 index 000000000..14c038ccd --- /dev/null +++ b/thirdparty/SameBoy-old/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 diff --git a/thirdparty/SameBoy-old/version.mk b/thirdparty/SameBoy-old/version.mk new file mode 100644 index 000000000..ee8731d3e --- /dev/null +++ b/thirdparty/SameBoy-old/version.mk @@ -0,0 +1 @@ +VERSION := 0.15.4 \ No newline at end of file diff --git a/thirdparty/SameBoy/.github/FUNDING.yml b/thirdparty/SameBoy/.github/FUNDING.yml new file mode 100644 index 000000000..a4c3a1bbd --- /dev/null +++ b/thirdparty/SameBoy/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: LIJI32 diff --git a/thirdparty/SameBoy/.github/actions/install_deps.sh b/thirdparty/SameBoy/.github/actions/install_deps.sh index 1c9749efc..52c66d96f 100755 --- a/thirdparty/SameBoy/.github/actions/install_deps.sh +++ b/thirdparty/SameBoy/.github/actions/install_deps.sh @@ -4,7 +4,7 @@ case `echo $1 | cut -d '-' -f 1` in sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev ( cd `mktemp -d` - curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip + curl -L https://github.com/rednex/rgbds/archive/v0.6.0.zip > rgbds.zip unzip rgbds.zip cd rgbds-* make -sj diff --git a/thirdparty/SameBoy/.github/actions/sanity_tests.sh b/thirdparty/SameBoy/.github/actions/sanity_tests.sh index 8984b264e..1d8b33e2c 100755 --- a/thirdparty/SameBoy/.github/actions/sanity_tests.sh +++ b/thirdparty/SameBoy/.github/actions/sanity_tests.sh @@ -1,10 +1,10 @@ set -e ./build/bin/tester/sameboy_tester --jobs 5 \ - --length 40 .github/actions/cgb_sound.gb \ + --length 45 .github/actions/cgb_sound.gb \ --length 10 .github/actions/cgb-acid2.gbc \ --length 10 .github/actions/dmg-acid2.gb \ ---dmg --length 40 .github/actions/dmg_sound-2.gb \ +--dmg --length 45 .github/actions/dmg_sound-2.gb \ --dmg --length 20 .github/actions/oam_bug-2.gb mv .github/actions/dmg{,-mode}-acid2.bmp @@ -15,18 +15,18 @@ mv .github/actions/dmg{,-mode}-acid2.bmp set +e FAILED_TESTS=` -shasum .github/actions/*.bmp | grep -q -E -v \(\ -44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\ +shasum .github/actions/*.bmp | grep -E -v \(\ +64c3fd9a5fe9aee40fe15f3371029c0d2f20f5bc\ \ .github/actions/cgb-acid2.bmp\|\ dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\ 0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\ -c50daed36c57a8170ff362042694786676350997\ \ .github/actions/dmg-mode-acid2.bmp\|\ +fbdb5e342bfdd2edda3ea5601d35d0ca60d18034\ \ .github/actions/dmg-mode-acid2.bmp\|\ c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\ f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\ \)` if [ -n "$FAILED_TESTS" ] ; then echo "Failed the following tests:" - echo $FAILED_TESTS | tr " " "\n" | grep -q -o -E "[^/]+\.bmp" | sed s/.bmp// | sort + echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort exit 1 fi diff --git a/thirdparty/SameBoy/.github/workflows/sanity.yml b/thirdparty/SameBoy/.github/workflows/sanity.yml index f460931b3..ac3732397 100644 --- a/thirdparty/SameBoy/.github/workflows/sanity.yml +++ b/thirdparty/SameBoy/.github/workflows/sanity.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-latest, ubuntu-latest, ubuntu-16.04] + os: [macos-latest, ubuntu-latest, ubuntu-18.04] cc: [gcc, clang] include: - os: macos-latest @@ -33,4 +33,4 @@ jobs: uses: actions/upload-artifact@v1 with: name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }} - path: build/bin \ No newline at end of file + path: build/bin diff --git a/thirdparty/SameBoy/BESS.md b/thirdparty/SameBoy/BESS.md index b4d96247b..1807943c6 100644 --- a/thirdparty/SameBoy/BESS.md +++ b/thirdparty/SameBoy/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 @@ -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 @@ -176,6 +176,48 @@ The length of this block is 0x11 bytes long and it follows the following structu | 0x0E | Scheduled alarm time days (16-bit) | | 0x10 | Alarm enabled flag (8-bits, either 0 or 1) | +#### TPP1 block +The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC. + +The length of this block is 0x11 bytes long and it follows the following structure: + +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | UNIX timestamp at the time of the save state (64-bit) | +| 0x08 | The current RTC data (4 bytes) | +| 0x0C | The latched RTC data (4 bytes) | +| 0x10 | The value of the MR4 register (8-bits) | + + +#### MBC7 block +The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state. + +The length of this block is 0xA bytes long and it follows the following structure: + +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | Flags (8-bits) | +| 0x01 | Argument bits left (8-bits) | +| 0x02 | Current EEPROM command (16-bits) | +| 0x04 | Pending bits to read (16-bits) | +| 0x06 | Latched gyro X value (16-bits) | +| 0x08 | Latched gyro Y value (16-bits) | + +The meaning of the individual bits in flags are: + * Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X` + * Bit 1: EEPROM DO line + * Bit 2: EEPROM DI line + * Bit 3: EEPROM CLK line + * Bit 4: EEPROM CS line + * Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command + * Bits 6-7: Unused. + +The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`. + +If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0. + +The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones. + #### SGB block The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. diff --git a/thirdparty/SameBoy/BootROMs/SameBoyLogo.png b/thirdparty/SameBoy/BootROMs/SameBoyLogo.png index c7cfc087d..ad1a760c0 100644 Binary files a/thirdparty/SameBoy/BootROMs/SameBoyLogo.png and b/thirdparty/SameBoy/BootROMs/SameBoyLogo.png differ diff --git a/thirdparty/SameBoy/BootROMs/cgb0_boot.asm b/thirdparty/SameBoy/BootROMs/cgb0_boot.asm new file mode 100644 index 000000000..d49166d89 --- /dev/null +++ b/thirdparty/SameBoy/BootROMs/cgb0_boot.asm @@ -0,0 +1,2 @@ +CGB0 EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy/BootROMs/cgb_boot.asm b/thirdparty/SameBoy/BootROMs/cgb_boot.asm index 1345915d6..f05c7fb15 100644 --- a/thirdparty/SameBoy/BootROMs/cgb_boot.asm +++ b/thirdparty/SameBoy/BootROMs/cgb_boot.asm @@ -1,5 +1,7 @@ ; SameBoy CGB bootstrap ROM -; Todo: use friendly names for HW registers instead of magic numbers + +INCLUDE "hardware.inc" + SECTION "BootCode", ROM0[$0] Start: ; Init stack pointer @@ -7,13 +9,6 @@ Start: ; Clear memory VRAM call ClearMemoryPage8000 - ld a, 2 - ld c, $70 - ld [c], a -; Clear RAM Bank 2 (Like the original boot ROM) - ld h, $D0 - call ClearMemoryPage - ld [c], a ; Clear OAM ld h, $fe @@ -23,32 +18,35 @@ Start: dec c jr nz, .clearOAMLoop +IF !DEF(CGB0) ; Init waveform ld c, $10 + ld hl, $FF30 .waveformLoop ldi [hl], a cpl dec c jr nz, .waveformLoop +ENDC ; Clear chosen input palette ldh [InputPalette], a ; Clear title checksum ldh [TitleChecksum], a +; Init Audio ld a, $80 - ldh [$26], a - ldh [$11], a + ldh [rNR52], a + ldh [rNR11], a ld a, $f3 - ldh [$12], a - ldh [$25], a + ldh [rNR12], a + ldh [rNR51], a ld a, $77 - ldh [$24], a - ld hl, $FF30 + ldh [rNR50], a ; Init BG palette ld a, $fc - ldh [$47], a + ldh [rBGP], a ; Load logo from ROM. ; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. @@ -70,14 +68,14 @@ Start: ; Clear the second VRAM bank ld a, 1 - ldh [$4F], a + ldh [rVBK], a call ClearMemoryPage8000 call LoadTileset ld b, 3 IF DEF(FAST) xor a - ldh [$4F], a + ldh [rVBK], a ELSE ; Load Tilemap ld hl, $98C2 @@ -127,11 +125,11 @@ ELSE push af ; Switch to second VRAM Bank ld a, 1 - ldh [$4F], a + ldh [rVBK], a ld [hl], 8 ; Switch to back first VRAM Bank xor a - ldh [$4F], a + ldh [rVBK], a pop af ldi [hl], a ret @@ -185,15 +183,14 @@ ENDC ; Turn on LCD ld a, $91 - ldh [$40], a + ldh [rLCDC], a IF !DEF(FAST) call DoIntroAnimation - ld a, 45 + ld a, 48 ; frames to wait after playing the chime ldh [WaitLoopCounter], a -; Wait ~0.75 seconds - ld b, a + ld b, 4 ; frames to wait before playing the chime call WaitBFrames ; Play first sound @@ -219,12 +216,15 @@ ENDC IF DEF(AGB) ld b, 1 ENDC + jr BootGame -; Will be filled with NOPs +HDMAData: + db $D0, $00, $98, $A0, $12 + db $D0, $00, $80, $00, $40 SECTION "BootGame", ROM0[$fe] BootGame: - ldh [$50], a + ldh [rBANK], a ; unmap boot ROM SECTION "MoreStuff", ROM0[$200] ; Game Palettes Data @@ -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 @@ -329,8 +329,8 @@ FirstChecksumWithDuplicate: ChecksumsEnd: PalettePerChecksum: -palette_index: MACRO ; palette, flags - db ((\1) * 3) | (\2) ; | $80 means game requires DMG boot tilemap +MACRO palette_index ; palette, flags + 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 @@ -433,10 +433,10 @@ Dups4thLetterArray: ; We assume the last three arrays fit in the same $100 byte page! PaletteCombinations: -palette_comb: MACRO ; Obj0, Obj1, Bg +MACRO palette_comb ; Obj0, Obj1, Bg db (\1) * 8, (\2) * 8, (\3) *8 ENDM -raw_palette_comb: MACRO ; Obj0, Obj1, Bg +MACRO raw_palette_comb ; Obj0, Obj1, Bg db (\1) * 2, (\2) * 2, (\3) * 2 ENDM palette_comb 4, 4, 29 @@ -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 @@ -565,7 +565,12 @@ AnimationColors: dw $017D ; Orange dw $241D ; Red dw $6D38 ; Purple - dw $7102 ; Blue +IF DEF(AGB) + dw $6D60 ; Blue +ELSE + dw $5500 ; Blue +ENDC + AnimationColorsEnd: ; Helper Functions @@ -609,9 +614,9 @@ WaitBFrames: ret PlaySound: - ldh [$13], a + ldh [rNR13], a ld a, $87 - ldh [$14], a + ldh [rNR14], a ret ClearMemoryPage8000: @@ -762,7 +767,7 @@ ReadTrademarkSymbol: DoIntroAnimation: ; Animate the intro ld a, 1 - ldh [$4F], a + ldh [rVBK], a ld d, 26 .animationLoop ld b, 2 @@ -834,8 +839,6 @@ IF !DEF(FAST) res 2, b .redNotMaxed - ; add de, bc - ; ld [hli], de ld a, e add c ld [hli], a @@ -853,12 +856,19 @@ IF !DEF(FAST) dec b jr nz, .fadeLoop ENDC - ld a, 1 + ld a, 2 + ldh [rSVBK], a + ; Clear RAM Bank 2 (Like the original boot ROM) + ld hl, $D000 + call ClearMemoryPage + inc a call ClearVRAMViaHDMA call _ClearVRAMViaHDMA call ClearVRAMViaHDMA ; A = $40, so it's bank 0 - ld a, $ff - ldh [$00], a + xor a + ldh [rSVBK], a + cpl + ldh [rJOYP], a ; Final values for CGB mode ld d, a @@ -870,7 +880,7 @@ ENDC call z, EmulateDMG bit 7, a - ldh [$4C], a + ldh [rKEY0], a ; write CGB compatibility byte, CGB mode ldh a, [TitleChecksum] ld b, a @@ -900,7 +910,7 @@ ENDC .emulateDMGForCGBGame call EmulateDMG - ldh [$4C], a + ldh [rKEY0], a ; write $04, DMG emulation mode ld a, $1 ret @@ -914,11 +924,14 @@ GetKeyComboPalette: EmulateDMG: ld a, 1 - ldh [$6C], a ; DMG Emulation + ldh [rOPRI], a ; DMG Emulation sprite priority call GetPaletteIndex bit 7, a call nz, LoadDMGTilemap - and $7F + res 7, a + ld b, a + add b + add b ld b, a ldh a, [InputPalette] and a @@ -978,7 +991,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 @@ -1048,7 +1061,7 @@ LoadPalettesFromHRAM: LoadBGPalettes: ld e, 0 - ld c, $68 + ld c, LOW(rBGPI) LoadPalettes: ld a, $80 @@ -1063,9 +1076,10 @@ LoadPalettes: ret ClearVRAMViaHDMA: - ldh [$4F], a + ldh [rVBK], a ld hl, HDMAData _ClearVRAMViaHDMA: + call WaitFrame ; Wait for vblank ld c, $51 ld b, 5 .loop @@ -1079,8 +1093,8 @@ _ClearVRAMViaHDMA: ; clobbers AF and HL GetInputPaletteIndex: ld a, $20 ; Select directions - ldh [$00], a - ldh a, [$00] + ldh [rJOYP], a + ldh a, [rJOYP] cpl and $F ret z ; No direction keys pressed, no palette @@ -1094,8 +1108,8 @@ GetInputPaletteIndex: ; c = 1: Right, 2: Left, 3: Up, 4: Down ld a, $10 ; Select buttons - ldh [$00], a - ldh a, [$00] + ldh [rJOYP], a + ldh a, [rJOYP] cpl rla rla @@ -1184,7 +1198,7 @@ ChangeAnimationPalette: call WaitFrame call LoadPalettesFromHRAM ; Delay the wait loop while the user is selecting a palette - ld a, 45 + ld a, 48 ldh [WaitLoopCounter], a pop de pop bc @@ -1219,10 +1233,6 @@ LoadDMGTilemap: pop af ret -HDMAData: - db $88, $00, $98, $A0, $12 - db $88, $00, $80, $00, $40 - BootEnd: IF BootEnd > $900 FAIL "BootROM overflowed: {BootEnd}" diff --git a/thirdparty/SameBoy/BootROMs/dmg_boot.asm b/thirdparty/SameBoy/BootROMs/dmg_boot.asm index 97a12e7cd..1d282744b 100644 --- a/thirdparty/SameBoy/BootROMs/dmg_boot.asm +++ b/thirdparty/SameBoy/BootROMs/dmg_boot.asm @@ -1,5 +1,7 @@ ; SameBoy DMG bootstrap ROM -; Todo: use friendly names for HW registers instead of magic numbers + +INCLUDE "hardware.inc" + SECTION "BootCode", ROM0[$0] Start: ; Init stack pointer @@ -15,17 +17,17 @@ Start: ; Init Audio ld a, $80 - ldh [$26], a - ldh [$11], a + ldh [rNR52], a + ldh [rNR11], a ld a, $f3 - ldh [$12], a - ldh [$25], a + ldh [rNR12], a + ldh [rNR51], a ld a, $77 - ldh [$24], a + ldh [rNR50], a ; Init BG palette ld a, $54 - ldh [$47], a + ldh [rBGP], a ; Load logo from ROM. ; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. @@ -70,11 +72,11 @@ Start: .tilemapDone ld a, 30 - ldh [$ff42], a + ldh [rSCY], a ; Turn on LCD ld a, $91 - ldh [$40], a + ldh [rLCDC], a ld d, (-119) & $FF ld c, 15 @@ -84,7 +86,7 @@ Start: ld a, d sra a sra a - ldh [$ff42], a + ldh [rSCY], a ld a, d add c ld d, a @@ -92,12 +94,12 @@ Start: cp 8 jr nz, .noPaletteChange ld a, $A8 - ldh [$47], a + ldh [rBGP], a .noPaletteChange dec c jr nz, .animate ld a, $fc - ldh [$47], a + ldh [rBGP], a ; Play first sound ld a, $83 @@ -115,7 +117,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 @@ -163,9 +169,9 @@ WaitBFrames: ret PlaySound: - ldh [$13], a + ldh [rNR13], a ld a, $87 - ldh [$14], a + ldh [rNR14], a ret @@ -174,4 +180,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SECTION "BootGame", ROM0[$fe] BootGame: - ldh [$50], a \ No newline at end of file + ldh [rBANK], a ; unmap boot ROM \ No newline at end of file diff --git a/thirdparty/SameBoy/BootROMs/hardware.inc b/thirdparty/SameBoy/BootROMs/hardware.inc new file mode 100644 index 000000000..882afc18e --- /dev/null +++ b/thirdparty/SameBoy/BootROMs/hardware.inc @@ -0,0 +1,988 @@ +;* +;* Gameboy Hardware definitions +;* +;* Based on Jones' hardware.inc +;* And based on Carsten Sorensen's ideas. +;* +;* Rev 1.1 - 15-Jul-97 : Added define check +;* Rev 1.2 - 18-Jul-97 : Added revision check macro +;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 +;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes +;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines +;* : and Nintendo Logo +;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC +;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 +;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC +;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format +;* Rev 2.0 - : Added GBC registers +;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines +;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates +;* Rev 2.3 - : Fixed incorrect _HRAM equate +;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) +;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) +;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) +;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) +;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta) +;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants +;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) +;* Rev 4.0 - 03-May-21 : Updated to use RGBDS 0.5.0 syntax, changed IEF_LCDC to IEF_STAT (Eievui) +;* Rev 4.1 - 16-Aug-21 : Added more flags, bit number defines, and offset constants for OAM and window positions (rondnelson99) +;* Rev 4.2 - 04-Sep-21 : Added CH3- and CH4-specific audio registers flags (ISSOtm) +;* Rev 4.3 - 07-Nov-21 : Deprecate VRAM address constants (Eievui) +;* Rev 4.4 - 11-Jan-22 : Deprecate VRAM CART_SRAM_2KB constant (avivace) +;* Rev 4.5 - 03-Mar-22 : Added bit number definitions for OCPS, BCPS and LCDC (sukus) + +IF __RGBDS_MAJOR__ == 0 && __RGBDS_MINOR__ < 5 + FAIL "This version of 'hardware.inc' requires RGBDS version 0.5.0 or later." +ENDC + +; If all of these are already defined, don't do it again. + + IF !DEF(HARDWARE_INC) +DEF HARDWARE_INC EQU 1 + +MACRO rev_Check_hardware_inc +;NOTE: REVISION NUMBER CHANGES MUST BE ADDED +;TO SECOND PARAMETER IN FOLLOWING LINE. + IF \1 > 4.5 ;PUT REVISION NUMBER HERE + WARN "Version \1 or later of 'hardware.inc' is required." + ENDC +ENDM + +DEF _VRAM EQU $8000 ; $8000->$9FFF +DEF _SCRN0 EQU $9800 ; $9800->$9BFF +DEF _SCRN1 EQU $9C00 ; $9C00->$9FFF +DEF _SRAM EQU $A000 ; $A000->$BFFF +DEF _RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF +DEF _RAMBANK EQU $D000 ; $D000->$DFFF +DEF _OAMRAM EQU $FE00 ; $FE00->$FE9F +DEF _IO EQU $FF00 ; $FF00->$FF7F,$FFFF +DEF _AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F +DEF _HRAM EQU $FF80 ; $FF80->$FFFE + +; *** MBC5 Equates *** + +DEF rRAMG EQU $0000 ; $0000->$1fff +DEF rROMB0 EQU $2000 ; $2000->$2fff +DEF rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. +DEF rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + +;*************************************************************************** +;* +;* Custom registers +;* +;*************************************************************************** + +; -- +; -- P1 ($FF00) +; -- Register for reading joy pad info. (R/W) +; -- +DEF rP1 EQU $FF00 + +DEF P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons +DEF P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad +DEF P1F_3 EQU %00001000 ; P13 in port +DEF P1F_2 EQU %00000100 ; P12 in port +DEF P1F_1 EQU %00000010 ; P11 in port +DEF P1F_0 EQU %00000001 ; P10 in port + +DEF P1F_GET_DPAD EQU P1F_5 +DEF P1F_GET_BTN EQU P1F_4 +DEF P1F_GET_NONE EQU P1F_4 | P1F_5 + + +; -- +; -- SB ($FF01) +; -- Serial Transfer Data (R/W) +; -- +DEF rSB EQU $FF01 + + +; -- +; -- SC ($FF02) +; -- Serial I/O Control (R/W) +; -- +DEF rSC EQU $FF02 + +DEF SCF_START EQU %10000000 ;Transfer Start Flag (1=Transfer in progress, or requested) +DEF SCF_SPEED EQU %00000010 ;Clock Speed (0=Normal, 1=Fast) ** CGB Mode Only ** +DEF SCF_SOURCE EQU %00000001 ;Shift Clock (0=External Clock, 1=Internal Clock) + +DEF SCB_START EQU 7 +DEF SCB_SPEED EQU 1 +DEF SCB_SOURCE EQU 0 + +; -- +; -- DIV ($FF04) +; -- Divider register (R/W) +; -- +DEF rDIV EQU $FF04 + + +; -- +; -- TIMA ($FF05) +; -- Timer counter (R/W) +; -- +DEF rTIMA EQU $FF05 + + +; -- +; -- TMA ($FF06) +; -- Timer modulo (R/W) +; -- +DEF rTMA EQU $FF06 + + +; -- +; -- TAC ($FF07) +; -- Timer control (R/W) +; -- +DEF rTAC EQU $FF07 + +DEF TACF_START EQU %00000100 +DEF TACF_STOP EQU %00000000 +DEF TACF_4KHZ EQU %00000000 +DEF TACF_16KHZ EQU %00000011 +DEF TACF_65KHZ EQU %00000010 +DEF TACF_262KHZ EQU %00000001 + +DEF TACB_START EQU 2 + + +; -- +; -- IF ($FF0F) +; -- Interrupt Flag (R/W) +; -- +DEF rIF EQU $FF0F + + +; -- +; -- AUD1SWEEP/NR10 ($FF10) +; -- Sweep register (R/W) +; -- +; -- Bit 6-4 - Sweep Time +; -- Bit 3 - Sweep Increase/Decrease +; -- 0: Addition (frequency increases???) +; -- 1: Subtraction (frequency increases???) +; -- Bit 2-0 - Number of sweep shift (# 0-7) +; -- Sweep Time: (n*7.8ms) +; -- +DEF rNR10 EQU $FF10 +DEF rAUD1SWEEP EQU rNR10 + +DEF AUD1SWEEP_UP EQU %00000000 +DEF AUD1SWEEP_DOWN EQU %00001000 + + +; -- +; -- AUD1LEN/NR11 ($FF11) +; -- Sound length/Wave pattern duty (R/W) +; -- +; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +DEF rNR11 EQU $FF11 +DEF rAUD1LEN EQU rNR11 + + +; -- +; -- AUD1ENV/NR12 ($FF12) +; -- Envelope (R/W) +; -- +; -- Bit 7-4 - Initial value of envelope +; -- Bit 3 - Envelope UP/DOWN +; -- 0: Decrease +; -- 1: Range of increase +; -- Bit 2-0 - Number of envelope sweep (# 0-7) +; -- +DEF rNR12 EQU $FF12 +DEF rAUD1ENV EQU rNR12 + + +; -- +; -- AUD1LOW/NR13 ($FF13) +; -- Frequency low byte (W) +; -- +DEF rNR13 EQU $FF13 +DEF rAUD1LOW EQU rNR13 + + +; -- +; -- AUD1HIGH/NR14 ($FF14) +; -- Frequency high byte (W) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- Bit 2-0 - Frequency's higher 3 bits +; -- +DEF rNR14 EQU $FF14 +DEF rAUD1HIGH EQU rNR14 + + +; -- +; -- AUD2LEN/NR21 ($FF16) +; -- Sound Length; Wave Pattern Duty (R/W) +; -- +; -- see AUD1LEN for info +; -- +DEF rNR21 EQU $FF16 +DEF rAUD2LEN EQU rNR21 + + +; -- +; -- AUD2ENV/NR22 ($FF17) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +DEF rNR22 EQU $FF17 +DEF rAUD2ENV EQU rNR22 + + +; -- +; -- AUD2LOW/NR23 ($FF18) +; -- Frequency low byte (W) +; -- +DEF rNR23 EQU $FF18 +DEF rAUD2LOW EQU rNR23 + + +; -- +; -- AUD2HIGH/NR24 ($FF19) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +DEF rNR24 EQU $FF19 +DEF rAUD2HIGH EQU rNR24 + + +; -- +; -- AUD3ENA/NR30 ($FF1A) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) +; -- +DEF rNR30 EQU $FF1A +DEF rAUD3ENA EQU rNR30 + +DEF AUD3ENA_OFF EQU %00000000 +DEF AUD3ENA_ON EQU %10000000 + + +; -- +; -- AUD3LEN/NR31 ($FF1B) +; -- Sound length (R/W) +; -- +; -- Bit 7-0 - Sound length +; -- +DEF rNR31 EQU $FF1B +DEF rAUD3LEN EQU rNR31 + + +; -- +; -- AUD3LEVEL/NR32 ($FF1C) +; -- Select output level +; -- +; -- Bit 6-5 - Select output level +; -- 00: 0/1 (mute) +; -- 01: 1/1 +; -- 10: 1/2 +; -- 11: 1/4 +; -- +DEF rNR32 EQU $FF1C +DEF rAUD3LEVEL EQU rNR32 + +DEF AUD3LEVEL_MUTE EQU %00000000 +DEF AUD3LEVEL_100 EQU %00100000 +DEF AUD3LEVEL_50 EQU %01000000 +DEF AUD3LEVEL_25 EQU %01100000 + + +; -- +; -- AUD3LOW/NR33 ($FF1D) +; -- Frequency low byte (W) +; -- +; -- see AUD1LOW for info +; -- +DEF rNR33 EQU $FF1D +DEF rAUD3LOW EQU rNR33 + + +; -- +; -- AUD3HIGH/NR34 ($FF1E) +; -- Frequency high byte (W) +; -- +; -- see AUD1HIGH for info +; -- +DEF rNR34 EQU $FF1E +DEF rAUD3HIGH EQU rNR34 + + +; -- +; -- AUD4LEN/NR41 ($FF20) +; -- Sound length (R/W) +; -- +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +DEF rNR41 EQU $FF20 +DEF rAUD4LEN EQU rNR41 + + +; -- +; -- AUD4ENV/NR42 ($FF21) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +DEF rNR42 EQU $FF21 +DEF rAUD4ENV EQU rNR42 + + +; -- +; -- AUD4POLY/NR43 ($FF22) +; -- Polynomial counter (R/W) +; -- +; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) +; -- polynomial counter (0000-1101) +; -- freq=drf*1/2^scf (not sure) +; -- Bit 3 - Selection of the polynomial counter's step +; -- 0: 15 steps +; -- 1: 7 steps +; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) +; -- 000: f/4 001: f/8 010: f/16 011: f/24 +; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz) +; -- +DEF rNR43 EQU $FF22 +DEF rAUD4POLY EQU rNR43 + +DEF AUD4POLY_15STEP EQU %00000000 +DEF AUD4POLY_7STEP EQU %00001000 + + +; -- +; -- AUD4GO/NR44 ($FF23) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- +DEF rNR44 EQU $FF23 +DEF rAUD4GO EQU rNR44 + + +; -- +; -- AUDVOL/NR50 ($FF24) +; -- Channel control / ON-OFF / Volume (R/W) +; -- +; -- Bit 7 - Vin->SO2 ON/OFF (left) +; -- Bit 6-4 - SO2 output level (left speaker) (# 0-7) +; -- Bit 3 - Vin->SO1 ON/OFF (right) +; -- Bit 2-0 - SO1 output level (right speaker) (# 0-7) +; -- +DEF rNR50 EQU $FF24 +DEF rAUDVOL EQU rNR50 + +DEF AUDVOL_VIN_LEFT EQU %10000000 ; SO2 +DEF AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 + + +; -- +; -- AUDTERM/NR51 ($FF25) +; -- Selection of Sound output terminal (R/W) +; -- +; -- Bit 7 - Output channel 4 to SO2 terminal (left) +; -- Bit 6 - Output channel 3 to SO2 terminal (left) +; -- Bit 5 - Output channel 2 to SO2 terminal (left) +; -- Bit 4 - Output channel 1 to SO2 terminal (left) +; -- Bit 3 - Output channel 4 to SO1 terminal (right) +; -- Bit 2 - Output channel 3 to SO1 terminal (right) +; -- Bit 1 - Output channel 2 to SO1 terminal (right) +; -- Bit 0 - Output channel 1 to SO1 terminal (right) +; -- +DEF rNR51 EQU $FF25 +DEF rAUDTERM EQU rNR51 + +; SO2 +DEF AUDTERM_4_LEFT EQU %10000000 +DEF AUDTERM_3_LEFT EQU %01000000 +DEF AUDTERM_2_LEFT EQU %00100000 +DEF AUDTERM_1_LEFT EQU %00010000 +; SO1 +DEF AUDTERM_4_RIGHT EQU %00001000 +DEF AUDTERM_3_RIGHT EQU %00000100 +DEF AUDTERM_2_RIGHT EQU %00000010 +DEF AUDTERM_1_RIGHT EQU %00000001 + + +; -- +; -- AUDENA/NR52 ($FF26) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - All sound on/off (sets all audio regs to 0!) +; -- Bit 3 - Sound 4 ON flag (read only) +; -- Bit 2 - Sound 3 ON flag (read only) +; -- Bit 1 - Sound 2 ON flag (read only) +; -- Bit 0 - Sound 1 ON flag (read only) +; -- +DEF rNR52 EQU $FF26 +DEF rAUDENA EQU rNR52 + +DEF AUDENA_ON EQU %10000000 +DEF AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! + + +; -- +; -- LCDC ($FF40) +; -- LCD Control (R/W) +; -- +DEF rLCDC EQU $FF40 + +DEF LCDCF_OFF EQU %00000000 ; LCD Control Operation +DEF LCDCF_ON EQU %10000000 ; LCD Control Operation +DEF LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select +DEF LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select +DEF LCDCF_WINOFF EQU %00000000 ; Window Display +DEF LCDCF_WINON EQU %00100000 ; Window Display +DEF LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select +DEF LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select +DEF LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select +DEF LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select +DEF LCDCF_OBJ8 EQU %00000000 ; OBJ Construction +DEF LCDCF_OBJ16 EQU %00000100 ; OBJ Construction +DEF LCDCF_OBJOFF EQU %00000000 ; OBJ Display +DEF LCDCF_OBJON EQU %00000010 ; OBJ Display +DEF LCDCF_BGOFF EQU %00000000 ; BG Display +DEF LCDCF_BGON EQU %00000001 ; BG Display + +DEF LCDCB_ON EQU 7 ; LCD Control Operation +DEF LCDCB_WIN9C00 EQU 6 ; Window Tile Map Display Select +DEF LCDCB_WINON EQU 5 ; Window Display +DEF LCDCB_BG8000 EQU 4 ; BG & Window Tile Data Select +DEF LCDCB_BG9C00 EQU 3 ; BG Tile Map Display Select +DEF LCDCB_OBJ16 EQU 2 ; OBJ Construction +DEF LCDCB_OBJON EQU 1 ; OBJ Display +DEF LCDCB_BGON EQU 0 ; BG Display +; "Window Character Data Select" follows BG + + +; -- +; -- STAT ($FF41) +; -- LCDC Status (R/W) +; -- +DEF rSTAT EQU $FF41 + +DEF STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable) +DEF STATF_MODE10 EQU %00100000 ; Mode 10 +DEF STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) +DEF STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) +DEF STATF_LYCF EQU %00000100 ; Coincidence Flag +DEF STATF_HBL EQU %00000000 ; H-Blank +DEF STATF_VBL EQU %00000001 ; V-Blank +DEF STATF_OAM EQU %00000010 ; OAM-RAM is used by system +DEF STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system +DEF STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe + +DEF STATB_LYC EQU 6 +DEF STATB_MODE10 EQU 5 +DEF STATB_MODE01 EQU 4 +DEF STATB_MODE00 EQU 3 +DEF STATB_LYCF EQU 2 +DEF STATB_BUSY EQU 1 + +; -- +; -- SCY ($FF42) +; -- Scroll Y (R/W) +; -- +DEF rSCY EQU $FF42 + + +; -- +; -- SCX ($FF43) +; -- Scroll X (R/W) +; -- +DEF rSCX EQU $FF43 + + +; -- +; -- LY ($FF44) +; -- LCDC Y-Coordinate (R) +; -- +; -- Values range from 0->153. 144->153 is the VBlank period. +; -- +DEF rLY EQU $FF44 + + +; -- +; -- LYC ($FF45) +; -- LY Compare (R/W) +; -- +; -- When LY==LYC, STATF_LYCF will be set in STAT +; -- +DEF rLYC EQU $FF45 + + +; -- +; -- DMA ($FF46) +; -- DMA Transfer and Start Address (W) +; -- +DEF rDMA EQU $FF46 + + +; -- +; -- BGP ($FF47) +; -- BG Palette Data (W) +; -- +; -- Bit 7-6 - Intensity for %11 +; -- Bit 5-4 - Intensity for %10 +; -- Bit 3-2 - Intensity for %01 +; -- Bit 1-0 - Intensity for %00 +; -- +DEF rBGP EQU $FF47 + + +; -- +; -- OBP0 ($FF48) +; -- Object Palette 0 Data (W) +; -- +; -- See BGP for info +; -- +DEF rOBP0 EQU $FF48 + + +; -- +; -- OBP1 ($FF49) +; -- Object Palette 1 Data (W) +; -- +; -- See BGP for info +; -- +DEF rOBP1 EQU $FF49 + + +; -- +; -- WY ($FF4A) +; -- Window Y Position (R/W) +; -- +; -- 0 <= WY <= 143 +; -- When WY = 0, the window is displayed from the top edge of the LCD screen. +; -- +DEF rWY EQU $FF4A + + +; -- +; -- WX ($FF4B) +; -- Window X Position (R/W) +; -- +; -- 7 <= WX <= 166 +; -- When WX = 7, the window is displayed from the left edge of the LCD screen. +; -- Values of 0-6 and 166 are unreliable due to hardware bugs. +; -- +DEF rWX EQU $FF4B + +DEF WX_OFS EQU 7 ; add this to a screen position to get a WX position + + +; -- +; -- SPEED ($FF4D) +; -- Select CPU Speed (R/W) +; -- +DEF rKEY1 EQU $FF4D +DEF rSPD EQU rKEY1 + +DEF KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) +DEF KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) + + +; -- +; -- VBK ($FF4F) +; -- Select Video RAM Bank (R/W) +; -- +; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) +; -- +DEF rVBK EQU $FF4F + + +; -- +; -- HDMA1 ($FF51) +; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +DEF rHDMA1 EQU $FF51 + + +; -- +; -- HDMA2 ($FF52) +; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) +; -- CGB Mode Only +; -- +DEF rHDMA2 EQU $FF52 + + +; -- +; -- HDMA3 ($FF53) +; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +DEF rHDMA3 EQU $FF53 + + +; -- +; -- HDMA4 ($FF54) +; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) +; -- CGB Mode Only +; -- +DEF rHDMA4 EQU $FF54 + + +; -- +; -- HDMA5 ($FF55) +; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) +; -- CGB Mode Only +; -- +DEF rHDMA5 EQU $FF55 + +DEF HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) +DEF HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) +DEF HDMA5B_MODE EQU 7 ; DMA mode select (W) + +; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete +DEF HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) + + +; -- +; -- RP ($FF56) +; -- Infrared Communications Port (R/W) +; -- CGB Mode Only +; -- +DEF rRP EQU $FF56 + +DEF RPF_ENREAD EQU %11000000 +DEF RPF_DATAIN EQU %00000010 ; 0=Receiving IR Signal, 1=Normal +DEF RPF_WRITE_HI EQU %00000001 +DEF RPF_WRITE_LO EQU %00000000 + + +; -- +; -- BCPS ($FF68) +; -- Background Color Palette Specification (R/W) +; -- +DEF rBCPS EQU $FF68 + +DEF BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) +DEF BCPSB_AUTOINC EQU 7 + + +; -- +; -- BCPD ($FF69) +; -- Background Color Palette Data (R/W) +; -- +DEF rBCPD EQU $FF69 + + +; -- +; -- OCPS ($FF6A) +; -- Object Color Palette Specification (R/W) +; -- +DEF rOCPS EQU $FF6A + +DEF OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) +DEF OCPSB_AUTOINC EQU 7 + + +; -- +; -- OCPD ($FF6B) +; -- Object Color Palette Data (R/W) +; -- +DEF rOCPD EQU $FF6B + + +; -- +; -- SMBK/SVBK ($FF70) +; -- Select Main RAM Bank (R/W) +; -- +; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) +; -- +DEF rSVBK EQU $FF70 +DEF rSMBK EQU rSVBK + + +; -- +; -- PCM12 ($FF76) +; -- Sound channel 1&2 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude +; -- +DEF rPCM12 EQU $FF76 + + +; -- +; -- PCM34 ($FF77) +; -- Sound channel 3&4 PCM amplitude (R) +; -- +; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude +; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude +; -- +DEF rPCM34 EQU $FF77 + + +; SameBoy additions +DEF rKEY0 EQU $FF4C +DEF rBANK EQU $FF50 +DEF rOPRI EQU $FF6C + +DEF rJOYP EQU rP1 +DEF rBGPI EQU rBCPS +DEF rBGPD EQU rBCPD +DEF rOBPI EQU rOCPS +DEF rOBPD EQU rOCPD + +; -- +; -- IE ($FFFF) +; -- Interrupt Enable (R/W) +; -- +DEF rIE EQU $FFFF + +DEF IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 +DEF IEF_SERIAL EQU %00001000 ; Serial I/O transfer end +DEF IEF_TIMER EQU %00000100 ; Timer Overflow +DEF IEF_STAT EQU %00000010 ; STAT +DEF IEF_VBLANK EQU %00000001 ; V-Blank + +DEF IEB_HILO EQU 4 +DEF IEB_SERIAL EQU 3 +DEF IEB_TIMER EQU 2 +DEF IEB_STAT EQU 1 +DEF IEB_VBLANK EQU 0 + + +;*************************************************************************** +;* +;* Flags common to multiple sound channels +;* +;*************************************************************************** + +; -- +; -- Square wave duty cycle +; -- +; -- Can be used with AUD1LEN and AUD2LEN +; -- See AUD1LEN for more info +; -- +DEF AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% +DEF AUDLEN_DUTY_25 EQU %01000000 ; 25% +DEF AUDLEN_DUTY_50 EQU %10000000 ; 50% +DEF AUDLEN_DUTY_75 EQU %11000000 ; 75% + + +; -- +; -- Audio envelope flags +; -- +; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV +; -- See AUD1ENV for more info +; -- +DEF AUDENV_UP EQU %00001000 +DEF AUDENV_DOWN EQU %00000000 + + +; -- +; -- Audio trigger flags +; -- +; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH +; -- See AUD1HIGH for more info +; -- + +DEF AUDHIGH_RESTART EQU %10000000 +DEF AUDHIGH_LENGTH_ON EQU %01000000 +DEF AUDHIGH_LENGTH_OFF EQU %00000000 + + +;*************************************************************************** +;* +;* CPU values on bootup (a=type, b=qualifier) +;* +;*************************************************************************** + +DEF BOOTUP_A_DMG EQU $01 ; Dot Matrix Game +DEF BOOTUP_A_CGB EQU $11 ; Color GameBoy +DEF BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) + +; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or +; other system running in GBC mode +DEF BOOTUP_B_CGB EQU %00000000 +DEF BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP + + +;*************************************************************************** +;* +;* Cart related +;* +;*************************************************************************** + +; $0143 Color GameBoy compatibility code +DEF CART_COMPATIBLE_DMG EQU $00 +DEF CART_COMPATIBLE_DMG_GBC EQU $80 +DEF CART_COMPATIBLE_GBC EQU $C0 + +; $0146 GameBoy/Super GameBoy indicator +DEF CART_INDICATOR_GB EQU $00 +DEF CART_INDICATOR_SGB EQU $03 + +; $0147 Cartridge type +DEF CART_ROM EQU $00 +DEF CART_ROM_MBC1 EQU $01 +DEF CART_ROM_MBC1_RAM EQU $02 +DEF CART_ROM_MBC1_RAM_BAT EQU $03 +DEF CART_ROM_MBC2 EQU $05 +DEF CART_ROM_MBC2_BAT EQU $06 +DEF CART_ROM_RAM EQU $08 +DEF CART_ROM_RAM_BAT EQU $09 +DEF CART_ROM_MMM01 EQU $0B +DEF CART_ROM_MMM01_RAM EQU $0C +DEF CART_ROM_MMM01_RAM_BAT EQU $0D +DEF CART_ROM_MBC3_BAT_RTC EQU $0F +DEF CART_ROM_MBC3_RAM_BAT_RTC EQU $10 +DEF CART_ROM_MBC3 EQU $11 +DEF CART_ROM_MBC3_RAM EQU $12 +DEF CART_ROM_MBC3_RAM_BAT EQU $13 +DEF CART_ROM_MBC5 EQU $19 +DEF CART_ROM_MBC5_BAT EQU $1A +DEF CART_ROM_MBC5_RAM_BAT EQU $1B +DEF CART_ROM_MBC5_RUMBLE EQU $1C +DEF CART_ROM_MBC5_RAM_RUMBLE EQU $1D +DEF CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E +DEF CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 +DEF CART_ROM_POCKET_CAMERA EQU $FC +DEF CART_ROM_BANDAI_TAMA5 EQU $FD +DEF CART_ROM_HUDSON_HUC3 EQU $FE +DEF CART_ROM_HUDSON_HUC1 EQU $FF + +; $0148 ROM size +; these are kilobytes +DEF CART_ROM_32KB EQU $00 ; 2 banks +DEF CART_ROM_64KB EQU $01 ; 4 banks +DEF CART_ROM_128KB EQU $02 ; 8 banks +DEF CART_ROM_256KB EQU $03 ; 16 banks +DEF CART_ROM_512KB EQU $04 ; 32 banks +DEF CART_ROM_1024KB EQU $05 ; 64 banks +DEF CART_ROM_2048KB EQU $06 ; 128 banks +DEF CART_ROM_4096KB EQU $07 ; 256 banks +DEF CART_ROM_8192KB EQU $08 ; 512 banks +DEF CART_ROM_1152KB EQU $52 ; 72 banks +DEF CART_ROM_1280KB EQU $53 ; 80 banks +DEF CART_ROM_1536KB EQU $54 ; 96 banks + +; $0149 SRAM size +; these are kilobytes +DEF CART_SRAM_NONE EQU 0 +DEF CART_SRAM_8KB EQU 2 ; 1 bank +DEF CART_SRAM_32KB EQU 3 ; 4 banks +DEF CART_SRAM_128KB EQU 4 ; 16 banks + +DEF CART_SRAM_ENABLE EQU $0A +DEF CART_SRAM_DISABLE EQU $00 + +; $014A Destination code +DEF CART_DEST_JAPANESE EQU $00 +DEF CART_DEST_NON_JAPANESE EQU $01 + + +;*************************************************************************** +;* +;* Keypad related +;* +;*************************************************************************** + +DEF PADF_DOWN EQU $80 +DEF PADF_UP EQU $40 +DEF PADF_LEFT EQU $20 +DEF PADF_RIGHT EQU $10 +DEF PADF_START EQU $08 +DEF PADF_SELECT EQU $04 +DEF PADF_B EQU $02 +DEF PADF_A EQU $01 + +DEF PADB_DOWN EQU $7 +DEF PADB_UP EQU $6 +DEF PADB_LEFT EQU $5 +DEF PADB_RIGHT EQU $4 +DEF PADB_START EQU $3 +DEF PADB_SELECT EQU $2 +DEF PADB_B EQU $1 +DEF PADB_A EQU $0 + + +;*************************************************************************** +;* +;* Screen related +;* +;*************************************************************************** + +DEF SCRN_X EQU 160 ; Width of screen in pixels +DEF SCRN_Y EQU 144 ; Height of screen in pixels +DEF SCRN_X_B EQU 20 ; Width of screen in bytes +DEF SCRN_Y_B EQU 18 ; Height of screen in bytes + +DEF SCRN_VX EQU 256 ; Virtual width of screen in pixels +DEF SCRN_VY EQU 256 ; Virtual height of screen in pixels +DEF SCRN_VX_B EQU 32 ; Virtual width of screen in bytes +DEF SCRN_VY_B EQU 32 ; Virtual height of screen in bytes + + +;*************************************************************************** +;* +;* OAM related +;* +;*************************************************************************** + +; OAM attributes +; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) +RSRESET +DEF OAMA_Y RB 1 ; y pos plus 16 +DEF OAMA_X RB 1 ; x pos plus 8 +DEF OAMA_TILEID RB 1 ; tile id +DEF OAMA_FLAGS RB 1 ; flags (see below) +DEF sizeof_OAM_ATTRS RB 0 + +DEF OAM_Y_OFS EQU 16 ; add this to a screen-relative Y position to get an OAM Y position +DEF OAM_X_OFS EQU 8 ; add this to a screen-relative X position to get an OAM X position + +DEF OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM + +; flags +DEF OAMF_PRI EQU %10000000 ; Priority +DEF OAMF_YFLIP EQU %01000000 ; Y flip +DEF OAMF_XFLIP EQU %00100000 ; X flip +DEF OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) +DEF OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) +DEF OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) +DEF OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) + +DEF OAMF_PALMASK EQU %00000111 ; Palette (GBC) + +DEF OAMB_PRI EQU 7 ; Priority +DEF OAMB_YFLIP EQU 6 ; Y flip +DEF OAMB_XFLIP EQU 5 ; X flip +DEF OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) +DEF OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) + + +;* +;* Nintendo scrolling logo +;* (Code won't work on a real GameBoy) +;* (if next lines are altered.) +MACRO NINTENDO_LOGO + DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D + DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 + DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E +ENDM + +; Deprecated constants. Please avoid using. + +DEF IEF_LCDC EQU %00000010 ; LCDC (see STAT) +DEF _VRAM8000 EQU _VRAM +DEF _VRAM8800 EQU _VRAM+$800 +DEF _VRAM9000 EQU _VRAM+$1000 +DEF CART_SRAM_2KB EQU 1 ; 1 incomplete bank + + + ENDC ;HARDWARE_INC diff --git a/thirdparty/SameBoy/BootROMs/mgb_boot.asm b/thirdparty/SameBoy/BootROMs/mgb_boot.asm new file mode 100644 index 000000000..3a98aefd8 --- /dev/null +++ b/thirdparty/SameBoy/BootROMs/mgb_boot.asm @@ -0,0 +1,2 @@ +MGB EQU 1 +include "dmg_boot.asm" \ No newline at end of file diff --git a/thirdparty/SameBoy/BootROMs/pb12.c b/thirdparty/SameBoy/BootROMs/pb12.c index cfedf6bb1..a022fce19 100644 --- a/thirdparty/SameBoy/BootROMs/pb12.c +++ b/thirdparty/SameBoy/BootROMs/pb12.c @@ -25,7 +25,7 @@ void write_all(int fd, const void *buf, size_t count) { } } -int main() +int main(void) { static uint8_t source[0x4000]; size_t size = read(STDIN_FILENO, &source, sizeof(source)); @@ -87,7 +87,7 @@ int main() prev[1] = byte; if (bits >= 8) { uint8_t outctl = control >> (bits - 8); - assert(outctl != 1); + assert(outctl != 1); // 1 is reserved as the end byte write_all(STDOUT_FILENO, &outctl, 1); write_all(STDOUT_FILENO, literals, literals_size); bits -= 8; diff --git a/thirdparty/SameBoy/BootROMs/sgb_boot.asm b/thirdparty/SameBoy/BootROMs/sgb_boot.asm index cdb9d774d..e0928616a 100644 --- a/thirdparty/SameBoy/BootROMs/sgb_boot.asm +++ b/thirdparty/SameBoy/BootROMs/sgb_boot.asm @@ -1,5 +1,7 @@ ; SameBoy SGB bootstrap ROM -; Todo: use friendly names for HW registers instead of magic numbers + +INCLUDE "hardware.inc" + SECTION "BootCode", ROM0[$0] Start: ; Init stack pointer @@ -15,17 +17,17 @@ Start: ; Init Audio ld a, $80 - ldh [$26], a - ldh [$11], a + ldh [rNR52], a + ldh [rNR11], a ld a, $f3 - ldh [$12], a - ldh [$25], a + ldh [rNR12], a + ldh [rNR51], a ld a, $77 - ldh [$24], a + ldh [rNR50], a ; Init BG palette to white ld a, $0 - ldh [$47], a + ldh [rBGP], a ; Load logo from ROM. ; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. @@ -71,10 +73,10 @@ Start: ; Turn on LCD ld a, $91 - ldh [$40], a + ldh [rLCDC], a ld a, $f1 ; Packet magic, increases by 2 for every packet - ldh [$80], a + ldh [_HRAM], a ld hl, $104 ; Header start xor a @@ -86,7 +88,7 @@ Start: ld a, $30 ld [c], a - ldh a, [$80] + ldh a, [_HRAM] call SendByte push hl ld b, $e @@ -117,9 +119,9 @@ Start: ld [c], a ; Update command - ldh a, [$80] + ldh a, [_HRAM] add 2 - ldh [$80], a + ldh [_HRAM], a ld a, $58 cp l @@ -135,7 +137,7 @@ Start: ; Init BG palette ld a, $fc - ldh [$47], a + ldh [rBGP], a ; Set registers to match the original SGB boot IF DEF(SGB2) @@ -210,4 +212,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c SECTION "BootGame", ROM0[$fe] BootGame: - ldh [$50], a \ No newline at end of file + ldh [rBANK], a \ No newline at end of file diff --git a/thirdparty/SameBoy/Cocoa/AppDelegate.h b/thirdparty/SameBoy/Cocoa/AppDelegate.h deleted file mode 100644 index e74b19163..000000000 --- a/thirdparty/SameBoy/Cocoa/AppDelegate.h +++ /dev/null @@ -1,16 +0,0 @@ -#import - -@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; -- (IBAction)showPreferences: (id) sender; -- (IBAction)toggleDeveloperMode:(id)sender; -- (IBAction)switchPreferencesTab:(id)sender; -@property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem; - -@end - diff --git a/thirdparty/SameBoy/Cocoa/AppDelegate.m b/thirdparty/SameBoy/Cocoa/AppDelegate.m deleted file mode 100644 index aee2111e7..000000000 --- a/thirdparty/SameBoy/Cocoa/AppDelegate.m +++ /dev/null @@ -1,142 +0,0 @@ -#import "AppDelegate.h" -#include "GBButtons.h" -#include "GBView.h" -#include -#import -#import - -@implementation AppDelegate -{ - NSWindow *preferences_window; - NSArray *preferences_tabs; -} - -- (void) applicationDidFinishLaunching:(NSNotification *)notification -{ - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - for (unsigned i = 0; i < GBButtonCount; i++) { - if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) { - [defaults removeObjectForKey:button_to_preference_name(i, 0)]; - } - } - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ - @"GBRight": @(kVK_RightArrow), - @"GBLeft": @(kVK_LeftArrow), - @"GBUp": @(kVK_UpArrow), - @"GBDown": @(kVK_DownArrow), - - @"GBA": @(kVK_ANSI_X), - @"GBB": @(kVK_ANSI_Z), - @"GBSelect": @(kVK_Delete), - @"GBStart": @(kVK_Return), - - @"GBTurbo": @(kVK_Space), - @"GBRewind": @(kVK_Tab), - @"GBSlow-Motion": @(kVK_Shift), - - @"GBFilter": @"NearestNeighbor", - @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), - @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), - @"GBRewindLength": @(10), - @"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE), - - @"GBDMGModel": @(GB_MODEL_DMG_B), - @"GBCGBModel": @(GB_MODEL_CGB_E), - @"GBSGBModel": @(GB_MODEL_SGB2), - @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), - }]; - - [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ - JOYAxes2DEmulateButtonsKey: @YES, - JOYHatsEmulateButtonsKey: @YES, - }]; - - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { - [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; - } -} - -- (IBAction)toggleDeveloperMode:(id)sender -{ - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - [defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"]; -} - -- (IBAction)switchPreferencesTab:(id)sender -{ - for (NSView *view in preferences_tabs) { - [view removeFromSuperview]; - } - NSView *tab = preferences_tabs[[sender tag]]; - NSRect old = [_preferencesWindow frame]; - 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.contentView addSubview:tab]; -} - -- (BOOL)validateMenuItem:(NSMenuItem *)anItem -{ - 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; - if (!_preferencesWindow) { - [[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]; - [self switchPreferencesTab:first_toolbar_item]; - [_preferencesWindow center]; - } - [_preferencesWindow makeKeyAndOrderFront:self]; -} - -- (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 true; -} - -- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification -{ - [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; -} - -- (IBAction)nop:(id)sender -{ -} -@end diff --git a/thirdparty/SameBoy/Cocoa/AppIcon.icns b/thirdparty/SameBoy/Cocoa/AppIcon.icns index 92ad4c651..2a85022a9 100644 Binary files a/thirdparty/SameBoy/Cocoa/AppIcon.icns and b/thirdparty/SameBoy/Cocoa/AppIcon.icns differ diff --git a/thirdparty/SameBoy/Cocoa/AudioRecordingAccessoryView.xib b/thirdparty/SameBoy/Cocoa/AudioRecordingAccessoryView.xib new file mode 100644 index 000000000..6dda38b7d --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/AudioRecordingAccessoryView.xib @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/CPU.png b/thirdparty/SameBoy/Cocoa/CPU.png index 7f1362135..2601ac464 100644 Binary files a/thirdparty/SameBoy/Cocoa/CPU.png and b/thirdparty/SameBoy/Cocoa/CPU.png differ diff --git a/thirdparty/SameBoy/Cocoa/CPU@2x.png b/thirdparty/SameBoy/Cocoa/CPU@2x.png index 3c86883ac..855fac302 100644 Binary files a/thirdparty/SameBoy/Cocoa/CPU@2x.png and b/thirdparty/SameBoy/Cocoa/CPU@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/CPU~solid.png b/thirdparty/SameBoy/Cocoa/CPU~solid.png new file mode 100644 index 000000000..75c96b763 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/CPU~solid.png differ diff --git a/thirdparty/SameBoy/Cocoa/CPU~solid@2x.png b/thirdparty/SameBoy/Cocoa/CPU~solid@2x.png new file mode 100644 index 000000000..ff17bd84a Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/CPU~solid@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/CPU~solid~dark.png b/thirdparty/SameBoy/Cocoa/CPU~solid~dark.png new file mode 100644 index 000000000..eea9a43d7 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/CPU~solid~dark.png differ diff --git a/thirdparty/SameBoy/Cocoa/CPU~solid~dark@2x.png b/thirdparty/SameBoy/Cocoa/CPU~solid~dark@2x.png new file mode 100644 index 000000000..e7e9653a2 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/CPU~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Cartridge.icns b/thirdparty/SameBoy/Cocoa/Cartridge.icns index 6e0c78dd8..1dae2b4a2 100644 Binary files a/thirdparty/SameBoy/Cocoa/Cartridge.icns and b/thirdparty/SameBoy/Cocoa/Cartridge.icns differ diff --git a/thirdparty/SameBoy/Cocoa/ContinueTemplate.png b/thirdparty/SameBoy/Cocoa/ContinueTemplate.png new file mode 100644 index 000000000..eb72962c0 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/ContinueTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/ContinueTemplate@2x.png b/thirdparty/SameBoy/Cocoa/ContinueTemplate@2x.png new file mode 100644 index 000000000..586ab3d27 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/ContinueTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display.png b/thirdparty/SameBoy/Cocoa/Display.png index 5753f5588..c955c4688 100644 Binary files a/thirdparty/SameBoy/Cocoa/Display.png and b/thirdparty/SameBoy/Cocoa/Display.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display@2x.png b/thirdparty/SameBoy/Cocoa/Display@2x.png index 6a71d2219..2cca50e3f 100644 Binary files a/thirdparty/SameBoy/Cocoa/Display@2x.png and b/thirdparty/SameBoy/Cocoa/Display@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display~solid.png b/thirdparty/SameBoy/Cocoa/Display~solid.png new file mode 100644 index 000000000..2980ad71a Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Display~solid.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display~solid@2x.png b/thirdparty/SameBoy/Cocoa/Display~solid@2x.png new file mode 100644 index 000000000..a73943778 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Display~solid@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display~solid~dark.png b/thirdparty/SameBoy/Cocoa/Display~solid~dark.png new file mode 100644 index 000000000..05245deea Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Display~solid~dark.png differ diff --git a/thirdparty/SameBoy/Cocoa/Display~solid~dark@2x.png b/thirdparty/SameBoy/Cocoa/Display~solid~dark@2x.png new file mode 100644 index 000000000..3dec49d5d Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Display~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Document.h b/thirdparty/SameBoy/Cocoa/Document.h index 884738591..eaf285d28 100644 --- a/thirdparty/SameBoy/Cocoa/Document.h +++ b/thirdparty/SameBoy/Cocoa/Document.h @@ -1,12 +1,16 @@ #import -#include "GBView.h" -#include "GBImageView.h" -#include "GBSplitView.h" -#include "GBVisualizerView.h" +#import "GBView.h" +#import "GBImageView.h" +#import "GBSplitView.h" +#import "GBVisualizerView.h" +#import "GBOSDView.h" +#import "GBDebuggerButton.h" @class GBCheatWindowController; +@class GBPaletteView; +@class GBObjectView; -@interface Document : NSDocument +@interface Document : NSDocument @property (nonatomic, readonly) GB_gameboy_t *gb; @property (nonatomic, strong) IBOutlet GBView *view; @property (nonatomic, strong) IBOutlet NSTextView *consoleOutput; @@ -28,8 +32,8 @@ @property (nonatomic, strong) IBOutlet NSTabView *vramTabView; @property (nonatomic, strong) IBOutlet NSPanel *vramWindow; @property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel; -@property (nonatomic, strong) IBOutlet NSTableView *paletteTableView; -@property (nonatomic, strong) IBOutlet NSTableView *spritesTableView; +@property (nonatomic, strong) IBOutlet GBPaletteView *paletteView; +@property (nonatomic, strong) IBOutlet GBObjectView *objectView; @property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow; @property (nonatomic, strong) IBOutlet NSImageView *feedImageView; @property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput; @@ -49,11 +53,25 @@ @property (strong) IBOutlet NSButton *gbsRewindButton; @property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton; @property (strong) IBOutlet GBVisualizerView *gbsVisualizer; +@property (strong) IBOutlet GBOSDView *osdView; +@property (readonly) GB_oam_info_t *oamInfo; +@property uint8_t oamCount; +@property uint8_t oamHeight; +@property (strong) IBOutlet NSView *audioRecordingAccessoryView; +@property (strong) IBOutlet NSPopUpButton *audioFormatButton; +@property (strong) IBOutlet NSVisualEffectView *debuggerSidebarEffectView API_AVAILABLE(macos(10.10)); +@property (strong) IBOutlet GBDebuggerButton *debuggerContinueButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerNextButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerStepButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerFinishButton; + + ++ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; -(void) connectLinkCable:(NSMenuItem *)sender; -- (bool)loadStateFile:(const char *)path; +-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; @end diff --git a/thirdparty/SameBoy/Cocoa/Document.m b/thirdparty/SameBoy/Cocoa/Document.m index 2fbec7c1d..1ecc04b1d 100644 --- a/thirdparty/SameBoy/Cocoa/Document.m +++ b/thirdparty/SameBoy/Cocoa/Document.m @@ -1,15 +1,47 @@ -#include -#include -#include -#include "GBAudioClient.h" -#include "Document.h" -#include "AppDelegate.h" -#include "HexFiend/HexFiend.h" -#include "GBMemoryByteArray.h" -#include "GBWarningPopover.h" -#include "GBCheatWindowController.h" -#include "GBTerminalTextFieldCell.h" -#include "BigSurToolbar.h" +#import +#import +#import +#import "GBAudioClient.h" +#import "Document.h" +#import "GBApp.h" +#import "HexFiend/HexFiend.h" +#import "GBMemoryByteArray.h" +#import "GBWarningPopover.h" +#import "GBCheatWindowController.h" +#import "GBTerminalTextFieldCell.h" +#import "BigSurToolbar.h" +#import "GBPaletteEditorController.h" +#import "GBObjectView.h" +#import "GBPaletteView.h" + +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +@implementation NSString (relativePath) + +- (NSString *)pathRelativeToDirectory:(NSString *)directory +{ + NSMutableArray *baseComponents = [[directory pathComponents] mutableCopy]; + NSMutableArray *selfComponents = [[self pathComponents] mutableCopy]; + + while (baseComponents.count) { + if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) { + break; + } + + [baseComponents removeObjectAtIndex:0]; + [selfComponents removeObjectAtIndex:0]; + } + while (baseComponents.count) { + [baseComponents removeObjectAtIndex:0]; + [selfComponents insertObject:@".." atIndex:0]; + } + return [selfComponents componentsJoinedByString:@"/"]; +} + +@end + +#define GB_MODEL_PAL_BIT_OLD 0x1000 /* 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!!! */ @@ -20,10 +52,22 @@ MODEL_CGB, MODEL_AGB, MODEL_SGB, + MODEL_MGB, + + MODEL_QUICK_RESET = -1, }; @interface Document () +@property GBAudioClient *audioClient; +@end + +@implementation Document { + GB_gameboy_t gb; + volatile bool running; + volatile bool stopping; + NSConditionLock *has_debugger_input; + NSMutableArray *debugger_input_queue; NSMutableAttributedString *pending_console_output; NSRecursiveLock *console_output_lock; @@ -32,20 +76,18 @@ @interface Document () bool fullScreen; bool in_sync_input; + NSString *_debuggerCommandWhilePaused; HFController *hex_controller; - + NSString *lastConsoleInput; HFLineCountingRepresenter *lineRep; - + CVImageBufferRef cameraImage; AVCaptureSession *cameraSession; AVCaptureConnection *cameraConnection; AVCaptureStillImageOutput *cameraOutput; - GB_oam_info_t oamInfo[40]; - uint16_t oamCount; - uint8_t oamHeight; - bool oamUpdating; + GB_oam_info_t _oamInfo[40]; NSMutableData *currentPrinterImageData; enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory; @@ -65,6 +107,7 @@ @interface Document () size_t audioBufferSize; size_t audioBufferPosition; size_t audioBufferNeeded; + double _volume; bool borderModeChanged; @@ -73,37 +116,23 @@ @interface Document () Document *slave; signed linkOffset; bool linkCableBit; + + NSSavePanel *_audioSavePanel; + bool _isRecordingAudio; + + void (^ volatile _pendingAtomicBlock)(); } -@property GBAudioClient *audioClient; -- (void) vblank; -- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes; -- (char *) getDebuggerInput; -- (char *) getAsyncDebuggerInput; -- (void) cameraRequestUpdate; -- (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y; -- (void) printImage:(uint32_t *)image height:(unsigned) height - topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin - exposure:(unsigned) exposure; -- (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) { Document *self = (__bridge Document *)GB_get_user_data(gb); [self loadBootROM: type]; } -static void vblank(GB_gameboy_t *gb) +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) { Document *self = (__bridge Document *)GB_get_user_data(gb); - [self vblank]; + [self vblankWithType:type]; } static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -191,15 +220,6 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) } -@implementation Document -{ - GB_gameboy_t gb; - volatile bool running; - volatile bool stopping; - NSConditionLock *has_debugger_input; - NSMutableArray *debugger_input_queue; -} - - (instancetype)init { self = [super init]; @@ -208,6 +228,7 @@ - (instancetype)init debugger_input_queue = [[NSMutableArray alloc] init]; console_output_lock = [[NSRecursiveLock alloc] init]; audioLock = [[NSCondition alloc] init]; + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; } return self; } @@ -234,6 +255,7 @@ - (GB_model_t)internalModel return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]; case MODEL_NONE: + case MODEL_QUICK_RESET: case MODEL_CGB: return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]; @@ -245,30 +267,17 @@ - (GB_model_t)internalModel return model; } + case MODEL_MGB: + return GB_MODEL_MGB; + case MODEL_AGB: - return GB_MODEL_AGB; + return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBAGBModel"]; } } - (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 @@ -314,28 +323,32 @@ - (void) updateMinSize 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 +- (void) vblankWithType:(GB_vblank_type_t)type { if (_gbsVisualizer) { dispatch_async(dispatch_get_main_queue(), ^{ - [_gbsVisualizer setNeedsDisplay:YES]; + [_gbsVisualizer setNeedsDisplay:true]; }); } - [self.view flip]; - if (borderModeChanged) { - dispatch_sync(dispatch_get_main_queue(), ^{ - size_t previous_width = GB_get_screen_width(&gb); - GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); - if (GB_get_screen_width(&gb) != previous_width) { - [self.view screenSizeChanged]; - [self updateMinSize]; - } - }); - borderModeChanged = false; + if (type != GB_VBLANK_TYPE_REPEAT) { + [self.view flip]; + if (borderModeChanged) { + dispatch_sync(dispatch_get_main_queue(), ^{ + size_t previous_width = GB_get_screen_width(&gb); + GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); + if (GB_get_screen_width(&gb) != previous_width) { + [self.view screenSizeChanged]; + [self updateMinSize]; + } + }); + borderModeChanged = false; + } + GB_set_pixels_output(&gb, self.view.pixels); } - GB_set_pixels_output(&gb, self.view.pixels); + if (self.vramWindow.isVisible) { dispatch_async(dispatch_get_main_queue(), ^{ self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; @@ -344,6 +357,7 @@ - (void) vblank } if (self.view.isRewinding) { rewind = true; + [self.osdView displayText:@"Rewinding..."]; } } @@ -353,7 +367,7 @@ - (void)gotNewSample:(GB_sample_t *)sample [_gbsVisualizer addSample:sample]; } [audioLock lock]; - if (self.audioClient.isPlaying) { + if (_audioClient.isPlaying) { if (audioBufferPosition == audioBufferSize) { if (audioBufferSize >= 0x4000) { audioBufferPosition = 0; @@ -369,6 +383,10 @@ - (void)gotNewSample:(GB_sample_t *)sample } audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize); } + if (_volume != 1) { + sample->left *= _volume; + sample->right *= _volume; + } audioBuffer[audioBufferPosition++] = *sample; } if (audioBufferPosition == audioBufferNeeded) { @@ -387,12 +405,12 @@ - (void) preRun { 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) { audioBufferNeeded = nFrames; - [audioLock wait]; + [audioLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.125]]; } if (stopping || GB_debugger_is_stopped(&gb)) { @@ -401,7 +419,13 @@ - (void) preRun 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; @@ -413,9 +437,9 @@ - (void) preRun [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: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 */ @@ -446,10 +470,9 @@ - (void) preRun return ret; } -- (void) run +- (void)run { assert(!master); - running = true; [self preRun]; if (slave) { [slave preRun]; @@ -462,6 +485,10 @@ - (void) run else { linkOffset -= masterTable[GB_run(&slave->gb)]; } + if (unlikely(_pendingAtomicBlock)) { + _pendingAtomicBlock(); + _pendingAtomicBlock = nil; + } } free(masterTable); free(slaveTable); @@ -479,6 +506,10 @@ - (void) run else { GB_run(&gb); } + if (unlikely(_pendingAtomicBlock)) { + _pendingAtomicBlock(); + _pendingAtomicBlock = nil; + } } } [self postRun]; @@ -493,11 +524,11 @@ - (void)postRun audioBufferPosition = audioBufferNeeded; [audioLock signal]; [audioLock unlock]; - [self.audioClient stop]; - self.audioClient = nil; - self.view.mouseHidingEnabled = NO; - GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]); - GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]); + [_audioClient stop]; + _audioClient = nil; + self.view.mouseHidingEnabled = false; + GB_save_battery(&gb, self.savPath.UTF8String); + GB_save_cheats(&gb, self.chtPath.UTF8String); unsigned time_to_alarm = GB_time_to_alarm(&gb); if (time_to_alarm) { @@ -521,6 +552,10 @@ - (void)postRun - (void) start { + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + [slave updateDebuggerButtons]; + }); self.gbsPlayPauseButton.state = true; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; if (master) { @@ -528,11 +563,16 @@ - (void) start return; } if (running) return; + running = true; [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; } - (void) stop { + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + [slave updateDebuggerButtons]; + }); self.gbsPlayPauseButton.state = false; if (master) { if (!master->running) return; @@ -565,12 +605,12 @@ - (void) stop - (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", }; @@ -586,8 +626,8 @@ - (IBAction)reset:(id)sender current_model = (enum model)[sender tag]; } - if (!modelsChanging && [sender tag] == MODEL_NONE) { - GB_reset(&gb); + if ([sender tag] == MODEL_QUICK_RESET) { + GB_quick_reset(&gb); } else { GB_switch_model_and_reset(&gb, [self internalModel]); @@ -599,11 +639,12 @@ - (IBAction)reset:(id)sender [self updateMinSize]; - if ([sender tag] != 0) { + if ([sender tag] > MODEL_NONE) { /* User explictly selected a model, save the preference */ [[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 */ @@ -616,6 +657,10 @@ - (IBAction)reset:(id)sender [(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 @@ -678,13 +723,18 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController 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:true animate:false]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]]; - self.debuggerSplitView.dividerColor = [NSColor clearColor]; + self.debuggerSplitView.dividerColor = self.debuggerVerticalLine.borderColor; + [self.debuggerVerticalLine removeFromSuperview]; // No longer used, just there for the color if (@available(macOS 11.0, *)) { self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded; self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact; @@ -763,21 +813,43 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController name:@"GBCGBModelChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(agbModelChanged) + name:@"GBAGBModelChanged" + object:nil]; + + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateVolume) + name:@"GBVolumeChanged" + object:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { current_model = MODEL_DMG; } 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; } [self initCommon]; self.view.gb = &gb; + self.view.osdView = _osdView; [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 @@ -822,13 +894,14 @@ - (void) initMemoryView [layoutView setFrame:layoutViewFrame]; [layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin]; [self.memoryView addSubview:layoutView]; + self.memoryView = layoutView; self.memoryBankItem.enabled = false; } + (BOOL)autosavesInPlace { - return YES; + return true; } - (NSString *)windowNibName @@ -840,7 +913,7 @@ - (NSString *)windowNibName - (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type { - return YES; + return true; } - (IBAction)changeGBSTrack:(id)sender @@ -879,10 +952,15 @@ - (void)prepareGBSInterface: (GB_gbs_info_t *)info { GB_set_rendering_disabled(&gb, true); _view = nil; - for (NSView *view in _mainWindow.contentView.subviews) { + for (NSView *view in [_mainWindow.contentView.subviews copy]) { [view removeFromSuperview]; } - [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil]; + if (@available(macOS 11, *)) { + [[NSBundle mainBundle] loadNibNamed:@"GBS11" owner:self topLevelObjects:nil]; + } + else { + [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil]; + } [_mainWindow setContentSize:self.gbsPlayerView.bounds.size]; _mainWindow.styleMask &= ~NSWindowStyleMaskResizable; dispatch_async(dispatch_get_main_queue(), ^{ // Cocoa is weird, no clue why it's needed @@ -910,8 +988,8 @@ - (void)prepareGBSInterface: (GB_gbs_info_t *)info [self.gbsNextPrevButton imageForSegment:i].template = true; } - if (!self.audioClient.isPlaying) { - [self.audioClient start]; + if (!_audioClient.isPlaying) { + [_audioClient start]; } if (@available(macOS 10.10, *)) { @@ -919,32 +997,136 @@ - (void)prepareGBSInterface: (GB_gbs_info_t *)info } } -- (void) loadROM +- (bool)isCartContainer +{ + return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"]; +} + +- (NSString *)savPath +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:@"battery.sav"]; + } + + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path; +} + +- (NSString *)chtPath +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:@"cheats.cht"]; + } + + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path; +} + +- (NSString *)saveStatePath:(unsigned)index +{ + if (self.isCartContainer) { + return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]]; + } + return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path; +} + +- (NSString *)romPath +{ + NSString *fileName = self.fileName; + if (self.isCartContainer) { + NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"] + encoding:NSUTF8StringEncoding + error:nil] componentsSeparatedByString:@"\n"]; + fileName = nil; + bool needsRebuild = false; + for (NSString *path in paths) { + NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL]; + if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { + if (fileName && ![fileName isEqualToString:url.path]) { + needsRebuild = true; + break; + } + fileName = url.path; + } + else { + needsRebuild = true; + } + } + if (fileName && needsRebuild) { + [[NSString stringWithFormat:@"%@\n%@\n%@", + [fileName pathRelativeToDirectory:self.fileName], + fileName, + [[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]] + writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"] + atomically:false + encoding:NSUTF8StringEncoding + error:nil]; + } + } + + return fileName; +} + +static bool is_path_writeable(const char *path) +{ + if (!access(path, W_OK)) return true; + int fd = creat(path, 0644); + if (fd == -1) return false; + close(fd); + unlink(path); + return true; +} + +- (int) loadROM { + __block int ret = 0; + NSString *fileName = self.romPath; + if (!fileName) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + return 1; + } + NSString *rom_warnings = [self captureOutputForBlock:^{ GB_debugger_clear_symbols(&gb); - if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) { - GB_load_isx(&gb, self.fileURL.path.UTF8String); - GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); + if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) { + ret = GB_load_isx(&gb, fileName.UTF8String); + if (!self.isCartContainer) { + GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); + } } - else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) { + else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) { __block GB_gbs_info_t info; - GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); + ret = GB_load_gbs(&gb, fileName.UTF8String, &info); [self prepareGBSInterface:&info]; } else { - GB_load_rom(&gb, [self.fileURL.path UTF8String]); + ret = GB_load_rom(&gb, [fileName UTF8String]); + } + if (GB_save_battery_size(&gb)) { + if (!is_path_writeable(self.savPath.UTF8String)) { + GB_log(&gb, "The save path for this ROM is not writeable, progress will not be saved.\n"); + } } - 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_load_battery(&gb, self.savPath.UTF8String); + GB_load_cheats(&gb, self.chtPath.UTF8String); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.cheatWindowController cheatsUpdated]; + }); GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); - GB_debugger_load_symbol_file(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sym"].path.UTF8String); + GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String); }]; - if (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 @@ -974,29 +1156,37 @@ - (IBAction) interrupt:(id)sender - (IBAction)mute:(id)sender { - if (self.audioClient.isPlaying) { - [self.audioClient stop]; + if (_audioClient.isPlaying) { + [_audioClient stop]; } else { - [self.audioClient start]; + [_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) isPaused +{ + if (self.partner) { + return !self.partner->running || GB_debugger_is_stopped(&gb) || GB_debugger_is_stopped(&self.partner->gb); + } + return (!running) || GB_debugger_is_stopped(&gb); } - (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) { - [(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))]; - } - [(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))]; + [(NSMenuItem *)anItem setState:self.isPaused]; return !GB_debugger_is_stopped(&gb); } - else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { - [(NSMenuItem*)anItem setState:anItem.tag == current_model]; + else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE && anItem.tag != MODEL_QUICK_RESET) { + [(NSMenuItem *)anItem setState:anItem.tag == current_model]; } else if ([anItem action] == @selector(interrupt:)) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { @@ -1004,21 +1194,34 @@ - (BOOL)validateUserInterfaceItem:(id)anItem } } else if ([anItem action] == @selector(disconnectAllAccessories:)) { - [(NSMenuItem*)anItem setState:accessory == GBAccessoryNone]; + [(NSMenuItem *)anItem setState:accessory == GBAccessoryNone]; } else if ([anItem action] == @selector(connectPrinter:)) { - [(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter]; + [(NSMenuItem *)anItem setState:accessory == GBAccessoryPrinter]; } else if ([anItem action] == @selector(connectWorkboy:)) { - [(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy]; + [(NSMenuItem *)anItem setState:accessory == GBAccessoryWorkboy]; } else if ([anItem action] == @selector(connectLinkCable:)) { - [(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master || + [(NSMenuItem *)anItem setState:[(NSMenuItem *)anItem representedObject] == master || [(NSMenuItem *)anItem representedObject] == slave]; } else if ([anItem action] == @selector(toggleCheats:)) { - [(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)]; + [(NSMenuItem *)anItem setState:GB_cheats_enabled(&gb)]; + } + else if ([anItem action] == @selector(toggleDisplayBackground:)) { + [(NSMenuItem *)anItem setState:!GB_is_background_rendering_disabled(&gb)]; + } + else if ([anItem action] == @selector(toggleDisplayObjects:)) { + [(NSMenuItem *)anItem setState:!GB_is_object_rendering_disabled(&gb)]; } + else if ([anItem action] == @selector(toggleAudioRecording:)) { + [(NSMenuItem *)anItem setTitle:_isRecordingAudio? @"Stop Audio Recording" : @"Start Audio Recording…"]; + } + else if ([anItem action] == @selector(toggleAudioChannel:)) { + [(NSMenuItem *)anItem setState:!GB_is_channel_muted(&gb, [anItem tag])]; + } + return [super validateUserInterfaceItem:anItem]; } @@ -1032,7 +1235,7 @@ - (void) windowWillEnterFullScreen:(NSNotification *)notification - (void) windowWillExitFullScreen:(NSNotification *)notification { fullScreen = false; - self.view.mouseHidingEnabled = NO; + self.view.mouseHidingEnabled = false; } - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame @@ -1085,9 +1288,8 @@ - (void) appendPendingOutput [self.consoleWindow orderFront:nil]; } pending_console_output = nil; -} + } [console_output_lock unlock]; - } - (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes @@ -1127,14 +1329,14 @@ - (void) log: (const char *) string withAttributes: (GB_log_attributes) attribut } 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 @@ -1142,6 +1344,25 @@ - (IBAction)showConsoleWindow:(id)sender [self.consoleWindow orderBack:nil]; } +- (void)queueDebuggerCommand:(NSString *)command +{ + if (!master && !running && !GB_debugger_is_stopped(&gb)) { + _debuggerCommandWhilePaused = command; + GB_debugger_break(&gb); + [self start]; + return; + } + + if (!in_sync_input) { + [self log:">"]; + } + [self log:[command UTF8String]]; + [self log:"\n"]; + [has_debugger_input lock]; + [debugger_input_queue addObject:command]; + [has_debugger_input unlockWithCondition:1]; +} + - (IBAction)consoleInput:(NSTextField *)sender { NSString *line = [sender stringValue]; @@ -1154,15 +1375,8 @@ - (IBAction)consoleInput:(NSTextField *)sender else { line = @""; } - - if (!in_sync_input) { - [self log:">"]; - } - [self log:[line UTF8String]]; - [self log:"\n"]; - [has_debugger_input lock]; - [debugger_input_queue addObject:line]; - [has_debugger_input unlockWithCondition:1]; + + [self queueDebuggerCommand: line]; [sender setStringValue:@""]; } @@ -1210,14 +1424,29 @@ - (void) updateSideView [console_output_lock unlock]; } -- (char *) getDebuggerInput +- (char *)getDebuggerInput { + bool isPlaying = _audioClient.isPlaying; + if (isPlaying) { + [_audioClient stop]; + } [audioLock lock]; [audioLock signal]; [audioLock unlock]; + in_sync_input = true; [self updateSideView]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + }); + [self.partner updateDebuggerButtons]; [self log:">"]; - in_sync_input = true; + if (_debuggerCommandWhilePaused) { + NSString *command = _debuggerCommandWhilePaused; + _debuggerCommandWhilePaused = nil; + dispatch_async(dispatch_get_main_queue(), ^{ + [self queueDebuggerCommand:command]; + }); + } [has_debugger_input lockWhenCondition:1]; NSString *input = [debugger_input_queue firstObject]; [debugger_input_queue removeObjectAtIndex:0]; @@ -1229,7 +1458,12 @@ - (char *) getDebuggerInput shouldClearSideView = false; [self.debuggerSideView setString:@""]; } + [self updateDebuggerButtons]; + [self.partner updateDebuggerButtons]; }); + if (isPlaying) { + [_audioClient start]; + } if ((id) input == [NSNull null]) { return NULL; } @@ -1254,35 +1488,48 @@ - (IBAction)saveState:(id)sender { bool __block success = false; [self performAtomicBlock:^{ - success = GB_save_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; + success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0; }]; if (!success) { [GBWarningPopover popoverWithContents:@"Failed to write save state." onWindow:self.mainWindow]; NSBeep(); } + else { + [self.osdView displayText:@"State saved"]; + } } -- (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(); } + else { + [self.osdView displayText:@"State loaded"]; + } 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 saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true]; + if (ret == ENOENT && !self.isCartContainer) { + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false]; + } } - (IBAction)clearConsole:(id)sender @@ -1298,7 +1545,7 @@ - (void)log:(const char *)log - (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 @@ -1310,20 +1557,25 @@ - (void) writeMemory:(uint16_t)addr value:(uint8_t)value - (void) performAtomicBlock: (void (^)())block { while (!GB_is_inited(&gb)); - bool was_running = running && !GB_debugger_is_stopped(&gb); + bool isRunning = running && !GB_debugger_is_stopped(&gb); if (master) { - was_running |= master->running; + isRunning |= master->running; } - if (was_running) { - [self stop]; + if (!isRunning) { + block(); + return; } - block(); - if (was_running) { - [self start]; + + if (master) { + [master performAtomicBlock:block]; + return; } + + _pendingAtomicBlock = block; + while (_pendingAtomicBlock); } -- (NSString *) captureOutputForBlock: (void (^)())block +- (NSString *)captureOutputForBlock: (void (^)())block { capturedOutput = [[NSMutableString alloc] init]; [self performAtomicBlock:block]; @@ -1334,28 +1586,21 @@ - (NSString *) captureOutputForBlock: (void (^)())block + (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale { - CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data); - CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); - CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; - CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; - - CGImageRef iref = CGImageCreate(width, - height, - 8, - 32, - 4 * width, - colorSpaceRef, - bitmapInfo, - provider, - NULL, - YES, - renderingIntent); - CGDataProviderRelease(provider); - CGColorSpaceRelease(colorSpaceRef); - - NSImage *ret = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(width * scale, height * scale)]; - CGImageRelease(iref); + NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(width * scale, height * scale)]; + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:false + isPlanar:false + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:0 + bytesPerRow:4 * width + bitsPerPixel:32]; + memcpy(rep.bitmapData, data.bytes, data.length); + [ret addRepresentation:rep]; return ret; } @@ -1369,6 +1614,7 @@ - (void) reloadMemoryView - (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 */ @@ -1407,8 +1653,8 @@ - (IBAction) reloadVRAMData: (id) sender (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; @@ -1418,13 +1664,9 @@ - (IBAction) reloadVRAMData: (id) sender case 2: /* OAM */ { - oamCount = GB_get_oam_info(&gb, oamInfo, &oamHeight); + _oamCount = GB_get_oam_info(&gb, _oamInfo, &_oamHeight); dispatch_async(dispatch_get_main_queue(), ^{ - if (!oamUpdating) { - oamUpdating = true; - [self.spritesTableView reloadData]; - oamUpdating = false; - } + [self.objectView reloadData:self]; }); } break; @@ -1433,7 +1675,7 @@ - (IBAction) reloadVRAMData: (id) sender /* Palettes */ { dispatch_async(dispatch_get_main_queue(), ^{ - [self.paletteTableView reloadData]; + [self.paletteView reloadData:self]; }); } break; @@ -1461,9 +1703,17 @@ - (IBAction)hexGoTo:(id)sender GB_log(&gb, "Value $%04x is out of range.\n", addr); return; } - [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; - [hex_controller _ensureVisibilityOfLocation:addr]; - [self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; + [hex_controller _ensureVisibilityOfLocation:addr]; + for (HFRepresenter *representer in hex_controller.representers) { + if ([representer isKindOfClass:[HFHexTextRepresenter class]]) { + [self.memoryWindow makeFirstResponder:representer.view]; + break; + } + } + }); }]; if (error) { NSBeep(); @@ -1511,7 +1761,9 @@ - (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors [sender setStringValue:[NSString stringWithFormat:@"$%x", bank]]; [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank]; - [hex_controller reloadData]; + dispatch_async(dispatch_get_main_queue(), ^{ + [hex_controller reloadData]; + }); }]; if (error && !ignore_errors) { @@ -1556,7 +1808,9 @@ - (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender } [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; [hex_controller reloadData]; - [self.memoryView setNeedsDisplay:YES]; + for (NSView *view in self.memoryView.subviews) { + [view setNeedsDisplay:true]; + } } - (GB_gameboy_t *) gameboy @@ -1566,12 +1820,12 @@ - (GB_gameboy_t *) gameboy + (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName { - return YES; + return true; } - (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, *)) { @@ -1700,21 +1954,21 @@ - (IBAction)vramTabChanged:(NSSegmentedControl *)sender window_rect.origin.y += window_rect.size.height; switch ([sender selectedSegment]) { case 0: + case 2: window_rect.size.height = 384 + height_diff + 48; break; case 1: - case 2: window_rect.size.height = 512 + height_diff + 48; break; case 3: - window_rect.size.height = 20 * 16 + height_diff + 24; + window_rect.size.height = 24 * 16 + height_diff; break; default: 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 @@ -1738,12 +1992,12 @@ - (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger) uint8_t lcdc = ((uint8_t *)GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC]; uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL); - if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) { - map_base = 0x1c00; + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & GB_LCDC_BG_MAP)) { + map_base = 0x1C00; } if (tileset_type == GB_TILESET_AUTO) { - tileset_type = (lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + tileset_type = (lcdc & GB_LCDC_TILE_SEL)? GB_TILESET_8800 : GB_TILESET_8000; } uint8_t tile = vram[map_base + map_offset]; @@ -1780,79 +2034,9 @@ - (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger) } } -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +- (GB_oam_info_t *)oamInfo { - if (tableView == self.paletteTableView) { - return 16; /* 8 BG palettes, 8 OBJ palettes*/ - } - else if (tableView == self.spritesTableView) { - return oamCount; - } - return 0; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; - if (tableView == self.paletteTableView) { - if (columnIndex == 0) { - return [NSString stringWithFormat:@"%s %u", row >= 8 ? "Object" : "Background", (unsigned)(row & 7)]; - } - - uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL); - - uint16_t index = columnIndex - 1 + (row & 7) * 4; - return @((palette_data[(index << 1) + 1] << 8) | palette_data[(index << 1)]); - } - else if (tableView == self.spritesTableView) { - switch (columnIndex) { - case 0: - return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image - length:64 * 4 * 2 - freeWhenDone:NO] - width:8 - height:oamHeight - scale:16.0/oamHeight]; - case 1: - return @((unsigned)oamInfo[row].x - 8); - case 2: - return @((unsigned)oamInfo[row].y - 16); - case 3: - return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile]; - case 4: - return [NSString stringWithFormat:@"$%04x", 0x8000 + oamInfo[row].tile * 0x10]; - case 5: - return [NSString stringWithFormat:@"$%04x", oamInfo[row].oam_addr]; - case 6: - if (GB_is_cgb(&gb)) { - return [NSString stringWithFormat:@"%c%c%c%d%d", - oamInfo[row].flags & 0x80? 'P' : '-', - oamInfo[row].flags & 0x40? 'Y' : '-', - oamInfo[row].flags & 0x20? 'X' : '-', - oamInfo[row].flags & 0x08? 1 : 0, - oamInfo[row].flags & 0x07]; - } - return [NSString stringWithFormat:@"%c%c%c%d", - oamInfo[row].flags & 0x80? 'P' : '-', - oamInfo[row].flags & 0x40? 'Y' : '-', - oamInfo[row].flags & 0x20? 'X' : '-', - oamInfo[row].flags & 0x10? 1 : 0]; - case 7: - return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many sprites in line": @""; - - } - } - return nil; -} - -- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row -{ - return tableView == self.spritesTableView; -} - -- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row -{ - return NO; + return _oamInfo; } - (IBAction)showVRAMViewer:(id)sender @@ -1882,7 +2066,7 @@ - (void) printImage:(uint32_t *)imageBytes height:(unsigned) height 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]; }); @@ -1902,7 +2086,7 @@ - (IBAction)savePrinterFeed:(id)sender { 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) { @@ -1913,8 +2097,8 @@ - (IBAction)savePrinterFeed:(id)sender 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]; @@ -1949,6 +2133,11 @@ - (IBAction)connectWorkboy:(id)sender }]; } +- (void) updateVolume +{ + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; +} + - (void) updateHighpassFilter { if (GB_is_inited(&gb)) { @@ -2025,6 +2214,15 @@ - (void)cgbModelChanged modelsChanging = false; } +- (void)agbModelChanged +{ + modelsChanging = true; + if (current_model == MODEL_AGB) { + [self reset:nil]; + } + modelsChanging = false; +} + - (void)setFileURL:(NSURL *)fileURL { [super setFileURL:fileURL]; @@ -2035,9 +2233,9 @@ - (void)setFileURL:(NSURL *)fileURL - (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 @@ -2053,9 +2251,9 @@ - (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)pr - (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { if ([[splitView arrangedSubviews] lastObject] == view) { - return NO; + return false; } - return YES; + return true; } - (void)splitViewDidResizeSubviews:(NSNotification *)notification @@ -2064,11 +2262,6 @@ - (void)splitViewDidResizeSubviews:(NSNotification *)notification if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) { [splitview setPosition:600 ofDividerAtIndex:0]; } - /* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an - NSBox-based separator that renders properly so it acts like the split view's separator. */ - NSRect rect = self.debuggerVerticalLine.frame; - rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1; - self.debuggerVerticalLine.frame = rect; } - (IBAction)showCheats:(id)sender @@ -2086,6 +2279,7 @@ - (void)disconnectLinkCable bool wasRunning = self->running; Document *partner = master ?: slave; if (partner) { + wasRunning |= partner->running; [self stop]; partner->master = nil; partner->slave = nil; @@ -2159,4 +2353,298 @@ - (GB_gameboy_t *)gb { return &gb; } + +- (NSImage *)takeScreenshot +{ + 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 + length:GB_get_screen_width(&gb) * GB_get_screen_height(&gb) * 4 + freeWhenDone:false] + width:GB_get_screen_width(&gb) + height:GB_get_screen_height(&gb) + scale:1.0]; + } + return ret; +} + +- (NSString *)screenshotFilename +{ + NSDate *date = [NSDate date]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateStyle = NSDateFormatterLongStyle; + dateFormatter.timeStyle = NSDateFormatterMediumStyle; + return [[NSString stringWithFormat:@"%@ – %@.png", + self.fileURL.lastPathComponent.stringByDeletingPathExtension, + [dateFormatter stringFromDate:date]] stringByReplacingOccurrencesOfString:@":" withString:@"."]; // Gotta love Mac OS Classic + +} + +- (IBAction)saveScreenshot:(id)sender +{ + NSString *folder = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBScreenshotFolder"]; + BOOL isDirectory = false; + if (folder) { + [[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDirectory]; + } + if (!folder) { + bool shouldResume = running; + [self stop]; + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.canChooseFiles = false; + openPanel.canChooseDirectories = true; + openPanel.message = @"Choose a folder for screenshots"; + [openPanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [[NSUserDefaults standardUserDefaults] setObject:openPanel.URL.path + forKey:@"GBScreenshotFolder"]; + [self saveScreenshot:sender]; + } + if (shouldResume) { + [self start]; + } + + }]; + return; + } + NSImage *image = [self takeScreenshot]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateStyle = NSDateFormatterLongStyle; + dateFormatter.timeStyle = NSDateFormatterMediumStyle; + NSString *filename = [self screenshotFilename]; + filename = [folder stringByAppendingPathComponent:filename]; + unsigned i = 2; + while ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { + filename = [[filename stringByDeletingPathExtension] stringByAppendingFormat:@" %d.png", i++]; + } + + NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; + NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + [data writeToFile:filename atomically:false]; + [self.osdView displayText:@"Screenshot saved"]; +} + +- (IBAction)saveScreenshotAs:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSImage *image = [self takeScreenshot]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setNameFieldStringValue:[self screenshotFilename]]; + [savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [savePanel orderOut:self]; + NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; + NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + [data writeToURL:savePanel.URL atomically:false]; + [[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent + forKey:@"GBScreenshotFolder"]; + } + if (shouldResume) { + [self start]; + } + }]; + [self.osdView displayText:@"Screenshot saved"]; +} + +- (IBAction)copyScreenshot:(id)sender +{ + NSImage *image = [self takeScreenshot]; + [[NSPasteboard generalPasteboard] clearContents]; + [[NSPasteboard generalPasteboard] writeObjects:@[image]]; + [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)); +} + +- (IBAction)newCartridgeInstance:(id)sender +{ + bool shouldResume = running; + [self stop]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:@[@"gbcart"]]; + [savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [savePanel orderOut:self]; + NSString *romPath = self.romPath; + [[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil]; + [[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil]; + [[NSString stringWithFormat:@"%@\n%@\n%@", + [romPath pathRelativeToDirectory:savePanel.URL.path], + romPath, + [[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")] + ] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil]; + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil]; + } + if (shouldResume) { + [self start]; + } + }]; +} + +- (IBAction)toggleAudioRecording:(id)sender +{ + + bool shouldResume = running; + [self stop]; + if (_isRecordingAudio) { + _isRecordingAudio = false; + int error = GB_stop_audio_recording(&gb); + if (error) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithFormat:@"Could not finalize recording: %s", strerror(error)]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + } + else { + [self.osdView displayText:@"Audio recording ended"]; + } + if (shouldResume) { + [self start]; + } + return; + } + _audioSavePanel = [NSSavePanel savePanel]; + if (!self.audioRecordingAccessoryView) { + [[NSBundle mainBundle] loadNibNamed:@"AudioRecordingAccessoryView" owner:self topLevelObjects:nil]; + } + _audioSavePanel.accessoryView = self.audioRecordingAccessoryView; + [self audioFormatChanged:self.audioFormatButton]; + + [_audioSavePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) { + if (result == NSModalResponseOK) { + [_audioSavePanel orderOut:self]; + int error = GB_start_audio_recording(&gb, _audioSavePanel.URL.fileSystemRepresentation, self.audioFormatButton.selectedTag); + if (error) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithFormat:@"Could not start recording: %s", strerror(error)]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + } + else { + [self.osdView displayText:@"Audio recording started"]; + _isRecordingAudio = true; + } + } + if (shouldResume) { + [self start]; + } + _audioSavePanel = nil; + }]; +} + +- (IBAction)audioFormatChanged:(NSPopUpButton *)sender +{ + switch ((GB_audio_format_t)sender.selectedTag) { + case GB_AUDIO_FORMAT_RAW: + _audioSavePanel.allowedFileTypes = @[@"raw", @"pcm"]; + break; + case GB_AUDIO_FORMAT_AIFF: + _audioSavePanel.allowedFileTypes = @[@"aiff", @"aif", @"aifc"]; + break; + case GB_AUDIO_FORMAT_WAV: + _audioSavePanel.allowedFileTypes = @[@"wav"]; + break; + } +} + +- (IBAction)toggleAudioChannel:(NSMenuItem *)sender +{ + GB_set_channel_muted(&gb, sender.tag, !GB_is_channel_muted(&gb, sender.tag)); +} + +- (IBAction)cartSwap:(id)sender +{ + bool wasRunning = running; + if (wasRunning) { + [self stop]; + } + [[NSDocumentController sharedDocumentController] beginOpenPanelWithCompletionHandler:^(NSArray *urls) { + if (urls.count == 1) { + bool ok = true; + for (Document *document in [NSDocumentController sharedDocumentController].documents) { + if ([document.fileURL isEqual:urls.firstObject]) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:[NSString stringWithFormat:@"‘%@’ is already open in another window. Close ‘%@’ before hot swapping it into this instance.", + urls.firstObject.lastPathComponent, urls.firstObject.lastPathComponent]]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + ok = false; + break; + } + } + if (ok) { + GB_save_battery(&gb, self.savPath.UTF8String); + self.fileURL = urls.firstObject; + [self loadROM]; + } + } + if (wasRunning) { + [self start]; + } + }]; +} + +- (void)updateDebuggerButtons +{ + bool updateContinue = false; + if (@available(macOS 10.10, *)) { + if ([self.consoleInput.placeholderAttributedString.string isEqualToString:self.debuggerContinueButton.alternateTitle]) { + [self.debuggerContinueButton mouseExited:nil]; + updateContinue = true; + } + } + if (self.isPaused) { + self.debuggerContinueButton.toolTip = self.debuggerContinueButton.title = @"Continue"; + self.debuggerContinueButton.alternateTitle = @"continue"; + self.debuggerContinueButton.imagePosition = NSImageOnly; + if (@available(macOS 10.14, *)) { + self.debuggerContinueButton.contentTintColor = nil; + } + self.debuggerContinueButton.image = [NSImage imageNamed:@"ContinueTemplate"]; + + self.debuggerNextButton.enabled = true; + self.debuggerStepButton.enabled = true; + self.debuggerFinishButton.enabled = true; + } + else { + self.debuggerContinueButton.toolTip = self.debuggerContinueButton.title = @"Interrupt"; + self.debuggerContinueButton.alternateTitle = @"interrupt"; + self.debuggerContinueButton.imagePosition = NSImageOnly; + if (@available(macOS 10.14, *)) { + self.debuggerContinueButton.contentTintColor = [NSColor controlAccentColor]; + } + self.debuggerContinueButton.image = [NSImage imageNamed:@"InterruptTemplate"]; + + self.debuggerNextButton.enabled = false; + self.debuggerStepButton.enabled = false; + self.debuggerFinishButton.enabled = false; + } + if (updateContinue) { + [self.debuggerContinueButton mouseEntered:nil]; + } +} + +- (IBAction)debuggerButtonPressed:(NSButton *)sender +{ + [self queueDebuggerCommand:sender.alternateTitle]; +} + @end diff --git a/thirdparty/SameBoy/Cocoa/Document.xib b/thirdparty/SameBoy/Cocoa/Document.xib index a2cf5ee30..bbdaf7ea7 100644 --- a/thirdparty/SameBoy/Cocoa/Document.xib +++ b/thirdparty/SameBoy/Cocoa/Document.xib @@ -3,6 +3,7 @@ + @@ -14,9 +15,14 @@ + + + + + @@ -25,9 +31,10 @@ - + + + - @@ -65,6 +72,10 @@ + + + + @@ -78,54 +89,35 @@ - + - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - + - + - + - + - - + + - - + + - - + + @@ -133,29 +125,112 @@ - - + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + - - + + - + - + @@ -181,22 +256,22 @@ - + - + - - + + - + - + @@ -211,7 +286,7 @@ - + @@ -336,17 +411,17 @@ - + - + - + @@ -355,7 +430,7 @@ - + @@ -387,7 +462,7 @@ - + @@ -426,7 +501,7 @@ - + @@ -459,7 +534,7 @@ - + @@ -477,7 +552,7 @@ - + @@ -497,252 +572,47 @@ - + - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -765,7 +635,7 @@ - + @@ -835,7 +705,7 @@ - + @@ -926,7 +796,6 @@ - @@ -942,7 +811,6 @@ - @@ -958,7 +826,6 @@ - @@ -966,7 +833,7 @@ - + @@ -978,10 +845,10 @@ - + - + @@ -993,13 +860,13 @@ - + - + @@ -1024,7 +891,7 @@ - + @@ -1046,7 +913,7 @@ - + @@ -1077,7 +944,12 @@ + + + + + diff --git a/thirdparty/SameBoy/Cocoa/FinishTemplate.png b/thirdparty/SameBoy/Cocoa/FinishTemplate.png new file mode 100644 index 000000000..5aa831df7 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/FinishTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/FinishTemplate@2x.png b/thirdparty/SameBoy/Cocoa/FinishTemplate@2x.png new file mode 100644 index 000000000..06bbcd490 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/FinishTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/GBApp.h b/thirdparty/SameBoy/Cocoa/GBApp.h new file mode 100644 index 000000000..0ad7ac309 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBApp.h @@ -0,0 +1,26 @@ +#import +#import +#import + +@interface GBApp : NSApplication + +@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/thirdparty/SameBoy/Cocoa/GBApp.m b/thirdparty/SameBoy/Cocoa/GBApp.m new file mode 100644 index 000000000..588de44b1 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBApp.m @@ -0,0 +1,663 @@ +#import "GBApp.h" +#import "GBButtons.h" +#import "GBView.h" +#import "Document.h" +#import "GBJoyConManager.h" +#import +#import +#import +#import +#import + +#define UPDATE_SERVER "https://sameboy.github.io" + +static uint32_t color_to_int(NSColor *color) +{ + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + return (((unsigned)(color.redComponent * 0xFF)) << 16) | + (((unsigned)(color.greenComponent * 0xFF)) << 8) | + ((unsigned)(color.blueComponent * 0xFF)); +} + +@implementation GBApp +{ + NSArray *_preferencesTabs; + NSString *_lastVersion; + NSString *_updateURL; + NSURLSessionDownloadTask *_updateTask; + enum { + UPDATE_DOWNLOADING, + UPDATE_EXTRACTING, + UPDATE_WAIT_INSTALL, + UPDATE_INSTALLING, + UPDATE_FAILED, + } _updateState; + NSString *_downloadDirectory; + AuthorizationRef _auth; + bool _simulatingMenuEvent; +} + +- (void) applicationDidFinishLaunching:(NSNotification *)notification +{ + // Refresh icon if launched via a software update + [NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + for (unsigned i = 0; i < GBButtonCount; i++) { + if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) { + [defaults removeObjectForKey:button_to_preference_name(i, 0)]; + } + } + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"GBRight": @(kVK_RightArrow), + @"GBLeft": @(kVK_LeftArrow), + @"GBUp": @(kVK_UpArrow), + @"GBDown": @(kVK_DownArrow), + + @"GBA": @(kVK_ANSI_X), + @"GBB": @(kVK_ANSI_Z), + @"GBSelect": @(kVK_Delete), + @"GBStart": @(kVK_Return), + + @"GBTurbo": @(kVK_Space), + @"GBRewind": @(kVK_Tab), + @"GBSlow-Motion": @(kVK_Shift), + + @"GBFilter": @"NearestNeighbor", + @"GBColorCorrection": @(GB_COLOR_CORRECTION_MODERN_BALANCED), + @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), + @"GBRewindLength": @(10), + @"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE), + + @"GBDMGModel": @(GB_MODEL_DMG_B), + @"GBCGBModel": @(GB_MODEL_CGB_E), + @"GBAGBModel": @(GB_MODEL_AGB_A), + @"GBSGBModel": @(GB_MODEL_SGB2), + @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), + + @"GBVolume": @(1.0), + + @"GBMBC7JoystickOverride": @NO, + @"GBMBC7AllowMouse": @YES, + + @"GBJoyConAutoPair": @YES, + @"GBJoyConsDefaultsToHorizontal": @YES, + + // Default themes + @"GBThemes": @{ + @"Desert": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8], + @"DisabledLCDColor": @YES, + @"HueBias": @0.10087773904382469, + @"HueBiasStrength": @0.062142056772908363, + @"Manual": @NO, + }, + @"Evening": @{ + @"BrightnessBias": @-0.10168700106441975, + @"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb], + @"DisabledLCDColor": @YES, + @"HueBias": @0.60027079191058874, + @"HueBiasStrength": @0.33816297305747867, + @"Manual": @NO, + }, + @"Fog": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6], + @"DisabledLCDColor": @YES, + @"HueBias": @0.55750435756972117, + @"HueBiasStrength": @0.18424738545816732, + @"Manual": @NO, + }, + @"Magic Eggplant": @{ + @"BrightnessBias": @0.0, + @"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2], + @"DisabledLCDColor": @YES, + @"HueBias": @0.87717878486055778, + @"HueBiasStrength": @0.65018052788844627, + @"Manual": @NO, + }, + @"Radioactive Pea": @{ + @"BrightnessBias": @-0.48079556772908372, + @"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1], + @"DisabledLCDColor": @YES, + @"HueBias": @0.3795131972111554, + @"HueBiasStrength": @0.34337649402390436, + @"Manual": @NO, + }, + @"Seaweed": @{ + @"BrightnessBias": @-0.28532744023904377, + @"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.2694067480079681, + @"HueBiasStrength": @0.51565612549800799, + @"Manual": @NO, + }, + @"Twilight": @{ + @"BrightnessBias": @-0.091789093625498031, + @"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee], + @"DisabledLCDColor": @YES, + @"HueBias": @0.0, + @"HueBiasStrength": @0.49710532868525897, + @"Manual": @NO, + }, + }, + + @"NSToolbarItemForcesStandardSize": @YES, // Forces Monterey to resepect toolbar item sizes + }]; + + [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ + JOYAxes2DEmulateButtonsKey: @YES, + JOYHatsEmulateButtonsKey: @YES, + }]; + + [GBJoyConManager sharedInstance]; // Starts handling Joy-Cons + + [JOYController registerListener:self]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; + } + + [self askAutoUpdates]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) { + [self checkForUpdates]; + } + + if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { + [NSApp activateIgnoringOtherApps:true]; + } +} + +- (IBAction)toggleDeveloperMode:(id)sender +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"]; +} + +- (IBAction)switchPreferencesTab:(id)sender +{ + for (NSView *view in _preferencesTabs) { + [view removeFromSuperview]; + } + NSView *tab = _preferencesTabs[[sender tag]]; + NSRect old = [_preferencesWindow frame]; + 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:true animate:_preferencesWindow.visible]; + [_preferencesWindow.contentView addSubview:tab]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)anItem +{ + 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; + if (!_preferencesWindow) { + [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; + NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; + _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; + _preferencesTabs = @[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 + if (@available(macOS 11.0, *)) { + [_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0]; + [_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count]; + } + } + [_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]]) { + [(NSOpenPanel *)window makeKeyAndOrderFront:nil]; + return true; + } + } + [[NSDocumentController sharedDocumentController] openDocument:self]; + return true; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true]; +} + +- (void)updateFound +{ + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0]; + if (@available(macOS 10.10, *)) { + linkColor = [NSColor linkColor]; + } + + NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSRange cutoffRange = [changes rangeOfString:@""]; + 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:true]; + }); + 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 (![@GB_VERSION isEqualToString:_lastVersion] && + ![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) { + [self updateFound]; + } + } + }] resume]; +#endif +} + +- (IBAction)userCheckForUpdates:(id)sender +{ + if (self.updateWindow) { + [self.updateWindow makeKeyAndOrderFront:sender]; + } + else { + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"]; + [self checkForUpdates]; + [sender setEnabled:false]; + [self.updatesSpinner startAnimation:sender]; + } +} + +- (void)askAutoUpdates +{ +#ifdef UPDATE_SUPPORT + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Should SameBoy check for updates when launched?"; + alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window."; + [alert addButtonWithTitle:@"Check on Launch"]; + [alert addButtonWithTitle:@"Don't Check on Launch"]; + + [[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"]; + } +#endif +} + +- (IBAction)skipVersion:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"]; + [self.updateWindow performClose:sender]; +} + +- (bool)executePath:(NSString *)path withArguments:(NSArray *)arguments +{ + if (!_auth) { + NSTask *task = [[NSTask alloc] init]; + task.launchPath = path; + task.arguments = arguments; + [task launch]; + [task waitUntilExit]; + return task.terminationStatus == 0 && task.terminationReason == NSTaskTerminationReasonExit; + } + + char *argv[arguments.count + 1]; + argv[arguments.count] = NULL; + for (unsigned i = 0; i < arguments.count; i++) { + argv[i] = (char *)arguments[i].UTF8String; + } + + return AuthorizationExecuteWithPrivileges(_auth, path.UTF8String, kAuthorizationFlagDefaults, argv, NULL) == errAuthorizationSuccess; +} + +- (void)deauthorize +{ + if (_auth) { + AuthorizationFree(_auth, kAuthorizationFlagDefaults); + _auth = nil; + } +} + +- (IBAction)installUpdate:(id)sender +{ + bool needsAuthorization = false; + if ([self executePath:@"/usr/sbin/spctl" withArguments:@[@"--status"]]) { // Succeeds when GateKeeper is on + // GateKeeper is on, we need to --add ourselves as root, else we might get a GateKeeper crash + needsAuthorization = true; + } + else if (access(_dyld_get_image_name(0), W_OK)) { + // We don't have write access, so we need authorization to update as root + needsAuthorization = true; + } + + if (needsAuthorization && !_auth) { + AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed, &_auth); + // Make sure we can modify the bundle + if (![self executePath:@"/usr/sbin/chown" withArguments:@[@"-R", [NSString stringWithFormat:@"%d:%d", getuid(), getgid()], [NSBundle mainBundle].bundlePath]]) { + [self deauthorize]; + return; + } + } + + [self.updateProgressSpinner startAnimation:nil]; + self.updateProgressButton.title = @"Cancel"; + self.updateProgressButton.enabled = true; + self.updateProgressLabel.stringValue = @"Downloading update..."; + _updateState = UPDATE_DOWNLOADING; + _updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + _updateTask = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Extracting update..."; + _updateState = UPDATE_EXTRACTING; + }); + + _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:[[NSBundle mainBundle] bundleURL] + create:true + error:nil] path]; + if (!_downloadDirectory) { + [self deauthorize]; + 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; + } + + NSTask *unzipTask; + unzipTask = [[NSTask alloc] init]; + unzipTask.launchPath = @"/usr/bin/unzip"; + unzipTask.arguments = @[location.path, @"-d", _downloadDirectory]; + [unzipTask launch]; + [unzipTask waitUntilExit]; + if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) { + [self deauthorize]; + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + 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) { + [self deauthorize]; + [[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) { + [self deauthorize]; + [[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)orderFrontAboutPanel:(id)sender +{ + // NSAboutPanelOptionApplicationIcon is not available prior to 10.13, but the key is still there and working. + [[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:@{ + @"ApplicationIcon": [NSImage imageNamed:@"Icon"] + }]; +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + if (!button.isPressed) return; + NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; + if (!mapping) { + mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName]; + } + + JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: -1; + if (!mapping && usage >= JOYButtonUsageGeneric0) { + usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3]; + } + + if (usage == GBJoyKitHotkey1 || usage == GBJoyKitHotkey2) { + if (_preferencesWindow && self.keyWindow == _preferencesWindow) { + return; + } + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"] && !self.keyWindow) { + return; + } + + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:usage == GBJoyKitHotkey1? @"GBJoypadHotkey1" : @"GBJoypadHotkey2"]; + NSEventModifierFlags flags = NSEventModifierFlagCommand; + if ([keyEquivalent hasPrefix:@"^"]) { + flags |= NSEventModifierFlagShift; + [keyEquivalent substringFromIndex:1]; + } + _simulatingMenuEvent = true; + [[NSApplication sharedApplication] sendEvent:[NSEvent keyEventWithType:NSEventTypeKeyDown + location:(NSPoint){0,} + modifierFlags:flags + timestamp:0 + windowNumber:0 + context:NULL + characters:keyEquivalent + charactersIgnoringModifiers:keyEquivalent + isARepeat:false + keyCode:0]]; + _simulatingMenuEvent = false; + } +} + +- (NSWindow *)keyWindow +{ + NSWindow *ret = [super keyWindow]; + if (!ret && _simulatingMenuEvent) { + ret = [(Document *)self.orderedDocuments.firstObject mainWindow]; + } + return ret; +} + +- (NSWindow *)mainWindow +{ + NSWindow *ret = [super mainWindow]; + if (!ret && _simulatingMenuEvent) { + ret = [(Document *)self.orderedDocuments.firstObject mainWindow]; + } + return ret; +} + +- (IBAction)openDebuggerHelp:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sameboy.github.io/debugger/"]]; +} + +- (IBAction)openSponsor:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/sponsors/LIJI32"]]; +} + +- (void)dealloc +{ + if (_downloadDirectory) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + } +} + +- (IBAction)nop:(id)sender +{ +} +@end diff --git a/thirdparty/SameBoy/Cocoa/GBAudioClient.m b/thirdparty/SameBoy/Cocoa/GBAudioClient.m index 7f2115d75..81a51fd56 100644 --- a/thirdparty/SameBoy/Cocoa/GBAudioClient.m +++ b/thirdparty/SameBoy/Cocoa/GBAudioClient.m @@ -90,7 +90,7 @@ -(void) start { OSErr err = AudioOutputUnitStart(audioUnit); NSAssert1(err == noErr, @"Error starting unit: %hd", err); - _playing = YES; + _playing = true; } @@ -98,7 +98,7 @@ -(void) start -(void) stop { AudioOutputUnitStop(audioUnit); - _playing = NO; + _playing = false; } -(void) dealloc @@ -108,4 +108,4 @@ -(void) dealloc AudioComponentInstanceDispose(audioUnit); } -@end \ No newline at end of file +@end diff --git a/thirdparty/SameBoy/Cocoa/GBBorderView.m b/thirdparty/SameBoy/Cocoa/GBBorderView.m index a5f5e8173..d992e298a 100644 --- a/thirdparty/SameBoy/Cocoa/GBBorderView.m +++ b/thirdparty/SameBoy/Cocoa/GBBorderView.m @@ -5,12 +5,12 @@ @implementation GBBorderView - (void)awakeFromNib { - self.wantsLayer = YES; + self.wantsLayer = true; } - (BOOL)wantsUpdateLayer { - return YES; + return true; } - (void)updateLayer diff --git a/thirdparty/SameBoy/Cocoa/GBButtons.h b/thirdparty/SameBoy/Cocoa/GBButtons.h index 1f8b5afbd..f5c76dab7 100644 --- a/thirdparty/SameBoy/Cocoa/GBButtons.h +++ b/thirdparty/SameBoy/Cocoa/GBButtons.h @@ -13,11 +13,17 @@ typedef enum : NSUInteger { GBTurbo, GBRewind, GBUnderclock, - GBButtonCount, + GBHotkey1, + GBHotkey2, + GBJoypadButtonCount, + GBButtonCount = GBUnderclock + 1, GBGameBoyButtonCount = GBStart + 1, } GBButton; -extern NSString const *GBButtonNames[GBButtonCount]; +#define GBJoyKitHotkey1 JOYButtonUsageGeneric0 + 0x100 +#define GBJoyKitHotkey2 JOYButtonUsageGeneric0 + 0x101 + +extern NSString const *GBButtonNames[GBJoypadButtonCount]; static inline NSString *n2s(uint64_t number) { diff --git a/thirdparty/SameBoy/Cocoa/GBButtons.m b/thirdparty/SameBoy/Cocoa/GBButtons.m index 044e93326..ef86738f0 100644 --- a/thirdparty/SameBoy/Cocoa/GBButtons.m +++ b/thirdparty/SameBoy/Cocoa/GBButtons.m @@ -1,4 +1,4 @@ #import #import "GBButtons.h" -NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"}; +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion", @"Hotkey 1", @"Hotkey 2"}; diff --git a/thirdparty/SameBoy/Cocoa/GBCheatTextFieldCell.m b/thirdparty/SameBoy/Cocoa/GBCheatTextFieldCell.m index 611cadebc..bc09e8152 100644 --- a/thirdparty/SameBoy/Cocoa/GBCheatTextFieldCell.m +++ b/thirdparty/SameBoy/Cocoa/GBCheatTextFieldCell.m @@ -6,7 +6,7 @@ @interface GBCheatTextView : NSTextView @implementation GBCheatTextView -- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range +- (bool)_internalInsertText:(NSString *)string replacementRange:(NSRange)range { if (range.location == NSNotFound) { range = self.selectedRange; @@ -60,19 +60,19 @@ - (bool)_insertText:(NSString *)string replacementRange:(NSRange)range return true; } if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) { - if ([self _insertText:@"$00:" replacementRange:range]) { + if ([self _internalInsertText:@"$00:" replacementRange:range]) { self.selectedRange = NSMakeRange(1, 2); return true; } } if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) { - if ([self _insertText:@":$0" replacementRange:range]) { + if ([self _internalInsertText:@":$0" replacementRange:range]) { self.selectedRange = NSMakeRange(self.string.length - 2, 2); return true; } } if ([string isEqualToString:@"$"]) { - if ([self _insertText:@"$0" replacementRange:range]) { + if ([self _internalInsertText:@"$0" replacementRange:range]) { self.selectedRange = NSMakeRange(range.location + 1, 1); return true; } @@ -88,8 +88,10 @@ - (NSUndoManager *)undoManager - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { - if (![self _insertText:string replacementRange:replacementRange]) { - NSBeep(); + if (![self _internalInsertText:string replacementRange:replacementRange]) { + if (![self _internalInsertText:[@"$" stringByAppendingString:string] replacementRange:replacementRange]) { + NSBeep(); + } } } @@ -114,7 +116,7 @@ - (NSTextView *)fieldEditorForView:(NSView *)controlView return _fieldEditor; } _fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame]; - _fieldEditor.fieldEditor = YES; + _fieldEditor.fieldEditor = true; _fieldEditor.usesAddressFormat = self.usesAddressFormat; return _fieldEditor; } diff --git a/thirdparty/SameBoy/Cocoa/GBCheatWindowController.m b/thirdparty/SameBoy/Cocoa/GBCheatWindowController.m index c10e2a945..24987dac6 100644 --- a/thirdparty/SameBoy/Cocoa/GBCheatWindowController.m +++ b/thirdparty/SameBoy/Cocoa/GBCheatWindowController.m @@ -52,7 +52,7 @@ - (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nul if (row >= cheatCount) { switch (columnIndex) { case 0: - return @(YES); + return @YES; case 1: return @NO; @@ -67,7 +67,7 @@ - (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nul switch (columnIndex) { case 0: - return @(NO); + return @NO; case 1: return @(cheats[row]->enabled); @@ -98,8 +98,10 @@ - (IBAction)importCheat:(id)sender [self tableViewSelectionDidChange:nil]; } else { - NSBeep(); - [GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSBeep(); + [GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField]; + }); } }]; } diff --git a/thirdparty/SameBoy/Cocoa/GBColorCell.h b/thirdparty/SameBoy/Cocoa/GBColorCell.h deleted file mode 100644 index a622c7883..000000000 --- a/thirdparty/SameBoy/Cocoa/GBColorCell.h +++ /dev/null @@ -1,5 +0,0 @@ -#import - -@interface GBColorCell : NSTextFieldCell - -@end diff --git a/thirdparty/SameBoy/Cocoa/GBColorCell.m b/thirdparty/SameBoy/Cocoa/GBColorCell.m deleted file mode 100644 index 0ad102a6c..000000000 --- a/thirdparty/SameBoy/Cocoa/GBColorCell.m +++ /dev/null @@ -1,48 +0,0 @@ -#import "GBColorCell.h" - -static inline double scale_channel(uint8_t x) -{ - x &= 0x1f; - return x / 31.0; -} - -@implementation GBColorCell -{ - NSInteger _integerValue; -} - -- (void)setObjectValue:(id)objectValue -{ - - _integerValue = [objectValue integerValue]; - uint8_t r = _integerValue & 0x1F, - g = (_integerValue >> 5) & 0x1F, - b = (_integerValue >> 10) & 0x1F; - super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{ - NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor] - }]; -} - -- (NSInteger)integerValue -{ - return _integerValue; -} - -- (int)intValue -{ - return (int)_integerValue; -} - - -- (NSColor *) backgroundColor -{ - uint16_t color = self.integerValue; - return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0]; -} - -- (BOOL)drawsBackground -{ - return YES; -} - -@end diff --git a/thirdparty/SameBoy/Cocoa/GBDebuggerButton.h b/thirdparty/SameBoy/Cocoa/GBDebuggerButton.h new file mode 100644 index 000000000..5c3a12f63 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBDebuggerButton.h @@ -0,0 +1,7 @@ +#import + +@class GBDocument; +@interface GBDebuggerButton : NSButton +@property (weak) IBOutlet NSTextField *textField; +@end + diff --git a/thirdparty/SameBoy/Cocoa/GBDebuggerButton.m b/thirdparty/SameBoy/Cocoa/GBDebuggerButton.m new file mode 100644 index 000000000..15e6eb02a --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBDebuggerButton.m @@ -0,0 +1,48 @@ +#import "GBDebuggerButton.h" + +@implementation GBDebuggerButton +{ + NSTrackingArea *_trackingArea; +} +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + self.toolTip = self.title; + return self; +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (@available(macOS 10.10, *)) { + NSDictionary *attributes = @{ + NSForegroundColorAttributeName: [NSColor colorWithWhite:1.0 alpha:0.5], + NSFontAttributeName: self.textField.font + }; + self.textField.placeholderAttributedString = + [[NSAttributedString alloc] initWithString:self.alternateTitle attributes:attributes]; + } +} + +- (void)mouseExited:(NSEvent *)event +{ + if (@available(macOS 10.10, *)) { + if ([self.textField.placeholderAttributedString.string isEqualToString:self.alternateTitle]) { + self.textField.placeholderAttributedString = nil; + } + } +} + +-(void)updateTrackingAreas +{ + [super updateTrackingAreas]; + if (_trackingArea) { + [self removeTrackingArea:_trackingArea]; + } + + _trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} +@end diff --git a/thirdparty/SameBoy/Cocoa/GBHueSliderCell.h b/thirdparty/SameBoy/Cocoa/GBHueSliderCell.h new file mode 100644 index 000000000..f293124b5 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBHueSliderCell.h @@ -0,0 +1,9 @@ +#import + +@interface NSSlider (GBHueSlider) +-(NSColor *)colorValue; +@end + +@interface GBHueSliderCell : NSSliderCell +-(NSColor *)colorValue; +@end diff --git a/thirdparty/SameBoy/Cocoa/GBHueSliderCell.m b/thirdparty/SameBoy/Cocoa/GBHueSliderCell.m new file mode 100644 index 000000000..9b397cb46 --- /dev/null +++ b/thirdparty/SameBoy/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; + break; + case 2: + g = 1; + b = t; + break; + case 3: + g = 1 - t; + b = 1; + break; + case 4: + b = 1; + r = t; + break; + case 5: + b = 1 - t; + r = 1; + break; + } + return [NSColor colorWithRed:r green:g blue:b alpha:1.0]; +} + +-(void)drawKnob:(NSRect)knobRect +{ + [super drawKnob:knobRect]; + NSRect peekRect = knobRect; + peekRect.size.width /= 2; + peekRect.size.height = peekRect.size.width; + peekRect.origin.x += peekRect.size.width / 2; + peekRect.origin.y += peekRect.size.height / 2; + NSColor *color = self.colorValue; + if (!self.enabled) { + color = [color colorWithAlphaComponent:0.5]; + } + [color setFill]; + NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect]; + [path fill]; + [[NSColor colorWithWhite:0 alpha:0.25] setStroke]; + [path setLineWidth:0.5]; + [path stroke]; +} + +-(double)_normalizedDoubleValue +{ + if (_drawingTrack) return 0; + return [super _normalizedDoubleValue]; +} + +-(void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped +{ + if (!self.enabled) { + [super drawBarInside:rect flipped:flipped]; + return; + } + + _drawingTrack = true; + [super drawBarInside:rect flipped:flipped]; + _drawingTrack = false; + + NSGradient *gradient = [[NSGradient alloc] initWithColors:@[ + [NSColor redColor], + [NSColor yellowColor], + [NSColor greenColor], + [NSColor cyanColor], + [NSColor blueColor], + [NSColor magentaColor], + [NSColor redColor], + ]]; + + rect.origin.y += rect.size.height / 2 - 0.5; + rect.size.height = 1; + rect.size.width -= 2; + rect.origin.x += 1; + [[NSColor redColor] set]; + NSRectFill(rect); + + rect.size.width -= self.knobThickness + 2; + rect.origin.x += self.knobThickness / 2 - 1; + + [gradient drawInRect:rect angle:0]; +} + +@end + +@implementation NSSlider (GBHueSlider) +- (NSColor *)colorValue +{ + return ((GBHueSliderCell *)self.cell).colorValue; +} +@end diff --git a/thirdparty/SameBoy/Cocoa/GBImageCell.h b/thirdparty/SameBoy/Cocoa/GBImageCell.h deleted file mode 100644 index 0323b41dc..000000000 --- a/thirdparty/SameBoy/Cocoa/GBImageCell.h +++ /dev/null @@ -1,5 +0,0 @@ -#import - -@interface GBImageCell : NSImageCell - -@end diff --git a/thirdparty/SameBoy/Cocoa/GBImageCell.m b/thirdparty/SameBoy/Cocoa/GBImageCell.m deleted file mode 100644 index de75e0e98..000000000 --- a/thirdparty/SameBoy/Cocoa/GBImageCell.m +++ /dev/null @@ -1,10 +0,0 @@ -#import "GBImageCell.h" - -@implementation GBImageCell -- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView -{ - CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; - CGContextSetInterpolationQuality(context, kCGInterpolationNone); - [super drawWithFrame:cellFrame inView:controlView]; -} -@end diff --git a/thirdparty/SameBoy/Cocoa/GBImageView.m b/thirdparty/SameBoy/Cocoa/GBImageView.m index 3525e72ec..c496d9667 100644 --- a/thirdparty/SameBoy/Cocoa/GBImageView.m +++ b/thirdparty/SameBoy/Cocoa/GBImageView.m @@ -10,18 +10,18 @@ - (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size } @end -@implementation GBImageView -{ - NSTrackingArea *trackingArea; -} +@interface GBGridView : NSView +@end + +@implementation GBGridView + - (void)drawRect:(NSRect)dirtyRect { - CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; - CGContextSetInterpolationQuality(context, kCGInterpolationNone); - [super drawRect:dirtyRect]; - CGFloat y_ratio = self.frame.size.height / self.image.size.height; - CGFloat x_ratio = self.frame.size.width / self.image.size.width; - for (GBImageViewGridConfiguration *conf in self.verticalGrids) { + GBImageView *parent = (GBImageView *)self.superview; + + CGFloat y_ratio = parent.frame.size.height / parent.image.size.height; + CGFloat x_ratio = parent.frame.size.width / parent.image.size.width; + for (GBImageViewGridConfiguration *conf in parent.verticalGrids) { [conf.color set]; for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) { NSBezierPath *line = [NSBezierPath bezierPath]; @@ -32,7 +32,7 @@ - (void)drawRect:(NSRect)dirtyRect } } - for (GBImageViewGridConfiguration *conf in self.horizontalGrids) { + for (GBImageViewGridConfiguration *conf in parent.horizontalGrids) { [conf.color set]; for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) { NSBezierPath *line = [NSBezierPath bezierPath]; @@ -43,11 +43,11 @@ - (void)drawRect:(NSRect)dirtyRect } } - if (self.displayScrollRect) { + if (parent.displayScrollRect) { NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite]; for (unsigned x = 0; x < 2; x++) { for (unsigned y = 0; y < 2; y++) { - NSRect rect = self.scrollRect; + NSRect rect = parent.scrollRect; rect.origin.x *= x_ratio; rect.origin.y *= y_ratio; rect.size.width *= x_ratio; @@ -56,7 +56,7 @@ - (void)drawRect:(NSRect)dirtyRect rect.origin.x -= self.frame.size.width * x; rect.origin.y += self.frame.size.height * y; - + NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect]; [path appendBezierPath:subpath]; @@ -72,36 +72,62 @@ - (void)drawRect:(NSRect)dirtyRect [path stroke]; } } +@end + +@implementation GBImageView +{ + NSTrackingArea *_trackingArea; + GBGridView *_gridView; + NSRect _scrollRect; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + self.wantsLayer = true; + _gridView = [[GBGridView alloc] initWithFrame:self.bounds]; + _gridView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [self addSubview:_gridView]; + return self; +} + +-(void)viewWillDraw +{ + [super viewWillDraw]; + for (CALayer *layer in self.layer.sublayers) { + layer.magnificationFilter = kCAFilterNearest; + } +} - (void)setHorizontalGrids:(NSArray *)horizontalGrids { self->_horizontalGrids = horizontalGrids; - [self setNeedsDisplay]; + [_gridView setNeedsDisplay:true]; } - (void)setVerticalGrids:(NSArray *)verticalGrids { self->_verticalGrids = verticalGrids; - [self setNeedsDisplay]; + [_gridView setNeedsDisplay:true]; } - (void)setDisplayScrollRect:(bool)displayScrollRect { self->_displayScrollRect = displayScrollRect; - [self setNeedsDisplay]; + [_gridView setNeedsDisplay:true]; } - (void)updateTrackingAreas { - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; + if (_trackingArea != nil) { + [self removeTrackingArea:_trackingArea]; } - trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + _trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved owner:self userInfo:nil]; - [self addTrackingArea:trackingArea]; + [self addTrackingArea:_trackingArea]; } - (void)mouseExited:(NSEvent *)theEvent @@ -124,4 +150,17 @@ - (void)mouseMoved:(NSEvent *)theEvent } } +- (void)setScrollRect:(NSRect)scrollRect +{ + if (memcmp(&scrollRect, &_scrollRect, sizeof(scrollRect)) != 0) { + _scrollRect = scrollRect; + [_gridView setNeedsDisplay:true]; + } +} + +- (NSRect)scrollRect +{ + return _scrollRect; +} + @end diff --git a/thirdparty/SameBoy/Cocoa/GBJoyConManager.h b/thirdparty/SameBoy/Cocoa/GBJoyConManager.h new file mode 100644 index 000000000..f016c3d24 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBJoyConManager.h @@ -0,0 +1,13 @@ +#import +#import +#import + +@interface GBJoyConManager : NSObject ++ (instancetype) sharedInstance; + +@property (nonatomic) bool arrangementMode; +@property (weak) IBOutlet NSTableView *tableView; +@property (nonatomic) IBOutlet NSButton *autoPairCheckbox; +@property (nonatomic) IBOutlet NSButton *horizontalCheckbox; +@end + diff --git a/thirdparty/SameBoy/Cocoa/GBJoyConManager.m b/thirdparty/SameBoy/Cocoa/GBJoyConManager.m new file mode 100644 index 000000000..fe0060207 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBJoyConManager.m @@ -0,0 +1,323 @@ +#import "GBJoyConManager.h" +#import "GBTintedImageCell.h" +#import + +@implementation GBJoyConManager +{ + GBTintedImageCell *_tintedImageCell; + NSImageCell *_imageCell; + NSMutableDictionary *_pairings; + NSMutableDictionary *_gripSettings; + NSButton *_autoPairCheckbox; + bool _unpairing; +} + ++ (instancetype)sharedInstance +{ + static GBJoyConManager *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[self alloc] _init]; + }); + return manager; +} + +- (NSArray *)joycons +{ + NSMutableArray *ret = [[JOYController allControllers] mutableCopy]; + for (JOYController *controller in [JOYController allControllers]) { + if (controller.joyconType == JOYJoyConTypeNone) { + [ret removeObject:controller]; + } + } + return ret; +} + +- (instancetype)init +{ + return [self.class sharedInstance]; +} + +- (instancetype) _init +{ + self = [super init]; + _imageCell = [[NSImageCell alloc] init]; + _tintedImageCell = [[GBTintedImageCell alloc] init]; + if (@available(macOS 10.14, *)) { + _tintedImageCell.tint = [NSColor controlAccentColor]; + } + else { + _tintedImageCell.tint = [NSColor selectedMenuItemColor]; + } + _pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy]; + _gripSettings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConGrips"] ?: @{} mutableCopy]; + + // Sanity check the pairings + for (NSString *key in _pairings) { + if (![_pairings[_pairings[key]] isEqualToString:key]) { + [_pairings removeAllObjects]; + break; + } + } + + [JOYController registerListener:self]; + for (JOYController *controller in [JOYController allControllers]) { + [self controllerConnected:controller]; + } + + return self; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.joycons.count; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + if (row >= [self numberOfRowsInTableView:tableView]) return nil; + + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + JOYController *controller = self.joycons[row]; + switch (columnIndex) { + case 0: { + switch (controller.joyconType) { + case JOYJoyConTypeNone: + return nil; + case JOYJoyConTypeLeft: + return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConLeftTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]]; + case JOYJoyConTypeRight: + return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConRightTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]]; + case JOYJoyConTypeDual: + return [NSImage imageNamed:@"JoyConDualTemplate"]; + } + } + case 1: { + NSMutableAttributedString *ret = [[NSMutableAttributedString alloc] initWithString:controller.deviceName + attributes:@{NSFontAttributeName: + [NSFont systemFontOfSize:[NSFont systemFontSize]]}]; + + [ret appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:controller.uniqueID] + attributes:@{NSFontAttributeName: + [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], + NSForegroundColorAttributeName:[NSColor disabledControlTextColor]}]]; + return ret; + } + case 2: + return @([(_gripSettings[controller.uniqueID] ?: @(-1)) unsignedIntValue] + 1); + } + return nil; +} + +- (void)updateGripForController:(JOYController *)controller +{ + NSNumber *grip = _gripSettings[controller.uniqueID]; + if (!grip) { + controller.usesHorizontalJoyConGrip = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"]; + return; + } + controller.usesHorizontalJoyConGrip = [grip unsignedIntValue] == 1; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (columnIndex != 2) return; + if (row >= [self numberOfRowsInTableView:tableView]) return; + JOYController *controller = self.joycons[row]; + if (controller.joyconType == JOYJoyConTypeDual) { + return; + } + switch ([object unsignedIntValue]) { + case 0: + [_gripSettings removeObjectForKey:controller.uniqueID]; + break; + case 1: + _gripSettings[controller.uniqueID] = @(0); + break; + case 2: + _gripSettings[controller.uniqueID] = @(1); + break; + } + [[NSUserDefaults standardUserDefaults] setObject:_gripSettings forKey:@"GBJoyConGrips"]; + [self updateGripForController:controller]; +} + +- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init]; + + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (columnIndex == 2) { + JOYCombinedController *controller = (JOYCombinedController *)self.joycons[row]; + if (controller.joyconType == JOYJoyConTypeDual) { + NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Separate Joy-Cons"]; + cell.bezelStyle = NSBezelStyleRounded; + cell.action = @selector(invoke); + id block = ^(void) { + dispatch_async(dispatch_get_main_queue(), ^{ + for (JOYController *child in controller.children) { + [_pairings removeObjectForKey:child.uniqueID]; + } + [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"]; + _unpairing = true; + [controller breakApart]; + _unpairing = false; + }); + }; + // To retain the block + objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN); + cell.target = block; + return cell; + } + } + if (columnIndex == 0) { + JOYController *controller = self.joycons[row]; + for (JOYButton *button in controller.buttons) { + if (button.isPressed) { + return _tintedImageCell; + } + } + return _imageCell; + } + return nil; +} + +- (void)controllerConnected:(JOYController *)controller +{ + [self updateGripForController:controller]; + for (JOYController *partner in [JOYController allControllers]) { + if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) { + [self pairJoyCon:controller withJoyCon:partner]; + break; + } + } + if (controller.joyconType == JOYJoyConTypeLeft || controller.joyconType == JOYJoyConTypeRight) { + [self autopair]; + } + if (_arrangementMode) { + [self.tableView reloadData]; + } +} + +- (void)autopair +{ + if (_unpairing) return; + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]) return; + NSArray *controllers = [[JOYController allControllers] copy]; + for (JOYController *first in controllers) { + if (_pairings[first.uniqueID]) continue; // Has an established partner + if (first.joyconType != JOYJoyConTypeLeft) continue; + for (JOYController *second in controllers) { + if (_pairings[second.uniqueID]) continue; // Has an established partner + if (second.joyconType != JOYJoyConTypeRight) continue; + [self pairJoyCon:first withJoyCon:second]; + break; + } + } + if (_arrangementMode) { + [self.tableView reloadData]; + } +} + +- (void)controllerDisconnected:(JOYController *)controller +{ + if (_arrangementMode) { + [self.tableView reloadData]; + } +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + return columnIndex == 2; +} + +- (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second +{ + if (first.joyconType != JOYJoyConTypeLeft && first.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con + if (second.joyconType != JOYJoyConTypeLeft && second.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con + if (first.joyconType == second.joyconType) return nil; // Not a sensible pair + + _pairings[first.uniqueID] = second.uniqueID; + _pairings[second.uniqueID] = first.uniqueID; + first.usesHorizontalJoyConGrip = false; + second.usesHorizontalJoyConGrip = false; + [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"]; + return [[JOYCombinedController alloc] initWithChildren:@[first, second]]; +} + +- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button +{ + if (!_arrangementMode) return; + if (controller.joyconType == JOYJoyConTypeNone) return; + [self.tableView setNeedsDisplay:true]; + if (controller.joyconType != JOYJoyConTypeLeft && controller.joyconType != JOYJoyConTypeRight) return; + if (button.usage != JOYButtonUsageL1 && button.usage != JOYButtonUsageR1) return; + + + // L or R were pressed on a single Joy-Con, try and pair available Joy-Cons + JOYController *left = nil; + JOYController *right = nil; + for (JOYController *controller in [JOYController allControllers]) { + if (!left && controller.joyconType == JOYJoyConTypeLeft) { + for (JOYButton *button in controller.buttons) { + if (button.usage == JOYButtonUsageL1 && button.isPressed) { + left = controller; + break; + } + } + } + if (!right && controller.joyconType == JOYJoyConTypeRight) { + for (JOYButton *button in controller.buttons) { + if (button.usage == JOYButtonUsageR1 && button.isPressed) { + right = controller; + break; + } + } + } + if (left && right) { + [self pairJoyCon:left withJoyCon:right]; + return; + } + } +} + +- (void)setAutoPairCheckbox:(NSButton *)autoPairCheckbox +{ + _autoPairCheckbox = autoPairCheckbox; + [_autoPairCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]]; +} + +- (IBAction)toggleAutoPair:(NSButton *)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConAutoPair"]; + [self autopair]; +} + +- (void)setHorizontalCheckbox:(NSButton *)horizontalCheckbox +{ + _horizontalCheckbox = horizontalCheckbox; + [_horizontalCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"]]; +} + +- (IBAction)toggleHorizontalDefault:(NSButton *)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConsDefaultsToHorizontal"]; + for (JOYController *controller in self.joycons) { + [self updateGripForController:controller]; + } + if (_arrangementMode) { + [self.tableView reloadData]; + } +} + +- (void)setArrangementMode:(bool)arrangementMode +{ + _arrangementMode = arrangementMode; + if (arrangementMode) { + [self.tableView reloadData]; + } +} + +@end diff --git a/thirdparty/SameBoy/Cocoa/GBMemoryByteArray.m b/thirdparty/SameBoy/Cocoa/GBMemoryByteArray.m index 32526ade2..55e1bd152 100644 --- a/thirdparty/SameBoy/Cocoa/GBMemoryByteArray.m +++ b/thirdparty/SameBoy/Cocoa/GBMemoryByteArray.m @@ -1,4 +1,3 @@ -#define GB_INTERNAL // Todo: Some memory accesses are being done using the struct directly #import "GBMemoryByteArray.h" #import "GBCompleteByteSlice.h" @@ -32,69 +31,110 @@ - (unsigned long long)length } } +- (uint16_t)base +{ + switch (_mode) { + case GBMemoryEntireSpace: return 0; + case GBMemoryROM: return 0; + case GBMemoryVRAM: return 0x8000; + case GBMemoryExternalRAM: return 0xA000; + case GBMemoryRAM: return 0xC000; + } +} + - (void)copyBytes:(unsigned char *)dst range:(HFRange)range { - __block uint16_t addr = (uint16_t) range.location; - __block unsigned long long length = range.length; - if (_mode == GBMemoryEntireSpace) { - while (length) { - *(dst++) = [_document readMemory:addr++]; - length--; - } + // Do everything in 0x1000 chunks, never cross a 0x1000 boundary + if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) { + size_t partial = 0x1000 - (range.location & 0xFFF); + [self copyBytes:dst + partial range:HFRangeMake(range.location + partial, range.length - partial)]; + range.length = partial; } - else { - [_document performAtomicBlock:^{ - unsigned char *_dst = dst; - uint16_t bank_backup = 0; - GB_gameboy_t *gb = _document.gameboy; - switch (_mode) { - case GBMemoryROM: - bank_backup = gb->mbc_rom_bank; - gb->mbc_rom_bank = self.selectedBank; - break; - case GBMemoryVRAM: - bank_backup = gb->cgb_vram_bank; - if (GB_is_cgb(gb)) { - gb->cgb_vram_bank = self.selectedBank; - } - addr += 0x8000; - break; - case GBMemoryExternalRAM: - bank_backup = gb->mbc_ram_bank; - gb->mbc_ram_bank = self.selectedBank; - addr += 0xA000; - break; - case GBMemoryRAM: - bank_backup = gb->cgb_ram_bank; - if (GB_is_cgb(gb)) { - gb->cgb_ram_bank = self.selectedBank; + range.location += self.base; + + GB_gameboy_t *gb = _document.gameboy; + + switch (range.location >> 12) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: { + uint16_t bank; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM0, NULL, &bank); + memcpy(dst, data + bank * 0x4000 + range.location, range.length); + break; + } + case 0x4: + case 0x5: + case 0x6: + case 0x7: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x4000 - 1); + } + memcpy(dst, data + bank * 0x4000 + range.location - 0x4000, range.length); + break; + } + case 0x8: + case 0x9: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x2000 - 1); + } + memcpy(dst, data + bank * 0x2000 + range.location - 0x8000, range.length); + break; + } + case 0xA: + case 0xB: { + // Some carts are special, use memory read directly in full mem mode + if (_mode == GBMemoryEntireSpace) { + case 0xF: + slow_path: + [_document performAtomicBlock:^{ + for (unsigned i = 0; i < range.length; i++) { + dst[i] = GB_safe_read_memory(gb, range.location + i); } - addr += 0xC000; - break; - default: - assert(false); + }]; + break; } - while (length) { - *(_dst++) = [_document readMemory:addr++]; - length--; + else { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + bank = self.selectedBank & (size / 0x2000 - 1); + if (size == 0) { + memset(dst, 0xFF, range.length); + } + else if (range.location + range.length - 0xA000 > size) { + goto slow_path; + } + else { + memcpy(dst, data + bank * 0x2000 + range.location - 0xA000, range.length); + } + break; } - switch (_mode) { - case GBMemoryROM: - gb->mbc_rom_bank = bank_backup; - break; - case GBMemoryVRAM: - gb->cgb_vram_bank = bank_backup; - break; - case GBMemoryExternalRAM: - gb->mbc_ram_bank = bank_backup; - break; - case GBMemoryRAM: - gb->cgb_ram_bank = bank_backup; - break; - default: - assert(false); + } + case 0xC: + case 0xE: { + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL); + memcpy(dst, data + (range.location & 0xFFF), range.length); + break; + } + + case 0xD: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x1000 - 1); } - }]; + memcpy(dst, data + bank * 0x1000 + range.location - 0xD000, range.length); + break; + } } } @@ -113,65 +153,104 @@ - (HFByteArray *)subarrayWithRange:(HFRange)range return ret; } -- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange +- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)range { - if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */ - [_document performAtomicBlock:^{ - uint16_t addr = (uint16_t) lrange.location; - uint16_t bank_backup = 0; - GB_gameboy_t *gb = _document.gameboy; - switch (_mode) { - case GBMemoryROM: - bank_backup = gb->mbc_rom_bank; - gb->mbc_rom_bank = self.selectedBank; + if (slice.length != range.length) return; /* Insertion is not allowed, only overwriting. */ + // Do everything in 0x1000 chunks, never cross a 0x1000 boundary + if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) { + size_t partial = 0x1000 - (range.location & 0xFFF); + if (slice.length - partial) { + [self insertByteSlice:[slice subsliceWithRange:HFRangeMake(partial, slice.length - partial)] + inRange:HFRangeMake(range.location + partial, range.length - partial)]; + } + range.length = partial; + } + range.location += self.base; + + GB_gameboy_t *gb = _document.gameboy; + + + switch (range.location >> 12) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: { + return; // ROM not writeable + } + case 0x8: + case 0x9: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x2000 - 1); + } + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + bank * 0x2000 + range.location - 0x8000, sliceData, range.length); + break; + } + case 0xA: + case 0xB: { + // Some carts are special, use memory write directly in full mem mode + if (_mode == GBMemoryEntireSpace) { + case 0xF: + slow_path: + [_document performAtomicBlock:^{ + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + for (unsigned i = 0; i < range.length; i++) { + GB_write_memory(gb, range.location + i, sliceData[i]); + } + }]; break; - case GBMemoryVRAM: - bank_backup = gb->cgb_vram_bank; - if (GB_is_cgb(gb)) { - gb->cgb_vram_bank = self.selectedBank; + } + else { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank); + bank = self.selectedBank & (size / 0x2000 - 1); + if (size == 0) { + // Nothing to write to } - addr += 0x8000; - break; - case GBMemoryExternalRAM: - bank_backup = gb->mbc_ram_bank; - gb->mbc_ram_bank = self.selectedBank; - addr += 0xA000; - break; - case GBMemoryRAM: - bank_backup = gb->cgb_ram_bank; - if (GB_is_cgb(gb)) { - gb->cgb_ram_bank = self.selectedBank; + else if (range.location + range.length - 0xA000 > size) { + goto slow_path; + } + else { + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + bank * 0x2000 + range.location - 0xA000, sliceData, range.length); } - addr += 0xC000; - break; - default: break; + } } - uint8_t values[lrange.length]; - [slice copyBytes:values range:HFRangeMake(0, lrange.length)]; - uint8_t *src = values; - unsigned long long length = lrange.length; - while (length) { - [_document writeMemory:addr++ value:*(src++)]; - length--; + case 0xC: + case 0xE: { + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL); + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + memcpy(data + (range.location & 0xFFF), sliceData, range.length); + break; } - switch (_mode) { - case GBMemoryROM: - gb->mbc_rom_bank = bank_backup; - break; - case GBMemoryVRAM: - gb->cgb_vram_bank = bank_backup; - break; - case GBMemoryExternalRAM: - gb->mbc_ram_bank = bank_backup; - break; - case GBMemoryRAM: - gb->cgb_ram_bank = bank_backup; - break; - default: - break; + + case 0xD: { + uint16_t bank; + size_t size; + uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank); + if (_mode != GBMemoryEntireSpace) { + bank = self.selectedBank & (size / 0x1000 - 1); + } + uint8_t sliceData[range.length]; + [slice copyBytes:sliceData range:HFRangeMake(0, range.length)]; + + memcpy(data + bank * 0x1000 + range.location - 0xD000, sliceData, range.length); + break; } - }]; + } } @end diff --git a/thirdparty/SameBoy/Cocoa/GBOSDView.h b/thirdparty/SameBoy/Cocoa/GBOSDView.h new file mode 100644 index 000000000..4771d2fd6 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBOSDView.h @@ -0,0 +1,6 @@ +#import + +@interface GBOSDView : NSView +@property bool usesSGBScale; +- (void)displayText:(NSString *)text; +@end diff --git a/thirdparty/SameBoy/Cocoa/GBOSDView.m b/thirdparty/SameBoy/Cocoa/GBOSDView.m new file mode 100644 index 000000000..710229ecd --- /dev/null +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Cocoa/GBObjectView.h b/thirdparty/SameBoy/Cocoa/GBObjectView.h new file mode 100644 index 000000000..2d1c955de --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBObjectView.h @@ -0,0 +1,6 @@ +#import +#import "Document.h" + +@interface GBObjectView : NSView +- (void)reloadData:(Document *)document; +@end diff --git a/thirdparty/SameBoy/Cocoa/GBObjectView.m b/thirdparty/SameBoy/Cocoa/GBObjectView.m new file mode 100644 index 000000000..0fb22a158 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBObjectView.m @@ -0,0 +1,124 @@ +#import "GBObjectView.h" + +@interface GBObjectViewItem : NSObject +@property IBOutlet NSView *view; +@property IBOutlet NSImageView *image; +@property IBOutlet NSTextField *oamAddress; +@property IBOutlet NSTextField *position; +@property IBOutlet NSTextField *attributes; +@property IBOutlet NSTextField *tile; +@property IBOutlet NSTextField *tileAddress; +@property IBOutlet NSImageView *warningIcon; +@property IBOutlet NSBox *verticalLine; +@end + +@implementation GBObjectViewItem +{ + @public + uint32_t _lastImageData[128]; + uint8_t _lastHeight; +} +@end + +@implementation GBObjectView +{ + NSMutableArray *_items; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + _items = [NSMutableArray array]; + CGFloat height = self.frame.size.height; + for (unsigned i = 0; i < 40; i++) { + GBObjectViewItem *item = [[GBObjectViewItem alloc] init]; + [_items addObject:item]; + [[NSBundle mainBundle] loadNibNamed:@"GBObjectViewItem" owner:item topLevelObjects:nil]; + item.view.hidden = true; + [self addSubview:item.view]; + [item.view setFrameOrigin:NSMakePoint((i % 4) * 120, height - (i / 4 * 68) - 68)]; + item.oamAddress.toolTip = @"OAM address"; + item.position.toolTip = @"Position"; + item.attributes.toolTip = @"Attributes"; + item.tile.toolTip = @"Tile index"; + item.tileAddress.toolTip = @"Tile address"; + item.warningIcon.toolTip = @"Dropped: too many objects in line"; + if ((i % 4) == 3) { + [item.verticalLine removeFromSuperview]; + } + item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin; + } + return self; +} + +- (void)reloadData:(Document *)document +{ + GB_oam_info_t *info = document.oamInfo; + uint8_t length = document.oamCount; + bool cgb = GB_is_cgb(document.gb); + uint8_t height = document.oamHeight; + for (unsigned i = 0; i < 40; i++) { + GBObjectViewItem *item = _items[i]; + if (i >= length) { + item.view.hidden = true; + } + else { + item.view.hidden = false; + item.oamAddress.stringValue = [NSString stringWithFormat:@"$%04X", info[i].oam_addr]; + item.position.stringValue = [NSString stringWithFormat:@"(%d, %d)", + ((signed)(unsigned)info[i].x) - 8, + ((signed)(unsigned)info[i].y) - 16]; + item.tile.stringValue = [NSString stringWithFormat:@"$%02X", info[i].tile]; + item.tileAddress.stringValue = [NSString stringWithFormat:@"$%04X", 0x8000 + info[i].tile * 0x10]; + item.warningIcon.hidden = !info[i].obscured_by_line_limit; + if (cgb) { + item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d%d", + info[i].flags & 0x80? 'P' : '-', + info[i].flags & 0x40? 'Y' : '-', + info[i].flags & 0x20? 'X' : '-', + info[i].flags & 0x08? 1 : 0, + info[i].flags & 0x07]; + } + else { + item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d", + info[i].flags & 0x80? 'P' : '-', + info[i].flags & 0x40? 'Y' : '-', + info[i].flags & 0x20? 'X' : '-', + info[i].flags & 0x10? 1 : 0]; + } + size_t imageSize = 8 * 4 * height; + if (height == item->_lastHeight && memcmp(item->_lastImageData, info[i].image, imageSize) == 0) { + continue; + } + memcpy(item->_lastImageData, info[i].image, imageSize); + item->_lastHeight = height; + item.image.image = [Document imageFromData:[NSData dataWithBytesNoCopy:info[i].image + length:64 * 4 * 2 + freeWhenDone:false] + width:8 + height:height + scale:32.0 / height]; + } + } + + NSRect frame = self.frame; + CGFloat newHeight = MAX(68 * ((length + 3) / 4), self.superview.frame.size.height); + frame.origin.y -= newHeight - frame.size.height; + frame.size.height = newHeight; + self.frame = frame; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].lastObject setFill]; + } + else { + [[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill]; + } + NSRect frame = self.frame; + for (unsigned i = 1; i <= 5; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 68 * 2, frame.size.width, 68)); + } +} +@end diff --git a/thirdparty/SameBoy/Cocoa/GBObjectViewItem.xib b/thirdparty/SameBoy/Cocoa/GBObjectViewItem.xib new file mode 100644 index 000000000..85b37820e --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBObjectViewItem.xib @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/GBOpenGLView.m b/thirdparty/SameBoy/Cocoa/GBOpenGLView.m index 90ebf8d57..2e4eb70f6 100644 --- a/thirdparty/SameBoy/Cocoa/GBOpenGLView.m +++ b/thirdparty/SameBoy/Cocoa/GBOpenGLView.m @@ -34,6 +34,6 @@ - (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat - (void) filterChanged { self.shader = nil; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } @end diff --git a/thirdparty/SameBoy/Cocoa/GBPaletteEditorController.h b/thirdparty/SameBoy/Cocoa/GBPaletteEditorController.h new file mode 100644 index 000000000..fd362ec14 --- /dev/null +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Cocoa/GBPaletteEditorController.m b/thirdparty/SameBoy/Cocoa/GBPaletteEditorController.m new file mode 100644 index 000000000..0a613fba4 --- /dev/null +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Cocoa/GBPaletteView.h b/thirdparty/SameBoy/Cocoa/GBPaletteView.h new file mode 100644 index 000000000..d92cb5f17 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBPaletteView.h @@ -0,0 +1,6 @@ +#import +#import "Document.h" + +@interface GBPaletteView : NSView +- (void)reloadData:(Document *)document; +@end diff --git a/thirdparty/SameBoy/Cocoa/GBPaletteView.m b/thirdparty/SameBoy/Cocoa/GBPaletteView.m new file mode 100644 index 000000000..6aeddc05c --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBPaletteView.m @@ -0,0 +1,91 @@ +#import "GBPaletteView.h" + +@interface GBPaletteViewItem : NSObject +@property IBOutlet NSView *view; +@property (strong) IBOutlet NSTextField *label; +@property (strong) IBOutlet NSTextField *color0; +@property (strong) IBOutlet NSTextField *color1; +@property (strong) IBOutlet NSTextField *color2; +@property (strong) IBOutlet NSTextField *color3; +@end + +@implementation GBPaletteViewItem +@end + +@implementation GBPaletteView +{ + NSMutableArray *_colors; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + _colors = [NSMutableArray array]; + CGFloat height = self.frame.size.height; + for (unsigned i = 0; i < 16; i++) { + GBPaletteViewItem *item = [[GBPaletteViewItem alloc] init]; + [[NSBundle mainBundle] loadNibNamed:@"GBPaletteViewRow" owner:item topLevelObjects:nil]; + [self addSubview:item.view]; + [item.view setFrameOrigin:NSMakePoint(0, height - (i * 24) - 24)]; + item.label.stringValue = [NSString stringWithFormat:@"%@ %d", i < 8? @"Background" : @"Object", i % 8]; + item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin; + [_colors addObject:item.color0]; + [_colors addObject:item.color1]; + [_colors addObject:item.color2]; + [_colors addObject:item.color3]; + + } + return self; +} + +- (void)reloadData:(Document *)document +{ + GB_gameboy_t *gb = document.gb; + uint8_t *bg = GB_get_direct_access(gb, GB_DIRECT_ACCESS_BGP, NULL, NULL); + uint8_t *obj = GB_get_direct_access(gb, GB_DIRECT_ACCESS_OBP, NULL, NULL); + + for (unsigned i = 0; i < 4 * 8 * 2; i++) { + uint8_t index = i % (4 * 8); + uint8_t *palette = i >= 4 * 8 ? obj : bg; + uint16_t color = (palette[(index << 1) + 1] << 8) | palette[(index << 1)]; + uint32_t nativeColor = GB_convert_rgb15(gb, color, false); + + uint8_t r = color & 0x1F, + g = (color >> 5) & 0x1F, + b = (color >> 10) & 0x1F; + + NSTextField *field = _colors[i]; + field.stringValue = [NSString stringWithFormat:@"$%04X", color]; + field.textColor = r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor]; + field.toolTip = [NSString stringWithFormat:@"Red: %d, Green: %d, Blue: %d", r, g, b]; + field.backgroundColor = [NSColor colorWithRed:(nativeColor & 0xFF) / 255.0 + green:((nativeColor >> 8) & 0xFF) / 255.0 + blue:((nativeColor >> 16) & 0xFF) / 255.0 + alpha:1.0]; + } +} + +- (void)drawRect:(NSRect)dirtyRect +{ + NSRect frame = self.frame; + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].lastObject setFill]; + } + else { + [[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill]; + } + for (unsigned i = 1; i <= 8; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2, frame.size.width, 24)); + } + + if (@available(macOS 10.14, *)) { + [[NSColor alternatingContentBackgroundColors].firstObject setFill]; + } + else { + [[NSColor controlBackgroundColor] setFill]; + } + for (unsigned i = 0; i < 8; i++) { + NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2 - 24, frame.size.width, 24)); + } +} +@end diff --git a/thirdparty/SameBoy/Cocoa/GBPaletteViewRow.xib b/thirdparty/SameBoy/Cocoa/GBPaletteViewRow.xib new file mode 100644 index 000000000..950b199a7 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBPaletteViewRow.xib @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.h b/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.h index b25e47673..15afcebfb 100644 --- a/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.h +++ b/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.h @@ -1,29 +1,42 @@ #import #import +#import "GBPaletteEditorController.h" @interface GBPreferencesWindow : NSWindow -@property (nonatomic, strong) IBOutlet NSTableView *controlsTableView; -@property (nonatomic, strong) IBOutlet NSPopUpButton *graphicsFilterPopupButton; -@property (nonatomic, strong) IBOutlet NSButton *analogControlsCheckbox; -@property (nonatomic, strong) IBOutlet NSButton *aspectRatioCheckbox; -@property (nonatomic, strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton; -@property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton; -@property (nonatomic, strong) IBOutlet NSButton *skipButton; -@property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem; -@property (nonatomic, strong) IBOutlet NSPopUpButtonCell *bootROMsButton; -@property (nonatomic, strong) IBOutlet NSPopUpButton *rumbleModePopupButton; -@property (nonatomic, weak) IBOutlet NSSlider *temperatureSlider; -@property (nonatomic, weak) IBOutlet NSSlider *interferenceSlider; -@property (nonatomic, weak) IBOutlet NSPopUpButton *dmgPopupButton; -@property (nonatomic, weak) IBOutlet NSPopUpButton *sgbPopupButton; -@property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton; -@property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton; -@property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; - +@property IBOutlet NSTableView *controlsTableView; +@property IBOutlet NSPopUpButton *graphicsFilterPopupButton; +@property IBOutlet NSButton *analogControlsCheckbox; +@property IBOutlet NSButton *controllersFocusCheckbox; +@property IBOutlet NSButton *aspectRatioCheckbox; +@property IBOutlet NSPopUpButton *highpassFilterPopupButton; +@property IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property IBOutlet NSPopUpButton *frameBlendingModePopupButton; +@property IBOutlet NSPopUpButton *colorPalettePopupButton; +@property IBOutlet NSPopUpButton *displayBorderPopupButton; +@property IBOutlet NSPopUpButton *rewindPopupButton; +@property IBOutlet NSPopUpButton *rtcPopupButton; +@property IBOutlet NSButton *configureJoypadButton; +@property IBOutlet NSButton *skipButton; +@property IBOutlet NSMenuItem *bootROMsFolderItem; +@property IBOutlet NSPopUpButtonCell *bootROMsButton; +@property IBOutlet NSPopUpButton *rumbleModePopupButton; +@property IBOutlet NSPopUpButton *hotkey1PopupButton; +@property IBOutlet NSPopUpButton *hotkey2PopupButton; +@property IBOutlet NSSlider *temperatureSlider; +@property IBOutlet NSSlider *interferenceSlider; +@property IBOutlet NSPopUpButton *dmgPopupButton; +@property IBOutlet NSPopUpButton *sgbPopupButton; +@property IBOutlet NSPopUpButton *cgbPopupButton; +@property IBOutlet NSPopUpButton *agbPopupButton; +@property IBOutlet NSPopUpButton *preferredJoypadButton; +@property IBOutlet NSPopUpButton *playerListButton; +@property IBOutlet NSButton *autoUpdatesCheckbox; +@property IBOutlet NSSlider *volumeSlider; +@property IBOutlet NSButton *OSDCheckbox; +@property IBOutlet NSButton *screenshotFilterCheckbox; +@property IBOutlet GBPaletteEditorController *paletteEditorController; +@property IBOutlet NSWindow *paletteEditor; +@property IBOutlet NSButton *joystickMBC7Checkbox; +@property IBOutlet NSButton *mouseMBC7Checkbox; +@property IBOutlet NSWindow *joyconsSheet; @end diff --git a/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.m b/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.m index 54d190fb9..c0efc3827 100644 --- a/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.m +++ b/thirdparty/SameBoy/Cocoa/GBPreferencesWindow.m @@ -1,7 +1,10 @@ #import "GBPreferencesWindow.h" +#import "GBJoyConManager.h" #import "NSString+StringForKey.h" #import "GBButtons.h" #import "BigSurToolbar.h" +#import "GBViewMetal.h" +#import "GBWarningPopover.h" #import @implementation GBPreferencesWindow @@ -22,13 +25,22 @@ @implementation GBPreferencesWindow NSPopUpButton *_rtcPopupButton; NSButton *_aspectRatioCheckbox; NSButton *_analogControlsCheckbox; + NSButton *_controllersFocusCheckbox; NSEventModifierFlags previousModifiers; - NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; + NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton, *_agbPopupButton; NSPopUpButton *_preferredJoypadButton; NSPopUpButton *_rumbleModePopupButton; + NSPopUpButton *_hotkey1PopupButton; + NSPopUpButton *_hotkey2PopupButton; NSSlider *_temperatureSlider; NSSlider *_interferenceSlider; + NSSlider *_volumeSlider; + NSButton *_autoUpdatesCheckbox; + NSButton *_OSDCheckbox; + NSButton *_screenshotFilterCheckbox; + NSButton *_joystickMBC7Checkbox; + NSButton *_mouseMBC7Checkbox; } + (NSArray *)filterList @@ -58,14 +70,14 @@ + (NSArray *)filterList - (NSWindowToolbarStyle)toolbarStyle { - return NSWindowToolbarStylePreference; + return NSWindowToolbarStyleExpanded; } - (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]; } @@ -91,7 +103,7 @@ - (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButto { _colorCorrectionPopupButton = colorCorrectionPopupButton; NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]; - [_colorCorrectionPopupButton selectItemAtIndex:mode]; + [_colorCorrectionPopupButton selectItemWithTag:mode]; } @@ -122,6 +134,17 @@ - (NSSlider *)interferenceSlider return _interferenceSlider; } +- (void)setVolumeSlider:(NSSlider *)volumeSlider +{ + _volumeSlider = volumeSlider; + [volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256]; +} + +- (NSSlider *)volumeSlider +{ + return _volumeSlider; +} + - (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton { _frameBlendingModePopupButton = frameBlendingModePopupButton; @@ -137,8 +160,14 @@ - (NSPopUpButton *)frameBlendingModePopupButton - (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 @@ -170,6 +199,45 @@ - (NSPopUpButton *)rumbleModePopupButton return _rumbleModePopupButton; } +static inline NSString *keyEquivalentString(NSMenuItem *item) +{ + return [NSString stringWithFormat:@"%s%@", (item.keyEquivalentModifierMask & NSEventModifierFlagShift)? "^":"", item.keyEquivalent]; +} + +- (void)setHotkey1PopupButton:(NSPopUpButton *)hotkey1PopupButton +{ + _hotkey1PopupButton = hotkey1PopupButton; + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey1"]; + for (NSMenuItem *item in _hotkey1PopupButton.menu.itemArray) { + if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) { + [_hotkey1PopupButton selectItem:item]; + break; + } + } +} + +- (NSPopUpButton *)hotkey1PopupButton +{ + return _hotkey1PopupButton; +} + +- (void)setHotkey2PopupButton:(NSPopUpButton *)hotkey2PopupButton +{ + _hotkey2PopupButton = hotkey2PopupButton; + NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey2"]; + for (NSMenuItem *item in _hotkey2PopupButton.menu.itemArray) { + if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) { + [_hotkey2PopupButton selectItem:item]; + break; + } + } +} + +- (NSPopUpButton *)hotkey2PopupButton +{ + return _hotkey2PopupButton; +} + - (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton { _rewindPopupButton = rewindPopupButton; @@ -252,12 +320,12 @@ - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn 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 @@ -273,8 +341,8 @@ -(void)keyDown:(NSEvent *)theEvent [[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]; } @@ -302,12 +370,31 @@ - (IBAction)highpassFilterChanged:(id)sender [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; } + +- (IBAction)changeMBC7JoystickOverride:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7JoystickOverride"]; +} + +- (IBAction)changeMBC7AllowMouse:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7AllowMouse"]; +} + - (IBAction)changeAnalogControls:(id)sender { [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState forKey:@"GBAnalogControls"]; } +- (IBAction)changeControllerFocus:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAllowBackgroundControllers"]; +} + - (IBAction)changeAspectRatio:(id)sender { [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState @@ -317,7 +404,7 @@ - (IBAction)changeAspectRatio:(id)sender - (IBAction)colorCorrectionChanged:(id)sender { - [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBColorCorrection"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; } @@ -329,12 +416,18 @@ - (IBAction)lightTemperatureChanged:(id)sender [[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"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBVolumeChanged" object:nil]; } - (IBAction)franeBlendingModeChanged:(id)sender @@ -345,10 +438,51 @@ - (IBAction)franeBlendingModeChanged:(id)sender } +- (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]; } @@ -366,6 +500,18 @@ - (IBAction)rumbleModeChanged:(id)sender [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil]; } +- (IBAction)hotkey1Changed:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem]) + forKey:@"GBJoypadHotkey1"]; +} + +- (IBAction)hotkey2Changed:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem]) + forKey:@"GBJoypadHotkey2"]; +} + - (IBAction)rewindLengthChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) @@ -381,10 +527,16 @@ - (IBAction)rtcModeChanged:(id)sender } +- (IBAction)changeAutoUpdates:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAutoUpdatesEnabled"]; +} + - (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]; } @@ -400,13 +552,13 @@ - (void) advanceConfigurationStateMachine if (joystick_configuration_state == GBUnderclock) { [self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :< } - else if (joystick_configuration_state < GBButtonCount) { + else if (joystick_configuration_state < GBJoypadButtonCount) { [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]]; } 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"]; } } @@ -422,7 +574,7 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b if (!button.isPressed) return; if (joystick_configuration_state == -1) return; - if (joystick_configuration_state == GBButtonCount) return; + if (joystick_configuration_state == GBJoypadButtonCount) return; if (!joystick_being_configured) { joystick_being_configured = controller.uniqueID; } @@ -453,30 +605,38 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b static const unsigned gb_to_joykit[] = { - [GBRight]=JOYButtonUsageDPadRight, - [GBLeft]=JOYButtonUsageDPadLeft, - [GBUp]=JOYButtonUsageDPadUp, - [GBDown]=JOYButtonUsageDPadDown, - [GBA]=JOYButtonUsageA, - [GBB]=JOYButtonUsageB, - [GBSelect]=JOYButtonUsageSelect, - [GBStart]=JOYButtonUsageStart, - [GBTurbo]=JOYButtonUsageL1, - [GBRewind]=JOYButtonUsageL2, - [GBUnderclock]=JOYButtonUsageR1, + [GBRight] = JOYButtonUsageDPadRight, + [GBLeft] = JOYButtonUsageDPadLeft, + [GBUp] = JOYButtonUsageDPadUp, + [GBDown] = JOYButtonUsageDPadDown, + [GBA] = JOYButtonUsageA, + [GBB] = JOYButtonUsageB, + [GBSelect] = JOYButtonUsageSelect, + [GBStart] = JOYButtonUsageStart, + [GBTurbo] = JOYButtonUsageL1, + [GBRewind] = JOYButtonUsageL2, + [GBUnderclock] = JOYButtonUsageR1, + [GBHotkey1] = GBJoyKitHotkey1, + [GBHotkey2] = GBJoyKitHotkey2, }; if (joystick_configuration_state == GBUnderclock) { + mapping[@"AnalogUnderclock"] = nil; + double max = 0; for (JOYAxis *axis in controller.axes) { - if (axis.value > 0.5) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { mapping[@"AnalogUnderclock"] = @(axis.uniqueID); + break; } } } if (joystick_configuration_state == GBTurbo) { + mapping[@"AnalogTurbo"] = nil; + double max = 0; for (JOYAxis *axis in controller.axes) { - if (axis.value > 0.5) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { + max = axis.value; mapping[@"AnalogTurbo"] = @(axis.uniqueID); } } @@ -491,6 +651,28 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b [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; @@ -502,6 +684,17 @@ - (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox [_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]]; } +- (NSButton *)controllersFocusCheckbox +{ + return _controllersFocusCheckbox; +} + +- (void)setControllersFocusCheckbox:(NSButton *)controllersFocusCheckbox +{ + _controllersFocusCheckbox = controllersFocusCheckbox; + [_controllersFocusCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]]; +} + - (NSButton *)aspectRatioCheckbox { return _aspectRatioCheckbox; @@ -531,8 +724,8 @@ - (void)dealloc - (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) { @@ -556,12 +749,12 @@ - (void) updateBootROMFolderButton [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]; } } @@ -605,6 +798,17 @@ - (NSPopUpButton *)cgbPopupButton return _cgbPopupButton; } +- (void)setAgbPopupButton:(NSPopUpButton *)agbPopupButton +{ + _agbPopupButton = agbPopupButton; + [_agbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBAGBModel"]]; +} + +- (NSPopUpButton *)agbPopupButton +{ + return _agbPopupButton; +} + - (IBAction)dmgModelChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) @@ -627,6 +831,13 @@ - (IBAction)cgbModelChanged:(id)sender [[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil]; } +- (IBAction)agbModelChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) + forKey:@"GBAGBModel"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAGBModelChanged" object:nil]; +} + - (IBAction)reloadButtonsData:(id)sender { [self.controlsTableView reloadData]; @@ -705,4 +916,100 @@ - (IBAction)changeDefaultJoypad:(id)sender } [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"]; } + +- (NSButton *)autoUpdatesCheckbox +{ + return _autoUpdatesCheckbox; +} + +- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox +{ + _autoUpdatesCheckbox = autoUpdatesCheckbox; + [_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]]; +} + +- (NSButton *)OSDCheckbox +{ + return _OSDCheckbox; +} + +- (void)setOSDCheckbox:(NSButton *)OSDCheckbox +{ + _OSDCheckbox = OSDCheckbox; + [_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]]; +} + +- (IBAction)changeOSDEnabled:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState + forKey:@"GBOSDEnabled"]; + +} + +- (IBAction)changeFilterScreenshots:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState + forKey:@"GBFilterScreenshots"]; +} + +- (NSButton *)screenshotFilterCheckbox +{ + return _screenshotFilterCheckbox; +} + +- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox +{ + _screenshotFilterCheckbox = screenshotFilterCheckbox; + if (![GBViewMetal isSupported]) { + [_screenshotFilterCheckbox setEnabled:false]; + } + else { + [_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]]; + } +} + +- (IBAction)displayColorCorrectionHelp:(id)sender +{ + [GBWarningPopover popoverWithContents: + (NSString * const[]){ + [GB_COLOR_CORRECTION_DISABLED] = @"Colors are directly interpreted as sRGB, resulting in unbalanced colors and inaccurate hues.", + [GB_COLOR_CORRECTION_CORRECT_CURVES] = @"Colors have their brightness corrected, but hues remain unbalanced.", + [GB_COLOR_CORRECTION_MODERN_BALANCED] = @"Emulates a modern display. Blue contrast is moderately enhanced at the cost of slight hue inaccuracy.", + [GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST] = @"Like Modern – Balanced, but further boosts the contrast of greens and magentas that is lacking on the original hardware.", + [GB_COLOR_CORRECTION_REDUCE_CONTRAST] = @"Slightly reduce the contrast to better represent the tint and contrast of the original display.", + [GB_COLOR_CORRECTION_LOW_CONTRAST] = @"Harshly reduce the contrast to accurately represent the tint low constrast of the original display.", + [GB_COLOR_CORRECTION_MODERN_ACCURATE] = @"Emulates a modern display. Colors have their hues and brightness corrected.", + } [self.colorCorrectionPopupButton.selectedItem.tag] + title:self.colorCorrectionPopupButton.selectedItem.title + onView:sender + timeout:6 + preferredEdge:NSRectEdgeMaxX]; +} + +- (IBAction)displayHighPassHelp:(id)sender +{ + [GBWarningPopover popoverWithContents: + (NSString * const[]){ + [GB_HIGHPASS_OFF] = @"No high-pass filter will be applied. DC offset will be kept, pausing and resuming will trigger audio pops.", + [GB_HIGHPASS_ACCURATE] = @"An accurate high-pass filter will be applied, removing the DC offset while somewhat attenuating the bass.", + [GB_HIGHPASS_REMOVE_DC_OFFSET] = @"A high-pass filter will be applied to the DC offset itself, removing the DC offset while preserving the waveform.", + } [self.highpassFilterPopupButton.indexOfSelectedItem] + title:self.highpassFilterPopupButton.selectedItem.title + onView:sender + timeout:6 + preferredEdge:NSRectEdgeMaxX]; +} + +- (IBAction)arrangeJoyCons:(id)sender +{ + [GBJoyConManager sharedInstance].arrangementMode = true; + [self beginSheet:self.joyconsSheet completionHandler:nil]; +} + +- (IBAction)closeJoyConsSheet:(id)sender +{ + [self endSheet:self.joyconsSheet]; + [GBJoyConManager sharedInstance].arrangementMode = false; +} + @end diff --git a/thirdparty/SameBoy/Cocoa/GBS.xib b/thirdparty/SameBoy/Cocoa/GBS.xib index 534ff5590..65bd44f82 100644 --- a/thirdparty/SameBoy/Cocoa/GBS.xib +++ b/thirdparty/SameBoy/Cocoa/GBS.xib @@ -44,7 +44,7 @@ - - - - - - - - - - - - - - + - - + + @@ -114,6 +91,29 @@ + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/GBS11.xib b/thirdparty/SameBoy/Cocoa/GBS11.xib new file mode 100644 index 000000000..b7a69fd56 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBS11.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/GBSplitView.m b/thirdparty/SameBoy/Cocoa/GBSplitView.m index a56c24e69..ca51068d0 100644 --- a/thirdparty/SameBoy/Cocoa/GBSplitView.m +++ b/thirdparty/SameBoy/Cocoa/GBSplitView.m @@ -8,7 +8,7 @@ @implementation GBSplitView - (void)setDividerColor:(NSColor *)color { _dividerColor = color; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } - (NSColor *)dividerColor @@ -19,6 +19,12 @@ - (NSColor *)dividerColor return [super dividerColor]; } +- (void)drawDividerInRect:(NSRect)rect +{ + [self.dividerColor set]; + NSRectFill(rect); +} + /* Mavericks comaptibility */ - (NSArray *)arrangedSubviews { diff --git a/thirdparty/SameBoy/Cocoa/GBTerminalTextFieldCell.m b/thirdparty/SameBoy/Cocoa/GBTerminalTextFieldCell.m index c1ed20368..a6f76a4bb 100644 --- a/thirdparty/SameBoy/Cocoa/GBTerminalTextFieldCell.m +++ b/thirdparty/SameBoy/Cocoa/GBTerminalTextFieldCell.m @@ -1,7 +1,11 @@ #import #import "GBTerminalTextFieldCell.h" +#import "NSTextFieldCell+Inset.h" @interface GBTerminalTextView : NSTextView +{ + @public __weak NSTextField *_field; +} @property GB_gameboy_t *gb; @end @@ -10,15 +14,19 @@ @implementation GBTerminalTextFieldCell GBTerminalTextView *field_editor; } -- (NSTextView *)fieldEditorForView:(NSView *)controlView +- (NSTextView *)fieldEditorForView:(NSTextField *)controlView { if (field_editor) { field_editor.gb = self.gb; return field_editor; } field_editor = [[GBTerminalTextView alloc] init]; - [field_editor setFieldEditor:YES]; + [field_editor setFieldEditor:true]; field_editor.gb = self.gb; + field_editor->_field = (NSTextField *)controlView; + ((NSTextFieldCell *)controlView.cell).textInset = + field_editor.textContainerInset = + NSMakeSize(0, 2); return field_editor; } @@ -109,7 +117,7 @@ - (void)keyDown:(NSEvent *)event [self updateReverseSearch]; } else { - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; reverse_search_mode = true; } @@ -185,14 +193,21 @@ - (BOOL)resignFirstResponder return [super resignFirstResponder]; } --(void)drawRect:(NSRect)dirtyRect +- (NSColor *)backgroundColor +{ + return nil; +} + +- (void)drawRect:(NSRect)dirtyRect { - [super drawRect:dirtyRect]; if (reverse_search_mode && [super string].length == 0) { NSMutableDictionary *attributes = [self.typingAttributes mutableCopy]; NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5]; [attributes setObject:color forKey:NSForegroundColorAttributeName]; - [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; + [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 2)]; + } + else { + [super drawRect:dirtyRect]; } } diff --git a/thirdparty/SameBoy/Cocoa/GBTintedImageCell.h b/thirdparty/SameBoy/Cocoa/GBTintedImageCell.h new file mode 100644 index 000000000..eb6c8b33c --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBTintedImageCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBTintedImageCell : NSImageCell +@property NSColor *tint; +@end diff --git a/thirdparty/SameBoy/Cocoa/GBTintedImageCell.m b/thirdparty/SameBoy/Cocoa/GBTintedImageCell.m new file mode 100644 index 000000000..af4faa665 --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/GBTintedImageCell.m @@ -0,0 +1,20 @@ +#import "GBTintedImageCell.h" + +@implementation GBTintedImageCell + +- (NSImage *)image +{ + if (!self.tint || !super.image.isTemplate) { + return [super image]; + } + + NSImage *tinted = [super.image copy]; + [tinted lockFocus]; + [self.tint set]; + NSRectFillUsingOperation((NSRect){.size = tinted.size}, NSCompositeSourceIn); + [tinted unlockFocus]; + tinted.template = false; + return tinted; +} + +@end diff --git a/thirdparty/SameBoy/Cocoa/GBView.h b/thirdparty/SameBoy/Cocoa/GBView.h index f9aab83e7..a264d29a4 100644 --- a/thirdparty/SameBoy/Cocoa/GBView.h +++ b/thirdparty/SameBoy/Cocoa/GBView.h @@ -1,6 +1,8 @@ #import #include #import +#import "GBOSDView.h" + @class Document; typedef enum { @@ -17,12 +19,14 @@ 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; - (void) createInternalView; - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; - (void)screenSizeChanged; - (void)setRumble: (double)amp; +- (NSImage *)renderToImage; @end diff --git a/thirdparty/SameBoy/Cocoa/GBView.m b/thirdparty/SameBoy/Cocoa/GBView.m index 5c1922c5f..65b807cff 100644 --- a/thirdparty/SameBoy/Cocoa/GBView.m +++ b/thirdparty/SameBoy/Cocoa/GBView.m @@ -106,9 +106,9 @@ @implementation GBView { 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; @@ -117,6 +117,8 @@ @implementation GBView NSEventModifierFlags previousModifiers; JOYController *lastController; GB_frame_blending_mode_t _frameBlendingMode; + bool _turbo; + bool _mouseControlEnabled; } + (instancetype)alloc @@ -146,7 +148,7 @@ - (void) _init [[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]; @@ -155,6 +157,7 @@ - (void) _init [self addSubview:self.internalView]; self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; [JOYController registerListener:self]; + _mouseControlEnabled = true; } - (void)screenSizeChanged @@ -182,7 +185,7 @@ - (void) ratioKeepingChanged - (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode { _frameBlendingMode = frameBlendingMode; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } @@ -259,6 +262,7 @@ - (void)setFrame:(NSRect)frame - (void) flip { if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { + clockMultiplier = 1.0; GB_set_clock_multiplier(_gb, analogClockMultiplier); if (self.document.partner) { GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier); @@ -266,6 +270,12 @@ - (void) flip if (analogClockMultiplier == 1.0) { analogClockMultiplierValid = false; } + if (analogClockMultiplier < 2.0 && analogClockMultiplier > 1.0) { + GB_set_turbo_mode(_gb, false, false); + if (self.document.partner) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + } } else { if (underclockKeyDown && clockMultiplier > 0.5) { @@ -283,6 +293,14 @@ - (void) flip } } } + if ((!analogClockMultiplierValid && clockMultiplier > 1) || + _turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) { + [self.osdView displayText:@"Fast forwarding..."]; + } + else if ((!analogClockMultiplierValid && clockMultiplier < 1) || + (analogClockMultiplierValid && analogClockMultiplier < 1)) { + [self.osdView displayText:@"Slow motion..."]; + } current_buffer = (current_buffer + 1) % self.numberOfBuffers; } @@ -329,6 +347,7 @@ -(void)keyDown:(NSEvent *)theEvent else { GB_set_turbo_mode(_gb, true, self.isRewinding); } + _turbo = true; analogClockMultiplierValid = false; break; @@ -336,6 +355,7 @@ -(void)keyDown:(NSEvent *)theEvent if (!self.document.partner) { self.isRewinding = true; GB_set_turbo_mode(_gb, false, false); + _turbo = false; } break; @@ -401,6 +421,7 @@ -(void)keyUp:(NSEvent *)theEvent else { GB_set_turbo_mode(_gb, false, false); } + _turbo = false; analogClockMultiplierValid = false; break; @@ -440,9 +461,34 @@ - (void)setRumble:(double)amp [lastController setRumbleAmplitude:amp]; } +- (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller +{ + if (!_gb) return false; + if (!GB_has_accelerometer(_gb)) return false; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true; + for (JOYAxes3D *axes in controller.axes3D) { + if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) { + return false; + } + } + return true; +} + +- (bool)allowController +{ + if ([self.window isMainWindow]) return true; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]) { + if ([(Document *)[NSApplication sharedApplication].orderedDocuments.firstObject mainWindow] == self.window) { + return true; + } + } + return false; +} + - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis { - if (![self.window isMainWindow]) return; + if (!_gb) return; + if (![self allowController]) return; NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; if (!mapping) { @@ -451,20 +497,64 @@ - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis if ((axis.usage == JOYAxisUsageR1 && !mapping) || axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){ - analogClockMultiplier = MIN(MAX(1 - axis.value + 0.2, 1.0 / 3), 1.0); + analogClockMultiplier = MIN(MAX(1 - axis.value + 0.05, 1.0 / 3), 1.0); analogClockMultiplierValid = true; } else if ((axis.usage == JOYAxisUsageL1 && !mapping) || axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){ - analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.8, 1.0), 3.0); + analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.95, 1.0), 3.0); analogClockMultiplierValid = true; } } +- (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes +{ + if (!_gb) return; + if ([self shouldControllerUseJoystickForMotion:controller]) { + if (!self.mouseControlsActive) { + GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y); + } + } +} + +- (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes +{ + if (!_gb) return; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return; + if (self.mouseControlsActive) return; + if (controller != lastController) return; + // When using Joy-Cons in dual-controller grip, ignore motion data from the left Joy-Con + if (controller.joyconType == JOYJoyConTypeDual) { + for (JOYController *child in [(JOYCombinedController *)controller children]) { + if (child.joyconType != JOYJoyConTypeRight && [child.axes3D containsObject:axes]) { + return; + } + } + } + + if (axes.usage == JOYAxes3DUsageOrientation) { + for (JOYAxes3D *axes in controller.axes3D) { + // Only use orientation if there's no acceleration axes + if (axes.usage == JOYAxes3DUsageAcceleration) { + return; + } + } + JOYPoint3D point = axes.normalizedValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } + else if (axes.usage == JOYAxes3DUsageAcceleration) { + JOYPoint3D point = axes.gUnitsValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } +} + - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { - if (![self.window isMainWindow]) return; + if (!_gb) return; + if (![self allowController]) return; + _mouseControlEnabled = false; + if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return; unsigned player_count = GB_get_player_count(_gb); if (self.document.partner) { @@ -483,7 +573,7 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b continue; } dispatch_async(dispatch_get_main_queue(), ^{ - [controller setPlayerLEDs:1 << player]; + [controller setPlayerLEDs:[controller LEDMaskForPlayer:player]]; }); NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; if (!mapping) { @@ -534,17 +624,22 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b else { GB_set_turbo_mode(_gb, false, false); } + _turbo = false; } 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; + if (!analogClockMultiplierValid || analogClockMultiplier == 1.0 || !button.isPressed) { + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); + } + else { + GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); + } + _turbo = button.isPressed; } + break; } case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break; @@ -561,14 +656,21 @@ - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)b - (BOOL)acceptsFirstResponder { - return YES; + return true; +} + +- (bool)mouseControlsActive +{ + return _gb && GB_is_inited(_gb) && GB_has_accelerometer(_gb) && + _mouseControlEnabled && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"]; } - (void)mouseEntered:(NSEvent *)theEvent { if (!mouse_hidden) { mouse_hidden = true; - if (_mouseHidingEnabled) { + if (_mouseHidingEnabled && + !self.mouseControlsActive) { [NSCursor hide]; } } @@ -586,7 +688,47 @@ - (void)mouseExited:(NSEvent *)theEvent [super mouseExited:theEvent]; } -- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled +- (void)mouseDown:(NSEvent *)event +{ + _mouseControlEnabled = true; + if (self.mouseControlsActive) { + if (event.type == NSEventTypeLeftMouseDown) { + GB_set_key_state(_gb, GB_KEY_A, true); + } + } +} + +- (void)mouseUp:(NSEvent *)event +{ + if (self.mouseControlsActive) { + if (event.type == NSEventTypeLeftMouseUp) { + GB_set_key_state(_gb, GB_KEY_A, false); + } + } +} + +- (void)mouseMoved:(NSEvent *)event +{ + if (self.mouseControlsActive) { + NSPoint point = [self convertPoint:[event locationInWindow] toView:nil]; + + point.x /= self.frame.size.width; + point.x *= 2; + point.x -= 1; + + point.y /= self.frame.size.height; + point.y *= 2; + point.y -= 1; + + if (GB_get_screen_width(_gb) != 160) { // has border + point.x *= 256 / 160.0; + point.y *= 224 / 114.0; + } + GB_set_accelerometer_values(_gb, -point.x, point.y); + } +} + +- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled { if (mouseHidingEnabled == _mouseHidingEnabled) return; @@ -601,7 +743,7 @@ - (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled } } -- (BOOL)isMouseHidingEnabled +- (bool)isMouseHidingEnabled { return _mouseHidingEnabled; } @@ -634,7 +776,7 @@ -(NSDragOperation)draggingEntered:(id)sender 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; } } @@ -647,10 +789,16 @@ -(BOOL)performDragOperation:(id)sender if ( [[pboard types] containsObject:NSURLPboardType] ) { NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; - return [_document loadStateFile:fileURL.fileSystemRepresentation]; + return [_document loadStateFile:fileURL.fileSystemRepresentation noErrorOnNotFound:false]; } return false; } +- (NSImage *)renderToImage; +{ + /* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me + to bother figuring out how the hell something so trivial can be done. */ + return nil; +} @end diff --git a/thirdparty/SameBoy/Cocoa/GBViewGL.m b/thirdparty/SameBoy/Cocoa/GBViewGL.m index b80973e40..dda85843e 100644 --- a/thirdparty/SameBoy/Cocoa/GBViewGL.m +++ b/thirdparty/SameBoy/Cocoa/GBViewGL.m @@ -19,7 +19,7 @@ - (void)createInternalView 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 @@ - (void)flip { [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/thirdparty/SameBoy/Cocoa/GBViewMetal.m b/thirdparty/SameBoy/Cocoa/GBViewMetal.m index 580db2ca3..ae7443f6b 100644 --- a/thirdparty/SameBoy/Cocoa/GBViewMetal.m +++ b/thirdparty/SameBoy/Cocoa/GBViewMetal.m @@ -1,3 +1,4 @@ +#import #import "GBViewMetal.h" #pragma clang diagnostic ignored "-Wpartial-availability" @@ -51,8 +52,9 @@ - (void)createInternalView MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; view.delegate = self; self.internalView = view; - view.paused = YES; - view.enableSetNeedsDisplay = YES; + view.paused = true; + view.enableSetNeedsDisplay = true; + view.framebufferOnly = false; vertices = [device newBufferWithBytes:rect length:sizeof(rect) @@ -92,7 +94,7 @@ - (void) loadShader withString:scaler_source]; MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; - options.fastMathEnabled = YES; + options.fastMathEnabled = true; id library = [device newLibraryWithSource:shader_source options:options error:&error]; @@ -208,8 +210,23 @@ - (void)flip { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ - [(MTKView *)self.internalView setNeedsDisplay:YES]; + [(MTKView *)self.internalView setNeedsDisplay:true]; }); } +- (NSImage *)renderToImage +{ + CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture] + options:@{ + kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB() + }]; + ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1), + 0, ciImage.extent.size.height)]; + CIContext *context = [CIContext context]; + CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent]; + NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size]; + CGImageRelease(cgImage); + return ret; +} + @end diff --git a/thirdparty/SameBoy/Cocoa/GBVisualizerView.h b/thirdparty/SameBoy/Cocoa/GBVisualizerView.h index 43cda4bfe..5ee4638e5 100644 --- a/thirdparty/SameBoy/Cocoa/GBVisualizerView.h +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Cocoa/GBVisualizerView.m b/thirdparty/SameBoy/Cocoa/GBVisualizerView.m index c09cfe1ef..08f6024db 100644 --- a/thirdparty/SameBoy/Cocoa/GBVisualizerView.m +++ b/thirdparty/SameBoy/Cocoa/GBVisualizerView.m @@ -1,12 +1,5 @@ -// -// GBVisualizerView.m -// SameBoySDL -// -// Created by Lior Halphon on 7/4/21. -// Copyright © 2021 Lior Halphon. All rights reserved. -// - #import "GBVisualizerView.h" +#import "GBPaletteEditorController.h" #include #define SAMPLE_COUNT 1024 @@ -36,24 +29,7 @@ @implementation GBVisualizerView - (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]; diff --git a/thirdparty/SameBoy/Cocoa/GBWarningPopover.h b/thirdparty/SameBoy/Cocoa/GBWarningPopover.h index 1d695b13e..953aa3c91 100644 --- a/thirdparty/SameBoy/Cocoa/GBWarningPopover.h +++ b/thirdparty/SameBoy/Cocoa/GBWarningPopover.h @@ -4,5 +4,6 @@ + (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view; + (GBWarningPopover *) popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window; ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents title:(NSString *)title onView:(NSView *)view timeout:(double)seconds preferredEdge:(NSRectEdge)preferredEdge; @end diff --git a/thirdparty/SameBoy/Cocoa/GBWarningPopover.m b/thirdparty/SameBoy/Cocoa/GBWarningPopover.m index 411e38837..b66186ba2 100644 --- a/thirdparty/SameBoy/Cocoa/GBWarningPopover.m +++ b/thirdparty/SameBoy/Cocoa/GBWarningPopover.m @@ -4,32 +4,44 @@ @implementation GBWarningPopover -+ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents title:(NSString *)title onView:(NSView *)view timeout:(double)seconds preferredEdge:(NSRectEdge)preferredEdge { [lastPopover close]; 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]; + if (!title) { + [field setStringValue:contents]; + } + else { + NSMutableAttributedString *fullContents = [[NSMutableAttributedString alloc] initWithString:title + attributes:@{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]}]; + [fullContents appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:contents] + attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}]]; + [field setAttributedStringValue:fullContents]; + + } NSSize textSize = [field.cell cellSizeForBounds:[field.cell drawingRectForBounds:NSMakeRect(0, 0, 240, CGFLOAT_MAX)]]; textSize.width = ceil(textSize.width) + 16; textSize.height = ceil(textSize.height) + 12; [lastPopover setContentSize:textSize]; if (!view.window.isVisible) { - [view.window setIsVisible:YES]; + [view.window setIsVisible:true]; } [lastPopover showRelativeToRect:view.bounds ofView:view - preferredEdge:NSMinYEdge]; + preferredEdge:preferredEdge]; NSRect frame = field.frame; frame.origin.x += 8; - frame.origin.y -= 6; + frame.origin.y += 6; + frame.size.width -= 16; + frame.size.height -= 12; field.frame = frame; @@ -38,6 +50,11 @@ + (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView * return lastPopover; } ++ (GBWarningPopover *) popoverWithContents:(NSString *)contents onView:(NSView *)view +{ + return [self popoverWithContents:contents title:nil onView:view timeout:3.0 preferredEdge:NSMinYEdge]; +} + + (GBWarningPopover *)popoverWithContents:(NSString *)contents onWindow:(NSWindow *)window { return [self popoverWithContents:contents onView:window.contentView.superview.subviews.lastObject]; diff --git a/thirdparty/SameBoy/Cocoa/HelpTemplate.png b/thirdparty/SameBoy/Cocoa/HelpTemplate.png new file mode 100644 index 000000000..6b12375e1 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HelpTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/HelpTemplate@2x.png b/thirdparty/SameBoy/Cocoa/HelpTemplate@2x.png new file mode 100644 index 000000000..d7f8237aa Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HelpTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate.png b/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate.png new file mode 100644 index 000000000..7c4b5974d Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate@2x.png b/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate@2x.png new file mode 100644 index 000000000..816706d04 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HorizontalJoyConLeftTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate.png b/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate.png new file mode 100644 index 000000000..866992bed Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate@2x.png b/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate@2x.png new file mode 100644 index 000000000..908ba48f6 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/HorizontalJoyConRightTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Icon.png b/thirdparty/SameBoy/Cocoa/Icon.png new file mode 100644 index 000000000..a5675ca2c Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Icon.png differ diff --git a/thirdparty/SameBoy/Cocoa/Icon@2x.png b/thirdparty/SameBoy/Cocoa/Icon@2x.png new file mode 100644 index 000000000..b3259ed2a Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Icon@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Info.plist b/thirdparty/SameBoy/Cocoa/Info.plist index 5e409c981..dce84c924 100644 --- a/thirdparty/SameBoy/Cocoa/Info.plist +++ b/thirdparty/SameBoy/Cocoa/Info.plist @@ -88,6 +88,24 @@ NSDocumentClass Document + + CFBundleTypeExtensions + + gbcart + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy Cartridge + CFBundleTypeRole + Viewer + LSItemContentTypes + + LSTypeIsPackage + 1 + NSDocumentClass + Document + CFBundleExecutable SameBoy @@ -95,6 +113,8 @@ AppIcon.icns CFBundleIdentifier com.github.liji32.sameboy + LSApplicationCategoryType + public.app-category.games CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -112,11 +132,11 @@ LSMinimumSystemVersion 10.9 NSHumanReadableCopyright - Copyright © 2015-2021 Lior Halphon + Copyright © 2015-@COPYRIGHT_YEAR Lior Halphon NSMainNibFile MainMenu NSPrincipalClass - NSApplication + GBApp UTExportedTypeDeclarations diff --git a/thirdparty/SameBoy/Cocoa/InterruptTemplate.png b/thirdparty/SameBoy/Cocoa/InterruptTemplate.png new file mode 100644 index 000000000..35307279f Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/InterruptTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/InterruptTemplate@2x.png b/thirdparty/SameBoy/Cocoa/InterruptTemplate@2x.png new file mode 100644 index 000000000..eb262436d Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/InterruptTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConDualTemplate.png b/thirdparty/SameBoy/Cocoa/JoyConDualTemplate.png new file mode 100644 index 000000000..42e7a2711 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConDualTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConDualTemplate@2x.png b/thirdparty/SameBoy/Cocoa/JoyConDualTemplate@2x.png new file mode 100644 index 000000000..938fd7f36 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConDualTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate.png b/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate.png new file mode 100644 index 000000000..924c427bd Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate@2x.png b/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate@2x.png new file mode 100644 index 000000000..6b2f99694 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConLeftTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConRightTemplate.png b/thirdparty/SameBoy/Cocoa/JoyConRightTemplate.png new file mode 100644 index 000000000..1fccf5f67 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConRightTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/JoyConRightTemplate@2x.png b/thirdparty/SameBoy/Cocoa/JoyConRightTemplate@2x.png new file mode 100644 index 000000000..d9c385caf Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/JoyConRightTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad.png b/thirdparty/SameBoy/Cocoa/Joypad.png index f30d8f99f..46cd64837 100644 Binary files a/thirdparty/SameBoy/Cocoa/Joypad.png and b/thirdparty/SameBoy/Cocoa/Joypad.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad@2x.png b/thirdparty/SameBoy/Cocoa/Joypad@2x.png index d91ee30b9..4fcc1f433 100644 Binary files a/thirdparty/SameBoy/Cocoa/Joypad@2x.png and b/thirdparty/SameBoy/Cocoa/Joypad@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad~dark.png b/thirdparty/SameBoy/Cocoa/Joypad~dark.png index 8a7687b5f..18bdccd95 100644 Binary files a/thirdparty/SameBoy/Cocoa/Joypad~dark.png and b/thirdparty/SameBoy/Cocoa/Joypad~dark.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad~dark@2x.png b/thirdparty/SameBoy/Cocoa/Joypad~dark@2x.png index ce2a07cc1..aa6b5c006 100644 Binary files a/thirdparty/SameBoy/Cocoa/Joypad~dark@2x.png and b/thirdparty/SameBoy/Cocoa/Joypad~dark@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad~solid.png b/thirdparty/SameBoy/Cocoa/Joypad~solid.png new file mode 100644 index 000000000..fecb8b320 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Joypad~solid.png differ diff --git a/thirdparty/SameBoy/Cocoa/Joypad~solid@2x.png b/thirdparty/SameBoy/Cocoa/Joypad~solid@2x.png new file mode 100644 index 000000000..197155a95 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Joypad~solid@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/License.html b/thirdparty/SameBoy/Cocoa/License.html index 984651413..16c26a942 100644 --- a/thirdparty/SameBoy/Cocoa/License.html +++ b/thirdparty/SameBoy/Cocoa/License.html @@ -30,7 +30,7 @@

SameBoy

MIT License

-

Copyright © 2015-2021 Lior Halphon

+

Copyright © 2015-@COPYRIGHT_YEAR 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/thirdparty/SameBoy/Cocoa/MainMenu.xib b/thirdparty/SameBoy/Cocoa/MainMenu.xib index 04bcf8f83..08e0771b9 100644 --- a/thirdparty/SameBoy/Cocoa/MainMenu.xib +++ b/thirdparty/SameBoy/Cocoa/MainMenu.xib @@ -7,12 +7,11 @@ - + - - + @@ -27,13 +26,19 @@ - + + + + + + + - + @@ -77,6 +82,12 @@ + + + + + +

@@ -91,6 +102,12 @@ + + + + + + @@ -185,6 +202,12 @@ + + + + + + @@ -316,12 +339,35 @@
+ + + + + + + + + + + + + + + + + + + + + + + @@ -341,9 +387,14 @@ - + - + + + + + + @@ -381,11 +432,11 @@ - + - + @@ -410,7 +461,7 @@ - + @@ -433,6 +484,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -477,9 +573,10 @@ - + + - + diff --git a/thirdparty/SameBoy/Cocoa/NSImageNamedDarkSupport.m b/thirdparty/SameBoy/Cocoa/NSImageNamedDarkSupport.m index 821ba3b94..73e7b64b7 100644 --- a/thirdparty/SameBoy/Cocoa/NSImageNamedDarkSupport.m +++ b/thirdparty/SameBoy/Cocoa/NSImageNamedDarkSupport.m @@ -11,6 +11,13 @@ @implementation NSImage(DarkHooks) + (NSImage *)imageNamedWithDark:(NSImageName)name { + if (@available(macOS 11.0, *)) { + if (![name containsString:@"~solid"]) { + NSImage *solid = [self imageNamed:[name stringByAppendingString:@"~solid"]]; + [solid setTemplate:true]; + if (solid) return solid; + } + } NSImage *light = imageNamed(self, _cmd, name); if (@available(macOS 10.14, *)) { NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]); diff --git a/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.h b/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.h new file mode 100644 index 000000000..3b3cac8cf --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.h @@ -0,0 +1,6 @@ +#import +#import + +@interface NSTextFieldCell (Inset) +@property NSSize textInset; +@end diff --git a/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.m b/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.m new file mode 100644 index 000000000..acc6b3baf --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/NSTextFieldCell+Inset.m @@ -0,0 +1,39 @@ +#import "NSTextFieldCell+Inset.h" +#import +#import + +@interface NSTextFieldCell () +- (CGRect)_textLayerDrawingRectForCellFrame:(CGRect)rect; +@property NSSize textInset; +@end + +@implementation NSTextFieldCell (Inset) + +- (void)setTextInset:(NSSize)textInset +{ + objc_setAssociatedObject(self, @selector(textInset), @(textInset), OBJC_ASSOCIATION_RETAIN); +} + +- (NSSize)textInset +{ + return [objc_getAssociatedObject(self, _cmd) sizeValue]; +} + +- (CGRect)_textLayerDrawingRectForCellFrameHook:(CGRect)rect +{ + CGRect ret = [self _textLayerDrawingRectForCellFrameHook:rect]; + NSSize inset = self.textInset; + ret.origin.x += inset.width; + ret.origin.y += inset.height; + ret.size.width -= inset.width; + ret.size.height -= inset.height; + return ret; +} + ++ (void)load +{ + method_exchangeImplementations(class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrame:)), + class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrameHook:))); +} + +@end diff --git a/thirdparty/SameBoy/Cocoa/Next.png b/thirdparty/SameBoy/Cocoa/Next.png index cd9a4c318..eb4d13522 100644 Binary files a/thirdparty/SameBoy/Cocoa/Next.png and b/thirdparty/SameBoy/Cocoa/Next.png differ diff --git a/thirdparty/SameBoy/Cocoa/Next@2x.png b/thirdparty/SameBoy/Cocoa/Next@2x.png index 1debb1d59..c6b9d3aca 100644 Binary files a/thirdparty/SameBoy/Cocoa/Next@2x.png and b/thirdparty/SameBoy/Cocoa/Next@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/NextTemplate.png b/thirdparty/SameBoy/Cocoa/NextTemplate.png new file mode 100644 index 000000000..071e750ea Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/NextTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/NextTemplate@2x.png b/thirdparty/SameBoy/Cocoa/NextTemplate@2x.png new file mode 100644 index 000000000..616fb2e3f Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/NextTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Pause.png b/thirdparty/SameBoy/Cocoa/Pause.png index 2bb380b7b..d81a4f632 100644 Binary files a/thirdparty/SameBoy/Cocoa/Pause.png and b/thirdparty/SameBoy/Cocoa/Pause.png differ diff --git a/thirdparty/SameBoy/Cocoa/Pause@2x.png b/thirdparty/SameBoy/Cocoa/Pause@2x.png index 36b6da018..965b40f24 100644 Binary files a/thirdparty/SameBoy/Cocoa/Pause@2x.png and b/thirdparty/SameBoy/Cocoa/Pause@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Play.png b/thirdparty/SameBoy/Cocoa/Play.png index 3f8709217..fd43bc97f 100644 Binary files a/thirdparty/SameBoy/Cocoa/Play.png and b/thirdparty/SameBoy/Cocoa/Play.png differ diff --git a/thirdparty/SameBoy/Cocoa/Play@2x.png b/thirdparty/SameBoy/Cocoa/Play@2x.png index 0de055300..a0edda659 100644 Binary files a/thirdparty/SameBoy/Cocoa/Play@2x.png and b/thirdparty/SameBoy/Cocoa/Play@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Preferences.xib b/thirdparty/SameBoy/Cocoa/Preferences.xib index ce3cb7c77..4641eba32 100644 --- a/thirdparty/SameBoy/Cocoa/Preferences.xib +++ b/thirdparty/SameBoy/Cocoa/Preferences.xib @@ -6,13 +6,16 @@ - + + + + @@ -21,10 +24,10 @@ - + - + @@ -49,23 +52,33 @@ + + + + + + + + + + @@ -73,25 +86,34 @@ + + + + + + + + + - + - - + + @@ -99,8 +121,8 @@ - - + + @@ -135,9 +157,20 @@ + - - + + @@ -145,8 +178,8 @@ - - + + @@ -155,10 +188,14 @@ - - - - + + + + + + + +
@@ -166,18 +203,35 @@
+ + + + + + + + + + + + + + + + + - - - + + + - - + + @@ -196,8 +250,8 @@ - - + + @@ -205,8 +259,8 @@ - - + + @@ -215,9 +269,9 @@ - - - + + +
@@ -226,8 +280,8 @@
- - + + @@ -235,8 +289,8 @@ - - + + @@ -254,44 +308,49 @@ - - - - + + + - + - + - - + + @@ -299,8 +358,8 @@ - - + + @@ -308,8 +367,8 @@ - - + + @@ -317,8 +376,8 @@ - - + + @@ -326,8 +385,8 @@ - - + + @@ -345,8 +404,8 @@ - - + + @@ -354,19 +413,19 @@ - - - + + + - - - - - - + + + + + + @@ -375,8 +434,8 @@ - - + + @@ -384,30 +443,12 @@ - - + + - - - - - - - - - - - - - - - - - - - - + + @@ -425,8 +466,8 @@ - - + + @@ -449,8 +490,8 @@ - - + + @@ -472,16 +513,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + - - + + @@ -500,8 +606,8 @@ - - + + @@ -509,8 +615,8 @@ - - + + @@ -518,22 +624,33 @@ - - + + - + + - + - + - + @@ -541,24 +658,302 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -577,7 +972,7 @@ - + @@ -609,7 +1004,7 @@ - + @@ -618,7 +1013,7 @@ - + @@ -633,97 +1028,432 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text Cell +Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/Previous.png b/thirdparty/SameBoy/Cocoa/Previous.png index cc91221dd..9dc6141a8 100644 Binary files a/thirdparty/SameBoy/Cocoa/Previous.png and b/thirdparty/SameBoy/Cocoa/Previous.png differ diff --git a/thirdparty/SameBoy/Cocoa/Previous@2x.png b/thirdparty/SameBoy/Cocoa/Previous@2x.png index 77b015751..f0f7f65cf 100644 Binary files a/thirdparty/SameBoy/Cocoa/Previous@2x.png and b/thirdparty/SameBoy/Cocoa/Previous@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Rewind.png b/thirdparty/SameBoy/Cocoa/Rewind.png index 999f358fa..9ac45228e 100644 Binary files a/thirdparty/SameBoy/Cocoa/Rewind.png and b/thirdparty/SameBoy/Cocoa/Rewind.png differ diff --git a/thirdparty/SameBoy/Cocoa/Rewind@2x.png b/thirdparty/SameBoy/Cocoa/Rewind@2x.png index d845b5498..6cb3417ac 100644 Binary files a/thirdparty/SameBoy/Cocoa/Rewind@2x.png and b/thirdparty/SameBoy/Cocoa/Rewind@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker.png b/thirdparty/SameBoy/Cocoa/Speaker.png index 1f6b556a5..e6464c928 100644 Binary files a/thirdparty/SameBoy/Cocoa/Speaker.png and b/thirdparty/SameBoy/Cocoa/Speaker.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker@2x.png b/thirdparty/SameBoy/Cocoa/Speaker@2x.png index 41c46ffdb..a07f21b1e 100644 Binary files a/thirdparty/SameBoy/Cocoa/Speaker@2x.png and b/thirdparty/SameBoy/Cocoa/Speaker@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker~dark.png b/thirdparty/SameBoy/Cocoa/Speaker~dark.png index f3f820a39..0b6d9271a 100644 Binary files a/thirdparty/SameBoy/Cocoa/Speaker~dark.png and b/thirdparty/SameBoy/Cocoa/Speaker~dark.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker~dark@2x.png b/thirdparty/SameBoy/Cocoa/Speaker~dark@2x.png index bdc3eb704..78f6b396b 100644 Binary files a/thirdparty/SameBoy/Cocoa/Speaker~dark@2x.png and b/thirdparty/SameBoy/Cocoa/Speaker~dark@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker~solid.png b/thirdparty/SameBoy/Cocoa/Speaker~solid.png new file mode 100644 index 000000000..bd9fce9c5 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Speaker~solid.png differ diff --git a/thirdparty/SameBoy/Cocoa/Speaker~solid@2x.png b/thirdparty/SameBoy/Cocoa/Speaker~solid@2x.png new file mode 100644 index 000000000..2b218275d Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Speaker~solid@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/StepTemplate.png b/thirdparty/SameBoy/Cocoa/StepTemplate.png new file mode 100644 index 000000000..1532bf63f Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/StepTemplate.png differ diff --git a/thirdparty/SameBoy/Cocoa/StepTemplate@2x.png b/thirdparty/SameBoy/Cocoa/StepTemplate@2x.png new file mode 100644 index 000000000..8d4b1af79 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/StepTemplate@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/UpdateWindow.xib b/thirdparty/SameBoy/Cocoa/UpdateWindow.xib new file mode 100644 index 000000000..0949af4fe --- /dev/null +++ b/thirdparty/SameBoy/Cocoa/UpdateWindow.xib @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/SameBoy/Cocoa/Updates.png b/thirdparty/SameBoy/Cocoa/Updates.png new file mode 100644 index 000000000..e6bbbb6a6 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates.png differ diff --git a/thirdparty/SameBoy/Cocoa/Updates@2x.png b/thirdparty/SameBoy/Cocoa/Updates@2x.png new file mode 100644 index 000000000..a5675ca2c Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Updates~solid.png b/thirdparty/SameBoy/Cocoa/Updates~solid.png new file mode 100644 index 000000000..0575ccc44 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates~solid.png differ diff --git a/thirdparty/SameBoy/Cocoa/Updates~solid@2x.png b/thirdparty/SameBoy/Cocoa/Updates~solid@2x.png new file mode 100644 index 000000000..df758e392 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates~solid@2x.png differ diff --git a/thirdparty/SameBoy/Cocoa/Updates~solid~dark.png b/thirdparty/SameBoy/Cocoa/Updates~solid~dark.png new file mode 100644 index 000000000..bb5f21264 Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates~solid~dark.png differ diff --git a/thirdparty/SameBoy/Cocoa/Updates~solid~dark@2x.png b/thirdparty/SameBoy/Cocoa/Updates~solid~dark@2x.png new file mode 100644 index 000000000..eb35dad9f Binary files /dev/null and b/thirdparty/SameBoy/Cocoa/Updates~solid~dark@2x.png differ diff --git a/thirdparty/SameBoy/Core/apu.c b/thirdparty/SameBoy/Core/apu.c index b159b2ea2..addda814d 100644 --- a/thirdparty/SameBoy/Core/apu.c +++ b/thirdparty/SameBoy/Core/apu.c @@ -2,11 +2,9 @@ #include #include #include +#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, @@ -14,7 +12,7 @@ static const uint8_t duties[] = { 0, 1, 1, 1, 1, 1, 1, 0, }; -static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_offset) +static void refresh_channel(GB_gameboy_t *gb, GB_channel_t index, unsigned cycles_offset) { unsigned multiplier = gb->apu_output.cycles_since_render + cycles_offset - gb->apu_output.last_update[index]; gb->apu_output.summed_samples[index].left += gb->apu_output.current_sample[index].left * multiplier; @@ -22,9 +20,9 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; } -bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, GB_channel_t index) { - if (gb->model >= GB_MODEL_AGB) { + if (gb->model > GB_MODEL_CGB_E) { /* On the AGB, mixing is done digitally, so there are no per-channel DACs. Instead, all channels are summed digital regardless of whatever the DAC state would be on a CGB or earlier model. */ @@ -43,12 +41,14 @@ 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; } -static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index) +static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, GB_channel_t index) { if (!gb->apu.is_active[index]) return 0; @@ -61,13 +61,16 @@ 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; } -static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) +static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, unsigned cycles_offset) { - if (gb->model >= GB_MODEL_AGB) { + + if (gb->model > GB_MODEL_CGB_E) { /* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different. A channel that is not connected to a terminal is idenitcal to a connected channel playing PCM sample 0. */ @@ -86,17 +89,21 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign uint8_t bias = agb_bias_for_channel(gb, index); if (gb->io_registers[GB_IO_NR51] & (1 << index)) { - output.right = (0xf - value * 2 + bias) * right_volume; + output.right = (0xF - value * 2 + bias) * right_volume; } else { - output.right = 0xf * right_volume; + output.right = 0xF * right_volume; } if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { - output.left = (0xf - value * 2 + bias) * left_volume; + output.left = (0xF - value * 2 + bias) * left_volume; } else { - output.left = 0xf * left_volume; + output.left = 0xF * left_volume; + } + + if (unlikely(gb->apu_output.channel_muted[index])) { + output.left = output.right = 0; } if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { @@ -108,6 +115,8 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign return; } + if (value == 0 && gb->apu.samples[index] == 0) return; + if (!GB_apu_is_DAC_enabled(gb, index)) { value = gb->apu.samples[index]; } @@ -124,7 +133,10 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; } - GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume}; + GB_sample_t output = {0, 0}; + if (likely(!gb->apu_output.channel_muted[index])) { + output = (GB_sample_t){(0xF - value * 2) * left_volume, (0xF - value * 2) * right_volume}; + } if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { refresh_channel(gb, index, cycles_offset); gb->apu_output.current_sample[index] = output; @@ -142,16 +154,16 @@ static signed interference(GB_gameboy_t *gb) /* These aren't scientifically measured, but based on ear based on several recordings */ signed ret = 0; if (gb->halted) { - if (gb->model != GB_MODEL_AGB) { + if (gb->model <= GB_MODEL_CGB_E) { ret -= MAX_CH_AMP / 5; } else { ret -= MAX_CH_AMP / 12; } } - if (gb->io_registers[GB_IO_LCDC] & 0x80) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE) { ret += MAX_CH_AMP / 7; - if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) { + if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model <= GB_MODEL_CGB_E) { ret += MAX_CH_AMP / 14; } else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) { @@ -163,7 +175,7 @@ static signed interference(GB_gameboy_t *gb) ret += MAX_CH_AMP / 10; } - if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) { + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E && (gb->io_registers[GB_IO_RP] & 1)) { ret += MAX_CH_AMP / 10; } @@ -183,7 +195,7 @@ static void render(GB_gameboy_t *gb) unrolled for (unsigned i = 0; i < GB_N_CHANNELS; i++) { double multiplier = CH_STEP; - if (gb->model < GB_MODEL_AGB) { + if (gb->model <= GB_MODEL_CGB_E) { if (!GB_apu_is_DAC_enabled(gb, i)) { gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; if (gb->apu_output.dac_discharge[i] < 0) { @@ -220,6 +232,8 @@ static void render(GB_gameboy_t *gb) gb->apu_output.last_update[i] = 0; } gb->apu_output.cycles_since_render = 0; + + if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return; GB_sample_t filtered_output = gb->apu_output.highpass_mode? (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, @@ -240,18 +254,14 @@ static void render(GB_gameboy_t *gb) unsigned left_volume = 0; unsigned right_volume = 0; unrolled for (unsigned i = GB_N_CHANNELS; i--;) { - if (gb->apu.is_active[i]) { + if (GB_apu_is_DAC_enabled(gb, i)) { if (mask & 1) { - left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF; + left_volume += ((gb->io_registers[GB_IO_NR50] & 7) + 1) * CH_STEP * 0xF; } if (mask & 0x10) { - right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF; + right_volume += (((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1) * CH_STEP * 0xF; } } - else { - left_volume += gb->apu_output.current_sample[i].left * CH_STEP; - right_volume += gb->apu_output.current_sample[i].right * CH_STEP; - } mask >>= 1; } gb->apu_output.highpass_diff = (GB_double_sample_t) @@ -276,11 +286,29 @@ static void render(GB_gameboy_t *gb) } assert(gb->apu_output.sample_callback); gb->apu_output.sample_callback(gb, &filtered_output); + if (unlikely(gb->apu_output.output_file)) { +#ifdef GB_BIG_ENDIAN + if (gb->apu_output.output_format == GB_AUDIO_FORMAT_WAV) { + filtered_output.left = LE16(filtered_output.left); + filtered_output.right = LE16(filtered_output.right); + } +#endif + if (fwrite(&filtered_output, sizeof(filtered_output), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + gb->apu_output.output_error = errno; + } + } } -static void update_square_sample(GB_gameboy_t *gb, unsigned index) +static void update_square_sample(GB_gameboy_t *gb, GB_channel_t index) { - if (gb->apu.square_channels[index].current_sample_index & 0x80) return; + if (gb->apu.square_channels[index].sample_surpressed) { + if (gb->model > GB_MODEL_CGB_E) { + update_sample(gb, index, gb->apu.samples[index], 0); + } + return; + } uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; update_sample(gb, index, @@ -289,12 +317,20 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index) 0); } +static inline void update_wave_sample(GB_gameboy_t *gb, unsigned cycles) +{ + if (gb->apu.wave_channel.current_sample_index & 1) { + update_sample(gb, GB_WAVE, + (gb->apu.wave_channel.current_sample_byte & 0xF) >> gb->apu.wave_channel.shift, + cycles); + } + else { + update_sample(gb, GB_WAVE, + (gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift, + 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) { @@ -308,7 +344,7 @@ static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value, uint } if (should_invert) { - // The weird way and over-the-top way clocks for this counter are connected cause + // The weird 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) { @@ -356,6 +392,11 @@ static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value, uint 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) { + /* Note: on pre-CGB models *some* of these are non-deterministic. Specifically, + $x0 writes seem to be non-deterministic while $x8 always work as expected. + TODO: Might be useful to find which cases are non-deterministic, and allow + the debugger to issue warnings when they're used. I suspect writes to/from + $xF are guaranteed to be deterministic. */ if (gb->model <= GB_MODEL_CGB_C) { _nrx2_glitch(volume, 0xFF, old_value, countdown, lock); _nrx2_glitch(volume, value, 0xFF, countdown, lock); @@ -365,11 +406,11 @@ static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_ } } -static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) +static void tick_square_envelope(GB_gameboy_t *gb, GB_channel_t 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) { @@ -385,7 +426,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 { @@ -393,7 +434,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; } } @@ -406,7 +447,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) { @@ -418,7 +459,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 { @@ -426,7 +467,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; } } @@ -464,6 +505,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; @@ -478,27 +520,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) { @@ -516,6 +558,16 @@ void GB_apu_div_event(GB_gameboy_t *gb) if (gb->apu.wave_channel.length_enabled) { if (gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) { + if (gb->apu.is_active[GB_WAVE] && gb->model > GB_MODEL_CGB_E) { + if (gb->apu.wave_channel.sample_countdown == 0) { + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)]; + } + else if (gb->apu.wave_channel.sample_countdown == 9) { + // TODO: wtf? + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START]; + } + } gb->apu.is_active[GB_WAVE] = false; update_sample(gb, GB_WAVE, 0, 0); } @@ -541,15 +593,17 @@ 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) { - 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); } } @@ -567,37 +621,63 @@ 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); } } -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_CGB_E && (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)) { + uint16_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; 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; - GB_apu_run(gb); + cycles -= gb->apu.noise_channel.dmg_delayed_start; + gb->apu.apu_cycles = gb->apu.noise_channel.dmg_delayed_start * 4; + GB_apu_run(gb, true); } } /* To align the square signal to 1MHz */ @@ -639,16 +719,25 @@ 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; + if (unlikely(gb->apu.square_channels[i].delay)) { + if (gb->apu.square_channels[i].delay < cycles_left) { + gb->apu.square_channels[i].delay = 0; + } + else { + gb->apu.square_channels[i].delay -= cycles_left; + } + } while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; 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; } - + gb->apu.square_channels[i].did_tick = true; update_square_sample(gb, i); } if (cycles_left) { @@ -659,17 +748,15 @@ 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; gb->apu.wave_channel.current_sample_index++; gb->apu.wave_channel.current_sample_index &= 0x1F; - gb->apu.wave_channel.current_sample = - gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - cycles - cycles_left); + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (gb->apu.wave_channel.current_sample_index >> 1)]; + update_wave_sample(gb, cycles - cycles_left); gb->apu.wave_channel.wave_form_just_read = true; } if (cycles_left) { @@ -677,19 +764,40 @@ 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.wave_channel.pulsed && gb->model <= GB_MODEL_CGB_E) { + uint16_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + cycles_left -= gb->apu.wave_channel.sample_countdown + 1; + gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; + if (cycles_left) { + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; + } + else { + 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 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.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; @@ -705,10 +813,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; } } } @@ -716,8 +824,8 @@ 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; + if (gb->apu_output.sample_cycles >= clock_rate) { + gb->apu_output.sample_cycles -= clock_rate; render(gb); } } @@ -729,22 +837,21 @@ void GB_apu_run(GB_gameboy_t *gb) void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); - /* Restore the wave form */ - for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; - } gb->apu.lf_div = 1; + gb->apu.wave_channel.shift = 4; /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, the first DIV/APU event is skipped. */ if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP; gb->apu.div_divider = 1; } + gb->apu.square_channels[GB_SQUARE_1].sample_countdown = -1; + gb->apu.square_channels[GB_SQUARE_2].sample_countdown = -1; } uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { + GB_apu_run(gb, true); if (reg == GB_IO_NR52) { uint8_t value = 0; for (unsigned i = 0; i < GB_N_CHANNELS; i++) { @@ -777,7 +884,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { return 0xFF; } - if (gb->model == GB_MODEL_AGB) { + if (gb->model > GB_MODEL_CGB_E) { return 0xFF; } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; @@ -799,7 +906,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? @@ -826,16 +932,16 @@ 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: 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_0: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { effective_counter |= 0xE; // Sometimes F on some instances @@ -865,7 +971,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; @@ -886,7 +991,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; @@ -895,7 +999,8 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x10; } break; - case GB_MODEL_AGB: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: /* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple pattern like the other revisions. */ /* For the most part, AGS seems to do: @@ -910,6 +1015,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 && @@ -922,7 +1028,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { - if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { + if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model > GB_MODEL_CGB_E) { return; } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; @@ -937,7 +1043,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/ /* We call update_samples with the current value so the APU output is updated with the new outputs */ for (unsigned i = GB_N_CHANNELS; i--;) { - update_sample(gb, i, gb->apu.samples[i], 0); + int8_t sample = gb->apu.samples[i]; + gb->apu.samples[i] = 0x10; // Invalidate to force update + update_sample(gb, i, sample, 0); } break; case GB_IO_NR52: { @@ -985,17 +1093,17 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR11: case GB_IO_NR21: { - unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; - gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f)); + GB_channel_t index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; + gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3F)); if (!gb->apu.global_enable) { - value &= 0x3f; + value &= 0x3F; } break; } case GB_IO_NR12: case GB_IO_NR22: { - unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; + GB_channel_t index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; if ((value & 0xF8) == 0) { /* This disables the DAC */ gb->io_registers[reg] = value; @@ -1005,7 +1113,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); } @@ -1014,7 +1122,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR13: case GB_IO_NR23: { - unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; + GB_channel_t index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; gb->apu.square_channels[index].sample_length &= ~0xFF; gb->apu.square_channels[index].sample_length |= value & 0xFF; break; @@ -1022,19 +1130,20 @@ 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; + GB_channel_t 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. */ - if ((value & 0x80) == 0 && gb->apu.is_active[index]) { + /* TODO: When the sample length changes right before being updated from ≥$700 to <$700, 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. */ + if ((value & 0x80) == 0 && gb->apu.is_active[index] && (gb->io_registers[reg] & 0x7) == 7 && (value & 7) != 7) { /* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on double speed. */ - if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) { - if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) { + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) { + if (gb->apu.square_channels[index].did_tick && + gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) { gb->apu.square_channels[index].current_sample_index--; gb->apu.square_channels[index].current_sample_index &= 7; + gb->apu.square_channels[index].sample_surpressed = false; } } } @@ -1045,33 +1154,46 @@ 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; + gb->apu.square_channels[index].did_tick = false; + bool force_unsurpressed = false; if (!gb->apu.is_active[index]) { - gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div; + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) { + if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - gb->apu.square_channels[index].delay) / 2) & 0x400)) { + gb->apu.square_channels[index].current_sample_index++; + gb->apu.square_channels[index].current_sample_index &= 0x7; + force_unsurpressed = true; + } + } + gb->apu.square_channels[index].delay = 6 - gb->apu.lf_div; + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay; if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { gb->apu.square_channels[index].sample_countdown += 2; + gb->apu.square_channels[index].delay += 2; } } else { unsigned extra_delay = 0; - if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { - if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) { + if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) { + if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1 - gb->apu.square_channels[index].delay) / 2) & 0x400)) { gb->apu.square_channels[index].current_sample_index++; gb->apu.square_channels[index].current_sample_index &= 0x7; - gb->apu.is_active[index] = true; + 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; } } /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ - gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div + extra_delay; + gb->apu.square_channels[index].delay = 4 - gb->apu.lf_div + extra_delay; + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay; if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { gb->apu.square_channels[index].sample_countdown += 2; + gb->apu.square_channels[index].delay += 2; } } gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; @@ -1087,8 +1209,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { gb->apu.is_active[index] = true; update_sample(gb, index, 0, 0); - /* 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 && !force_unsurpressed; } if (gb->apu.square_channels[index].pulse_length == 0) { gb->apu.square_channels[index].pulse_length = 0x40; @@ -1118,16 +1239,18 @@ 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; } } /* 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) { @@ -1150,6 +1273,16 @@ 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.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_CGB_E) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->pc & 0xF)]; + } + else if (gb->apu.wave_channel.wave_form_just_read && gb->model <= GB_MODEL_CGB_C) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (GB_IO_NR30 & 0xF)]; + } + } gb->apu.is_active[GB_WAVE] = false; update_sample(gb, GB_WAVE, 0, 0); } @@ -1158,9 +1291,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.pulse_length = (0x100 - value); break; case GB_IO_NR32: - gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; + gb->apu.wave_channel.shift = (const uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; if (gb->apu.is_active[GB_WAVE]) { - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + update_wave_sample(gb, 0); } break; case GB_IO_NR33: @@ -1170,44 +1303,45 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR34: gb->apu.wave_channel.sample_length &= 0xFF; gb->apu.wave_channel.sample_length |= (value & 7) << 8; - if ((value & 0x80)) { + if (value & 0x80) { + 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) && gb->apu.is_active[GB_WAVE] && - gb->apu.wave_channel.sample_countdown == 0 && - gb->apu.wave_channel.enable) { + gb->apu.wave_channel.sample_countdown == 0) { unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; /* This glitch varies between models and even specific instances: 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]; - gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; - gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1]; } else { memcpy(gb->io_registers + GB_IO_WAV_START, gb->io_registers + GB_IO_WAV_START + (offset & ~3), 4); - memcpy(gb->apu.wave_channel.wave_form, - gb->apu.wave_channel.wave_form + (offset & ~3) * 2, - 8); } } - if (!gb->apu.is_active[GB_WAVE]) { + gb->apu.wave_channel.current_sample_index = 0; + if (gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0) { + gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START]; + } + if (gb->apu.wave_channel.enable) { gb->apu.is_active[GB_WAVE] = true; update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + (gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift, 0); } gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; - gb->apu.wave_channel.current_sample_index = 0; if (gb->apu.wave_channel.pulse_length == 0) { gb->apu.wave_channel.pulse_length = 0x100; gb->apu.wave_channel.length_enabled = false; @@ -1216,7 +1350,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) { @@ -1232,17 +1366,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } gb->apu.wave_channel.length_enabled = value & 0x40; - if (gb->apu.is_active[GB_WAVE] && !gb->apu.wave_channel.enable) { - gb->apu.is_active[GB_WAVE] = false; - update_sample(gb, GB_WAVE, 0, 0); - } break; /* Noise Channel */ case GB_IO_NR41: { - gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f)); + gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3F)); break; } @@ -1256,9 +1386,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); } @@ -1271,18 +1401,18 @@ 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) { gb->apu.noise_channel.counter_countdown = - divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + divisor + (divisor == 2? 0 : (const 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]); + divisor + (divisor == 2? 0 : (const 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)) { @@ -1301,15 +1431,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) { @@ -1324,15 +1454,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } else { 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]; + gb->apu.noise_channel.counter_countdown += (const 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]; + gb->apu.noise_channel.counter_countdown += (const 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; + gb->apu.noise_channel.delta = 2; } else { gb->apu.noise_channel.counter_countdown -= 4; @@ -1362,12 +1492,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) { @@ -1401,12 +1531,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.length_enabled = value & 0x40; break; } - - default: - if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; - } } gb->io_registers[reg] = value; } @@ -1418,8 +1542,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) @@ -1429,10 +1551,13 @@ 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; +} + +unsigned GB_get_sample_rate(GB_gameboy_t *gb) +{ + return gb->apu_output.sample_rate; } void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) @@ -1445,15 +1570,195 @@ 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) +void GB_set_interference_volume(GB_gameboy_t *gb, double volume) { - 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 */ + gb->apu_output.interference_volume = volume; +} + +typedef struct __attribute__((packed)) { + uint32_t format_chunk; // = BE32('FORM') + uint32_t size; // = BE32(file size - 8) + uint32_t format; // = BE32('AIFC') + + uint32_t fver_chunk; // = BE32('FVER') + uint32_t fver_size; // = BE32(4) + uint32_t fver; + + uint32_t comm_chunk; // = BE32('COMM') + uint32_t comm_size; // = BE32(0x18) + + uint16_t channels; // = BE16(2) + uint32_t samples_per_channel; // = BE32(total number of samples / 2) + uint16_t bit_depth; // = BE16(16) + uint16_t frequency_exponent; + uint64_t frequency_significand; + uint32_t compression_type; // = 'NONE' (BE) or 'twos' (LE) + uint16_t compression_name; // = 0 + + uint32_t ssnd_chunk; // = BE32('SSND') + uint32_t ssnd_size; // = BE32(length of samples - 8) + uint32_t ssnd_offset; // = 0 + uint32_t ssnd_block; // = 0 +} aiff_header_t; + +typedef struct __attribute__((packed)) { + uint32_t marker; // = BE32('RIFF') + uint32_t size; // = LE32(file size - 8) + uint32_t type; // = BE32('WAVE') + + uint32_t fmt_chunk; // = BE32('fmt ') + uint32_t fmt_size; // = LE16(16) + uint16_t format; // = LE16(1) + uint16_t channels; // = LE16(2) + uint32_t sample_rate; // = LE32(sample_rate) + uint32_t byte_rate; // = LE32(sample_rate * 4) + uint16_t frame_size; // = LE32(4) + uint16_t bit_depth; // = LE16(16) + + uint32_t data_chunk; // = BE32('data') + uint32_t data_size; // = LE32(length of samples) +} wav_header_t; + + +int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format) +{ + if (gb->apu_output.sample_rate == 0) { + return EINVAL; + } + + if (gb->apu_output.output_file) { + GB_stop_audio_recording(gb); + } + gb->apu_output.output_file = fopen(path, "wb"); + if (!gb->apu_output.output_file) return errno; + + gb->apu_output.output_format = format; + switch (format) { + case GB_AUDIO_FORMAT_RAW: + return 0; + case GB_AUDIO_FORMAT_AIFF: { + aiff_header_t header = {0,}; + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return errno; + } + return 0; + } + case GB_AUDIO_FORMAT_WAV: { + wav_header_t header = {0,}; + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return errno; + } + return 0; + } + default: + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + return EINVAL; + } +} +int GB_stop_audio_recording(GB_gameboy_t *gb) +{ + if (!gb->apu_output.output_file) { + int ret = gb->apu_output.output_error ?: -1; + gb->apu_output.output_error = 0; + return ret; + } + gb->apu_output.output_error = 0; + switch (gb->apu_output.output_format) { + case GB_AUDIO_FORMAT_RAW: + break; + case GB_AUDIO_FORMAT_AIFF: { + size_t file_size = ftell(gb->apu_output.output_file); + size_t frames = (file_size - sizeof(aiff_header_t)) / sizeof(GB_sample_t); + aiff_header_t header = { + .format_chunk = BE32('FORM'), + .size = BE32(file_size - 8), + .format = BE32('AIFC'), + + .fver_chunk = BE32('FVER'), + .fver_size = BE32(4), + .fver = BE32(0xA2805140), + + .comm_chunk = BE32('COMM'), + .comm_size = BE32(0x18), + .channels = BE16(2), + .samples_per_channel = BE32(frames), + .bit_depth = BE16(16), +#ifdef GB_BIG_ENDIAN + .compression_type = 'NONE', +#else + .compression_type = 'twos', +#endif + .compression_name = 0, + .ssnd_chunk = BE32('SSND'), + .ssnd_size = BE32(frames * sizeof(GB_sample_t) - 8), + .ssnd_offset = 0, + .ssnd_block = 0, + }; + + uint64_t significand = gb->apu_output.sample_rate; + uint16_t exponent = 0x403E; + while ((int64_t)significand > 0) { + significand <<= 1; + exponent--; + } + header.frequency_exponent = BE16(exponent); + header.frequency_significand = BE64(significand); + + fseek(gb->apu_output.output_file, 0, SEEK_SET); + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + gb->apu_output.output_error = errno; + } + break; + } + case GB_AUDIO_FORMAT_WAV: { + size_t file_size = ftell(gb->apu_output.output_file); + size_t frames = (file_size - sizeof(wav_header_t)) / sizeof(GB_sample_t); + wav_header_t header = { + .marker = BE32('RIFF'), + .size = LE32(file_size - 8), + .type = BE32('WAVE'), + + .fmt_chunk = BE32('fmt '), + .fmt_size = LE16(16), + .format = LE16(1), + .channels = LE16(2), + .sample_rate = LE32(gb->apu_output.sample_rate), + .byte_rate = LE32(gb->apu_output.sample_rate * 4), + .frame_size = LE32(4), + .bit_depth = LE16(16), + + .data_chunk = BE32('data'), + .data_size = LE32(frames * sizeof(GB_sample_t)), + }; + + fseek(gb->apu_output.output_file, 0, SEEK_SET); + if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) { + gb->apu_output.output_error = errno; + } + break; + } } + fclose(gb->apu_output.output_file); + gb->apu_output.output_file = NULL; + + int ret = gb->apu_output.output_error; + gb->apu_output.output_error = 0; + return ret; } -void GB_set_interference_volume(GB_gameboy_t *gb, double volume) + +void GB_set_channel_muted(GB_gameboy_t *gb, GB_channel_t channel, bool muted) { - gb->apu_output.interference_volume = volume; + assert(channel < GB_N_CHANNELS); + gb->apu_output.channel_muted[channel] = muted; +} + +bool GB_is_channel_muted(GB_gameboy_t *gb, GB_channel_t channel) +{ + return gb->apu_output.channel_muted[channel]; } diff --git a/thirdparty/SameBoy/Core/apu.h b/thirdparty/SameBoy/Core/apu.h index e44b0b6ff..8f51c7a13 100644 --- a/thirdparty/SameBoy/Core/apu.h +++ b/thirdparty/SameBoy/Core/apu.h @@ -3,8 +3,8 @@ #include #include #include -#include "gb_struct_def.h" - +#include +#include "defs.h" #ifdef GB_INTERNAL /* Speed = 1 / Length (in seconds) */ @@ -38,13 +38,13 @@ typedef struct double right; } GB_double_sample_t; -enum GB_CHANNELS { +typedef enum { GB_SQUARE_1, GB_SQUARE_2, GB_WAVE, GB_NOISE, GB_N_CHANNELS -}; +} GB_channel_t; typedef struct { @@ -58,7 +58,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]; @@ -75,19 +75,22 @@ 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; + uint8_t delay; // Hack for CGB D/E phantom step due to how sample_countdown is implemented in SameBoy + bool did_tick; } square_channels[2]; struct { @@ -99,10 +102,10 @@ typedef struct uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint8_t current_sample_index; - uint8_t current_sample; // Current sample before shifting. - - int8_t wave_form[32]; + uint8_t current_sample_byte; // Current sample byte. bool wave_form_just_read; + bool pulsed; + uint8_t bugged_read_countdown; } wave_channel; struct { @@ -113,29 +116,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; -#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; } GB_apu_t; typedef enum { @@ -145,11 +143,16 @@ typedef enum { GB_HIGHPASS_MAX } GB_highpass_mode_t; +typedef enum { + GB_AUDIO_FORMAT_RAW, // Native endian + GB_AUDIO_FORMAT_AIFF, // Native endian + GB_AUDIO_FORMAT_WAV, +} GB_audio_format_t; + typedef struct { unsigned sample_rate; - 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; @@ -157,6 +160,7 @@ typedef struct { GB_sample_t current_sample[GB_N_CHANNELS]; GB_sample_t summed_samples[GB_N_CHANNELS]; double dac_discharge[GB_N_CHANNELS]; + bool channel_muted[GB_N_CHANNELS]; GB_highpass_mode_t highpass_mode; double highpass_rate; @@ -164,27 +168,33 @@ typedef struct { GB_sample_callback_t sample_callback; - bool rate_set_in_clocks; double interference_volume; double interference_highpass; + + FILE *output_file; + GB_audio_format_t output_format; + int output_error; + } GB_apu_output_t; +void GB_set_channel_muted(GB_gameboy_t *gb, GB_channel_t channel, bool muted); +bool GB_is_channel_muted(GB_gameboy_t *gb, GB_channel_t channel); void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); +unsigned GB_get_sample_rate(GB_gameboy_t *gb); void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_set_interference_volume(GB_gameboy_t *gb, double volume); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); - +int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format); +int GB_stop_audio_recording(GB_gameboy_t *gb); #ifdef GB_INTERNAL -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); -void GB_borrow_sgb_border(GB_gameboy_t *gb); +internal bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, GB_channel_t 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, bool force); #endif #endif /* apu_h */ diff --git a/thirdparty/SameBoy/Core/camera.c b/thirdparty/SameBoy/Core/camera.c index 7751f18fe..22ecf5de0 100644 --- a/thirdparty/SameBoy/Core/camera.c +++ b/thirdparty/SameBoy/Core/camera.c @@ -3,7 +3,7 @@ static uint32_t noise_seed = 0; /* This is not a complete emulation of the camera chip. Only the features used by the Game Boy Camera ROMs are supported. - We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ + We also do not emulate the timing of the real cart when a webcam is used, as it might be actually faster than the webcam. */ static uint8_t generate_noise(uint8_t x, uint8_t y) { @@ -55,10 +55,6 @@ static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) { - if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { - /* Forbid reading the image while the camera is busy. */ - return 0xFF; - } uint8_t tile_x = addr / 0x10 % 0x10; uint8_t tile_y = addr / 0x10 / 0x10; @@ -112,6 +108,12 @@ void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_call void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback) { + if (gb->camera_countdown > 0 && callback) { + GB_log(gb, "Camera update request callback set while camera was proccessing, clearing camera countdown.\n"); + gb->camera_countdown = 0; + GB_camera_updated(gb); + } + gb->camera_update_request_callback = callback; } @@ -125,12 +127,25 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) addr &= 0x7F; if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { value &= 0x7; - noise_seed = rand(); - if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) { - /* If no callback is set, ignore the write as if the camera is instantly done */ - gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1; - gb->camera_update_request_callback(gb); + noise_seed = GB_random(); + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + if (gb->camera_update_request_callback) { + gb->camera_update_request_callback(gb); + } + else { + /* If no callback is set, wait the amount of time the real camera would take before clearing the busy bit */ + uint16_t exposure = (gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) | gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]; + gb->camera_countdown = 129792 + ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x80)? 0 : 2048) + (exposure * 64) + (gb->camera_alignment & 4); + } } + + if (!(value & 1) && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + /* We don't support cancelling a camera shoot */ + GB_log(gb, "ROM attempted to cancel camera shoot, which is currently not supported. The camera shoot will not be cancelled.\n"); + value |= 1; + } + + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] = value; } else { if (addr >= 0x36) { diff --git a/thirdparty/SameBoy/Core/camera.h b/thirdparty/SameBoy/Core/camera.h index 21c69b68e..1461f3adf 100644 --- a/thirdparty/SameBoy/Core/camera.h +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Core/cheats.c b/thirdparty/SameBoy/Core/cheats.c index c7c43fe3f..8b5a7a0ee 100644 --- a/thirdparty/SameBoy/Core/cheats.c +++ b/thirdparty/SameBoy/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)) { @@ -250,7 +251,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 +268,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/thirdparty/SameBoy/Core/cheats.h b/thirdparty/SameBoy/Core/cheats.h index cf8aa20d9..f9c076c64 100644 --- a/thirdparty/SameBoy/Core/cheats.h +++ b/thirdparty/SameBoy/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); +internal void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); #endif #endif diff --git a/thirdparty/SameBoy/Core/debugger.c b/thirdparty/SameBoy/Core/debugger.c index 371a86521..f2554e95e 100644 --- a/thirdparty/SameBoy/Core/debugger.c +++ b/thirdparty/SameBoy/Core/debugger.c @@ -155,12 +155,20 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer return output; } +static GB_symbol_map_t *get_symbol_map(GB_gameboy_t *gb, uint16_t bank) +{ + if (bank >= gb->n_symbol_maps) { + return NULL; + } + return gb->bank_symbols[bank]; +} + static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) { if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); static __thread char output[256]; - const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); + const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value); if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { symbol = NULL; @@ -428,23 +436,23 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (length == 1) { switch (string[0]) { - case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; - case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->af}; + case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->af}; + case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->bc}; + case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->bc}; + case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->de}; + case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->de}; + case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->hl}; + case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->hl}; } } else if (length == 2) { switch (string[0]) { - case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; - case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; + case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->af}; + case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->bc}; + case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->de}; + case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->hl}; + case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->sp}; case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } @@ -606,23 +614,23 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (length == 1) { switch (string[0]) { - case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; - case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; - case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; - case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; - case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; - case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; - case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; - case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; + case 'a': ret = VALUE_16(gb->af >> 8); goto exit; + case 'f': ret = VALUE_16(gb->af & 0xFF); goto exit; + case 'b': ret = VALUE_16(gb->bc >> 8); goto exit; + case 'c': ret = VALUE_16(gb->bc & 0xFF); goto exit; + case 'd': ret = VALUE_16(gb->de >> 8); goto exit; + case 'e': ret = VALUE_16(gb->de & 0xFF); goto exit; + case 'h': ret = VALUE_16(gb->hl >> 8); goto exit; + case 'l': ret = VALUE_16(gb->hl & 0xFF); goto exit; } } else if (length == 2) { switch (string[0]) { - case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} - case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} - case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} - case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} - case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} + case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->af); goto exit;} + case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->bc); goto exit;} + case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->de); goto exit;} + case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->hl); goto exit;} + case 's': if (string[1] == 'p') {ret = VALUE_16(gb->sp); goto exit;} case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} } } @@ -702,7 +710,7 @@ static const char *lstrip(const char *str) #define STOPPED_ONLY \ if (!gb->debug_stopped) { \ -GB_log(gb, "Program is running. \n"); \ +GB_log(gb, "Program is running, use 'interrupt' to stop execution.\n"); \ return false; \ } @@ -741,23 +749,25 @@ static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return false; } -static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +static bool interrupt(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS - STOPPED_ONLY - + if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; } - - gb->debug_stopped = false; - gb->debug_next_command = true; - gb->debug_call_depth = 0; - return false; + + if (gb->debug_stopped) { + GB_log(gb, "Program already stopped.\n"); + return true; + } + + gb->debug_stopped = true; + return true; } -static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS STOPPED_ONLY @@ -767,10 +777,13 @@ static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; return false; } -static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS STOPPED_ONLY @@ -780,13 +793,10 @@ static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb return true; } - gb->debug_stopped = false; - gb->debug_fin_command = true; - gb->debug_call_depth = 0; return false; } -static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS STOPPED_ONLY @@ -797,7 +807,7 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifi } gb->debug_stopped = false; - gb->stack_leak_detection = true; + gb->debug_fin_command = true; gb->debug_call_depth = 0; return false; } @@ -811,15 +821,15 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const } - GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ + GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->af, /* AF can't really be an address */ (gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); - GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); - GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); - GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); - GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); return true; @@ -911,13 +921,14 @@ static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_ size_t length = strlen(symbol_prefix); while (context->bank < 0x200) { - if (gb->bank_symbols[context->bank] == NULL || - context->symbol >= gb->bank_symbols[context->bank]->n_symbols) { + GB_symbol_map_t *map = get_symbol_map(gb, context->bank); + if (map == NULL || + context->symbol >= map->n_symbols) { context->bank++; context->symbol = 0; continue; } - const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name; + const char *candidate = map->symbols[context->symbol++].name; if (memcmp(symbol_prefix, candidate, length) == 0) { return strdup(candidate + length); } @@ -1451,7 +1462,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 +1475,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; @@ -1537,20 +1548,27 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } else { static const char *const mapper_names[] = { - [GB_MBC1] = "MBC1", - [GB_MBC2] = "MBC2", - [GB_MBC3] = "MBC3", - [GB_MBC5] = "MBC5", - [GB_HUC1] = "HUC-1", - [GB_HUC3] = "HUC-3", + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_MBC7] = "MBC7", + [GB_MMM01] = "MMM01", + [GB_HUC1] = "HUC-1", + [GB_HUC3] = "HUC-3", + [GB_CAMERA] = "MAC-GBD", + }; GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); } + if (cartridge->mbc_type == GB_MMM01 || cartridge->mbc_type == GB_MBC1) { + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + } GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); if (cartridge->has_ram) { GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); if (gb->cartridge_type->mbc_type != GB_HUC1) { - GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + GB_log(gb, "RAM is currently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } } if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { @@ -1596,18 +1614,40 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +static char *keep_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"keep"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS STOPPED_ONLY - - if (strlen(lstrip(arguments))) { + bool keep = false; + if (strcmp(lstrip(arguments), "keep") == 0) { + keep = true; + } + else if (lstrip(arguments)[0]) { print_usage(gb, command); return true; } - GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks); - gb->debugger_ticks = 0; + 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"); + if (!keep) { + gb->debugger_ticks = 0; + gb->absolute_debugger_ticks = 0; + } return true; } @@ -1634,9 +1674,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"); } @@ -1645,6 +1685,29 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d return true; } +static bool dma(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!GB_is_dma_active(gb)) { + GB_log(gb, "DMA is inactive\n"); + return true; + } + + if (gb->dma_current_dest == 0xFF) { + GB_log(gb, "DMA warming up\n"); // Shouldn't actually happen, as it only lasts 2 T-cycles + return true; + } + + GB_log(gb, "Next DMA write: [$FE%02X] = [$%04X]\n", gb->dma_current_dest, gb->dma_current_src); + + return true; +} + static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS @@ -1653,15 +1716,15 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg return true; } 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->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"); - GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800"); - GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); - GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); - GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); + GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)? "Enabled" : "Disabled"); + GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Object priority flags" : "Background and Window"), + (gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_EN)? "Enabled" : "Disabled"); + GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN)? "Enabled" : "Disabled"); + GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_SIZE)? "8x16" : "8x8"); + GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_MAP)? "$9C00" : "$9800"); + GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL)? "$8000" : "$8800"); + GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)? "Enabled" : "Disabled"); + GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_MAP)? "$9C00" : "$9800"); GB_log(gb, "\nSTAT:\n"); static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; @@ -1676,7 +1739,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "\nCurrent line: %d\n", gb->current_line); GB_log(gb, "Current state: "); - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) { GB_log(gb, "Off\n"); } else if (gb->display_state == 7 || gb->display_state == 8) { @@ -1686,8 +1749,13 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2); } else if (gb->mode_for_interrupt == 3) { - signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; - GB_log(gb, "Rendering pixel (%d/160)\n", pixel); + if (((uint8_t)(gb->position_in_line + 16) < 8)) { + GB_log(gb, "Adjusting for scrolling (%d/%d)\n", gb->position_in_line & 7, gb->io_registers[GB_IO_SCX] & 7); + } + else { + signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; + GB_log(gb, "Rendering pixel (%d/160)\n", pixel); + } } else { GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2); @@ -1696,6 +1764,17 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7, gb->io_registers[GB_IO_WY]); GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); + GB_log(gb, "Background shifter size: %d\n", gb->bg_fifo.size); + GB_log(gb, "Background fetcher state: %s\n", (const char *[]){ + "Tile (1/2)", + "Tile (2/2)", + "Low data (1/2)", + "Low data (2/2)", + "High data (1/2)", + "High data (2/2)", + "Push (1/2)", + "Push (2/2)", + }[gb->fetcher_state & 7]); return true; } @@ -1703,54 +1782,60 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; + const char *stripped = lstrip(arguments); + if (strlen(stripped)) { + if (stripped[0] != 0 && (stripped[0] < '1' || stripped[0] > '5')) { + print_usage(gb, command); + return true; + } } - - GB_log(gb, "Current state: "); - if (!gb->apu.global_enable) { - GB_log(gb, "Disabled\n"); - } - else { - GB_log(gb, "Enabled\n"); - for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { - GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, - gb->apu.is_active[channel] ? "active " : "inactive", - GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", - gb->apu.samples[channel]); + if (stripped[0] == 0 || stripped[0] == '5') { + GB_log(gb, "Current state: "); + if (!gb->apu.global_enable) { + GB_log(gb, "Disabled\n"); + } + else { + GB_log(gb, "Enabled\n"); + for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { + GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, + gb->apu.is_active[channel] ? "active " : "inactive", + GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", + gb->apu.samples[channel]); + } } - } - GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); - if (gb->io_registers[GB_IO_NR51] & 0x0f) { - for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { - if (gb->io_registers[GB_IO_NR51] & mask) { - GB_log(gb, " CH%u", channel + 1); + GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); + if (gb->io_registers[GB_IO_NR51] & 0x0F) { + for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } } } - } - else { - GB_log(gb, " no channels"); - } - GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); - GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); - if (gb->io_registers[GB_IO_NR51] & 0xf0) { - for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { - if (gb->io_registers[GB_IO_NR51] & mask) { - GB_log(gb, " CH%u", channel + 1); + GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); + if (gb->io_registers[GB_IO_NR51] & 0xF0) { + for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } } } + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); } - else { - GB_log(gb, " no channels"); - } - GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { + if (stripped[0] != 0 && stripped[0] != ('1') + channel) continue; + GB_log(gb, "\nCH%u:\n", channel + 1); GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", gb->apu.square_channels[channel].current_volume, @@ -1765,10 +1850,10 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; 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)" : ""); + duty > 3? "" : (const char *const[]){"12.5", " 25", " 50", " 75"}[duty], + duty > 3? "" : (const char *const[]){"_______-", "-______-", "-____---", "_------_"}[duty], + 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", @@ -1790,49 +1875,53 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } } + if (stripped[0] == 0 || stripped[0] == '3') { + GB_log(gb, "\nCH3:\n"); + GB_log(gb, " Wave:"); + for (uint8_t i = 0; i < 16; i++) { + GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4); + GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF); + } + GB_log(gb, "\n"); + GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); - GB_log(gb, "\nCH3:\n"); - GB_log(gb, " Wave:"); - for (uint8_t i = 0; i < 32; i++) { - GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); - } - GB_log(gb, "\n"); - GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); - - GB_log(gb, " Volume %s (right-shifted %u times)\n", - gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift], - gb->apu.wave_channel.shift); + GB_log(gb, " Volume %s (right-shifted %u times)\n", + gb->apu.wave_channel.shift > 4? "" : (const char *const[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift], + gb->apu.wave_channel.shift); - GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", - gb->apu.wave_channel.sample_length ^ 0x7ff, - gb->apu.wave_channel.sample_countdown); + GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.wave_channel.sample_length ^ 0x7FF, + gb->apu.wave_channel.sample_countdown); - if (gb->apu.wave_channel.length_enabled) { - GB_log(gb, " Channel will end in %u 256 Hz ticks\n", - gb->apu.wave_channel.pulse_length); + if (gb->apu.wave_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.wave_channel.pulse_length); + } } - GB_log(gb, "\nCH4:\n"); - GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n", - gb->apu.noise_channel.current_volume, - gb->apu.noise_channel.counter, - gb->apu.noise_channel.counter_countdown); + if (stripped[0] == 0 || stripped[0] == '4') { + GB_log(gb, "\nCH4:\n"); + GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n", + gb->apu.noise_channel.current_volume, + gb->apu.noise_channel.counter, + gb->apu.noise_channel.counter_countdown); - GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", - gb->apu.noise_channel.volume_countdown, - gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", - gb->io_registers[GB_IO_NR42] & 7); + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.noise_channel.volume_countdown, + gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", + gb->io_registers[GB_IO_NR42] & 7); - GB_log(gb, " LFSR in %u-step mode, current value ", - gb->apu.noise_channel.narrow? 7 : 15); - for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { - GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); - } + GB_log(gb, " LFSR in %u-step mode, current value ", + gb->apu.noise_channel.narrow? 7 : 15); + for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { + GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); + } - if (gb->apu.noise_channel.length_enabled) { - GB_log(gb, " Channel will end in %u 256 Hz ticks\n", - gb->apu.noise_channel.pulse_length); + if (gb->apu.noise_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.noise_channel.pulse_length); + } } @@ -1872,15 +1961,18 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug break; } } - mask = (0xf << (shift_amount - 1)) & 0xf; + mask = (0xF << (shift_amount - 1)) & 0xF; - for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { + for (int8_t cur_val = 0xF & mask; cur_val >= 0; cur_val -= shift_amount) { for (uint8_t i = 0; i < 32; i++) { - if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { - GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); + uint8_t sample = i & 1? + (gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) : + (gb->io_registers[GB_IO_WAV_START + i / 2] >> 4); + if ((sample & mask) == cur_val) { + GB_log(gb, "%X", sample); } else { - GB_log(gb, "%c", i%4 == 2 ? '-' : ' '); + GB_log(gb, "%c", i % 4 == 2 ? '-' : ' '); } } GB_log(gb, "\n"); @@ -1892,6 +1984,8 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS + STOPPED_ONLY + if (strlen(lstrip(arguments))) { print_usage(gb, command); return true; @@ -1914,54 +2008,55 @@ static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); -#define HELP_NEWLINE "\n " /* Commands without implementations are aliases of the previous non-alias commands */ static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, + {"interrupt", 1, interrupt, "Interrupt the program execution"}, {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, - {"undo", 1, undo, "Reverts the last command"}, - {"backtrace", 2, backtrace, "Displays the current call stack"}, - {"bt", 2, }, /* Alias */ - {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"}, - {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE - "used"}, + {"undo", 1, undo, "Revert the last command"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, - {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, - {"mbc", 3, }, /* Alias */ - {"apu", 3, apu, "Displays information about the current state of the audio chip"}, - {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE - "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE - "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, - {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, - {"palettes", 3, palettes, "Displays the current CGB palettes"}, - {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE - "Can also modify the condition of existing breakpoints." HELP_NEWLINE - "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE + {"backtrace", 2, backtrace, "Display the current call stack"}, + {"bt", 2, }, /* Alias */ + {"print", 1, print, "Evaluate and print an expression " + "Use modifier to format as an address (a, default) or as a number in " + "decimal (d), hexadecimal (x), octal (o) or binary (b).", + "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, + {"eval", 2, }, /* Alias */ + {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, + {"x", 1, }, /* Alias */ + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression " + "Can also modify the condition of existing breakpoints. " + "If the j modifier is used, the breakpoint will occur just before " "jumping to the target.", "[ if ]", "j", .argument_completer = symbol_completer, .modifiers_completer = j_completer}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]", .argument_completer = symbol_completer}, - {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE - "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression. " + "Can also modify the condition and type of existing watchpoints. " "Default watchpoint type is write-only.", "[ if ]", "(r|w|rw)", .argument_completer = symbol_completer, .modifiers_completer = rw_completer }, {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]", .argument_completer = symbol_completer}, + {"softbreak", 2, softbreak, "Enable or disable software breakpoints ('ld b, b' opcodes)", "(on|off)", .argument_completer = on_off_completer}, {"list", 1, list, "List all set breakpoints and watchpoints"}, - {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE - "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE - "decimal (d), hexadecimal (x), octal (o) or binary (b).", - "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, - {"eval", 2, }, /* Alias */ - {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, - {"x", 1, }, /* Alias */ - {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, - + {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was " + "used. Use 'keep' as an argument to display ticks without reseeting " + "the count.", "(keep)", .argument_completer = keep_completer}, + {"cartridge", 2, mbc, "Display information about the MBC and cartridge"}, + {"mbc", 3, }, /* Alias */ + {"apu", 3, apu, "Display information about the current state of the audio processing " + "unit", "[channel (1-4, 5 for NR5x)]"}, + {"wave", 3, wave, "Print a visual representation of the wave RAM. " + "Modifiers can be used for a (f)ull print (the default), " + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, + {"lcd", 3, lcd, "Display information about the current state of the LCD controller"}, + {"palettes", 3, palettes, "Display the current CGB palettes"}, + {"dma", 3, dma, "Display the current OAM DMA status"}, {"help", 1, help, "List available commands or show help for the specified command", "[]"}, {NULL,}, /* Null terminator */ @@ -1994,7 +2089,24 @@ static void print_command_description(GB_gameboy_t *gb, const debugger_command_t { print_command_shortcut(gb, command); GB_log(gb, ": "); - GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); + GB_log(gb, "%s", (const char *)&" " + strlen(command->command)); + + const char *string = command->help_string; + const unsigned width = 80 - 13; + while (strlen(string) > width) { + const char *space = string + width; + while (*space != ' ') { + space--; + if (space == string) { + // This help string has some extra long word? Abort line-breaking, it's going to break anyway. + GB_log(gb, "%s\n", string); + return; + } + } + GB_log(gb, "%.*s\n ", (unsigned)(space - string), string); + string = space + 1; + } + GB_log(gb, "%s\n", string); } static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) @@ -2029,61 +2141,33 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) { /* Called just after the CPU calls a function/enters an interrupt/etc... */ - if (gb->stack_leak_detection) { - if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { - GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); - gb->debug_stopped = true; - } - else { - gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; - gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; - } - } - if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { - while (gb->backtrace_size) { - if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { + if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) { gb->backtrace_size--; + gb->debug_call_depth--; } else { break; } } - gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; + gb->backtrace_sps[gb->backtrace_size] = gb->sp; gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); gb->backtrace_returns[gb->backtrace_size].addr = call_addr; gb->backtrace_size++; + gb->debug_call_depth++; } - - gb->debug_call_depth++; } void GB_debugger_ret_hook(GB_gameboy_t *gb) { /* Called just before the CPU runs ret/reti */ - gb->debug_call_depth--; - - if (gb->stack_leak_detection) { - if (gb->debug_call_depth < 0) { - GB_log(gb, "Function finished without a stack leak.\n"); - gb->debug_stopped = true; - } - else { - if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { - GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); - GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], - gb->sp_for_call_depth[gb->debug_call_depth]); - gb->debug_stopped = true; - } - } - } - while (gb->backtrace_size) { - if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { + if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) { gb->backtrace_size--; + gb->debug_call_depth--; } else { break; @@ -2184,9 +2268,15 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) /* Returns true if debugger waits for more commands */ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) { + while (*input == ' ') { + input++; + } if (!input[0]) { return true; } + + GB_display_sync(gb); + GB_apu_run(gb, true); char *command_string = input; char *arguments = strchr(input, ' '); @@ -2206,6 +2296,7 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) modifiers++; } + gb->help_shown = true; const debugger_command_t *command = find_command(command_string); if (command) { uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb)); @@ -2234,12 +2325,11 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) return ret; } else { - GB_log(gb, "%s: no such command.\n", command_string); + GB_log(gb, "%s: no such command. Type 'help' to list the available debugger commands.\n", command_string); return true; } } -/* Returns true if debugger waits for more commands */ char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context) { char *command_string = input; @@ -2317,10 +2407,14 @@ void GB_debugger_run(GB_gameboy_t *gb) if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { gb->debug_stopped = true; } - if (gb->debug_fin_command && gb->debug_call_depth == -1) { + if (gb->debug_fin_command && gb->debug_call_depth <= -1) { gb->debug_stopped = true; } if (gb->debug_stopped) { + if (!gb->help_shown) { + gb->help_shown = true; + GB_log(gb, "Type 'help' to list the available debugger commands.\n"); + } GB_cpu_disassemble(gb, gb->pc, 5); } next_command: @@ -2379,7 +2473,6 @@ void GB_debugger_run(GB_gameboy_t *gb) if (gb->debug_stopped && !gb->debug_disable) { gb->debug_next_command = false; gb->debug_fin_command = false; - gb->stack_leak_detection = false; input = gb->input_callback(gb); if (input == NULL) { @@ -2408,7 +2501,12 @@ void GB_debugger_handle_async_commands(GB_gameboy_t *gb) void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol) { - bank &= 0x1FF; + if (bank >= gb->n_symbol_maps) { + gb->bank_symbols = realloc(gb->bank_symbols, (bank + 1) * sizeof(*gb->bank_symbols)); + while (bank >= gb->n_symbol_maps) { + gb->bank_symbols[gb->n_symbol_maps++] = NULL; + } + } if (!gb->bank_symbols[bank]) { gb->bank_symbols[bank] = GB_map_alloc(); @@ -2450,7 +2548,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) void GB_debugger_clear_symbols(GB_gameboy_t *gb) { - for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) { + for (unsigned i = gb->n_symbol_maps; i--;) { if (gb->bank_symbols[i]) { GB_map_free(gb->bank_symbols[i]); gb->bank_symbols[i] = 0; @@ -2463,15 +2561,20 @@ void GB_debugger_clear_symbols(GB_gameboy_t *gb) gb->reversed_symbol_map.buckets[i] = next; } } + gb->n_symbol_maps = 0; + if (gb->bank_symbols) { + free(gb->bank_symbols); + gb->bank_symbols = NULL; + } } const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) { uint16_t bank = bank_for_addr(gb, addr); - const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); + const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr); if (symbol) return symbol; - if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + if (bank != 0) return GB_map_find_symbol(get_symbol_map(gb, 0), addr); /* Maybe the symbol incorrectly uses bank 0? */ return NULL; } @@ -2534,7 +2637,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) { @@ -2560,13 +2663,13 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) { switch ((opcode >> 3) & 0x3) { case 0: - return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + return !(gb->af & GB_ZERO_FLAG); case 1: - return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + return (gb->af & GB_ZERO_FLAG); case 2: - return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + return !(gb->af & GB_CARRY_FLAG); case 3: - return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + return (gb->af & GB_CARRY_FLAG); } return false; @@ -2583,8 +2686,8 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) { - return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | - (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); + return GB_read_memory(gb, gb->sp) | + (GB_read_memory(gb, gb->sp + 1) << 8); } @@ -2624,7 +2727,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 */ @@ -2666,7 +2769,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || - !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { + !is_in_trivial_memory(gb->sp) || !is_in_trivial_memory(gb->sp + 1)) { return JUMP_TO_NONTRIVIAL; } @@ -2702,7 +2805,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/thirdparty/SameBoy/Core/debugger.h b/thirdparty/SameBoy/Core/debugger.h index 0678b30c9..3d77b7a8c 100644 --- a/thirdparty/SameBoy/Core/debugger.h +++ b/thirdparty/SameBoy/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); +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/thirdparty/SameBoy/Core/defs.h b/thirdparty/SameBoy/Core/defs.h new file mode 100644 index 000000000..53dd38fc5 --- /dev/null +++ b/thirdparty/SameBoy/Core/defs.h @@ -0,0 +1,50 @@ +#ifndef defs_h +#define defs_h + +#define GB_likely(x) __builtin_expect((bool)(x), 1) +#define GB_unlikely(x) __builtin_expect((bool)(x), 0) + +#ifdef GB_INTERNAL + +// "Keyword" definitions +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +#define internal +//#define internal __attribute__((visibility("internal"))) + +#if __clang__ +#define unrolled _Pragma("unroll") +#elif __GNUC__ >= 8 +#define unrolled _Pragma("GCC unroll 8") +#else +#define unrolled +#endif + +#define unreachable() __builtin_unreachable(); +#define nodefault default: unreachable() + +#ifdef GB_BIG_ENDIAN +#define LE16(x) __builtin_bswap16(x) +#define LE32(x) __builtin_bswap32(x) +#define LE64(x) __builtin_bswap64(x) +#define BE16(x) (x) +#define BE32(x) (x) +#define BE64(x) (x) +#else +#define LE16(x) (x) +#define LE32(x) (x) +#define LE64(x) (x) +#define BE16(x) __builtin_bswap16(x) +#define BE32(x) __builtin_bswap32(x) +#define BE64(x) __builtin_bswap64(x) +#endif +#endif + +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) +#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) +#endif + +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +#endif diff --git a/thirdparty/SameBoy/Core/display.c b/thirdparty/SameBoy/Core/display.c index b94ff26e7..30af98b0e 100644 --- a/thirdparty/SameBoy/Core/display.c +++ b/thirdparty/SameBoy/Core/display.c @@ -9,27 +9,32 @@ static inline unsigned fifo_size(GB_fifo_t *fifo) { - return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1); + return fifo->size; } static void fifo_clear(GB_fifo_t *fifo) { - fifo->read_end = fifo->write_end = 0; + fifo->read_end = fifo->size = 0; } -static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) +static const GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) { + assert(fifo->size); + assert(fifo->size <= 8); GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end]; fifo->read_end++; fifo->read_end &= (GB_FIFO_LENGTH - 1); + fifo->size--; return ret; } static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) { + assert(fifo->size == 0); + fifo->size = 8; if (!flip_x) { - unrolled for (unsigned i = 8; i--;) { - fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + unrolled for (unsigned i = 0; i < 8; i++) { + fifo->fifo[i] = (GB_fifo_item_t) { (lower >> 7) | ((upper >> 7) << 1), palette, 0, @@ -37,14 +42,11 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint }; lower <<= 1; upper <<= 1; - - fifo->write_end++; - fifo->write_end &= (GB_FIFO_LENGTH - 1); } } else { - unrolled for (unsigned i = 8; i--;) { - fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + unrolled for (unsigned i = 0; i < 8; i++) { + fifo->fifo[i] = (GB_fifo_item_t) { (lower & 1) | ((upper & 1) << 1), palette, 0, @@ -52,19 +54,15 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint }; lower >>= 1; upper >>= 1; - - fifo->write_end++; - fifo->write_end &= (GB_FIFO_LENGTH - 1); } } } static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x) { - while (fifo_size(fifo) < 8) { - fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,}; - fifo->write_end++; - fifo->write_end &= (GB_FIFO_LENGTH - 1); + while (fifo->size < GB_FIFO_LENGTH) { + fifo->fifo[(fifo->read_end + fifo->size) & (GB_FIFO_LENGTH - 1)] = (GB_fifo_item_t) {0,}; + fifo->size++; } uint8_t flip_xor = flip_x? 0: 0x7; @@ -85,7 +83,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 @@ -107,11 +105,13 @@ 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) -{ +void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type) +{ 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)) { @@ -124,9 +124,19 @@ 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_is_cgb(gb) && type == GB_VBLANK_TYPE_NORMAL_FRAME && gb->frame_repeat_countdown > 0 && gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + GB_handle_rumble(gb); + + if (gb->vblank_callback) { + gb->vblank_callback(gb, GB_VBLANK_TYPE_REPEAT); + } + GB_timing_sync(gb); + return; + } + + bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE; - 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] & GB_LCDC_ENABLE) || is_ppu_stopped) || 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; @@ -157,13 +167,19 @@ static void display_vblank(GB_gameboy_t *gb) GB_borrow_sgb_border(gb); uint32_t border_colors[16 * 4]; - if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) { + if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E) { uint16_t colors[] = { 0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648, 0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C, 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, }; - unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0; + unsigned index = gb->rom? gb->rom[0x14E] % 5 : 0; + if (gb->model == GB_MODEL_CGB_0) { + index = 1; // CGB 0 was only available in indigo! + } + else if (gb->model == GB_MODEL_CGB_A) { + index = 0; // CGB A was only available in red! + } gb->borrowed_border.palette[0] = LE16(colors[index]); gb->borrowed_border.palette[10] = LE16(colors[5 + index]); gb->borrowed_border.palette[14] = LE16(colors[10 + index]); @@ -206,7 +222,7 @@ static void display_vblank(GB_gameboy_t *gb) GB_handle_rumble(gb); if (gb->vblank_callback) { - gb->vblank_callback(gb); + gb->vblank_callback(gb, type); } GB_timing_sync(gb); } @@ -238,17 +254,17 @@ static inline uint8_t scale_channel(uint8_t x) static inline uint8_t scale_channel_with_curve(uint8_t 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]; + return (const 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,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]; + return (const 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) { - return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x]; + return (const uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x]; } @@ -269,36 +285,76 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) b = scale_channel_with_curve_sgb(b); } else { - bool agb = gb->model == GB_MODEL_AGB; + bool agb = gb->model > GB_MODEL_CGB_E; r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r); g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g); b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b); if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { uint8_t new_r, new_g, new_b; - if (agb) { - new_g = (g * 6 + b * 1) / 7; + if (g != b) { // Minor optimization + double gamma = 2.2; + if (gb->color_correction_mode < GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + /* Don't use absolutely gamma-correct mixing for the high-contrast + modes, to prevent the blue hues from being too washed out */ + gamma = 1.6; + } + + // TODO: Optimze pow out using a LUT + if (agb) { + new_g = round(pow((pow(g / 255.0, gamma) * 5 + pow(b / 255.0, gamma)) / 6, 1 / gamma) * 255); + } + else { + new_g = round(pow((pow(g / 255.0, gamma) * 3 + pow(b / 255.0, gamma)) / 4, 1 / gamma) * 255); + } } else { - new_g = (g * 3 + b) / 4; + new_g = g; } + new_r = r; new_b = b; if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { r = new_r; - g = new_r; - b = new_r; + g = new_g; + b = new_b; + + new_r = new_r * 15 / 16 + ( g + b) / 32; + new_g = new_g * 15 / 16 + (r + b) / 32; + new_b = new_b * 15 / 16 + (r + g ) / 32; - 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; + if (agb) { + new_r = new_r * (224 - 20) / 255 + 20; + new_g = new_g * (220 - 18) / 255 + 18; + new_b = new_b * (216 - 16) / 255 + 16; + } + else { + new_r = new_r * (220 - 40) / 255 + 40; + new_g = new_g * (224 - 36) / 255 + 36; + new_b = new_b * (216 - 32) / 255 + 32; + } + } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) { + r = new_r; + g = new_g; + b = new_b; + new_r = new_r * 15 / 16 + ( g + b) / 32; + new_g = new_g * 15 / 16 + (r + b) / 32; + new_b = new_b * 15 / 16 + (r + g ) / 32; - new_r = new_r * (224 - 32) / 255 + 32; - new_g = new_g * (220 - 36) / 255 + 36; - new_b = new_b * (216 - 40) / 255 + 40; + if (agb) { + new_r = new_r * (167 - 27) / 255 + 27; + new_g = new_g * (165 - 24) / 255 + 24; + new_b = new_b * (157 - 22) / 255 + 22; + } + else { + new_r = new_r * (162 - 45) / 255 + 45; + new_g = new_g * (167 - 41) / 255 + 41; + new_b = new_b * (157 - 38) / 255 + 38; + } } - else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST) { uint8_t old_max = MAX(r, MAX(g, b)); uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); @@ -311,10 +367,10 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) uint8_t old_min = MIN(r, MIN(g, b)); uint8_t new_min = MIN(new_r, MIN(new_g, new_b)); - if (new_min != 0xff) { - new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min); - new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min); - new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min); + if (new_min != 0xFF) { + new_r = 0xFF - (0xFF - new_r) * (0xFF - old_min) / (0xFF - new_min); + new_g = 0xFF - (0xFF - new_g) * (0xFF - old_min) / (0xFF - new_min); + new_b = 0xFF - (0xFF - new_b) * (0xFF - old_min) / (0xFF - new_min); } } r = new_r; @@ -337,10 +393,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) @@ -376,7 +432,10 @@ void GB_set_light_temperature(GB_gameboy_t *gb, double temperature) void GB_STAT_update(GB_gameboy_t *gb) { - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) return; + if (GB_is_dma_active(gb) && (gb->io_registers[GB_IO_STAT] & 3) == 2) { + gb->io_registers[GB_IO_STAT] &= ~3; + } bool previous_interrupt_line = gb->stat_interrupt_line; /* Set LY=LYC bit */ @@ -413,20 +472,19 @@ void GB_STAT_update(GB_gameboy_t *gb) void GB_lcd_off(GB_gameboy_t *gb) { + gb->cycles_for_line = 0; gb->display_state = 0; gb->display_cycles = 0; /* When the LCD is disabled, state is constant */ + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3)) { + gb->hdma_on = true; + } + /* When the LCD is off, LY is 0 and STAT mode is 0. */ gb->io_registers[GB_IO_LY] = 0; gb->io_registers[GB_IO_STAT] &= ~3; - if (gb->hdma_on_hblank) { - gb->hdma_on_hblank = false; - gb->hdma_on = false; - - /* Todo: is this correct? */ - gb->hdma_steps_left = 0xff; - } + gb->oam_read_blocked = false; gb->vram_read_blocked = false; @@ -439,50 +497,93 @@ 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 inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr) +{ + if (unlikely(gb->oam_ppu_blocked)) { + return 0xFF; + } + if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles? + if (gb->hdma_in_progress) { + return GB_read_oam(gb, (gb->hdma_current_src & ~1) | (addr & 1)); + } + if (gb->dma_current_dest != 0xA0) { + return gb->oam[(gb->dma_current_dest & ~1) | (addr & 1)]; + } + } + return gb->oam[addr]; } static void add_object_from_index(GB_gameboy_t *gb, unsigned index) { + if (likely(!GB_is_dma_active(gb) || gb->halted || gb->stopped)) { + gb->mode2_y_bus = oam_read(gb, index * 4); + gb->mode2_x_bus = oam_read(gb, index * 4 + 1); + } + if (gb->n_visible_objs == 10) return; /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */ - if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) { - return; + if (unlikely(GB_is_dma_active(gb) && (gb->halted || gb->stopped))) { + if (gb->model < GB_MODEL_CGB_E) { + return; + } + /* CGB-0 to CGB-D: Halted DMA blocks Mode 2; + Pre-CGB: Unit specific behavior, some units read FFs, some units read using + several different corruption pattterns. For simplicity, we emulate + FFs. */ } - if (gb->oam_ppu_blocked) { + if (unlikely(gb->oam_ppu_blocked)) { return; } - + + bool height_16 = (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_SIZE) != 0; + signed y = gb->mode2_y_bus - 16; /* This reverse sorts the visible objects by location and priority */ - GB_object_t *objects = (GB_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) { unsigned j = 0; for (; j < gb->n_visible_objs; j++) { - if (gb->obj_comparators[j] <= objects[index].x) break; + if (gb->objects_x[j] <= gb->mode2_x_bus) break; } memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); - memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j); + memmove(gb->objects_x + j + 1, gb->objects_x + j, gb->n_visible_objs - j); + memmove(gb->objects_y + j + 1, gb->objects_y + j, gb->n_visible_objs - j); gb->visible_objs[j] = index; - gb->obj_comparators[j] = objects[index].x; + gb->objects_x[j] = gb->mode2_x_bus; + gb->objects_y[j] = gb->mode2_y_bus; gb->n_visible_objs++; } } -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; - 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 + *cgb_d_glitch = false; + + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { + 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] &= ~GB_LCDC_TILE_SEL; + if (gb->fetcher_state == 3) { + *should_use = false; + *cgb_d_glitch = true; + return 0; + } + return 0; } return gb->data_for_sel_glitch; } @@ -490,26 +591,47 @@ static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use) 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; + + // Rendering (including scrolling adjustment) does not occur as long as an object at x=0 is pending + if (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN || GB_is_cgb(gb)) && + gb->objects_x[gb->n_visible_objs - 1] == 0) { + return; + } - if (fifo_size(&gb->bg_fifo)) { + if (unlikely(!fifo_size(&gb->bg_fifo))) return; + + if (unlikely(gb->insert_bg_pixel)) { + gb->insert_bg_pixel = false; + fifo_item = ({static const GB_fifo_item_t empty_item = {0,}; &empty_item;}); + } + else { fifo_item = fifo_pop(&gb->bg_fifo); - bg_priority = fifo_item->bg_priority; - - if (fifo_size(&gb->oam_fifo)) { - oam_fifo_item = fifo_pop(&gb->oam_fifo); - if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { - draw_oam = true; - bg_priority |= oam_fifo_item->bg_priority; - } + } + bg_priority = fifo_item->bg_priority; + + if (fifo_size(&gb->oam_fifo)) { + oam_fifo_item = fifo_pop(&gb->oam_fifo); + if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN) && unlikely(!gb->objects_disabled)) { + draw_oam = true; + bg_priority |= oam_fifo_item->bg_priority; } } - - if (!fifo_item) return; + // (gb->position_in_line + 16 < 8) is (gb->position_in_line < -8) in unsigned logic + if (((uint8_t)(gb->position_in_line + 16) < 8)) { + if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) { + gb->position_in_line = -8; + } + else if (gb->position_in_line == (uint8_t) -9) { + gb->position_in_line = -16; + return; + } + } /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { @@ -519,7 +641,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Mixing */ - if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_EN) == 0) { if (gb->cgb_mode) { bg_priority = false; } @@ -527,6 +649,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; @@ -585,7 +713,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]; } } @@ -600,6 +728,23 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) gb->window_is_being_fetched = false; } +static inline void dma_sync(GB_gameboy_t *gb, unsigned *cycles) +{ + if (unlikely(GB_is_dma_active(gb))) { + unsigned offset = *cycles - gb->display_cycles; // Time passed in 8MHz ticks + if (offset) { + *cycles = gb->display_cycles; + if (!gb->cgb_double_speed) { + offset >>= 1; // Convert to T-cycles + } + unsigned old = gb->dma_cycles; + gb->dma_cycles = offset; + GB_dma_run(gb); + gb->dma_cycles = old - offset; + } + } +} + /* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have slightly different timings than CPUs <= C. @@ -610,7 +755,45 @@ static inline uint8_t fetcher_y(GB_gameboy_t *gb) return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY]; } -static void advance_fetcher_state_machine(GB_gameboy_t *gb) +static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr) +{ + if (unlikely(gb->vram_ppu_blocked)) { + return 0xFF; + } + if (unlikely(gb->hdma_in_progress)) { + gb->addr_for_hdma_conflict = addr; + return 0; + } + // TODO: what if both? + else if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0 && (gb->dma_current_src & 0xE000) == 0x8000)) { // TODO: what happens in the last and first M cycles? + // DMAing from VRAM! + /* TODO: AGS has its own, very different pattern, but AGS is not currently a supported model */ + /* TODO: Research this when researching odd modes */ + /* TODO: probably not 100% on the first few reads during halt/stop modes*/ + unsigned offset = 1 - (gb->halted || gb->stopped); + if (GB_is_cgb(gb)) { + if (gb->dma_ppu_vram_conflict) { + addr = (gb->dma_ppu_vram_conflict_addr & 0x1FFF) | (addr & 0x2000); + } + else if (gb->dma_cycles_modulo && !gb->halted && !gb->stopped) { + addr &= 0x2000; + addr |= ((gb->dma_current_src - offset) & 0x1FFF); + } + else { + addr &= 0x2000 | ((gb->dma_current_src - offset) & 0x1FFF); + gb->dma_ppu_vram_conflict_addr = addr; + gb->dma_ppu_vram_conflict = !gb->halted && !gb->stopped; + } + } + else { + addr |= ((gb->dma_current_src - offset) & 0x1FFF); + } + gb->oam[gb->dma_current_dest - offset] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)]; + } + return gb->vram[addr]; +} + +static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) { typedef enum { GB_FETCHER_GET_TILE, @@ -620,7 +803,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) GB_FETCHER_SLEEP, } fetcher_step_t; - fetcher_step_t fetcher_state_machine [8] = { + static const fetcher_step_t fetcher_state_machine [8] = { GB_FETCHER_SLEEP, GB_FETCHER_GET_TILE, GB_FETCHER_SLEEP, @@ -632,18 +815,19 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) }; switch (fetcher_state_machine[gb->fetcher_state & 7]) { case GB_FETCHER_GET_TILE: { + dma_sync(gb, cycles); uint16_t map = 0x1800; - if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) { + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)) { gb->wx_triggered = false; gb->wx166_glitch = false; } /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_MAP && !gb->wx_triggered) { map = 0x1C00; } - else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) { + else if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_MAP && gb->wx_triggered) { map = 0x1C00; } @@ -653,41 +837,43 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->wx_triggered) { x = gb->window_tile_x; } + else if ((uint8_t)(gb->position_in_line + 16) < 8) { + x = gb->io_registers[GB_IO_SCX] >> 3; + } else { - x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; + /* TODO: There is some CGB timing error around here. + Adjusting SCX by 7 or less shouldn't have an effect on a CGB, + but SameBoy is affected by a change of both 7 and 6 (but not less). */ + x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8) / 8) & 0x1F; } if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } gb->last_tile_index_address = map + x + y / 8 * 32; - gb->current_tile = gb->vram[gb->last_tile_index_address]; - if (gb->vram_ppu_blocked) { - gb->current_tile = 0xFF; - } + gb->current_tile = vram_read(gb, gb->last_tile_index_address); if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. This probably means the CGB has a 16-bit data bus for the VRAM. */ - gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000]; - if (gb->vram_ppu_blocked) { - gb->current_tile_attributes = 0xFF; - } + gb->current_tile_attributes = vram_read(gb, gb->last_tile_index_address + 0x2000); } } gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_LOWER: { + dma_sync(gb, cycles); bool use_glitched = false; + bool cgb_d_glitch = false; if (gb->tile_sel_glitch) { - 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; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & 0x10) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { tile_address = gb->current_tile * 0x10; } else { @@ -701,34 +887,34 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) } if (!use_glitched) { gb->current_tile_data[0] = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[0] = 0xFF; - } + vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + } - else { + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) && gb->tile_sel_glitch) { gb->data_for_sel_glitch = - gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; - if (gb->vram_ppu_blocked) { - gb->data_for_sel_glitch = 0xFF; - } + vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + } + else if (cgb_d_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2); } } gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_HIGH: { + dma_sync(gb, cycles); /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ bool use_glitched = false; + 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; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); - if (gb->io_registers[GB_IO_LCDC] & 0x10) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { tile_address = gb->current_tile * 0x10; } else { @@ -741,41 +927,47 @@ 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]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[1] = 0xFF; - } + vram_read(gb, gb->last_tile_data_address); } - 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] & GB_LCDC_TILE_SEL) && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address); + + } + else if (cgb_d_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1); + } } if (gb->wx_triggered) { gb->window_tile_x++; - gb->window_tile_x &= 0x1f; + gb->window_tile_x &= 0x1F; } // fallthrough case GB_FETCHER_PUSH: { - if (gb->fetcher_state == 6) { - /* The background map index increase at this specific point. If this state is not reached, - it will simply not increase. */ - gb->fetcher_x++; - gb->fetcher_x &= 0x1f; - } if (gb->fetcher_state < 7) { gb->fetcher_state++; } if (fifo_size(&gb->bg_fifo) > 0) break; + if (unlikely(gb->wy_triggered && !(gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && !GB_is_cgb(gb) && !gb->disable_window_pixel_insertion_glitch)) { + /* See https://github.com/LIJI32/SameBoy/issues/278 for documentation */ + uint8_t logical_position = gb->position_in_line + 7; + if (logical_position > 167) { + logical_position = 0; + } + if (gb->io_registers[GB_IO_WX] == logical_position) { + gb->bg_fifo.read_end--; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; + gb->bg_fifo.size = 1; + break; + } + } + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); gb->fetcher_state = 0; @@ -787,60 +979,468 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state++; } break; + + nodefault; } } -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, uint8_t y, uint8_t tile, uint8_t flags) { - /* TODO: what does the PPU read if DMA is active? */ - if (gb->oam_ppu_blocked) { - static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; - object = &blocked; - } - - bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ - uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); + bool height_16 = (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_SIZE) != 0; /* Todo: Which T-cycle actually reads this? */ + uint8_t tile_y = (gb->current_line - y) & (height_16? 0xF : 7); - if (object->flags & 0x40) { /* Flip Y */ + if (flags & 0x40) { /* Flip Y */ tile_y ^= height_16? 0xF : 7; } /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ - uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; + uint16_t line_address = (height_16? tile & 0xFE : tile) * 0x10 + tile_y * 2; - if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ + if (gb->cgb_mode && (flags & 0x8)) { /* Use VRAM bank 2 */ line_address += 0x2000; } return line_address; } +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] & GB_LCDC_TILE_SEL) { + 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 + 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] & GB_LCDC_OBJ_EN)) { + 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; + const object_t *object = &objects[object_index]; + gb->n_visible_objs--; + + uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (gb->n_visible_objs == 0) { + gb->data_for_sel_glitch = data1; + } + 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++; + } + } + } + else { + object_buffer = (const void *)empty_object_buffer; + } + + + 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] & GB_LCDC_BG_EN))) { + 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] & GB_LCDC_BG_MAP) { + 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] & GB_LCDC_BG_EN))) {\ + 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] & GB_LCDC_WIN_ENABLE); + 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] & GB_LCDC_WIN_MAP? 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 + 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] & GB_LCDC_OBJ_EN)) { + 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]]; + gb->n_visible_objs--; + + uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (object->flags & 0x20) { + 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++; + } + } + } + else { + object_buffer = (const void *)empty_object_buffer; + } + + + 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] & GB_LCDC_BG_EN))) { + 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] & GB_LCDC_BG_MAP) { + 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] & GB_LCDC_BG_EN))) {\ + 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] & GB_LCDC_WIN_ENABLE); + 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] & GB_LCDC_WIN_MAP? 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->position_in_line != (uint8_t)-16) return 0; + if (gb->model & GB_MODEL_NO_SFC_BIT) return 0; + if (gb->hdma_on) return 0; + if (gb->stopped) return 0; + if (GB_is_dma_active(gb)) return 0; + if (gb->wy_triggered) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) { + if ((gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) { + return 0; + } + } + else { + if (gb->io_registers[GB_IO_WX] < 167 && !GB_is_cgb(gb)) { + return 0; + } + } + } + + // No objects or window, timing is trivial + if (gb->n_visible_objs == 0 && !(gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE))) 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; +} + +static inline uint8_t x_for_object_match(GB_gameboy_t *gb) +{ + uint8_t ret = gb->position_in_line + 8; + if (ret > (uint8_t)-16) return 0; + return ret; +} + /* TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles. The PPU logic can be greatly simplified if that delay is simply emulated. */ -void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) +void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) { + if (unlikely((gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE) && (signed)(gb->cycles_for_line * 2 + cycles + gb->display_cycles) > LINE_LENGTH * 2)) { + unsigned first_batch = (LINE_LENGTH * 2 - gb->cycles_for_line * 2 + gb->display_cycles); + GB_display_run(gb, first_batch, force); + cycles -= first_batch; + if (gb->display_state == 22) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + GB_STAT_update(gb); + } + gb->display_state = 9; + gb->display_cycles = 0; + } + if (unlikely(gb->delayed_glitch_hblank_interrupt && cycles && gb->current_line < LINES)) { + gb->delayed_glitch_hblank_interrupt = false; + gb->mode_for_interrupt = 0; + GB_STAT_update(gb); + gb->mode_for_interrupt = 3; + } + gb->cycles_since_vblank_callback += cycles / 2; + + if (cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= cycles; + } + else { + gb->frame_repeat_countdown = 0; + } + /* 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, GB_VBLANK_TYPE_ARTIFICIAL); } return; } - GB_object_t *objects = (GB_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); - // GB_STATE(gb, display, 9); + GB_STATE(gb, display, 9); GB_STATE(gb, display, 10); GB_STATE(gb, display, 11); GB_STATE(gb, display, 12); @@ -849,13 +1449,12 @@ 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); GB_STATE(gb, display, 23); - // GB_STATE(gb, display, 24); - GB_STATE(gb, display, 25); + GB_STATE(gb, display, 24); GB_STATE(gb, display, 26); GB_STATE(gb, display, 27); GB_STATE(gb, display, 28); @@ -873,13 +1472,15 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 40); GB_STATE(gb, display, 41); GB_STATE(gb, display, 42); + GB_STATE(gb, display, 43); } - if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) { while (true) { - GB_SLEEP(gb, display, 1, LCDC_PERIOD); - display_vblank(gb); - gb->cgb_repeated_a_frame = true; + 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_VBLANK_TYPE_LCD_OFF); } return; } @@ -894,6 +1495,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->current_line = 0; gb->window_y = -1; gb->wy_triggered = false; + gb->position_in_line = -16; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -913,6 +1515,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 34, 2); gb->n_visible_objs = 0; + gb->orig_n_visible_objs = 0; gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles. gb->io_registers[GB_IO_STAT] &= ~3; @@ -939,11 +1542,54 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->wx_triggered = false; gb->wx166_glitch = false; goto mode_3_start; - + + // Mode 3 abort, state 9 + display9: { + // TODO: Timing of things in this scenario is almost completely untested + if (gb->current_line < LINES && !GB_is_sgb(gb) && !gb->disable_rendering) { + GB_log(gb, "The ROM is preventing line %d from fully rendering, this could damage a real device's LCD display.\n", gb->current_line); + uint32_t *dest = NULL; + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + uint32_t color = GB_is_cgb(gb)? GB_convert_rgb15(gb, 0x7FFF, false) : gb->background_palettes_rgb[4]; + while (gb->lcd_x < 160) { + *(dest++) = color; + gb->lcd_x++; + } + } + gb->n_visible_objs = gb->orig_n_visible_objs; + gb->current_line++; + gb->cycles_for_line = 0; + if (gb->current_line != LINES) { + gb->cycles_for_line = 2; + GB_SLEEP(gb, display, 28, 2); + gb->io_registers[GB_IO_LY] = gb->current_line; + if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) { + gb->delayed_glitch_hblank_interrupt = true; + } + GB_STAT_update(gb); + gb->position_in_line = -15; + goto mode_3_start; + } + else { + if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) { + gb->delayed_glitch_hblank_interrupt = true; + } + gb->position_in_line = -16; + } + } + while (true) { /* Lines 0 - 143 */ gb->window_y = -1; 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; @@ -978,7 +1624,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->mode_for_interrupt = -1; GB_STAT_update(gb); gb->n_visible_objs = 0; + gb->orig_n_visible_objs = 0; + if (!GB_is_dma_active(gb) && !gb->oam_ppu_blocked) { + GB_BATCHPOINT(gb, display, 5, 80); + } for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { if (GB_is_cgb(gb)) { add_object_from_index(gb, gb->oam_search_index); @@ -994,11 +1644,10 @@ 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; - + gb->orig_n_visible_objs = gb->n_visible_objs; gb->accessed_oam_row = -1; gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 3; @@ -1023,8 +1672,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: + gb->disable_window_pixel_insertion_glitch = false; /* TODO: Timing seems incorrect, might need an access conflict handling. */ - if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && gb->io_registers[GB_IO_WY] == gb->current_line) { gb->wy_triggered = true; } @@ -1033,16 +1683,26 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) fifo_clear(&gb->oam_fifo); /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */ fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); - /* Todo: find out actual access time of SCX */ - gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->lcd_x = 0; - - gb->fetcher_x = 0; - gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); - /* 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, @@ -1051,16 +1711,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) that point. The code should be updated to represent this, and this will fix the time travel hack in WX's access conflict code. */ - if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)) { 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}; + static const uint8_t scx_to_wx0_comparisons[] = {-7, -1, -2, -3, -4, -5, -6, -6}; if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { should_activate_window = true; } } else if (gb->wx166_glitch) { - static const uint8_t scx_to_wx166_comparisons[] = {-8, -9, -10, -11, -12, -13, -14, -15}; + static const uint8_t scx_to_wx166_comparisons[] = {-16, -1, -2, -3, -4, -5, -6, -7}; if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { should_activate_window = true; } @@ -1098,34 +1758,30 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->window_y++; } } - - /* TODO: What happens when WX=0? */ + + /* TODO: What happens when WX=0?*/ if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && - gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) { + gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) && gb->bg_fifo.size == 8) { // Insert a pixel right at the FIFO's end - gb->bg_fifo.read_end--; - gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; - gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; - gb->window_is_being_fetched = false; + gb->insert_bg_pixel = true; } - + /* 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 && - (gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) && - gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { + gb->objects_x[gb->n_visible_objs - 1] < x_for_object_match(gb)) { gb->n_visible_objs--; } gb->during_object_fetch = true; while (gb->n_visible_objs != 0 && - (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && - gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { + (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN || GB_is_cgb(gb)) && + gb->objects_x[gb->n_visible_objs - 1] == x_for_object_match(gb)) { while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) { - advance_fetcher_state_machine(gb); + advance_fetcher_state_machine(gb, &cycles); gb->cycles_for_line++; GB_SLEEP(gb, display, 27, 1); if (gb->object_fetch_aborted) { @@ -1133,20 +1789,8 @@ 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->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; - if (gb->object_fetch_aborted) { - goto abort_fetching_object; - } - } - } - /* TODO: Can this be deleted? { */ - advance_fetcher_state_machine(gb); + advance_fetcher_state_machine(gb, &cycles); gb->cycles_for_line++; GB_SLEEP(gb, display, 41, 1); if (gb->object_fetch_aborted) { @@ -1154,18 +1798,28 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* } */ - advance_fetcher_state_machine(gb); - - gb->cycles_for_line += 3; - GB_SLEEP(gb, display, 20, 3); + advance_fetcher_state_machine(gb, &cycles); + dma_sync(gb, &cycles); + gb->mode2_y_bus = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 2); + gb->object_flags = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 3); + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 20, 2); if (gb->object_fetch_aborted) { goto abort_fetching_object; } - gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]); + /* TODO: timing not verified */ + dma_sync(gb, &cycles); + gb->object_low_line_address = get_object_line_address(gb, + gb->objects_y[gb->n_visible_objs - 1], + gb->mode2_y_bus, + gb->object_flags); + gb->object_tile_data[0] = vram_read(gb, gb->object_low_line_address); + - gb->cycles_for_line++; - GB_SLEEP(gb, display, 39, 1); + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 39, 2); if (gb->object_fetch_aborted) { goto abort_fetching_object; } @@ -1173,24 +1827,28 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->during_object_fetch = false; gb->cycles_for_line++; GB_SLEEP(gb, display, 40, 1); - - const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; - uint16_t line_address = get_object_line_address(gb, object); + /* TODO: timing not verified */ + dma_sync(gb, &cycles); + gb->object_tile_data[1] = vram_read(gb, get_object_line_address(gb, + gb->objects_y[gb->n_visible_objs - 1], + gb->mode2_y_bus, + gb->object_flags) + 1); + - uint8_t palette = (object->flags & 0x10) ? 1 : 0; + uint8_t palette = (gb->object_flags & 0x10) ? 1 : 0; if (gb->cgb_mode) { - palette = object->flags & 0x7; + palette = gb->object_flags & 0x7; } fifo_overlay_object_row(&gb->oam_fifo, - gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address], - gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1], + gb->object_tile_data[0], + gb->object_tile_data[1], palette, - object->flags & 0x80, + gb->object_flags & 0x80, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, - object->flags & 0x20); + gb->object_flags & 0x20); - gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1]; + gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address + 1]; gb->n_visible_objs--; } @@ -1199,23 +1857,26 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->during_object_fetch = false; render_pixel_if_possible(gb); - advance_fetcher_state_machine(gb); + advance_fetcher_state_machine(gb, &cycles); if (gb->position_in_line == 160) break; gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } - - /* TODO: Verify */ +skip_slow_mode_3: + gb->position_in_line = -16; + + /* TODO: This seems incorrect (glitches Tesserae), verify further */ + /* if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { 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. */ + /* Oh no! The PPU and LCD desynced! Fill the rest of the line with the last color. */ uint32_t *dest = NULL; if (gb->border_mode != GB_BORDER_ALWAYS) { dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; @@ -1223,13 +1884,13 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) else { dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; } - *dest = gb->background_palettes_rgb[0]; + *dest = (gb->lcd_x == 0)? gb->background_palettes_rgb[0] : dest[-1]; gb->lcd_x++; } /* TODO: Verify timing */ - if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { + if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && gb->io_registers[GB_IO_WX] == 166) { gb->wx166_glitch = true; } else { @@ -1267,32 +1928,43 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_SLEEP(gb, display, 33, 2); gb->cgb_palettes_blocked = !gb->cgb_double_speed; + if (gb->hdma_on_hblank && !gb->halted && !gb->stopped) { + gb->hdma_on = true; + } + gb->cycles_for_line += 2; GB_SLEEP(gb, display, 36, 2); gb->cgb_palettes_blocked = false; - gb->cycles_for_line += 8; - GB_SLEEP(gb, display, 25, 8); + if (gb->cycles_for_line > LINE_LENGTH - 2) { + gb->cycles_for_line = 0; + GB_SLEEP(gb, display, 43, LINE_LENGTH - gb->cycles_for_line); + goto display9; + } - if (gb->hdma_on_hblank) { - gb->hdma_starting = true; + { + uint16_t cycles_for_line = gb->cycles_for_line; + gb->cycles_for_line = 0; + GB_SLEEP(gb, display, 11, LINE_LENGTH - cycles_for_line - 2); } - GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2); /* TODO: Verify double speed timing TODO: Timing differs on a DMG */ - if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && (gb->io_registers[GB_IO_WY] == gb->current_line)) { gb->wy_triggered = true; } + gb->cycles_for_line = 0; GB_SLEEP(gb, display, 31, 2); - gb->mode_for_interrupt = 2; + if (gb->current_line != LINES - 1) { + gb->mode_for_interrupt = 2; + } // Todo: unverified timing gb->current_lcd_line++; if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { - display_vblank(gb); + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); } if (gb->icd_hreset_callback) { @@ -1302,66 +1974,79 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) 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; - GB_SLEEP(gb, display, 26, 2); - if (gb->current_line == LINES) { - gb->mode_for_interrupt = 2; + 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; + } GB_SLEEP(gb, display, 12, 2); + if (gb->delayed_glitch_hblank_interrupt) { + gb->delayed_glitch_hblank_interrupt = false; + gb->mode_for_interrupt = 0; + } gb->ly_for_comparison = gb->current_line; - + GB_STAT_update(gb); + GB_SLEEP(gb, display, 24, 1); + if (gb->current_line == LINES) { /* Entering VBlank state triggers the OAM interrupt */ gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 1; gb->io_registers[GB_IO_IF] |= 1; - gb->mode_for_interrupt = 2; - GB_STAT_update(gb); + if (!gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) { + gb->io_registers[GB_IO_IF] |= 2; + } gb->mode_for_interrupt = 1; GB_STAT_update(gb); if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { if (GB_is_cgb(gb)) { - GB_timing_sync(gb); - gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; - display_vblank(gb); + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); } - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } } else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; - display_vblank(gb); - } - if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { - gb->cgb_repeated_a_frame = true; - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; - } - else { - gb->cgb_repeated_a_frame = false; + GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME); } } } - GB_STAT_update(gb); - GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); + /* 3640 is just a few cycles less than 4 lines, no clue where the + AGB constant comes from (These are measured and confirmed) */ + gb->frame_repeat_countdown = LINES * LINE_LENGTH * 2 + (gb->model > GB_MODEL_CGB_E? 5982 : 3640); // 8MHz units + if (gb->display_cycles < gb->frame_repeat_countdown) { + gb->frame_repeat_countdown -= gb->display_cycles; + } + else { + gb->frame_repeat_countdown = 0; + } + + GB_SLEEP(gb, display, 13, LINE_LENGTH - 5); } /* 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; @@ -1408,7 +2093,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; } @@ -1458,18 +2143,18 @@ 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; } - if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { - map = 0x1c00; + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_MAP)) { + map = 0x1C00; } if (tileset_type == GB_TILESET_AUTO) { - tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + tileset_type = (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL)? GB_TILESET_8800 : GB_TILESET_8000; } for (unsigned y = 0; y < 256; y++) { @@ -1510,31 +2195,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] & GB_LCDC_OBJ_SIZE) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; - for (unsigned 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++) { - signed sprite_y = sprite->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; + for (signed y = 0; y < LINES; y++) { + object_t *object = (object_t *) &gb->oam; + uint8_t objects_in_line = 0; + bool obscured = false; + for (uint8_t i = 0; i < 40; i++, object++) { + signed object_y = object->y - 16; + // 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; @@ -1551,7 +2236,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 ); @@ -1559,7 +2244,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; } @@ -1572,3 +2257,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/thirdparty/SameBoy/Core/display.h b/thirdparty/SameBoy/Core/display.h index fdaf172ad..38105bb3c 100644 --- a/thirdparty/SameBoy/Core/display.h +++ b/thirdparty/SameBoy/Core/display.h @@ -5,14 +5,22 @@ #include #include +typedef enum { + GB_VBLANK_TYPE_NORMAL_FRAME, // An actual Vblank-triggered frame + GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off + GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason + GB_VBLANK_TYPE_REPEAT, // A frame that would not render on actual hardware, but the screen should retain the previous frame +} GB_vblank_type_t; + #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); +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, GB_vblank_type_t type); +#define GB_display_sync(gb) GB_display_run(gb, 0, true) enum { - GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility GB_OBJECT_PRIORITY_X, GB_OBJECT_PRIORITY_INDEX, }; @@ -48,16 +56,28 @@ typedef struct { typedef enum { GB_COLOR_CORRECTION_DISABLED, GB_COLOR_CORRECTION_CORRECT_CURVES, - GB_COLOR_CORRECTION_EMULATE_HARDWARE, - GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, + GB_COLOR_CORRECTION_MODERN_BALANCED, + GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST, GB_COLOR_CORRECTION_REDUCE_CONTRAST, + GB_COLOR_CORRECTION_LOW_CONTRAST, + GB_COLOR_CORRECTION_MODERN_ACCURATE, } GB_color_correction_mode_t; +static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BALANCED instead"))) GB_COLOR_CORRECTION_EMULATE_HARDWARE = GB_COLOR_CORRECTION_MODERN_BALANCED; +static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST instead"))) GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST; + 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); 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/thirdparty/SameBoy/Core/gb.c b/thirdparty/SameBoy/Core/gb.c index ae2a30c48..62cff3417 100644 --- a/thirdparty/SameBoy/Core/gb.c +++ b/thirdparty/SameBoy/Core/gb.c @@ -121,10 +121,21 @@ static void load_default_border(GB_gameboy_t *gb) } #endif - if (gb->model == GB_MODEL_AGB) { + if (gb->model > GB_MODEL_CGB_E) { #include "graphics/agb_border.inc" LOAD_BORDER(); } + 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(); @@ -135,7 +146,19 @@ static void load_default_border(GB_gameboy_t *gb) } } -void GB_init(GB_gameboy_t *gb, GB_model_t model) +size_t GB_allocation_size(void) +{ + return sizeof(GB_gameboy_t); +} + +GB_gameboy_t *GB_alloc(void) +{ + GB_gameboy_t *ret = malloc(sizeof(*ret)); + ret->magic = 0; + return ret; +} + +GB_gameboy_t *GB_init(GB_gameboy_t *gb, GB_model_t model) { memset(gb, 0, sizeof(*gb)); gb->model = model; @@ -162,6 +185,7 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model) GB_reset(gb); load_default_border(gb); + return gb; } GB_model_t GB_get_model(GB_gameboy_t *gb) @@ -205,7 +229,16 @@ void GB_free(GB_gameboy_t *gb) GB_remove_cheat(gb, gb->cheats[0]); } #endif - memset(gb, 0, sizeof(*gb)); + GB_stop_audio_recording(gb); + memset(gb, 0, sizeof(*gb)); +} + +void GB_dealloc(GB_gameboy_t *gb) +{ + if (GB_is_inited(gb)) { + GB_free(gb); + } + free(gb); } int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) @@ -284,7 +317,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) gb->rom_size |= gb->rom_size >> 1; gb->rom_size++; } - if (gb->rom_size == 0) { + if (gb->rom_size < 0x8000) { gb->rom_size = 0x8000; } fseek(f, 0, SEEK_SET); @@ -302,10 +335,32 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +#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) { GB_reset(gb); - GB_write_memory(gb, 0xFF00 + GB_IO_LCDC, 0x80); + GB_write_memory(gb, 0xFF00 + GB_IO_LCDC, GB_LCDC_ENABLE); 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); @@ -323,33 +378,49 @@ 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 = 0x100; + 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) { 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) +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) < 0x400 || - LE16(gb->gbs_header.load_address) >= 0x8000) { - GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno)); - fclose(f); + ((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"); 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)) { @@ -358,22 +429,23 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) gb->rom_size++; } - if (gb->rom_size == 0) { + if (gb->rom_size < 0x8000) { 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) { free(gb->mbc_ram); gb->mbc_ram = NULL; + gb->mbc_ram_size = 0; } if (gb->cartridge_type->has_ram) { @@ -382,32 +454,23 @@ 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); } - // 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 - } + bool has_interrupts = gb->gbs_header.TAC & 0x40; - // 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); + 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); + } + GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1); if (info) { @@ -425,6 +488,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 = 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); + 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"); @@ -436,11 +518,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); @@ -467,15 +546,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; @@ -496,14 +571,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; @@ -527,9 +598,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; @@ -544,9 +613,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; @@ -559,9 +626,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; @@ -569,9 +634,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; @@ -662,7 +725,7 @@ done:; void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) { - gb->rom_size = (size + 0x3fff) & ~0x3fff; + gb->rom_size = (size + 0x3FFF) & ~0x3FFF; while (gb->rom_size & (gb->rom_size - 1)) { gb->rom_size |= gb->rom_size >> 1; gb->rom_size++; @@ -674,7 +737,7 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz free(gb->rom); } gb->rom = malloc(gb->rom_size); - memset(gb->rom, 0xff, gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); memcpy(gb->rom, buffer, size); GB_configure_cart(gb); gb->tried_loading_sgb_border = false; @@ -693,7 +756,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; @@ -702,7 +765,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)) { @@ -711,17 +774,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); @@ -745,10 +808,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); } @@ -764,36 +827,25 @@ 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) { 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), - 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, + 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, }; -#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; @@ -804,11 +856,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)); } @@ -832,8 +880,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); @@ -841,25 +889,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), - gb->huc3_alarm_enabled, + 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); @@ -867,7 +904,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; @@ -878,11 +915,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; @@ -895,7 +928,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--;) { @@ -911,13 +944,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. */ @@ -932,21 +965,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->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 + 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; if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ goto reset_rtc; @@ -954,7 +978,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): @@ -974,11 +998,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): @@ -992,11 +1012,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: @@ -1016,9 +1032,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; } @@ -1036,12 +1054,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. */ @@ -1055,21 +1073,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->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 + 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; + if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ goto reset_rtc; @@ -1077,7 +1087,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)); @@ -1096,11 +1106,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): @@ -1114,11 +1120,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: @@ -1138,26 +1140,28 @@ 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; } -uint8_t GB_run(GB_gameboy_t *gb) +unsigned GB_run(GB_gameboy_t *gb) { gb->vblank_just_occured = false; - if (gb->sgb && gb->sgb->intro_animation < 140) { + if (gb->sgb && gb->sgb->intro_animation < 96) { /* On the SGB, the GB is halted after finishing the boot ROM. Then, after the boot animation is almost done, it's reset. Since the SGB HLE does not perform any header validity checks, 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; } @@ -1169,6 +1173,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; } @@ -1197,6 +1204,11 @@ void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) gb->screen = output; } +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb) +{ + return gb->screen; +} + void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) { gb->vblank_callback = callback; @@ -1224,22 +1236,37 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } -const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}}; -const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}}; -const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}}; -const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}}; +void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback) +{ + 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; +} + +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_callback_t callback) +{ + gb->lcd_status_callback = callback; +} + +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xAA, 0xAA, 0xAA}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xA5, 0x63}, {0xC6, 0xDE, 0x8C}, {0xD2, 0xE6, 0xA6}}}; +const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0E}, {0x3A, 0x4C, 0x3A}, {0x81, 0x8D, 0x66}, {0xC2, 0xCE, 0x93}, {0xCF, 0xDA, 0xAC}}}; +const GB_palette_t GB_PALETTE_GBL = {{{0x0A, 0x1C, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xB4, 0x95}, {0x7F, 0xE2, 0xC3}, {0x91, 0xEA, 0xD0}}}; 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 @@ -1254,6 +1281,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) { @@ -1293,26 +1325,38 @@ void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfe bool GB_serial_get_data_bit(GB_gameboy_t *gb) { + if (!(gb->io_registers[GB_IO_SC] & 0x80)) { + /* Disabled serial returns 0 bits */ + return false; + } + if (gb->io_registers[GB_IO_SC] & 1) { /* Internal Clock */ GB_log(gb, "Serial read request while using internal clock. \n"); - return 0xFF; + return true; } return gb->io_registers[GB_IO_SB] & 0x80; } void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data) { + if (!(gb->io_registers[GB_IO_SC] & 0x80)) { + /* Serial disabled */ + return; + } + if (gb->io_registers[GB_IO_SC] & 1) { /* Internal Clock */ GB_log(gb, "Serial write request while using internal clock. \n"); return; } + gb->io_registers[GB_IO_SB] <<= 1; gb->io_registers[GB_IO_SB] |= data; gb->serial_count++; if (gb->serial_count == 8) { gb->io_registers[GB_IO_IF] |= 8; + gb->io_registers[GB_IO_SC] &= ~0x80; gb->serial_count = 0; } } @@ -1332,9 +1376,14 @@ 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; +} + +bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb) { - return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; + return gb->cgb_mode; } bool GB_is_sgb(GB_gameboy_t *gb) @@ -1371,8 +1420,10 @@ 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 */ + case GB_MODEL_AGB_A: /* Unverified */ + case GB_MODEL_GBP_A: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = GB_random(); } @@ -1401,14 +1452,28 @@ 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_A: + 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; + 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; @@ -1416,16 +1481,21 @@ 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: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: - case GB_MODEL_AGB: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: for (unsigned i = 0; i < sizeof(gb->hram); i++) { gb->hram[i] = GB_random(); } 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 */ @@ -1445,13 +1515,19 @@ 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: case GB_MODEL_CGB_E: - case GB_MODEL_AGB: - /* Zero'd out by boot ROM anyway*/ + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + /* Zero'd out by boot ROM anyway */ 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 */ @@ -1474,12 +1550,27 @@ 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: case GB_MODEL_CGB_E: - case GB_MODEL_AGB: - /* Initialized by CGB-A and newer, 0s in CGB-0*/ + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + /* 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 */ @@ -1487,18 +1578,13 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: { - uint8_t temp; for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { if (i & 1) { - temp = GB_random() & GB_random() & GB_random(); + gb->io_registers[GB_IO_WAV_START + i] = GB_random() & GB_random() & GB_random(); } else { - temp = GB_random() | GB_random() | GB_random(); + gb->io_registers[GB_IO_WAV_START + i] = GB_random() | GB_random() | GB_random(); } - gb->apu.wave_channel.wave_form[i * 2] = temp >> 4; - gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF; - gb->io_registers[GB_IO_WAV_START + i] = temp; - } break; } @@ -1511,13 +1597,17 @@ 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); GB_palette_changed(gb, false, i * 2); } } + + if (!gb->cartridge_type->has_battery) { + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + } } static void request_boot_rom(GB_gameboy_t *gb) @@ -1528,6 +1618,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: @@ -1538,11 +1631,18 @@ 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_A: + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: type = GB_BOOT_ROM_CGB; break; - case GB_MODEL_AGB: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: type = GB_BOOT_ROM_AGB; break; } @@ -1550,15 +1650,41 @@ static void request_boot_rom(GB_gameboy_t *gb) } } -void GB_reset(GB_gameboy_t *gb) +static void GB_reset_internal(GB_gameboy_t *gb, bool quick) { + struct { + uint8_t hram[sizeof(gb->hram)]; + uint8_t background_palettes_data[sizeof(gb->background_palettes_data)]; + uint8_t object_palettes_data[sizeof(gb->object_palettes_data)]; + uint8_t oam[sizeof(gb->oam)]; + uint8_t extra_oam[sizeof(gb->extra_oam)]; + uint8_t dma, obp0, obp1; + } *preserved_state = NULL; + + if (quick) { + preserved_state = alloca(sizeof(*preserved_state)); + memcpy(preserved_state->hram, gb->hram, sizeof(gb->hram)); + memcpy(preserved_state->background_palettes_data, gb->background_palettes_data, sizeof(gb->background_palettes_data)); + memcpy(preserved_state->object_palettes_data, gb->object_palettes_data, sizeof(gb->object_palettes_data)); + memcpy(preserved_state->oam, gb->oam, sizeof(gb->oam)); + memcpy(preserved_state->extra_oam, gb->extra_oam, sizeof(gb->extra_oam)); + preserved_state->dma = gb->io_registers[GB_IO_DMA]; + preserved_state->obp0 = gb->io_registers[GB_IO_OBP0]; + preserved_state->obp1 = gb->io_registers[GB_IO_OBP1]; + } + 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)); + memcpy(GB_GET_SECTION(gb, rtc), rtc_section, sizeof(rtc_section)); gb->model = model; gb->version = GB_STRUCT_VERSION; - gb->mbc_rom_bank = 1; + GB_reset_mbc(gb); + gb->last_rtc_second = time(NULL); gb->cgb_ram_bank = 1; gb->io_registers[GB_IO_JOYP] = 0xCF; @@ -1578,18 +1704,12 @@ void GB_reset(GB_gameboy_t *gb) update_dmg_palette(gb); } - reset_ram(gb); - /* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */ - gb->serial_cycles = 0x100-0xF7; + gb->serial_mask = 0x80; gb->io_registers[GB_IO_SC] = 0x7E; - - /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ - gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; - gb->accessed_oam_row = -1; - - + gb->dma_current_dest = 0xA1; + if (GB_is_hle_sgb(gb)) { if (!gb->sgb) { gb->sgb = malloc(sizeof(*gb->sgb)); @@ -1611,20 +1731,44 @@ 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); - if (gb->nontrivial_jump_state) { free(gb->nontrivial_jump_state); gb->nontrivial_jump_state = NULL; } + if (!quick) { + reset_ram(gb); + /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far. + The retain their previous values on quick resets */ + gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; + } + else { + memcpy(gb->hram, preserved_state->hram, sizeof(gb->hram)); + memcpy(gb->background_palettes_data, preserved_state->background_palettes_data, sizeof(gb->background_palettes_data)); + memcpy(gb->object_palettes_data, preserved_state->object_palettes_data, sizeof(gb->object_palettes_data)); + memcpy(gb->oam, preserved_state->oam, sizeof(gb->oam)); + memcpy(gb->extra_oam, preserved_state->extra_oam, sizeof(gb->extra_oam)); + gb->io_registers[GB_IO_DMA] = preserved_state->dma; + gb->io_registers[GB_IO_OBP0] = preserved_state->obp0; + gb->io_registers[GB_IO_OBP1] = preserved_state->obp1; + } + gb->magic = state_magic(); request_boot_rom(gb); } +void GB_reset(GB_gameboy_t *gb) +{ + GB_reset_internal(gb, false); +} + +void GB_quick_reset(GB_gameboy_t *gb) +{ + GB_reset_internal(gb, true); +} + void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) { gb->model = model; @@ -1662,7 +1806,11 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * switch (access) { case GB_DIRECT_ACCESS_ROM: *size = gb->rom_size; - *bank = gb->mbc_rom_bank; + *bank = gb->mbc_rom_bank & (gb->rom_size / 0x4000 - 1); + return gb->rom; + case GB_DIRECT_ACCESS_ROM0: + *size = gb->rom_size; + *bank = gb->mbc_rom0_bank & (gb->rom_size / 0x4000 - 1); return gb->rom; case GB_DIRECT_ACCESS_RAM: *size = gb->ram_size; @@ -1670,7 +1818,7 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * return gb->ram; case GB_DIRECT_ACCESS_CART_RAM: *size = gb->mbc_ram_size; - *bank = gb->mbc_ram_bank; + *bank = gb->mbc_ram_bank & (gb->mbc_ram_size / 0x2000 - 1); return gb->mbc_ram; case GB_DIRECT_ACCESS_VRAM: *size = gb->vram_size; @@ -1697,9 +1845,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; @@ -1711,28 +1859,42 @@ 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; - GB_apu_update_cycles_per_sample(gb); + 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) +{ + return gb->unmultiplied_clock_rate; +} + +void GB_update_clock_rate(GB_gameboy_t *gb) { if (gb->model & GB_MODEL_PAL_BIT) { - return SGB_PAL_FREQUENCY; + gb->unmultiplied_clock_rate = SGB_PAL_FREQUENCY; } - if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { - return SGB_NTSC_FREQUENCY; + else if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + gb->unmultiplied_clock_rate = SGB_NTSC_FREQUENCY; } - return CPU_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; @@ -1810,19 +1972,89 @@ 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; } -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) { - if (gb->rtc_mode != mode) { - gb->rtc_mode = mode; - gb->rtc_cycles = 0; - gb->last_rtc_second = time(NULL); + memset(title, 0, 17); + if (gb->rom_size >= 0x4000) { + for (unsigned i = 0; i < 0x10; i++) { + if (gb->rom[0x134 + i] < 0x20 || gb->rom[0x134 + i] >= 0x80) break; + title[i] = gb->rom[0x134 + i]; + } + } +} + +uint32_t GB_get_rom_crc32(GB_gameboy_t *gb) +{ + static const uint32_t table[] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + const uint8_t *byte = gb->rom; + uint32_t size = gb->rom_size; + uint32_t ret = 0xFFFFFFFF; + while (size--) { + ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8); } + return ~ret; } diff --git a/thirdparty/SameBoy/Core/gb.h b/thirdparty/SameBoy/Core/gb.h index a2e8db19f..de4c331a3 100644 --- a/thirdparty/SameBoy/Core/gb.h +++ b/thirdparty/SameBoy/Core/gb.h @@ -3,9 +3,10 @@ #define typeof __typeof__ #include #include +#include #include -#include "gb_struct_def.h" +#include "defs.h" #include "save_state.h" #include "apu.h" @@ -24,29 +25,19 @@ #include "cheats.h" #include "rumble.h" #include "workboy.h" +#include "random.h" -#define GB_STRUCT_VERSION 13 +#define GB_STRUCT_VERSION 14 #define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_GBP_BIT 0x20 #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") -#elif __GNUC__ >= 8 -#define unrolled _Pragma("GCC unroll 8") -#else -#define unrolled -#endif - -#endif +#define GB_REWIND_FRAMES_PER_KEY 255 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define GB_BIG_ENDIAN @@ -56,31 +47,20 @@ #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) +#define GB_REGISTER_ORDER a, f, \ + b, c, \ + d, e, \ + h, l #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; }) +#define GB_REGISTER_ORDER f, a, \ + c, b, \ + e, d, \ + l, h #endif typedef struct { - struct { + struct GB_color_s { uint8_t r, g, b; } colors[5]; } GB_palette_t; @@ -127,16 +107,23 @@ 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, - // GB_MODEL_CGB_A = 0x201, - // GB_MODEL_CGB_B = 0x202, + GB_MODEL_CGB_0 = 0x200, + 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_AGB_0 = 0x206, + GB_MODEL_AGB_A = 0x207, + GB_MODEL_GBP_A = GB_MODEL_AGB_A | GB_MODEL_GBP_BIT, // AGB-A inside a Game Boy Player + GB_MODEL_AGB = GB_MODEL_AGB_A, + GB_MODEL_GBP = GB_MODEL_GBP_A, + //GB_MODEL_AGB_B = 0x208 + //GB_MODEL_AGB_E = 0x209 + //GB_MODEL_GBP_E = GB_MODEL_AGB_E | GB_MODEL_GBP_BIT, // AGB-E inside a Game Boy Player } GB_model_t; enum { @@ -145,15 +132,27 @@ enum { GB_REGISTER_DE, GB_REGISTER_HL, GB_REGISTER_SP, + GB_REGISTER_PC, GB_REGISTERS_16_BIT /* Count */ }; /* Todo: Actually use these! */ enum { - GB_CARRY_FLAG = 16, - GB_HALF_CARRY_FLAG = 32, - GB_SUBTRACT_FLAG = 64, - GB_ZERO_FLAG = 128, + GB_CARRY_FLAG = 0x10, + GB_HALF_CARRY_FLAG = 0x20, + GB_SUBTRACT_FLAG = 0x40, + GB_ZERO_FLAG = 0x80, +}; + +enum { + GB_LCDC_BG_EN = 1, + GB_LCDC_OBJ_EN = 2, + GB_LCDC_OBJ_SIZE = 4, + GB_LCDC_BG_MAP = 8, + GB_LCDC_TILE_SEL = 0x10, + GB_LCDC_WIN_ENABLE = 0x20, + GB_LCDC_WIN_MAP = 0x40, + GB_LCDC_ENABLE = 0x80, }; typedef enum { @@ -178,7 +177,7 @@ enum { /* Missing */ - GB_IO_IF = 0x0f, // Interrupt Flag (R/W) + GB_IO_IF = 0x0F, // Interrupt Flag (R/W) /* Sound */ GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W) @@ -191,11 +190,11 @@ enum { GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W) GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W) GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W) - GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W) - GB_IO_NR31 = 0x1b, // Channel 3 Sound Length - GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W) - GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W) - GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W) + GB_IO_NR30 = 0x1A, // Channel 3 Sound on/off (R/W) + GB_IO_NR31 = 0x1B, // Channel 3 Sound Length + GB_IO_NR32 = 0x1C, // Channel 3 Select output level (R/W) + GB_IO_NR33 = 0x1D, // Channel 3 Frequency's lower data (W) + GB_IO_NR34 = 0x1E, // Channel 3 Frequency's higher data (R/W) /* NR40 does not exist */ GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W) GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W) @@ -208,7 +207,7 @@ enum { /* Missing */ GB_IO_WAV_START = 0x30, // Wave pattern start - GB_IO_WAV_END = 0x3f, // Wave pattern end + GB_IO_WAV_END = 0x3F, // Wave pattern end /* Graphics */ GB_IO_LCDC = 0x40, // LCD Control (R/W) @@ -221,20 +220,17 @@ enum { GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only - GB_IO_WY = 0x4a, // Window Y Position (R/W) - GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W) - // 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. - GB_IO_KEY0 = 0x4c, + GB_IO_WY = 0x4A, // Window Y Position (R/W) + GB_IO_WX = 0x4B, // Window X Position minus 7 (R/W) + // Controls DMG mode and PGB mode + GB_IO_KEY0 = 0x4C, /* General CGB features */ - GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch + GB_IO_KEY1 = 0x4D, // CGB Mode Only - Prepare Speed Switch /* Missing */ - GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank + GB_IO_VBK = 0x4F, // CGB Mode Only - VRAM Bank GB_IO_BANK = 0x50, // Write to disable the BIOS mapping /* CGB DMA */ @@ -249,23 +245,23 @@ enum { /* Missing */ - /* CGB Paletts */ + /* CGB Palettes */ GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data - GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index - GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data - GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based) + GB_IO_OBPI = 0x6A, // CGB Mode Only - Object Palette Index + GB_IO_OBPD = 0x6B, // CGB Mode Only - Object Palette Data + GB_IO_OPRI = 0x6C, // Affects object priority (X based or index based) /* Missing */ GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank - GB_IO_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_PSM = 0x71, // Palette Selection Mode, controls the PSW and key combo + GB_IO_PSWX = 0x72, // X position of the palette switching window + GB_IO_PSWY = 0x73, // Y position of the palette switching window + GB_IO_PSW = 0x74, // Key combo to trigger the palette switching window 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 { @@ -276,28 +272,22 @@ 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; -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 #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; }) @@ -308,7 +298,7 @@ typedef enum { #endif #endif -typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type); typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); @@ -323,21 +313,25 @@ 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); +typedef void (*GB_lcd_line_callback_t)(GB_gameboy_t *gb, uint8_t line); +typedef void (*GB_lcd_status_callback_t)(GB_gameboy_t *gb, bool on); + struct GB_breakpoint_s; 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 +#define GB_FIFO_LENGTH 8 typedef struct { GB_fifo_item_t fifo[GB_FIFO_LENGTH]; uint8_t read_end; - uint8_t write_end; + uint8_t size; } GB_fifo_t; typedef struct { @@ -363,6 +357,22 @@ 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, + pc; + }; + struct { + uint8_t GB_REGISTER_ORDER; + }; +} 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. @@ -382,35 +392,24 @@ struct GB_gameboy_internal_s { /* The version field makes sure we don't load save state files with a completely different structure. This happens when struct fields are removed/resized in an backward incompatible manner. */ uint32_t version; - ); + ) GB_SECTION(core_state, /* Registers */ - uint16_t pc; - 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 - }; - - }; + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp, + pc; + }; + struct { + uint8_t GB_REGISTER_ORDER; + }; + }; uint8_t ime; uint8_t interrupt_enable; uint8_t cgb_ram_bank; @@ -429,34 +428,38 @@ struct GB_gameboy_internal_s { /* Misc state */ bool infrared_input; GB_printer_t printer; - uint8_t extra_oam[0xff00 - 0xfea0]; + uint8_t extra_oam[0xFF00 - 0xFEA0]; uint32_t ram_size; // Different between CGB and DMG GB_workboy_t workboy; int32_t ir_sensor; bool effective_ir_input; - ); + uint16_t address_bus; + ) /* DMA and HDMA */ GB_SECTION(dma, bool hdma_on; bool hdma_on_hblank; uint8_t hdma_steps_left; - int16_t hdma_cycles; // in 8MHz units uint16_t hdma_current_src, hdma_current_dest; - uint8_t dma_steps_left; uint8_t dma_current_dest; + uint8_t last_dma_read; uint16_t dma_current_src; - int16_t dma_cycles; - bool is_dma_restarting; - uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ - bool hdma_starting; - ); + uint16_t dma_cycles; + int8_t dma_cycles_modulo; + bool dma_ppu_vram_conflict; + uint16_t dma_ppu_vram_conflict_addr; + uint8_t hdma_open_bus; /* Required to emulate HDMA reads from Exxx */ + bool allow_hdma_on_wake; + bool dma_restarting; + ) /* MBC */ GB_SECTION(mbc, uint16_t mbc_rom_bank; + uint16_t mbc_rom0_bank; /* For multicart mappings . */ uint8_t mbc_ram_bank; uint32_t mbc_ram_size; bool mbc_ram_enable; @@ -474,53 +477,86 @@ struct GB_gameboy_internal_s { struct { uint8_t rom_bank:8; uint8_t ram_bank:3; + bool rtc_mapped:1; } mbc3; struct { uint8_t rom_bank_low; uint8_t rom_bank_high:1; uint8_t ram_bank:4; - } mbc5; + } mbc5; // Also used for GB_CAMERA + + struct { + uint8_t rom_bank; + uint16_t x_latch; + uint16_t y_latch; + bool latch_ready:1; + bool eeprom_do:1; + bool eeprom_di:1; + bool eeprom_clk:1; + bool eeprom_cs:1; + uint16_t eeprom_command:11; + uint16_t read_bits; + uint8_t argument_bits_left:5; + bool secondary_ram_enable:1; + bool eeprom_write_enabled:1; + } mbc7; + + struct { + uint8_t rom_bank_low:5; + uint8_t rom_bank_mid:2; + bool mbc1_mode:1; + + uint8_t rom_bank_mask:4; + uint8_t rom_bank_high:2; + uint8_t ram_bank_low:2; + + uint8_t ram_bank_high:2; + uint8_t ram_bank_mask:2; + + bool locked:1; + bool mbc1_mode_disable:1; + bool multiplex_mode:1; + } mmm01; struct { uint8_t bank_low:6; uint8_t bank_high:3; - bool mode:1; - bool ir_mode:1; + bool ir_mode; } huc1; struct { 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; - ); - + uint8_t camera_alignment; + int32_t camera_countdown; + ) /* HRAM and HW Registers */ GB_SECTION(hram, uint8_t hram[0xFFFF - 0xFF80]; uint8_t io_registers[0x80]; - ); + ) /* Timing */ GB_SECTION(timing, @@ -528,36 +564,45 @@ struct GB_gameboy_internal_s { GB_UNIT(div); uint16_t div_counter; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ - uint16_t serial_cycles; - uint16_t serial_length; + bool serial_master_clock; + uint8_t serial_mask; uint8_t double_speed_alignment; uint8_t serial_count; - ); + int32_t speed_switch_halt_countdown; + uint8_t speed_switch_countdown; // To compensate for the lack of pipeline emulation + uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented + /* For timing of the vblank callback */ + uint32_t cycles_since_vblank_callback; + bool lcd_disabled_outside_of_vblank; + int32_t allowed_pending_cycles; + uint16_t mode3_batching_length; + uint8_t joyp_switching_delay; + uint8_t joyp_switch_value; + uint16_t key_bounce_timing[GB_KEY_MAX]; + ) /* APU */ GB_SECTION(apu, GB_apu_t apu; - ); + ) /* RTC */ GB_SECTION(rtc, GB_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; - bool rtc_latch; uint32_t rtc_cycles; uint8_t tpp1_mr4; - ); + ) /* 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]; + uint8_t object_palettes_data[0x40]; uint8_t position_in_line; bool stat_interrupt_line; - uint8_t effective_scx; uint8_t window_y; /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. @@ -568,18 +613,16 @@ struct GB_gameboy_internal_s { GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, // on a CGB, the previous frame is repeated (which might be // blank if the LCD was off for more than a few cycles) - GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG - GB_FRAMESKIP_SECOND_FRAME_RENDERED, + GB_FRAMESKIP_FIRST_FRAME_SKIPPED__DEPRECATED, + GB_FRAMESKIP_FIRST_FRAME_RENDERED, } frame_skip_state; bool oam_read_blocked; bool vram_read_blocked; bool oam_write_blocked; bool vram_write_blocked; - bool fifo_insertion_glitch; uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; - uint8_t fetcher_x; uint8_t fetcher_y; uint16_t cycles_for_line; uint8_t current_tile; @@ -590,16 +633,23 @@ struct GB_gameboy_internal_s { bool wx166_glitch; bool wx_triggered; uint8_t visible_objs[10]; - uint8_t obj_comparators[10]; + uint8_t objects_x[10]; + uint8_t objects_y[10]; + uint8_t object_tile_data[2]; + uint8_t mode2_y_bus; + // They're the same bus + union { + uint8_t mode2_x_bus; + uint8_t object_flags; + }; uint8_t n_visible_objs; + uint8_t orig_n_visible_objs; uint8_t oam_search_index; uint8_t accessed_oam_row; - uint8_t extra_penalty_for_sprite_at_0; uint8_t mode_for_interrupt; 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; @@ -613,9 +663,13 @@ struct GB_gameboy_internal_s { bool is_odd_frame; uint16_t last_tile_data_address; uint16_t last_tile_index_address; - bool cgb_repeated_a_frame; + GB_PADDING(bool, cgb_repeated_a_frame); uint8_t data_for_sel_glitch; - ); + bool delayed_glitch_hblank_interrupt; + uint32_t frame_repeat_countdown; + bool disable_window_pixel_insertion_glitch; + bool insert_bg_pixel; + ) /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* This data is reserved on reset and must come last in the struct */ @@ -640,20 +694,30 @@ 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; 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; bool has_sgb_border; - + bool objects_disabled; + bool background_disabled; + bool joyp_accessed; + bool illegal_inputs_allowed; + bool no_bouncing_emulation; + bool joypad_is_stable; + /* Timing */ uint64_t last_sync; 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; @@ -677,14 +741,18 @@ 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; GB_workboy_get_time_callback workboy_get_time_callback; - + GB_execution_callback_t execution_callback; + GB_lcd_line_callback_t lcd_line_callback; + GB_lcd_status_callback_t lcd_status_callback; /*** Debugger ***/ volatile bool debug_stopped, debug_disable; bool debug_fin_command, debug_next_command; + bool help_shown; /* Breakpoints */ uint16_t n_breakpoints; @@ -693,8 +761,6 @@ struct GB_gameboy_internal_s { void *nontrivial_jump_state; bool non_trivial_jump_breakpoint_occured; - /* SLD (Todo: merge with backtrace) */ - bool stack_leak_detection; signed debug_call_depth; uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ uint16_t addr_for_call_depth[0x200]; @@ -712,18 +778,19 @@ struct GB_gameboy_internal_s { struct GB_watchpoint_s *watchpoints; /* Symbol tables */ - GB_symbol_map_t *bank_symbols[0x200]; + GB_symbol_map_t **bank_symbols; + size_t n_symbol_maps; GB_reversed_symbol_map_t reversed_symbol_map; /* Ticks command */ uint64_t debugger_ticks; + uint64_t absolute_debugger_ticks; /* Undo */ uint8_t *undo_state; const char *undo_label; /* Rewind */ -#define GB_REWIND_FRAMES_PER_KEY 255 size_t rewind_buffer_length; struct { uint8_t *key_state; @@ -751,7 +818,7 @@ struct GB_gameboy_internal_s { bool disable_rendering; uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank - uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units + unsigned cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units double clock_multiplier; GB_rumble_mode_t rumble_mode; uint32_t rumble_on_cycles; @@ -760,14 +827,18 @@ struct GB_gameboy_internal_s { /* Temporary state */ bool wx_just_changed; bool tile_sel_glitch; + bool disable_oam_corruption; // For safe memory reads + bool in_dma_read; + bool hdma_in_progress; + uint16_t addr_for_hdma_conflict; GB_gbs_header_t gbs_header; - ); + ) }; #ifndef GB_INTERNAL 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 @@ -778,18 +849,43 @@ struct GB_gameboy_s { __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #endif -void GB_init(GB_gameboy_t *gb, GB_model_t model); +/* + There are two instance allocation styles – one where you manage your + own instance allocation, and one where you use provided allocators. + + Managing allocations yourself: + GB_gameboy_t gb; + GB_init(&gb, model); + ... + GB_free(&gb); + + Using the provided allocators: + GB_gameboy_t *gb = GB_init(GB_alloc(), model); + ... + GB_free(gb); // optional + GB_dealloc(gb); + +*/ +GB_gameboy_t *GB_init(GB_gameboy_t *gb, GB_model_t model); +void GB_free(GB_gameboy_t *gb); +GB_gameboy_t *GB_alloc(void); +void GB_dealloc(GB_gameboy_t *gb); + +// For when you want to use your own malloc implementation without having to rely on the header struct +size_t GB_allocation_size(void); + 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 GB_model_t GB_get_model(GB_gameboy_t *gb); -void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); +void GB_quick_reset(GB_gameboy_t *gb); // Similar to the cart reset line void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model); /* Returns the time passed, in 8MHz ticks. */ -uint8_t GB_run(GB_gameboy_t *gb); +unsigned GB_run(GB_gameboy_t *gb); /* Returns the time passed since the last frame, in nanoseconds */ uint64_t GB_run_frame(GB_gameboy_t *gb); @@ -805,11 +901,13 @@ typedef enum { GB_DIRECT_ACCESS_BGP, GB_DIRECT_ACCESS_OBP, GB_DIRECT_ACCESS_IE, + GB_DIRECT_ACCESS_ROM0, // Identical to ROM, but returns the correct rom0 bank in the bank output argument } GB_direct_access_t; /* 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); @@ -819,6 +917,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); @@ -836,6 +935,7 @@ void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); +uint32_t *GB_get_pixels_output(GB_gameboy_t *gb); 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); @@ -851,7 +951,12 @@ 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_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback); +void GB_set_lcd_status_callback(GB_gameboy_t *gb, GB_lcd_status_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); @@ -865,9 +970,12 @@ 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). +// Values within ±4 recommended +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); @@ -883,5 +991,15 @@ 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); + +/* Handy ROM info APIs */ +// `title` must be at least 17 bytes in size +void GB_get_rom_title(GB_gameboy_t *gb, char *title); +uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); + +#ifdef GB_INTERNAL +internal void GB_borrow_sgb_border(GB_gameboy_t *gb); +internal void GB_update_clock_rate(GB_gameboy_t *gb); +#endif #endif /* GB_h */ diff --git a/thirdparty/SameBoy/Core/gb_struct_def.h b/thirdparty/SameBoy/Core/gb_struct_def.h deleted file mode 100644 index 0e0ebd12e..000000000 --- a/thirdparty/SameBoy/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/thirdparty/SameBoy/Core/graphics/mgb_border.inc b/thirdparty/SameBoy/Core/graphics/mgb_border.inc new file mode 100644 index 000000000..f19ed8a1b --- /dev/null +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Core/joypad.c b/thirdparty/SameBoy/Core/joypad.c index b8d4fdb47..0d001ee78 100644 --- a/thirdparty/SameBoy/Core/joypad.c +++ b/thirdparty/SameBoy/Core/joypad.c @@ -1,6 +1,41 @@ #include "gb.h" #include +static inline bool should_bounce(GB_gameboy_t *gb) +{ + // Bouncing is super rare on an AGS, so don't emulate it on GB_MODEL_AGB_B (when addeed) + return !GB_is_sgb(gb) && !gb-> no_bouncing_emulation && !(gb->model & GB_MODEL_GBP_BIT) /*&& gb->model != GB_MODEL_AGB_B*/; +} + +static inline uint16_t bounce_for_key(GB_gameboy_t *gb, GB_key_t key) +{ + if (gb->model > GB_MODEL_CGB_E) { + // AGB are less bouncy + return 0xBFF; + } + if (key == GB_KEY_START || key == GB_KEY_SELECT) { + return 0x1FFF; + } + return 0xFFF; +} + +static inline bool get_input(GB_gameboy_t *gb, uint8_t player, GB_key_t key) +{ + if (player != 0) { + return gb->keys[player][key]; + } + bool ret = gb->keys[player][key]; + + if (likely(gb->key_bounce_timing[key] == 0)) return ret; + if (likely((gb->key_bounce_timing[key] & 0x3FF) > 0x300)) return ret; + uint16_t semi_random = ((((key << 5) + gb->div_counter) * 17) ^ ((gb->apu.apu_cycles + gb->display_cycles) * 13)); + semi_random >>= 3; + if (semi_random < gb->key_bounce_timing[key]) { + ret ^= true; + } + return ret; +} + void GB_update_joyp(GB_gameboy_t *gb) { if (gb->model & GB_MODEL_NO_SFC_BIT) return; @@ -12,7 +47,7 @@ void GB_update_joyp(GB_gameboy_t *gb) previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; gb->io_registers[GB_IO_JOYP] &= 0xF0; - uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0; + uint8_t current_player = gb->sgb? gb->sgb->current_player : 0; switch (key_selection) { case 3: if (gb->sgb && gb->sgb->player_count > 1) { @@ -27,38 +62,41 @@ void GB_update_joyp(GB_gameboy_t *gb) case 2: /* Direction keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; + gb->io_registers[GB_IO_JOYP] |= (!get_input(gb, 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; case 1: /* Other keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i + 4]) << i; + gb->io_registers[GB_IO_JOYP] |= (!get_input(gb, current_player, i + 4)) << i; } break; case 0: for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[current_player][i] || gb->keys[current_player][i + 4])) << i; + gb->io_registers[GB_IO_JOYP] |= (!(get_input(gb, current_player, i) || get_input(gb, current_player, i + 4))) << i; } break; - default: - break; + nodefault; } - /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */ - if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { - /* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */ - gb->io_registers[GB_IO_IF] |= 0x10; + // TODO: Implement the lame anti-debouncing mechanism as seen on the DMG schematics + if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { + if (!(gb->io_registers[GB_IO_IF] & 0x10)) { + gb->joyp_accessed = true; + gb->io_registers[GB_IO_IF] |= 0x10; + } } gb->io_registers[GB_IO_JOYP] |= 0xC0; @@ -71,7 +109,10 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) gb->io_registers[GB_IO_JOYP] |= value & 0xF; if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { - gb->io_registers[GB_IO_IF] |= 0x10; + if (!(gb->io_registers[GB_IO_IF] & 0x10)) { + gb->joyp_accessed = true; + gb->io_registers[GB_IO_IF] |= 0x10; + } } gb->io_registers[GB_IO_JOYP] |= 0xC0; } @@ -79,6 +120,10 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) { assert(index >= 0 && index < GB_KEY_MAX); + if (should_bounce(gb) && pressed != gb->keys[0][index]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[index] = bounce_for_key(gb, index); + } gb->keys[0][index] = pressed; GB_update_joyp(gb); } @@ -87,6 +132,93 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play { assert(index >= 0 && index < GB_KEY_MAX); assert(player < 4); + if (should_bounce(gb) && pressed != gb->keys[player][index]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[index] = bounce_for_key(gb, index); + } gb->keys[player][index] = pressed; GB_update_joyp(gb); } + +void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask) +{ + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + bool pressed = mask & (1 << i); + if (should_bounce(gb) && pressed != gb->keys[0][i]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[i] = bounce_for_key(gb, i); + } + gb->keys[0][i] = pressed; + } + + GB_update_joyp(gb); +} + +void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player) +{ + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + bool pressed = mask & (1 << i); + if (should_bounce(gb) && pressed != gb->keys[player][i]) { + gb->joypad_is_stable = false; + gb->key_bounce_timing[i] = bounce_for_key(gb, i); + } + gb->keys[player][i] = pressed; + } + + GB_update_joyp(gb); +} + +void GB_joypad_run(GB_gameboy_t *gb, unsigned cycles) +{ + if (gb->joypad_is_stable) return; + bool should_update_joyp = false; + gb->joypad_is_stable = true; + if (gb->joyp_switching_delay) { + gb->joypad_is_stable = false; + if (gb->joyp_switching_delay > cycles) { + gb->joyp_switching_delay -= cycles; + } + else { + gb->joyp_switching_delay = 0; + gb->io_registers[GB_IO_JOYP] = (gb->joyp_switch_value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + should_update_joyp = true; + } + } + + for (unsigned i = 0; i < GB_KEY_MAX; i++) { + if (gb->key_bounce_timing[i]) { + gb->joypad_is_stable = false; + should_update_joyp = true; + if (gb->key_bounce_timing[i] > cycles) { + gb->key_bounce_timing[i] -= cycles; + } + else { + gb->key_bounce_timing[i] = 0; + } + } + } + + if (should_update_joyp) { + GB_update_joyp(gb); + } +} + +bool GB_get_joyp_accessed(GB_gameboy_t *gb) +{ + return gb->joyp_accessed; +} + +void GB_clear_joyp_accessed(GB_gameboy_t *gb) +{ + gb->joyp_accessed = false; +} + +void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow) +{ + gb->illegal_inputs_allowed = allow; +} + +void GB_set_emulate_joypad_bouncing(GB_gameboy_t *gb, bool emulate) +{ + gb->no_bouncing_emulation = !emulate; +} diff --git a/thirdparty/SameBoy/Core/joypad.h b/thirdparty/SameBoy/Core/joypad.h index 21fad5343..d39eff38d 100644 --- a/thirdparty/SameBoy/Core/joypad.h +++ b/thirdparty/SameBoy/Core/joypad.h @@ -1,6 +1,6 @@ #ifndef joypad_h #define joypad_h -#include "gb_struct_def.h" +#include "defs.h" #include typedef enum { @@ -15,11 +15,32 @@ 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); +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); +void GB_set_emulate_joypad_bouncing(GB_gameboy_t *gb, bool emulate); #ifdef GB_INTERNAL -void GB_update_joyp(GB_gameboy_t *gb); +internal void GB_update_joyp(GB_gameboy_t *gb); +internal void GB_joypad_run(GB_gameboy_t *gb, unsigned cycles); #endif #endif /* joypad_h */ diff --git a/thirdparty/SameBoy/Core/mbc.c b/thirdparty/SameBoy/Core/mbc.c index 1e15f8a69..5f9373f9c 100644 --- a/thirdparty/SameBoy/Core/mbc.c +++ b/thirdparty/SameBoy/Core/mbc.c @@ -5,40 +5,41 @@ const GB_cartridge_t GB_cart_defs[256] = { // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type - /* MBC SUBTYPE RAM BAT. RTC RUMB. */ - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY - { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1 - { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM - { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY + /* MBC RAM BAT. RTC RUMB. */ + { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , false, false, false, false}, // 01h MBC1 + { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY [5] = - { GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2 - { GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY + { GB_MBC2 , true , false, false, false}, // 05h MBC2 + { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY [8] = - { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM - { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY [0xB] = - /* Todo: Not supported yet */ - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01 - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + { GB_MMM01 , false, false, false, false}, // 0Bh MMM01 + { GB_MMM01 , true , false, false, false}, // 0Ch MMM01+RAM + { GB_MMM01 , true , true , false, false}, // 0Dh MMM01+RAM+BATTERY [0xF] = - { GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY - { GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY - { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3 - { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM - { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY + { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , false, false, false, false}, // 11h MBC3 + { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY [0x19] = - { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5 - { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM - { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY - { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE - { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM - { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + { GB_MBC5 , false, false, false, false}, // 19h MBC5 + { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0x22] = + { GB_MBC7 , true, true, false, false}, // 22h MBC7+ACCEL+EEPROM [0xFC] = - { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA - { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) - { GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3 - { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY + { GB_CAMERA, true , true , false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) + { GB_HUC3 , true , true , true, false}, // FEh HuC3 + { GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY }; void GB_update_mbc_mappings(GB_gameboy_t *gb) @@ -75,6 +76,7 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank++; } break; + nodefault; } break; case GB_MBC2: @@ -94,59 +96,116 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) } break; case GB_MBC5: + case GB_CAMERA: gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); gb->mbc_ram_bank = gb->mbc5.ram_bank; break; - case GB_HUC1: - if (gb->huc1.mode == 0) { - gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6); - gb->mbc_ram_bank = 0; + case GB_MBC7: + gb->mbc_rom_bank = gb->mbc7.rom_bank; + break; + case GB_MMM01: + if (gb->mmm01.locked) { + if (gb->mmm01.multiplex_mode) { + gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | + ((gb->mmm01.mbc1_mode? 0 : gb->mmm01.ram_bank_low) << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_rom_bank = gb->mmm01.rom_bank_low | + (gb->mmm01.ram_bank_low << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_ram_bank = gb->mmm01.rom_bank_mid | (gb->mmm01.ram_bank_high << 2); + } + else { + gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | + (gb->mmm01.rom_bank_mid << 5) | + (gb->mmm01.rom_bank_high << 7); + gb->mbc_rom_bank = gb->mmm01.rom_bank_low | + (gb->mmm01.rom_bank_mid << 5) | + (gb->mmm01.rom_bank_high << 7); + if (gb->mmm01.mbc1_mode) { + gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2); + } + else { + gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2); + } + } + if (gb->mbc_rom_bank == gb->mbc_rom0_bank) { + gb->mbc_rom_bank++; + } } else { - gb->mbc_rom_bank = gb->huc1.bank_low; - gb->mbc_ram_bank = gb->huc1.bank_high; + gb->mbc_rom_bank = -1; + gb->mbc_rom0_bank = -2; } break; + case GB_HUC1: + gb->mbc_rom_bank = gb->huc1.bank_low; + gb->mbc_ram_bank = gb->huc1.bank_high; + break; case GB_HUC3: gb->mbc_rom_bank = gb->huc3.rom_bank; gb->mbc_ram_bank = gb->huc3.ram_bank; 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; + nodefault; } } void GB_configure_cart(GB_gameboy_t *gb) { + memset(GB_GET_SECTION(gb, mbc), 0, GB_SECTION_SIZE(mbc)); 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->cartridge_type->mbc_type == GB_MMM01) { + uint8_t *temp = malloc(0x8000); + memcpy(temp, gb->rom, 0x8000); + memmove(gb->rom, gb->rom + 0x8000, gb->rom_size - 0x8000); + memcpy(gb->rom + gb->rom_size - 0x8000, temp, 0x8000); + free(temp); } - - if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { - GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); - gb->cartridge_type = &GB_cart_defs[0x11]; + else { + const GB_cartridge_t *maybe_mmm01_type = &GB_cart_defs[gb->rom[gb->rom_size - 0x8000 + 0x147]]; + if (memcmp(gb->rom + 0x104, gb->rom + gb->rom_size - 0x8000 + 0x104, 0x30) == 0) { + if (maybe_mmm01_type->mbc_type == GB_MMM01) { + gb->cartridge_type = maybe_mmm01_type; + } + else if(gb->rom[gb->rom_size - 0x8000 + 0x147] == 0x11) { + GB_log(gb, "ROM header reports MBC3, but it appears to be an MMM01 ROM. Assuming cartridge uses MMM01."); + gb->cartridge_type = &GB_cart_defs[0xB]; + } + } } - 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->rom[0x147] == 0xBC && + gb->rom[0x149] == 0xC1 && + gb->rom[0x14A] == 0x65) { + static const GB_cartridge_t tpp1 = {GB_TPP1, true, true, true, true}; + gb->cartridge_type = &tpp1; + gb->tpp1.rom_bank = 1; } - if (gb->mbc_ram) { - free(gb->mbc_ram); - gb->mbc_ram = NULL; + if (gb->cartridge_type->mbc_type != GB_MMM01) { + if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { + GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); + gb->cartridge_type = &GB_cart_defs[0x11]; + } + else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) { + GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); + } } - + + size_t old_mbc_ram_size = gb->mbc_ram_size; + gb->mbc_ram_size = 0; + if (gb->cartridge_type->has_ram) { 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); @@ -154,15 +213,24 @@ void GB_configure_cart(GB_gameboy_t *gb) } else { static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; - gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + if (gb->cartridge_type->mbc_type == GB_MMM01) { + gb->mbc_ram_size = ram_sizes[gb->rom[gb->rom_size - 0x8000 + 0x149]]; + } + else { + gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + } + } + + if (gb->mbc_ram && old_mbc_ram_size != gb->mbc_ram_size) { + free(gb->mbc_ram); + gb->mbc_ram = NULL; } - if (gb->mbc_ram_size) { + if (gb->mbc_ram_size && !gb->mbc_ram) { gb->mbc_ram = malloc(gb->mbc_ram_size); + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridge types? */ + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); } - - /* 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); } /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). @@ -182,8 +250,29 @@ void GB_configure_cart(GB_gameboy_t *gb) } } - /* Set MBC5's bank to 1 correctly */ - if (gb->cartridge_type->mbc_type == GB_MBC5) { + GB_reset_mbc(gb); +} + +void GB_reset_mbc(GB_gameboy_t *gb) +{ + gb->mbc_rom0_bank = 0; + if (gb->cartridge_type->mbc_type == GB_MMM01) { + gb->mbc_rom_bank = -1; + gb->mbc_rom0_bank = -2; + gb->mmm01.ram_bank_mask = -1; + } + else if (gb->cartridge_type->mbc_type == GB_MBC5 || + gb->cartridge_type->mbc_type == GB_CAMERA) { gb->mbc5.rom_bank_low = 1; + gb->mbc_rom_bank = 1; + } + else if (gb->cartridge_type->mbc_type == GB_MBC7) { + gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000; + gb->mbc7.latch_ready = true; + gb->mbc7.read_bits = -1; + gb->mbc7.eeprom_do = true; + } + else { + gb->mbc_rom_bank = 1; } } diff --git a/thirdparty/SameBoy/Core/mbc.h b/thirdparty/SameBoy/Core/mbc.h index 3bbe78277..8bdb07991 100644 --- a/thirdparty/SameBoy/Core/mbc.h +++ b/thirdparty/SameBoy/Core/mbc.h @@ -1,6 +1,6 @@ #ifndef MBC_h #define MBC_h -#include "gb_struct_def.h" +#include "defs.h" #include typedef struct { @@ -10,14 +10,13 @@ typedef struct { GB_MBC2, GB_MBC3, GB_MBC5, + GB_MBC7, + GB_MMM01, GB_HUC1, GB_HUC3, GB_TPP1, - } mbc_type; - enum { - GB_STANDARD_MBC, GB_CAMERA, - } mbc_subtype; + } mbc_type; bool has_ram; bool has_battery; bool has_rtc; @@ -25,9 +24,10 @@ typedef struct { } GB_cartridge_t; #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); +internal extern const GB_cartridge_t GB_cart_defs[256]; +internal void GB_update_mbc_mappings(GB_gameboy_t *gb); +internal void GB_configure_cart(GB_gameboy_t *gb); +internal void GB_reset_mbc(GB_gameboy_t *gb); #endif #endif /* MBC_h */ diff --git a/thirdparty/SameBoy/Core/memory.c b/thirdparty/SameBoy/Core/memory.c index a25b860f8..84456aa67 100644 --- a/thirdparty/SameBoy/Core/memory.c +++ b/thirdparty/SameBoy/Core/memory.c @@ -2,17 +2,16 @@ #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; @@ -23,39 +22,84 @@ static GB_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 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]); + 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], + 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,53 +107,163 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) } } -void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) +static void oam_bug_secondary_read_corruption(GB_gameboy_t *gb) { - 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++) { - gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; - } + 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_increase(GB_gameboy_t *gb, uint16_t address) +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 >= 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] - ); + if (gb->accessed_oam_row != 0xFF && gb->accessed_oam_row >= 8) { + 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->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: + 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 - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + 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); + } + else if (gb->model == GB_MODEL_MGB && gb->accessed_oam_row == 0x40) { + memcpy(gb->oam, gb->oam + gb->accessed_oam_row, 8); } } } } + 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_is_dma_active(gb) || addr >= 0xFE00 || gb->hdma_in_progress) return false; + if (gb->dma_current_dest == 0xFF || gb->dma_current_dest == 0x0) return false; // Warm up + if (addr >= 0xFE00) return false; + if (gb->dma_current_src == addr) return false; // Shortcut for DMA access flow + if (gb->dma_current_src >= 0xE000 && (gb->dma_current_src & ~0x2000) == addr) return false; + if (GB_is_cgb(gb)) { + if (addr >= 0xC000) { + 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); } @@ -138,10 +292,21 @@ 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) { - if (gb->vram_read_blocked) { + if (likely(!GB_is_dma_active(gb))) { + /* Prevent syncing from a DMA read. Batching doesn't happen during DMA anyway. */ + GB_display_sync(gb); + } + else { + if ((gb->dma_current_dest & 0xE000) == 0x8000) { + // TODO: verify conflict behavior + return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)]; + } + } + + if (unlikely(gb->vram_read_blocked && !gb->in_dma_read)) { return 0xFF; } - if (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; } @@ -153,24 +318,44 @@ 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_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) { + 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 @@ -179,13 +364,14 @@ 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; + nodefault; } case 2: case 3: @@ -197,18 +383,18 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } } else if ((!gb->mbc_ram_enable) && - gb->cartridge_type->mbc_subtype != GB_CAMERA && + gb->cartridge_type->mbc_type != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) { return 0xFF; } if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { - return 0xc0 | gb->effective_ir_input; + return 0xC0 | gb->effective_ir_input; } if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && - gb->mbc3_rtc_mapped) { + gb->mbc3.rtc_mapped) { /* RTC read */ if (gb->mbc_ram_bank <= 4) { gb->rtc_latched.seconds &= 0x3F; @@ -228,8 +414,15 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) return 0xFF; } - if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) { - return GB_camera_read_image(gb, addr - 0xa100); + if (gb->cartridge_type->mbc_type == GB_CAMERA) { + /* Forbid reading RAM while the camera is busy. */ + if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { + return 0; + } + + if (gb->mbc_ram_bank == 0 && addr >= 0xA100 && addr < 0xAF00) { + return GB_camera_read_image(gb, addr - 0xA100); + } } uint8_t effective_bank = gb->mbc_ram_bank; @@ -256,107 +449,159 @@ static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; } -static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) +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; + } +} - if (gb->hdma_on) { - return gb->last_opcode_read; +internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr) +{ + if (addr < 0xA0) { + return gb->oam[addr]; } + switch (gb->model) { + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + return (addr & 0xF0) | (addr >> 4); + + case GB_MODEL_CGB_D: + if (addr >= 0xC0) { + addr |= 0xF0; + } + return gb->extra_oam[addr - 0xA0]; + + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + addr &= ~0x18; + return gb->extra_oam[addr - 0xA0]; + + case GB_MODEL_DMG_B: + case GB_MODEL_MGB: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + return 0; + } + unreachable(); +} + +static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) +{ if (addr < 0xFE00) { return read_banked_ram(gb, addr); } if (addr < 0xFF00) { + GB_display_sync(gb); if (gb->oam_write_blocked && !GB_is_cgb(gb)) { - GB_trigger_oam_bug_read(gb, addr); - return 0xff; + if (!gb->disable_oam_corruption) { + GB_trigger_oam_bug_read(gb, addr); + } + return 0xFF; } - if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + if (GB_is_dma_active(gb) && (gb->dma_current_dest != 0 || gb->dma_restarting)) { /* Todo: Does reading from OAM during DMA causes the OAM bug? */ - return 0xff; + return 0xFF; } 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) { - 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]; + 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]); + else if (gb->accessed_oam_row == 0xA0) { + 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; + + nodefault; + } for (unsigned i = 0; i < 8; i++) { - gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i]; } } } } - return 0xff; - } - - if (addr < 0xFEA0) { - return gb->oam[addr & 0xFF]; - } - - if (gb->oam_read_blocked) { return 0xFF; } - switch (gb->model) { - case GB_MODEL_CGB_E: - case GB_MODEL_AGB: - return (addr & 0xF0) | ((addr >> 4) & 0xF); - - /* - case GB_MODEL_CGB_D: - if (addr > 0xfec0) { - addr |= 0xf0; - } - return gb->extra_oam[addr - 0xfea0]; - */ - - case GB_MODEL_CGB_C: - /* - case GB_MODEL_CGB_B: - case GB_MODEL_CGB_A: - case GB_MODEL_CGB_0: - */ - addr &= ~0x18; - return gb->extra_oam[addr - 0xfea0]; - - case GB_MODEL_DMG_B: - case GB_MODEL_SGB_NTSC: - case GB_MODEL_SGB_PAL: - case GB_MODEL_SGB_NTSC_NO_SFC: - case GB_MODEL_SGB_PAL_NO_SFC: - case GB_MODEL_SGB2: - case GB_MODEL_SGB2_NO_SFC: - break; - } - } - - if (addr < 0xFF00) { - return 0; + return GB_read_oam(gb, addr); } if (addr < 0xFF80) { + sync_ppu_if_needed(gb, addr); switch (addr & 0xFF) { case GB_IO_IF: return gb->io_registers[GB_IO_IF] | 0xE0; @@ -370,16 +615,23 @@ 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; + 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_PCM_34: + 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: + gb->joyp_accessed = true; GB_timing_sync(gb); + if (unlikely(gb->joyp_switching_delay)) { + return (gb->io_registers[addr & 0xFF] & ~0x30) | (gb->joyp_switch_value & 0x30); + } + return gb->io_registers[addr & 0xFF]; case GB_IO_TMA: case GB_IO_LCDC: case GB_IO_SCY: @@ -436,7 +688,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: @@ -452,15 +704,15 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (gb->model != GB_MODEL_CGB_E) { ret |= 0x10; } - if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model != GB_MODEL_AGB) { + if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model <= GB_MODEL_CGB_E) { ret &= ~2; } return ret; } - 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; @@ -470,8 +722,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return 0xFF; } - /* Hardware registers */ - return 0; + unreachable(); } if (addr == 0xFFFF) { @@ -483,7 +734,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 */ @@ -500,15 +751,45 @@ 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)) { - addr = gb->dma_current_src; + if (unlikely(is_addr_in_dma_use(gb, addr))) { + if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) { + /* 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 - 1) & 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 - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + } + else { + addr = (gb->dma_current_src - 1); + } } 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; +} + +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; + GB_apply_cheat(gb, addr, &data); + if (unlikely(gb->read_memory_callback)) { data = gb->read_memory_callback(gb, addr, data); } return data; @@ -538,19 +819,16 @@ 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: - 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; 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 == 0x0A; break; case 0x2000: gb->mbc5.rom_bank_low = value; break; case 0x3000: gb->mbc5.rom_bank_high = value; break; case 0x4000: case 0x5000: @@ -561,7 +839,58 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) value &= 7; } gb->mbc5.ram_bank = value; - gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA; + break; + } + break; + case GB_CAMERA: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc5.rom_bank_low = value; break; + case 0x4000: case 0x5000: + gb->mbc5.ram_bank = value; + gb->camera_registers_mapped = (value & 0x10); + break; + } + break; + 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_MMM01: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: + gb->mbc_ram_enable = (value & 0xF) == 0xA; + if (!gb->mmm01.locked) { + gb->mmm01.ram_bank_mask = value >> 4; + gb->mmm01.locked = value & 0x40; + } + break; + case 0x2000: case 0x3000: + if (!gb->mmm01.locked) { + gb->mmm01.rom_bank_mid = value >> 5; + } + gb->mmm01.rom_bank_low &= (gb->mmm01.rom_bank_mask << 1); + gb->mmm01.rom_bank_low |= ~(gb->mmm01.rom_bank_mask << 1) & value; + break; + case 0x4000: case 0x5000: + gb->mmm01.ram_bank_low = value | ~gb->mmm01.ram_bank_mask; + if (!gb->mmm01.locked) { + gb->mmm01.ram_bank_high = value >> 2; + gb->mmm01.rom_bank_high = value >> 4; + gb->mmm01.mbc1_mode_disable = value & 0x40; + } + break; + case 0x6000: case 0x7000: + if (!gb->mmm01.mbc1_mode_disable) { + gb->mmm01.mbc1_mode = (value & 1); + } + if (!gb->mmm01.locked) { + gb->mmm01.rom_bank_mask = value >> 2; + gb->mmm01.multiplex_mode = value & 0x40; + } break; } break; @@ -570,14 +899,13 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; - case 0x6000: case 0x7000: gb->huc1.mode = value; break; } break; case GB_HUC3: 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; @@ -586,15 +914,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) { @@ -602,7 +930,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)); @@ -630,87 +958,76 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } } break; + nodefault; } GB_update_mbc_mappings(gb); } static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->vram_write_blocked) { + GB_display_sync(gb); + 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) + (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) { - 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: @@ -740,8 +1057,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.argument_bits_left == 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.argument_bits_left = 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.argument_bits_left = 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.argument_bits_left--; + gb->mbc7.eeprom_do = true; + if (gb->mbc7.eeprom_di) { + uint16_t bit = LE16(1 << gb->mbc7.argument_bits_left); + if (gb->mbc7.eeprom_command & 0x100) { + // WRITE + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit; + } + else { + // WRAL + for (unsigned i = 0; i < 0x7F; i++) { + ((uint16_t *)gb->mbc_ram)[i] |= bit; + } + } + } + if (gb->mbc7.argument_bits_left == 0) { // We're done + gb->mbc7.eeprom_command = 0; + gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle + } + } + } + } + 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; } @@ -752,7 +1194,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: @@ -776,12 +1218,12 @@ 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; } - 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; } @@ -789,7 +1231,12 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (!gb->mbc_ram || !gb->mbc_ram_size) { return; } - + + if (gb->cartridge_type->mbc_type == GB_CAMERA && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1)) { + /* Forbid writing to RAM while the camera is busy. */ + return; + } + 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) { @@ -811,6 +1258,41 @@ static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value; } +static void write_oam(GB_gameboy_t *gb, uint8_t addr, uint8_t value) +{ + if (addr < 0xA0) { + gb->oam[addr] = value; + return; + } + switch (gb->model) { + case GB_MODEL_CGB_D: + if (addr >= 0xC0) { + addr |= 0xF0; + } + gb->extra_oam[addr - 0xA0] = value; + break; + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + addr &= ~0x18; + gb->extra_oam[addr - 0xA0] = value; + break; + case GB_MODEL_CGB_E: + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + case GB_MODEL_DMG_B: + case GB_MODEL_MGB: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + break; + } +} + static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { if (addr < 0xFE00) { @@ -820,60 +1302,30 @@ 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; } - if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + if (GB_is_dma_active(gb)) { /* Todo: Does writing to OAM during DMA causes the OAM bug? */ return; } if (GB_is_cgb(gb)) { - if (addr < 0xFEA0) { - gb->oam[addr & 0xFF] = value; - } - switch (gb->model) { - /* - case GB_MODEL_CGB_D: - if (addr > 0xfec0) { - addr |= 0xf0; - } - gb->extra_oam[addr - 0xfea0] = value; - break; - */ - case GB_MODEL_CGB_C: - /* - case GB_MODEL_CGB_B: - case GB_MODEL_CGB_A: - case GB_MODEL_CGB_0: - */ - addr &= ~0x18; - gb->extra_oam[addr - 0xfea0] = value; - break; - case GB_MODEL_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_E: - case GB_MODEL_AGB: - break; - } + write_oam(gb, addr, value); return; } if (addr < 0xFEA0) { - if (gb->accessed_oam_row == 0xa0) { + if (gb->accessed_oam_row == 0xA0) { for (unsigned i = 0; i < 8; i++) { if ((i & 6) != (addr & 6)) { - gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i]; } else { - gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]); + gb->oam[(addr & 0xF8) + i] = bitwise_glitch(gb->oam[(addr & 0xF8) + i], gb->oam[0x9C], gb->oam[0x98 + i]); } } } @@ -882,13 +1334,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->accessed_oam_row == 0) { gb->oam[0] = bitwise_glitch(gb->oam[0], - gb->oam[(addr & 0xf8)], - gb->oam[(addr & 0xfe)]); + gb->oam[(addr & 0xF8)], + gb->oam[(addr & 0xFE)]); gb->oam[1] = bitwise_glitch(gb->oam[1], - gb->oam[(addr & 0xf8) + 1], - gb->oam[(addr & 0xfe) | 1]); + gb->oam[(addr & 0xF8) + 1], + gb->oam[(addr & 0xFE) | 1]); for (unsigned i = 2; i < 8; i++) { - gb->oam[i] = gb->oam[(addr & 0xf8) + i]; + gb->oam[i] = gb->oam[(addr & 0xF8) + i]; } } } @@ -901,6 +1353,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: @@ -915,9 +1369,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; @@ -984,26 +1438,40 @@ 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 & GB_LCDC_ENABLE) && !(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) { + // LCD turned on + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, true); + } + if (!gb->lcd_disabled_outside_of_vblank && + (gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) { + // Trigger a vblank here so we don't exceed LCDC_PERIOD + GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL); + } + gb->display_cycles = 0; gb->display_state = 0; gb->double_speed_alignment = 0; + gb->cycles_for_line = 0; if (GB_is_sgb(gb)) { - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_RENDERED; } - else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { + else { gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; } GB_timing_sync(gb); } - else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { + else if (!(value & GB_LCDC_ENABLE) && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) { /* Sync after turning off LCD */ + if (gb->lcd_status_callback) { + gb->lcd_status_callback(gb, false); + } gb->double_speed_alignment = 0; GB_timing_sync(gb); GB_lcd_off(gb); } /* Handle disabling objects while already fetching an object */ - if ((gb->io_registers[GB_IO_LCDC] & 2) && !(value & 2)) { + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN) && !(value & GB_LCDC_OBJ_EN)) { if (gb->during_object_fetch) { gb->cycles_for_line += gb->display_cycles; gb->display_cycles = 0; @@ -1011,7 +1479,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } } gb->io_registers[GB_IO_LCDC] = value; - if (!(value & 0x20)) { + if (!(value & GB_LCDC_WIN_ENABLE)) { gb->wx_triggered = false; gb->wx166_glitch = false; } @@ -1029,6 +1497,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; @@ -1040,6 +1509,15 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_update_joyp(gb); } else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { + if (gb->model < GB_MODEL_SGB) { // DMG only + if (gb->joyp_switching_delay) { + gb->io_registers[GB_IO_JOYP] = (gb->joyp_switch_value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + } + gb->joyp_switch_value = value; + gb->joyp_switching_delay = 24; + value &= gb->io_registers[GB_IO_JOYP]; + gb->joypad_is_stable = false; + } GB_sgb_write(gb, value); gb->io_registers[GB_IO_JOYP] = (value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); GB_update_joyp(gb); @@ -1058,26 +1536,20 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DMA: - if (gb->dma_steps_left) { - /* This is not correct emulation, since we're not really delaying the second DMA. - One write that should have happened in the first DMA will not happen. However, - since that byte will be overwritten by the second DMA before it can actually be - read, it doesn't actually matter. */ - gb->is_dma_restarting = true; - } - gb->dma_cycles = -7; - gb->dma_current_dest = 0; + gb->dma_restarting = (gb->dma_current_dest != 0xA1 && gb->dma_current_dest != 0xA0); + gb->dma_cycles = 0; + gb->dma_cycles_modulo = 2; + gb->dma_current_dest = 0xFF; gb->dma_current_src = value << 8; - gb->dma_steps_left = 0xa0; gb->io_registers[GB_IO_DMA] = value; + GB_STAT_update(gb); return; case GB_IO_SVBK: - if (!gb->cgb_mode) { - 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: @@ -1112,7 +1584,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]++; @@ -1130,6 +1602,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->hdma_current_src &= 0xF0; gb->hdma_current_src |= value << 8; } + /* Range 0xE*** like 0xF*** and can't overflow (with 0x800 bytes) to anything meaningful */ + if (gb->hdma_current_src >= 0xE000) { + gb->hdma_current_src |= 0xF000; + } return; case GB_IO_HDMA2: if (gb->cgb_mode) { @@ -1145,7 +1621,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_HDMA4: if (gb->cgb_mode) { - gb->hdma_current_dest &= 0x1F00; + gb->hdma_current_dest &= 0xFF00; gb->hdma_current_dest |= value & 0xF0; } return; @@ -1157,38 +1633,31 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } gb->hdma_on = (value & 0x80) == 0; gb->hdma_on_hblank = (value & 0x80) != 0; - if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) { + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->display_state != 7) { gb->hdma_on = true; } gb->io_registers[GB_IO_HDMA5] = value; gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; - /* Todo: Verify this. Gambatte's DMA tests require this. */ - if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { - gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; - } - gb->hdma_cycles = -12; return; - /* Todo: what happens when starting a transfer during a transfer? - What happens when starting a transfer during external clock? - */ + /* TODO: What happens when starting a transfer during external clock? + TODO: When a cable is connected, the clock of the other side affects "zombie" serial clocking */ case GB_IO_SC: + gb->serial_count = 0; if (!gb->cgb_mode) { value |= 2; } + if (gb->serial_master_clock) { + GB_serial_master_edge(gb); + } gb->io_registers[GB_IO_SC] = value | (~0x83); + gb->serial_mask = gb->cgb_mode && (value & 2)? 4 : 0x80; if ((value & 0x80) && (value & 0x1) ) { - gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512; - gb->serial_count = 0; - /* Todo: This is probably incorrect for CGB's faster clock mode. */ - gb->serial_cycles &= 0xFF; if (gb->serial_transfer_bit_start_callback) { gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); } } - else { - gb->serial_length = 0; - } + return; case GB_IO_RP: { @@ -1216,6 +1685,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; @@ -1227,72 +1697,176 @@ 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 */ 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_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)) { - /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ - return; + + if (unlikely(gb->write_memory_callback)) { + if (!gb->write_memory_callback(gb, addr, value)) return; } + + 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; + } + + if (GB_is_cgb(gb) && (gb->dma_current_src < 0xC000 || gb->dma_current_src >= 0xE000) && addr >= 0xC000) { + // TODO: this should probably affect the DMA dest as well + addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + goto write; + } + 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 - 1) & 0x1000) | (addr & 0xFFF) | 0xC000; + } + else { + addr = (gb->dma_current_src - 1); + } + if (GB_is_cgb(gb) || addr >= 0xA000) { + if (addr < 0xA000) { + gb->oam[gb->dma_current_dest - 1] = 0; + } + else if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B)) { + gb->oam[gb->dma_current_dest - 1] &= value; + } + else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && !oam_write) { + gb->oam[gb->dma_current_dest - 1] = value; + } + if (gb->model < GB_MODEL_CGB_E || addr >= 0xA000) return; + } + } +write: write_map[addr >> 12](gb, addr, value); } +bool GB_is_dma_active(GB_gameboy_t *gb) +{ + return gb->dma_current_dest != 0xA1; +} + void GB_dma_run(GB_gameboy_t *gb) { - while (gb->dma_cycles >= 4 && gb->dma_steps_left) { - /* Todo: measure this value */ - gb->dma_cycles -= 4; - gb->dma_steps_left--; - - if (gb->dma_current_src < 0xe000) { + if (gb->dma_current_dest == 0xA1) return; + if (unlikely(gb->halted || gb->stopped)) return; + signed cycles = gb->dma_cycles + gb->dma_cycles_modulo; + gb->in_dma_read = true; + while (unlikely(cycles >= 4)) { + cycles -= 4; + if (gb->dma_current_dest >= 0xA0) { + gb->dma_current_dest++; + if (gb->display_state == 8) { + gb->io_registers[GB_IO_STAT] |= 2; + GB_STAT_update(gb); + } + break; + } + if (unlikely(gb->hdma_in_progress && (gb->hdma_steps_left > 1 || (gb->hdma_current_dest & 0xF) != 0xF))) { + gb->dma_current_dest++; + } + else if (gb->dma_current_src < 0xE000) { gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); } 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 */ gb->dma_current_src++; - if (!gb->dma_steps_left) { - gb->is_dma_restarting = false; - } + gb->dma_ppu_vram_conflict = false; } + gb->in_dma_read = false; + gb->dma_cycles_modulo = cycles; + gb->dma_cycles = 0; } void GB_hdma_run(GB_gameboy_t *gb) { - if (!gb->hdma_on) return; - - while (gb->hdma_cycles >= 0x4) { - gb->hdma_cycles -= 0x4; - - GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); + unsigned cycles = gb->cgb_double_speed? 4 : 2; + /* This is a bit cart, revision and unit specific. TODO: what if PC is in cart RAM? */ + if (gb->model < GB_MODEL_CGB_D || gb->pc > 0x8000) { + gb->hdma_open_bus = 0xFF; + } + gb->addr_for_hdma_conflict = 0xFFFF; + uint16_t vram_base = gb->cgb_vram_bank? 0x2000 : 0; + gb->hdma_in_progress = true; + GB_advance_cycles(gb, cycles); + while (gb->hdma_on) { + uint8_t byte = gb->hdma_open_bus; + gb->addr_for_hdma_conflict = 0xFFFF; - if ((gb->hdma_current_dest & 0xf) == 0) { - if (--gb->hdma_steps_left == 0) { + if (gb->hdma_current_src < 0x8000 || + (gb->hdma_current_src & 0xE000) == 0xC000 || + (gb->hdma_current_src & 0xE000) == 0xA000) { + byte = GB_read_memory(gb, gb->hdma_current_src); + } + if (unlikely(GB_is_dma_active(gb)) && (gb->dma_cycles_modulo == 2 || gb->cgb_double_speed)) { + write_oam(gb, gb->hdma_current_src, byte); + } + gb->hdma_current_src++; + GB_advance_cycles(gb, cycles); + if (gb->addr_for_hdma_conflict == 0xFFFF /* || ((gb->model & ~GB_MODEL_GBP_BIT) >= GB_MODEL_AGB_B && gb->cgb_double_speed) */) { + uint16_t addr = (gb->hdma_current_dest++ & 0x1FFF); + gb->vram[vram_base + addr] = byte; + // TODO: vram_write_blocked might not be the correct timing + if (gb->vram_write_blocked /* && (gb->model & ~GB_MODEL_GBP_BIT) < GB_MODEL_AGB_B */) { + gb->vram[(vram_base ^ 0x2000) + addr] = byte; + } + } + else { + if (gb->model == GB_MODEL_CGB_E || gb->cgb_double_speed) { + /* + These corruptions revision (unit?) specific in single speed. They happen only on my CGB-E. + */ + gb->addr_for_hdma_conflict &= 0x1FFF; + // TODO: there are *some* scenarions in single speed mode where this write doesn't happen. What's the logic? + uint16_t addr = (gb->hdma_current_dest & gb->addr_for_hdma_conflict & 0x1FFF); + gb->vram[vram_base + addr] = byte; + // TODO: vram_write_blocked might not be the correct timing + if (gb->vram_write_blocked /* && (gb->model & ~GB_MODEL_GBP_BIT) < GB_MODEL_AGB_B */) { + gb->vram[(vram_base ^ 0x2000) + addr] = byte; + } + } + gb->hdma_current_dest++; + } + gb->hdma_open_bus = 0xFF; + + if ((gb->hdma_current_dest & 0xF) == 0) { + if (--gb->hdma_steps_left == 0 || gb->hdma_current_dest == 0) { gb->hdma_on = false; gb->hdma_on_hblank = false; - gb->hdma_starting = false; gb->io_registers[GB_IO_HDMA5] &= 0x7F; - break; } - if (gb->hdma_on_hblank) { + else if (gb->hdma_on_hblank) { gb->hdma_on = false; - break; } } } + gb->hdma_in_progress = false; // TODO: timing? (affects VRAM reads) + if (!gb->cgb_double_speed) { + GB_advance_cycles(gb, 2); + } } diff --git a/thirdparty/SameBoy/Core/memory.h b/thirdparty/SameBoy/Core/memory.h index f0d03907f..9a5824d9b 100644 --- a/thirdparty/SameBoy/Core/memory.h +++ b/thirdparty/SameBoy/Core/memory.h @@ -1,18 +1,22 @@ #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); +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 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 GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address); +internal void GB_dma_run(GB_gameboy_t *gb); +internal bool GB_is_dma_active(GB_gameboy_t *gb); +internal void GB_hdma_run(GB_gameboy_t *gb); +internal void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr); #endif #endif /* memory_h */ diff --git a/thirdparty/SameBoy/Core/printer.c b/thirdparty/SameBoy/Core/printer.c index c8514b410..1394a6a7e 100644 --- a/thirdparty/SameBoy/Core/printer.c +++ b/thirdparty/SameBoy/Core/printer.c @@ -22,8 +22,8 @@ static void handle_command(GB_gameboy_t *gb) gb->printer.status = 6; /* Printing */ uint32_t image[gb->printer.image_offset]; uint8_t palette = gb->printer.command_data[2]; - uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff), - gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa), + uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF), + gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA), gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55), gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)}; for (unsigned i = 0; i < gb->printer.image_offset; i++) { diff --git a/thirdparty/SameBoy/Core/printer.h b/thirdparty/SameBoy/Core/printer.h index f5c9b2777..f4ccfe47f 100644 --- a/thirdparty/SameBoy/Core/printer.h +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/Core/rewind.c b/thirdparty/SameBoy/Core/rewind.c index d30552845..2be73a6b7 100644 --- a/thirdparty/SameBoy/Core/rewind.c +++ b/thirdparty/SameBoy/Core/rewind.c @@ -17,7 +17,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t while (uncompressed_size) { if (prev_mode) { - if (*data == *prev && COUNTER != 0xffff) { + if (*data == *prev && COUNTER != 0xFFFF) { COUNTER++; data++; prev++; @@ -35,7 +35,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t } } else { - if (*data != *prev && COUNTER != 0xffff) { + if (*data != *prev && COUNTER != 0xFFFF) { COUNTER++; DATA = *data; data_pos++; diff --git a/thirdparty/SameBoy/Core/rewind.h b/thirdparty/SameBoy/Core/rewind.h index ad5484108..3cc23ed90 100644 --- a/thirdparty/SameBoy/Core/rewind.h +++ b/thirdparty/SameBoy/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); +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/thirdparty/SameBoy/Core/rumble.h b/thirdparty/SameBoy/Core/rumble.h index eae9f372b..ca3473781 100644 --- a/thirdparty/SameBoy/Core/rumble.h +++ b/thirdparty/SameBoy/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); +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/thirdparty/SameBoy/Core/save_state.c b/thirdparty/SameBoy/Core/save_state.c index b83790b6d..a4b316c3e 100644 --- a/thirdparty/SameBoy/Core/save_state.c +++ b/thirdparty/SameBoy/Core/save_state.c @@ -3,14 +3,21 @@ #include #include -#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" GB_VERSION " (Big Endian)" #else -#define BESS_NAME "SameBoy v" xstr(VERSION) +#define BESS_NAME "SameBoy v" GB_VERSION #endif +_Static_assert((GB_SECTION_OFFSET(core_state) & 7) == 0, "Section core_state is not aligned"); +_Static_assert((GB_SECTION_OFFSET(dma) & 7) == 0, "Section dma is not aligned"); +_Static_assert((GB_SECTION_OFFSET(mbc) & 7) == 0, "Section mbc is not aligned"); +_Static_assert((GB_SECTION_OFFSET(hram) & 7) == 0, "Section hram is not aligned"); +_Static_assert((GB_SECTION_OFFSET(timing) & 7) == 0, "Section timing is not aligned"); +_Static_assert((GB_SECTION_OFFSET(apu) & 7) == 0, "Section apu is not aligned"); +_Static_assert((GB_SECTION_OFFSET(rtc) & 7) == 0, "Section rtc is not aligned"); +_Static_assert((GB_SECTION_OFFSET(video) & 7) == 0, "Section video is not aligned"); + typedef struct __attribute__((packed)) { uint32_t magic; uint32_t size; @@ -60,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)) { @@ -112,6 +119,28 @@ typedef struct __attribute__((packed)){ GB_huc3_rtc_time_t data; } BESS_HUC3_t; +typedef struct __attribute__((packed)) { + BESS_block_t header; + + // Flags + bool latch_ready:1; + bool eeprom_do:1; + bool eeprom_di:1; + bool eeprom_clk:1; + bool eeprom_cs:1; + bool eeprom_write_enabled:1; + uint8_t padding:2; + + uint8_t argument_bits_left; + + uint16_t eeprom_command; + uint16_t read_bits; + + uint16_t x_latch; + uint16_t y_latch; + +} BESS_MBC7_t; + typedef struct __attribute__((packed)){ BESS_block_t header; uint64_t last_rtc_second; @@ -222,11 +251,17 @@ 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_CAMERA: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + case GB_MBC7: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_MBC7_t); + case GB_MMM01: + return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t); case GB_HUC1: - return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); case GB_HUC3: return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); case GB_TPP1: @@ -261,7 +296,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_CORE_t) + sizeof(BESS_XOAM_t) + (gb->sgb? sizeof(BESS_SGB_t) : 0) - + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1/MBC7 block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -269,30 +304,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"); @@ -300,6 +311,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; @@ -316,17 +332,34 @@ 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; } - 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_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; + case GB_MODEL_CGB_E: return true; + case GB_MODEL_AGB_A: return true; + case GB_MODEL_GBP_A: 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) @@ -336,66 +369,49 @@ static void sanitize_state(GB_gameboy_t *gb) GB_palette_changed(gb, true, i * 2); } - gb->bg_fifo.read_end &= 0xF; - gb->bg_fifo.write_end &= 0xF; - gb->oam_fifo.read_end &= 0xF; - gb->oam_fifo.write_end &= 0xF; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->oam_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->last_tile_index_address &= 0x1FFF; + gb->window_tile_x &= 0x1F; + + /* These are kind of DOS-ish if too large */ + if (abs(gb->display_cycles) > 0x80000) { + 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; - gb->fetcher_x &= 0x1f; if (gb->lcd_x > gb->position_in_line) { 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 && !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)); - } - } + if (gb->sgb) { + if (gb->sgb->player_count != 1 && gb->sgb->player_count != 2 && gb->sgb->player_count != 4) { + gb->sgb->player_count = 1; } - memcpy(gb->sgb->pending_border.tiles, converted_tiles, sizeof(converted_tiles)); + 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) @@ -417,7 +433,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) { BESS_block_t mbc_block = {BE32('MBC '), 0}; - BESS_MBC_pair_t pairs[4]; + BESS_MBC_pair_t pairs[8]; switch (gb->cartridge_type->mbc_type) { default: case GB_NO_MBC: return 0; @@ -436,9 +452,8 @@ 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[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->rtc_latch}; - mbc_block.size = 4 * sizeof(pairs[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: pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; @@ -447,25 +462,46 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank}; mbc_block.size = 4 * sizeof(pairs[0]); break; + case GB_CAMERA: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc5.rom_bank_low}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + case GB_MBC7: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc7.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc7.secondary_ram_enable? 0x40 : 0}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + case GB_MMM01: + pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2) | (gb->mmm01.rom_bank_high << 4) | (gb->mmm01.mbc1_mode_disable << 6)}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x0000), (gb->mbc_ram_enable? 0xA : 0x0) | (gb->mmm01.ram_bank_mask << 4) | (gb->mmm01.locked << 6)}; + /* For compatibility with emulators that inaccurately emulate MMM01, and also require two writes per register */ + pairs[4] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & ~(gb->mmm01.rom_bank_mask << 1))}; + pairs[5] = pairs[1]; + pairs[6] = pairs[2]; + pairs[7] = pairs[3]; + mbc_block.size = 8 * sizeof(pairs[0]); + break; case GB_HUC1: pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc1.ir_mode? 0xE : 0x0}; pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc1.bank_low}; pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc1.bank_high}; - pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->huc1.mode}; - mbc_block.size = 4 * sizeof(pairs[0]); - + mbc_block.size = 3 * sizeof(pairs[0]); case GB_HUC3: - pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc3_mode}; + pairs[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; } @@ -483,6 +519,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) return 0; } +static const uint8_t *get_header_bank(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type == GB_MMM01) { + return gb->rom + gb->rom_size - 0x8000; + } + return gb->rom; +} + static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess) { if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; @@ -500,7 +544,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; } @@ -553,11 +596,13 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe goto error; } - if (file->write(file, gb->rom + 0x134, 0x10) != 0x10) { + const uint8_t *bank = get_header_bank(gb); + + if (file->write(file, bank + 0x134, 0x10) != 0x10) { goto error; } - if (file->write(file, gb->rom + 0x14e, 2) != 2) { + if (file->write(file, bank + 0x14E, 2) != 2) { goto error; } @@ -569,6 +614,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: @@ -582,10 +628,15 @@ 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_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; case GB_MODEL_CGB_E: bess_core.full_model = BE32('CCE '); break; - case GB_MODEL_AGB: bess_core.full_model = BE32('CA '); break; // SameBoy doesn't emulate a specific AGB revision yet + case GB_MODEL_AGB_A: + case GB_MODEL_GBP_A: + bess_core.full_model = BE32('CAA '); break; } bess_core.pc = LE16(gb->pc); @@ -616,8 +667,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)) { @@ -677,11 +728,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; @@ -689,6 +740,30 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe } } + if (gb->cartridge_type ->mbc_type == GB_MBC7) { + BESS_MBC7_t bess_mbc7 = { + .latch_ready = gb->mbc7.latch_ready, + .eeprom_do = gb->mbc7.eeprom_do, + .eeprom_di = gb->mbc7.eeprom_di, + .eeprom_clk = gb->mbc7.eeprom_clk, + .eeprom_cs = gb->mbc7.eeprom_cs, + .eeprom_write_enabled = gb->mbc7.eeprom_write_enabled, + + .argument_bits_left = gb->mbc7.argument_bits_left, + + .eeprom_command = LE16(gb->mbc7.eeprom_command), + .read_bits = LE16(gb->mbc7.read_bits), + + .x_latch = LE16(gb->mbc7.x_latch), + .y_latch = LE16(gb->mbc7.y_latch), + }; + bess_mbc7.header = (BESS_block_t){BE32('MBC7'), LE32(sizeof(bess_mbc7) - sizeof(bess_mbc7.header))}; + + if (file->write(file, &bess_mbc7, sizeof(bess_mbc7)) != sizeof(bess_mbc7)) { + goto error; + } + } + bool needs_sgb_padding = false; if (gb->sgb) { /* BESS SGB */ @@ -714,7 +789,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe 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)); + bess_sgb.multiplayer_state = (gb->sgb->player_count << 4) | gb->sgb->current_player; if (file->write(file, &bess_sgb, sizeof(bess_sgb)) != sizeof(bess_sgb)) { goto error; } @@ -907,7 +982,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 @@ -968,11 +1045,15 @@ 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]); + /* Required to be compatible with both SameBoy 0.14.x AND BGB */ + if (GB_is_cgb(&save) && !save.cgb_mode && save.cgb_ram_bank == 7) { + save.cgb_ram_bank = 1; + } + break; case BE32('NAME'): if (LE32(block.size) > sizeof(emulator_name) - 1) { @@ -986,7 +1067,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo BESS_INFO_t bess_info = {0,}; if (LE32(block.size) != sizeof(bess_info) - sizeof(block)) goto parse_error; if (file->read(file, &bess_info.header + 1, LE32(block.size)) != LE32(block.size)) goto error; - if (memcmp(bess_info.title, gb->rom + 0x134, sizeof(bess_info.title))) { + const uint8_t *bank = get_header_bank(gb); + if (memcmp(bess_info.title, bank + 0x134, sizeof(bess_info.title))) { char ascii_title[0x11] = {0,}; for (unsigned i = 0; i < 0x10; i++) { if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break; @@ -994,7 +1076,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo } GB_log(gb, "Save state was made on another ROM: '%s'\n", ascii_title); } - else if (memcmp(bess_info.checksum, gb->rom + 0x14E, 2)) { + else if (memcmp(bess_info.checksum, bank + 0x14E, 2)) { GB_log(gb, "Save state was potentially made on another revision of the same ROM.\n"); } break; @@ -1007,6 +1089,12 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo case BE32('MBC '): if (!found_core) goto parse_error; if (LE32(block.size) % 3 != 0) goto parse_error; + if (LE32(block.size) > 0x1000) goto parse_error; + /* Inject some default writes, as some emulators omit them */ + if (gb->cartridge_type->mbc_type == GB_MMM01) { + GB_write_memory(&save, 0x6000, 0x30); + GB_write_memory(&save, 0x4000, 0x70); + } for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); @@ -1045,11 +1133,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; @@ -1065,6 +1153,29 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i]; } save.tpp1_mr4 = bess_tpp1.mr4; + break; + case BE32('MBC7'): + if (!found_core) goto parse_error; + BESS_MBC7_t bess_mbc7; + if (LE32(block.size) != sizeof(bess_mbc7) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_mbc7.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type != GB_MBC7) break; + + save.mbc7.latch_ready = bess_mbc7.latch_ready; + save.mbc7.eeprom_do = bess_mbc7.eeprom_do; + save.mbc7.eeprom_di = bess_mbc7.eeprom_di; + save.mbc7.eeprom_clk = bess_mbc7.eeprom_clk; + save.mbc7.eeprom_cs = bess_mbc7.eeprom_cs; + save.mbc7.eeprom_write_enabled = bess_mbc7.eeprom_write_enabled; + + save.mbc7.argument_bits_left = bess_mbc7.argument_bits_left; + + save.mbc7.eeprom_command = LE16(bess_mbc7.eeprom_command); + save.mbc7.read_bits = LE16(bess_mbc7.read_bits); + + save.mbc7.x_latch = LE16(bess_mbc7.x_latch); + save.mbc7.y_latch = LE16(bess_mbc7.y_latch); + break; case BE32('SGB '): if (!found_core) goto parse_error; @@ -1098,7 +1209,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo 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); @@ -1154,6 +1265,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo 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; } @@ -1261,7 +1373,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/thirdparty/SameBoy/Core/save_state.h b/thirdparty/SameBoy/Core/save_state.h index 79e8c0615..acb6b89bd 100644 --- a/thirdparty/SameBoy/Core/save_state.h +++ b/thirdparty/SameBoy/Core/save_state.h @@ -8,9 +8,14 @@ #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 */ +#if __clang__ #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] +// GCC's handling of attributes is so awfully bad, that it is alone a good enough reason to never use that compiler +#define GB_SECTION(name, ...) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wpedantic\"") alignas(8) char _align_##name[0]; __VA_ARGS__ _Pragma("GCC diagnostic pop") +#endif +#else +#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]; #define GB_SECTION_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)) @@ -27,7 +32,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) { @@ -36,8 +41,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); +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/thirdparty/SameBoy/Core/sgb.c b/thirdparty/SameBoy/Core/sgb.c index 894ae9663..891a27dd9 100644 --- a/thirdparty/SameBoy/Core/sgb.c +++ b/thirdparty/SameBoy/Core/sgb.c @@ -165,7 +165,7 @@ static void command_ready(GB_gameboy_t *gb) return; } memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14); - if (gb->sgb->command[0] == 0xfb) { + if (gb->sgb->command[0] == 0xFB) { if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) { gb->sgb->disable_commands = true; for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) { @@ -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) { @@ -375,39 +373,36 @@ static void command_ready(GB_gameboy_t *gb) } break; case PAL_TRN: - gb->sgb->vram_transfer_countdown = 2; + gb->sgb->vram_transfer_countdown = 3; gb->sgb->transfer_dest = TRANSFER_PALETTES; break; case DATA_SND: // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this break; case MLT_REQ: - if (gb->sgb->player_count == 1) { - gb->sgb->current_player = 0; - } gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility, fix this to be 0 based. */ if (gb->sgb->player_count == 3) { - gb->sgb->current_player++; + gb->sgb->player_count++; } - gb->sgb->mlt_lock = true; + gb->sgb->current_player &= (gb->sgb->player_count - 1); break; case CHR_TRN: - gb->sgb->vram_transfer_countdown = 2; + gb->sgb->vram_transfer_countdown = 3; gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES; break; case PCT_TRN: - gb->sgb->vram_transfer_countdown = 2; + gb->sgb->vram_transfer_countdown = 3; gb->sgb->transfer_dest = TRANSFER_BORDER_DATA; break; case ATTR_TRN: - gb->sgb->vram_transfer_countdown = 2; + gb->sgb->vram_transfer_countdown = 3; gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES; break; case ATTR_SET: - load_attribute_file(gb, gb->sgb->command[0] & 0x3F); + load_attribute_file(gb, gb->sgb->command[1] & 0x3F); - if (gb->sgb->command[0] & 0x40) { + if (gb->sgb->command[1] & 0x40) { gb->sgb->mask_mode = MASK_DISABLED; } break; @@ -437,27 +432,22 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) return; } if (gb->sgb->disable_commands) return; - if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) { - return; - } uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; if ((gb->sgb->command[0] & 0xF1) == 0xF1) { command_size = SGB_PACKET_SIZE * 8; } - if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) { - gb->sgb->mlt_lock ^= true; + if ((value & 0x20) != 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) == 0) { + if ((gb->sgb->player_count & 1) == 0) { + gb->sgb->current_player++; + gb->sgb->current_player &= (gb->sgb->player_count - 1); + } } switch ((value >> 4) & 3) { case 3: gb->sgb->ready_for_pulse = true; - if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) { - gb->sgb->current_player++; - gb->sgb->current_player &= 3; - gb->sgb->mlt_lock = true; - } break; case 2: // Zero @@ -473,10 +463,12 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) gb->sgb->ready_for_stop = false; } else { - gb->sgb->command_write_index++; - gb->sgb->ready_for_pulse = false; - if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb->ready_for_stop = true; + if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) { + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } } } break; @@ -490,11 +482,13 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); } else { - gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); - gb->sgb->command_write_index++; - gb->sgb->ready_for_pulse = false; - if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb->ready_for_stop = true; + if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) { + gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } } } break; @@ -537,7 +531,6 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_ return GB_convert_rgb15(gb, color, false); } -#include static void render_boot_animation (GB_gameboy_t *gb) { #include "graphics/sgb_animation_logo.inc" @@ -646,14 +639,12 @@ 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++; } } if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { - gb->sgb->border_animation = 64; + gb->sgb->border_animation = 105; // Measured on an SGB2, but might be off by ±2 } } } @@ -734,7 +725,10 @@ void GB_sgb_render(GB_gameboy_t *gb) } uint32_t border_colors[16 * 4]; - if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { + if (gb->sgb->border_animation == 0 || gb->sgb->border_animation > 64 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { + if (gb->sgb->border_animation != 0) { + gb->sgb->border_animation--; + } for (unsigned i = 0; i < 16 * 4; i++) { border_colors[i] = convert_rgb15(gb, LE16(gb->sgb->border.palette[i])); } @@ -767,6 +761,7 @@ void GB_sgb_render(GB_gameboy_t *gb) continue; } uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]); + if (tile & 0x300) continue; // Unused tile uint8_t flip_x = (tile & 0x4000)? 0:7; uint8_t flip_y = (tile & 0x8000)? 7:0; uint8_t palette = (tile >> 10) & 3; diff --git a/thirdparty/SameBoy/Core/sgb.h b/thirdparty/SameBoy/Core/sgb.h index 1e67835d3..3069c368e 100644 --- a/thirdparty/SameBoy/Core/sgb.h +++ b/thirdparty/SameBoy/Core/sgb.h @@ -1,15 +1,13 @@ #ifndef sgb_h #define sgb_h -#include "gb_struct_def.h" +#include "defs.h" #include #include 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]; +#ifdef GB_INTERNAL union { struct { uint16_t map[32 * 32]; @@ -17,6 +15,9 @@ typedef struct { }; uint16_t raw_data[0x440]; }; +#else + uint16_t raw_data[0x440]; +#endif } GB_sgb_border_t; #ifdef GB_INTERNAL @@ -59,16 +60,11 @@ struct GB_sgb_s { /* GB Header */ uint8_t received_header[0x54]; - - /* Multiplayer (cont) */ - 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); -void GB_sgb_render(GB_gameboy_t *gb); -void 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/thirdparty/SameBoy/Core/sm83_cpu.c b/thirdparty/SameBoy/Core/sm83_cpu.c index 377f519fe..f2f704723 100644 --- a/thirdparty/SameBoy/Core/sm83_cpu.c +++ b/thirdparty/SameBoy/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,11 @@ typedef enum { GB_CONFLICT_WX, GB_CONFLICT_CGB_LCDC, GB_CONFLICT_NR10, -} GB_conflict_t; + GB_CONFLICT_CGB_SCX, +} 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, @@ -35,12 +36,11 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB, [GB_IO_NR10] = GB_CONFLICT_NR10, - - /* Todo: most values not verified, and probably differ between revisions */ + [GB_IO_SCX] = GB_CONFLICT_CGB_SCX, }; /* 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, @@ -59,7 +59,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, @@ -82,17 +82,7 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) if (gb->pending_cycles) { GB_advance_cycles(gb, gb->pending_cycles); } - uint8_t ret = GB_read_memory(gb, addr); - gb->pending_cycles = 4; - 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 */ + gb->address_bus = addr; uint8_t ret = GB_read_memory(gb, addr); gb->pending_cycles = 4; return ret; @@ -103,10 +93,12 @@ static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) is both read be the CPU, modified by the ISR, and modified by an actual interrupt. If this timing proves incorrect, the ISR emulation must be updated so IF reads are timed correctly. */ +/* TODO: Does this affect the address bus? Verify. */ static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) { assert(gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles); + gb->address_bus = 0xFF00 + GB_IO_IF; uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F; GB_write_memory(gb, 0xFF00 + GB_IO_IF, value); gb->pending_cycles = 4; @@ -116,9 +108,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; } @@ -135,23 +127,24 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, gb->pending_cycles); GB_write_memory(gb, addr, value); gb->pending_cycles = 4; - return; + break; case GB_CONFLICT_READ_NEW: GB_advance_cycles(gb, gb->pending_cycles - 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 5; - return; + break; case GB_CONFLICT_WRITE_CPU: GB_advance_cycles(gb, gb->pending_cycles + 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; - return; + break; /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ case GB_CONFLICT_STAT_DMG: GB_advance_cycles(gb, gb->pending_cycles); + GB_display_sync(gb); /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite the timing not making much sense for that. @@ -165,7 +158,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; - return; + break; case GB_CONFLICT_STAT_CGB: { /* Todo: Verify this with SCX adjustments */ @@ -176,7 +169,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 3; - return; + break; } /* There is some "time travel" going on with these two values, as it appears @@ -191,21 +184,21 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 5; - return; + break; } case GB_CONFLICT_PALETTE_CGB: { GB_advance_cycles(gb, gb->pending_cycles - 2); GB_write_memory(gb, addr, value); gb->pending_cycles = 6; - return; + break; } case GB_CONFLICT_DMG_LCDC: { /* 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. */ @@ -213,16 +206,20 @@ 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)) { - old_value &= ~2; + GB_display_sync(gb); + if (gb->model != GB_MODEL_MGB && gb->position_in_line == 0 && (old_value & GB_LCDC_OBJ_EN) && !(value & GB_LCDC_OBJ_EN)) { + old_value &= ~GB_LCDC_OBJ_EN; } - GB_write_memory(gb, addr, old_value | (value & 1)); + GB_write_memory(gb, addr, old_value | (value & GB_LCDC_BG_EN)); GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); + + if ((old_value & GB_LCDC_WIN_ENABLE) && !(value & GB_LCDC_WIN_ENABLE) && gb->window_is_being_fetched) { + gb->disable_window_pixel_insertion_glitch = true; + } gb->pending_cycles = 5; - return; + break; } case GB_CONFLICT_SGB_LCDC: { @@ -236,7 +233,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); GB_write_memory(gb, addr, value); gb->pending_cycles = 5; - return; + break; } case GB_CONFLICT_WX: @@ -246,14 +243,14 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, 1); gb->wx_just_changed = false; gb->pending_cycles = 3; - return; + break; case GB_CONFLICT_CGB_LCDC: - if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) { + if ((~value & gb->io_registers[GB_IO_LCDC]) & GB_LCDC_TILE_SEL) { // 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_write_memory(gb, addr, value ^ GB_LCDC_TILE_SEL); // Write with the old TILE_SET first gb->tile_sel_glitch = true; GB_advance_cycles(gb, 1); gb->tile_sel_glitch = false; @@ -262,7 +259,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_write_memory(gb, addr, value ^ GB_LCDC_TILE_SEL); // Write with the old TILE_SET first gb->tile_sel_glitch = true; GB_advance_cycles(gb, 1); gb->tile_sel_glitch = false; @@ -275,7 +272,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_write_memory(gb, addr, value); gb->pending_cycles = 4; } - return; + break; case GB_CONFLICT_NR10: /* Hack: Due to the coupling between DIV and the APU, GB_apu_run only runs at M-cycle @@ -284,17 +281,34 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) GB_advance_cycles(gb, gb->pending_cycles); if (gb->model <= GB_MODEL_CGB_C) { // TODO: Double speed mode? This logic is also a bit weird, it needs more tests + GB_apu_run(gb, true); if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) { gb->apu.square_sweep_calculate_countdown -= 2; } gb->apu.enable_zombie_calculate_stepping = true; - GB_write_memory(gb, addr, 0xFF); + /* TODO: this causes audio regressions in the Donkey Kong Land series. + The exact behavior of this quirk should be further investigated, as it seems + more complicated than a single FF pseudo-write. */ + // GB_write_memory(gb, addr, 0xFF); } GB_write_memory(gb, addr, value); gb->pending_cycles = 4; - return; - + break; + + case GB_CONFLICT_CGB_SCX: + if (gb->cgb_double_speed) { + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 6; + } + else { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + break; } + gb->address_bus = addr; } static void cycle_no_access(GB_gameboy_t *gb) @@ -304,28 +318,20 @@ static void cycle_no_access(GB_gameboy_t *gb) static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) { - if (GB_is_cgb(gb)) { - /* Slight optimization */ - gb->pending_cycles += 4; - return; - } if (gb->pending_cycles) { GB_advance_cycles(gb, gb->pending_cycles); } + gb->address_bus = gb->registers[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->address_bus = gb->pc; GB_trigger_oam_bug(gb, gb->pc); /* Todo: test T-cycle timing */ gb->pending_cycles = 4; } @@ -354,7 +360,11 @@ 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->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); gb->oam_ppu_blocked = !gb->oam_read_blocked; gb->vram_ppu_blocked = !gb->vram_read_blocked; gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked; @@ -363,56 +373,87 @@ static void enter_stop_mode(GB_gameboy_t *gb) static void leave_stop_mode(GB_gameboy_t *gb) { gb->stopped = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + // TODO: verify this + gb->dma_cycles = 4; + GB_dma_run(gb); gb->oam_ppu_blocked = false; gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x1FFF; i--;) { - GB_advance_cycles(gb, 0x10); - } - GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xF); - GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } +/* 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) { - if (gb->io_registers[GB_IO_KEY1] & 0x1) { + 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; + bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F); + // When entering with IF&IE, the 2nd byte of STOP is actually executed + if (!exit_by_joyp) { + if (!immediate_exit) { + GB_dma_run(gb); + } + enter_stop_mode(gb); + } + + if (!interrupt_pending) { + 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) */ + 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; - 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] & GB_LCDC_ENABLE && 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 = 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->cartridge_type->mbc_type == GB_CAMERA && (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && !gb->cgb_double_speed) { + GB_log(gb, "ROM entered double speed mode with a camera cartridge, this could damage a real cartridge's camera.\n"); } - - gb->cgb_double_speed ^= true; - gb->io_registers[GB_IO_KEY1] = 0; - enter_stop_mode(gb); - leave_stop_mode(gb); + if (gb->cgb_double_speed) { + gb->cgb_double_speed = false; + } + else { + gb->speed_switch_countdown = 6; + gb->speed_switch_freeze = 1; + } - if (!needs_alignment) { - GB_advance_cycles(gb, 0x4); + if (interrupt_pending) { + } + else { + gb->speed_switch_halt_countdown = 0x20008; + gb->speed_switch_freeze = 5; } + gb->io_registers[GB_IO_KEY1] = 0; } - 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_dma_run(gb); gb->halted = true; + gb->just_halted = true; + gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); } else { - enter_stop_mode(gb); + gb->speed_switch_halt_countdown = 0; } } - /* Todo: is PC being actually read? */ - gb->pc++; } /* Operand naming conventions for functions: @@ -431,8 +472,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; } @@ -440,7 +481,7 @@ static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; register_id = (opcode >> 4) + 1; - cycle_write(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); + cycle_write(gb, gb->registers[register_id], gb->af >> 8); } static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -455,14 +496,14 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] += 0x100; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F00) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((gb->registers[register_id] & 0xFF00) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) @@ -470,15 +511,15 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] -= 0x100; - gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + gb->af &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F00) == 0xF00) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((gb->registers[register_id] & 0xFF00) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -487,30 +528,30 @@ 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) { - bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + bool carry = (gb->af & 0x8000) != 0; - gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + gb->af = (gb->af & 0xFF00) << 1; if (carry) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; + gb->af |= GB_CARRY_FLAG | 0x0100; } } static void rla(GB_gameboy_t *gb, uint8_t opcode) { - bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; - bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bool bit7 = (gb->af & 0x8000) != 0; + bool carry = (gb->af & GB_CARRY_FLAG) != 0; - gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + gb->af = (gb->af & 0xFF00) << 1; if (carry) { - gb->registers[GB_REGISTER_AF] |= 0x0100; + gb->af |= 0x0100; } if (bit7) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -518,30 +559,30 @@ 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; - cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); - cycle_write(gb, addr + 1, gb->registers[GB_REGISTER_SP] >> 8); + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->sp & 0xFF); + cycle_write(gb, addr + 1, gb->sp >> 8); } static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t hl = gb->registers[GB_REGISTER_HL]; + uint16_t hl = gb->hl; uint16_t rr; uint8_t register_id; cycle_no_access(gb); register_id = (opcode >> 4) + 1; rr = gb->registers[register_id]; - gb->registers[GB_REGISTER_HL] = hl + rr; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + gb->hl = hl + rr; + gb->af &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); /* The meaning of the Half Carry flag is really hard to track -_- */ if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ( ((unsigned) hl + (unsigned) rr) & 0x10000) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -549,8 +590,8 @@ static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; register_id = (opcode >> 4) + 1; - gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[register_id]) << 8; + gb->af &= 0xFF; + gb->af |= cycle_read(gb, gb->registers[register_id]) << 8; } static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) @@ -569,14 +610,14 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) value = (gb->registers[register_id] & 0xFF) + 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((gb->registers[register_id] & 0xFF) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) @@ -588,15 +629,15 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) value = (gb->registers[register_id] & 0xFF) - 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; - gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + gb->af &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F) == 0xF) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((gb->registers[register_id] & 0xFF) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -605,37 +646,37 @@ 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) { - bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; + bool carry = (gb->af & 0x100) != 0; - gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + gb->af = (gb->af >> 1) & 0xFF00; if (carry) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; + gb->af |= GB_CARRY_FLAG | 0x8000; } } static void rra(GB_gameboy_t *gb, uint8_t opcode) { - bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; - bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bool bit1 = (gb->af & 0x0100) != 0; + bool carry = (gb->af & GB_CARRY_FLAG) != 0; - gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + gb->af = (gb->af >> 1) & 0xFF00; if (carry) { - gb->registers[GB_REGISTER_AF] |= 0x8000; + gb->af |= 0x8000; } if (bit1) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } 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); } @@ -643,13 +684,14 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) { switch ((opcode >> 3) & 0x3) { case 0: - return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + return !(gb->af & GB_ZERO_FLAG); case 1: - return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + return (gb->af & GB_ZERO_FLAG); case 2: - return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + return !(gb->af & GB_CARRY_FLAG); case 3: - return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + return (gb->af & GB_CARRY_FLAG); + nodefault; } return false; @@ -657,7 +699,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); @@ -666,118 +708,118 @@ static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) static void daa(GB_gameboy_t *gb, uint8_t opcode) { - int16_t result = gb->registers[GB_REGISTER_AF] >> 8; + int16_t result = gb->af >> 8; - gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG); + gb->af &= ~(0xFF00 | GB_ZERO_FLAG); - if (gb->registers[GB_REGISTER_AF] & GB_SUBTRACT_FLAG) { - if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + if (gb->af & GB_SUBTRACT_FLAG) { + if (gb->af & GB_HALF_CARRY_FLAG) { result = (result - 0x06) & 0xFF; } - if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + if (gb->af & GB_CARRY_FLAG) { result -= 0x60; } } else { - if ((gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) { + if ((gb->af & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) { result += 0x06; } - if ((gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) || result > 0x9F) { + if ((gb->af & GB_CARRY_FLAG) || result > 0x9F) { result += 0x60; } } if ((result & 0xFF) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((result & 0x100) == 0x100) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } - gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] |= result << 8; + gb->af &= ~GB_HALF_CARRY_FLAG; + gb->af |= result << 8; } static void cpl(GB_gameboy_t *gb, uint8_t opcode) { - gb->registers[GB_REGISTER_AF] ^= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; + gb->af ^= 0xFF00; + gb->af |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; } static void scf(GB_gameboy_t *gb, uint8_t opcode) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); + gb->af |= GB_CARRY_FLAG; + gb->af &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ccf(GB_gameboy_t *gb, uint8_t opcode) { - gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); + gb->af ^= GB_CARRY_FLAG; + gb->af &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) { - cycle_write(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); + cycle_write(gb, gb->hl++, gb->af >> 8); } static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) { - cycle_write(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); + cycle_write(gb, gb->hl--, gb->af >> 8); } 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->af &= 0xFF; + gb->af |= cycle_read(gb, gb->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->af &= 0xFF; + gb->af |= cycle_read(gb, gb->hl--) << 8; } static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1; - cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + value = cycle_read(gb, gb->hl) + 1; + cycle_write(gb, gb->hl, value); - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((value & 0x0F) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((value & 0xFF) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; - value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) - 1; - cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + value = cycle_read(gb, gb->hl) - 1; + cycle_write(gb, gb->hl, value); - gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + gb->af &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->af |= GB_SUBTRACT_FLAG; if ((value & 0x0F) == 0x0F) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((value & 0xFF) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t data = cycle_read_inc_oam_bug(gb, gb->pc++); - cycle_write(gb, gb->registers[GB_REGISTER_HL], data); + uint8_t data = cycle_read(gb, gb->pc++); + cycle_write(gb, gb->hl, data); } static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) @@ -788,9 +830,9 @@ static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) src_low = opcode & 1; if (src_register_id == GB_REGISTER_AF) { if (src_low) { - return gb->registers[GB_REGISTER_AF] >> 8; + return gb->af >> 8; } - return cycle_read(gb, gb->registers[GB_REGISTER_HL]); + return cycle_read(gb, gb->hl); } if (src_low) { return gb->registers[src_register_id] & 0xFF; @@ -807,11 +849,11 @@ static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) if (src_register_id == GB_REGISTER_AF) { if (src_low) { - gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= value << 8; + gb->af &= 0xFF; + gb->af |= value << 8; } else { - cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + cycle_write(gb, gb->hl, value); } } else { @@ -840,16 +882,16 @@ static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ #define LD_X_DHL(x) \ static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ { \ -gb->x = cycle_read(gb, gb->registers[GB_REGISTER_HL]); \ +gb->x = cycle_read(gb, gb->hl); \ } #define LD_DHL_Y(y) \ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ { \ -cycle_write(gb, gb->registers[GB_REGISTER_HL], gb->y); \ +cycle_write(gb, gb->hl, gb->y); \ } -LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) + LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) @@ -870,16 +912,16 @@ static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a + value) << 8; + a = gb->af >> 8; + gb->af = (a + value) << 8; if ((uint8_t)(a + value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) > 0x0F) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) + ((unsigned) value) > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -887,18 +929,18 @@ static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; value = get_src_value(gb, opcode); - 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; + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = (a + value + carry) << 8; if ((uint8_t)(a + value + carry) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -906,16 +948,16 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; + a = gb->af >> 8; + gb->af = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF)) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (a < value) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -923,18 +965,18 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; value = get_src_value(gb, opcode); - 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; + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = (((uint8_t)(a - value - carry)) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF) + carry) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -942,10 +984,10 @@ static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + a = gb->af >> 8; + gb->af = ((a & value) << 8) | GB_HALF_CARRY_FLAG; if ((a & value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -953,10 +995,10 @@ static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + a = gb->af >> 8; + gb->af = (a ^ value) << 8; if ((a ^ value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -964,10 +1006,10 @@ static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a | value) << 8; + a = gb->af >> 8; + gb->af = (a | value) << 8; if ((a | value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -975,17 +1017,17 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; value = get_src_value(gb, opcode); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] &= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + a = gb->af >> 8; + gb->af &= 0xFF00; + gb->af |= GB_SUBTRACT_FLAG; if (a == value) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF)) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (a < value) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -995,7 +1037,6 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) gb->pending_cycles = 0; GB_advance_cycles(gb, 4); - gb->halted = true; /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) { if (gb->ime) { @@ -1007,6 +1048,10 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) gb->halt_bug = true; } } + else { + gb->halted = true; + gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3); + } gb->just_halted = true; } @@ -1014,15 +1059,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]++) << 8; - gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. + gb->registers[register_id] = cycle_read(gb, gb->sp++); + gb->registers[register_id] |= cycle_read(gb, gb->sp++) << 8; + gb->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; @@ -1031,8 +1076,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; @@ -1041,12 +1086,12 @@ 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); - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + cycle_write(gb, --gb->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); @@ -1058,130 +1103,130 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; cycle_oam_bug(gb, GB_REGISTER_SP); register_id = ((opcode >> 4) + 1) & 3; - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + cycle_write(gb, --gb->sp, (gb->registers[register_id]) >> 8); + cycle_write(gb, --gb->sp, (gb->registers[register_id]) & 0xFF); } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a + value) << 8; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a + value) << 8; if ((uint8_t) (a + value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) > 0x0F) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) + ((unsigned) value) > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } 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++); - 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; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = (a + value + carry) << 8; - if (gb->registers[GB_REGISTER_AF] == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + if (gb->af == 0) { + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF)) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (a < value) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } 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++); - 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; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + carry = (gb->af & GB_CARRY_FLAG) != 0; + gb->af = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF) + carry) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = ((a & value) << 8) | GB_HALF_CARRY_FLAG; if ((a & value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a ^ value) << 8; if ((a ^ value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = (a | value) << 8; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af = (a | value) << 8; if ((a | value) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } 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++); - a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] &= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + value = cycle_read(gb, gb->pc++); + a = gb->af >> 8; + gb->af &= 0xFF00; + gb->af |= GB_SUBTRACT_FLAG; if (a == value) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } if ((a & 0xF) < (value & 0xF)) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if (a < value) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } @@ -1189,8 +1234,8 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; 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); + cycle_write(gb, --gb->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); gb->pc = opcode ^ 0xC7; GB_debugger_call_hook(gb, call_addr); } @@ -1198,8 +1243,8 @@ 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]++) << 8; + gb->pc = cycle_read(gb, gb->sp++); + gb->pc |= cycle_read(gb, gb->sp++) << 8; cycle_no_access(gb); } @@ -1223,80 +1268,80 @@ 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); + cycle_write(gb, --gb->sp, (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); gb->pc = addr; GB_debugger_call_hook(gb, call_addr); } static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); - cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); + uint8_t temp = cycle_read(gb, gb->pc++); + cycle_write(gb, 0xFF00 + temp, gb->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++); - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8; + gb->af &= 0xFF; + uint8_t temp = cycle_read(gb, gb->pc++); + gb->af |= cycle_read(gb, 0xFF00 + temp) << 8; } static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) { - cycle_write(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); + cycle_write(gb, 0xFF00 + (gb->bc & 0xFF), gb->af >> 8); } static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) { - gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; + gb->af &= 0xFF; + gb->af |= cycle_read(gb, 0xFF00 + (gb->bc & 0xFF)) << 8; } 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++); + uint16_t sp = gb->sp; + offset = (int8_t) cycle_read(gb, gb->pc++); cycle_no_access(gb); cycle_no_access(gb); - gb->registers[GB_REGISTER_SP] += offset; + gb->sp += offset; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; /* A new instruction, a new meaning for Half Carry! */ if ((sp & 0xF) + (offset & 0xF) > 0xF) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; } if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } } static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) { - gb->pc = gb->registers[GB_REGISTER_HL]; + gb->pc = gb->hl; } 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; - cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->af >> 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; - gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; + gb->af &= 0xFF; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; + gb->af |= cycle_read(gb, addr) << 8; } static void di(GB_gameboy_t *gb, uint8_t opcode) @@ -1317,24 +1362,24 @@ static void ei(GB_gameboy_t *gb, uint8_t opcode) 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++); + gb->af &= 0xFF00; + offset = (int8_t) cycle_read(gb, gb->pc++); cycle_no_access(gb); - gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; + gb->hl = gb->sp + offset; - if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + if ((gb->sp & 0xF) + (offset & 0xF) > 0xF) { + gb->af |= GB_HALF_CARRY_FLAG; } - if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + if ((gb->sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->af |= GB_CARRY_FLAG; } } static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) { - gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; - cycle_no_access(gb); + gb->sp = gb->hl; + cycle_oam_bug(gb, GB_REGISTER_HL); } static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) @@ -1343,13 +1388,13 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; set_src_value(gb, opcode, (value << 1) | carry); if (carry) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } - if (!(value << 1)) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + if (value == 0) { + gb->af |= GB_ZERO_FLAG; } } @@ -1359,14 +1404,14 @@ static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; value = get_src_value(gb, opcode); carry = (value & 0x01) != 0; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; value = (value >> 1) | (carry << 7); set_src_value(gb, opcode, value); if (carry) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } if (value == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1376,17 +1421,17 @@ static void rl_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; bool bit7; value = get_src_value(gb, opcode); - carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + carry = (gb->af & GB_CARRY_FLAG) != 0; bit7 = (value & 0x80) != 0; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; value = (value << 1) | carry; set_src_value(gb, opcode, value); if (bit7) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } if (value == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1397,17 +1442,17 @@ static void rr_r(GB_gameboy_t *gb, uint8_t opcode) bool bit1; value = get_src_value(gb, opcode); - carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + carry = (gb->af & GB_CARRY_FLAG) != 0; bit1 = (value & 0x1) != 0; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; value = (value >> 1) | (carry << 7); set_src_value(gb, opcode, value); if (bit1) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } if (value == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1417,13 +1462,13 @@ static void sla_r(GB_gameboy_t *gb, uint8_t opcode) bool carry; value = get_src_value(gb, opcode); carry = (value & 0x80) != 0; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; set_src_value(gb, opcode, (value << 1)); if (carry) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } if ((value & 0x7F) == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1433,14 +1478,14 @@ static void sra_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value; value = get_src_value(gb, opcode); bit7 = value & 0x80; - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; if (value & 1) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } value = (value >> 1) | bit7; set_src_value(gb, opcode, value); if (value == 0) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1448,13 +1493,13 @@ static void srl_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; value = get_src_value(gb, opcode); - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; set_src_value(gb, opcode, (value >> 1)); if (value & 1) { - gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->af |= GB_CARRY_FLAG; } if (!(value >> 1)) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1462,10 +1507,10 @@ static void swap_r(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value; value = get_src_value(gb, opcode); - gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->af &= 0xFF00; set_src_value(gb, opcode, (value >> 4) | (value << 4)); if (!value) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } @@ -1476,10 +1521,10 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) value = get_src_value(gb, opcode); bit = 1 << ((opcode >> 3) & 7); if ((opcode & 0xC0) == 0x40) { /* Bit */ - gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + gb->af &= 0xFF00 | GB_CARRY_FLAG; + gb->af |= GB_HALF_CARRY_FLAG; if (!(bit & value)) { - gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + gb->af |= GB_ZERO_FLAG; } } else if ((opcode & 0xC0) == 0x80) { /* res */ @@ -1492,7 +1537,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); @@ -1524,7 +1569,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 */ @@ -1562,13 +1607,12 @@ static GB_opcode_t *opcodes[256] = { }; void GB_cpu_run(GB_gameboy_t *gb) { - if (gb->hdma_on) { - GB_advance_cycles(gb, 4); - return; - } if (gb->stopped) { GB_timing_sync(gb); GB_advance_cycles(gb, 4); + 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); @@ -1600,28 +1644,41 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Wake up from HALT mode without calling interrupt code. */ if (gb->halted && !effective_ime && interrupt_queue) { gb->halted = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + gb->dma_cycles = 4; + GB_dma_run(gb); + gb->speed_switch_halt_countdown = 0; } /* Call interrupt */ else if (effective_ime && interrupt_queue) { gb->halted = false; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) { + gb->hdma_on = true; + } + // TODO: verify the timing! + gb->dma_cycles = 4; + GB_dma_run(gb); + gb->speed_switch_halt_countdown = 0; uint16_t call_addr = gb->pc; - gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); + 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 */ + GB_trigger_oam_bug(gb, gb->sp); /* Todo: test T-cycle timing */ cycle_no_access(gb); - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->sp, (gb->pc) >> 8); interrupt_queue = gb->interrupt_enable; - if (gb->registers[GB_REGISTER_SP] == GB_IO_IF + 0xFF00 + 1) { - gb->registers[GB_REGISTER_SP]--; + if (gb->sp == GB_IO_IF + 0xFF00 + 1) { + gb->sp--; interrupt_queue &= cycle_write_if(gb, (gb->pc) & 0xFF); } else { - cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + cycle_write(gb, --gb->sp, (gb->pc) & 0xFF); interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; } @@ -1631,6 +1688,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; } @@ -1642,19 +1703,19 @@ 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++); - if (gb->halt_bug) { + uint8_t opcode = gb->hdma_open_bus = cycle_read(gb, gb->pc++); + if (unlikely(gb->hdma_on)) { + GB_hdma_run(gb); + } + if (unlikely(gb->execution_callback)) { + gb->execution_callback(gb, gb->pc - 1, opcode); + } + if (unlikely(gb->halt_bug)) { gb->pc--; gb->halt_bug = false; } - opcodes[gb->last_opcode_read](gb, gb->last_opcode_read); + opcodes[opcode](gb, opcode); } flush_pending_cycles(gb); - - if (gb->hdma_starting) { - gb->hdma_starting = false; - gb->hdma_on = true; - gb->hdma_cycles = -8; - } } diff --git a/thirdparty/SameBoy/Core/sm83_cpu.h b/thirdparty/SameBoy/Core/sm83_cpu.h index 49fa80b5a..1221fd763 100644 --- a/thirdparty/SameBoy/Core/sm83_cpu.h +++ b/thirdparty/SameBoy/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); +internal void GB_cpu_run(GB_gameboy_t *gb); #endif #endif /* sm83_cpu_h */ diff --git a/thirdparty/SameBoy/Core/sm83_disassembler.c b/thirdparty/SameBoy/Core/sm83_disassembler.c index 7dacd9ebc..b3d583630 100644 --- a/thirdparty/SameBoy/Core/sm83_disassembler.c +++ b/thirdparty/SameBoy/Core/sm83_disassembler.c @@ -2,8 +2,9 @@ #include #include "gb.h" +#define GB_read_memory GB_safe_read_memory -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) { @@ -518,7 +519,7 @@ static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; uint8_t addr = GB_read_memory(gb, (*pc)++); - const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr); + const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr); if (symbol) { GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr); } @@ -531,7 +532,7 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { (*pc)++; uint8_t addr = GB_read_memory(gb, (*pc)++); - const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr); + const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr); if (symbol) { GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr); } @@ -716,7 +717,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/thirdparty/SameBoy/Core/symbol_hash.c b/thirdparty/SameBoy/Core/symbol_hash.c index a3718b830..2b2758f80 100644 --- a/thirdparty/SameBoy/Core/symbol_hash.c +++ b/thirdparty/SameBoy/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,11 +39,14 @@ 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--; } if (index < map->n_symbols) { + while (index && map->symbols[index].addr == map->symbols[index - 1].addr) { + index--; + } return &map->symbols[index]; } return NULL; @@ -74,13 +77,13 @@ static unsigned hash_name(const char *name) unsigned r = 0; while (*name) { r <<= 1; - if (r & 0x400) { - r ^= 0x401; + if (r & 0x2000) { + r ^= 0x2001; } - r += (unsigned char)*(name++); + r ^= (unsigned char)*(name++); } - return r & 0x3FF; + return r; } void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol) diff --git a/thirdparty/SameBoy/Core/symbol_hash.h b/thirdparty/SameBoy/Core/symbol_hash.h index 2a03c96bb..47d488424 100644 --- a/thirdparty/SameBoy/Core/symbol_hash.h +++ b/thirdparty/SameBoy/Core/symbol_hash.h @@ -23,16 +23,16 @@ typedef struct { } GB_symbol_map_t; typedef struct { - GB_symbol_t *buckets[0x400]; + GB_symbol_t *buckets[0x2000]; } 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); +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/thirdparty/SameBoy/Core/timing.c b/thirdparty/SameBoy/Core/timing.c index 7b79b7259..041b4d226 100644 --- a/thirdparty/SameBoy/Core/timing.c +++ b/thirdparty/SameBoy/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) @@ -54,13 +54,17 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) void GB_timing_sync(GB_gameboy_t *gb) { + /* 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; } - /* Prevent syncing if not enough time has passed.*/ - if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) 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; @@ -91,28 +95,45 @@ 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; + + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } + return; } #endif #define IR_DECAY 31500 -#define IR_THRESHOLD 19900 -#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY +#define IR_WARMUP 19900 +#define IR_THRESHOLD 240 +#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + 268 -static void 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)) { + /* TODO: the way this thing works makes the CGB IR port behave inaccurately when used together with HUC1/3 IR ports*/ + if ((gb->model > GB_MODEL_CGB_E || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return; + bool is_sensing = (gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 || + (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) || + (gb->cartridge_type->mbc_type == GB_HUC3 && gb->huc3.mode == 0xE); + if (is_sensing && (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1))) { gb->ir_sensor += cycles; if (gb->ir_sensor > IR_MAX) { gb->ir_sensor = IR_MAX; } - gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY; + gb->effective_ir_input = gb->ir_sensor >= IR_WARMUP + IR_THRESHOLD && gb->ir_sensor <= IR_WARMUP + IR_THRESHOLD + IR_DECAY; } else { - if (gb->ir_sensor <= cycles) { - gb->ir_sensor = 0; + unsigned target = is_sensing? IR_WARMUP : 0; + if (gb->ir_sensor < target) { + gb->ir_sensor += cycles; + } + else if (gb->ir_sensor <= target + cycles) { + gb->ir_sensor = target; } else { gb->ir_sensor -= cycles; @@ -142,32 +163,69 @@ static void increase_tima(GB_gameboy_t *gb) } } -static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) +void GB_serial_master_edge(GB_gameboy_t *gb) +{ + if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) { + gb->printer.idle_time += 1 << gb->serial_mask; + } + + gb->serial_master_clock ^= true; + + if (!gb->serial_master_clock && (gb->io_registers[GB_IO_SC] & 0x81) == 0x81) { + gb->serial_count++; + if (gb->serial_count == 8) { + gb->serial_count = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_IF] |= 8; + } + + gb->io_registers[GB_IO_SB] <<= 1; + + if (gb->serial_transfer_bit_end_callback) { + gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] |= 1; + } + + if (gb->serial_count) { + /* Still more bits to send */ + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + } +} + + +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ - 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])) { + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); } + if (triggers & gb->serial_mask) { + GB_serial_master_edge(gb); + } + /* TODO: Can switching to double speed mode trigger an event? */ uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000; if (triggers & apu_bit) { - 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; } -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)) { @@ -179,11 +237,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); @@ -191,68 +246,34 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) 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) +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) { - if (gb->printer.command_state || gb->printer.bits_received) { - gb->printer.idle_time += cycles; + if (gb->rtc_mode != mode) { + gb->rtc_mode = mode; + gb->rtc_cycles = 0; + gb->last_rtc_second = time(NULL); } - if (gb->serial_length == 0) { - gb->serial_cycles += cycles; +} + + +void GB_set_rtc_multiplier(GB_gameboy_t *gb, double multiplier) +{ + if (multiplier == 1) { + gb->rtc_second_length = 0; return; } - while (cycles > gb->serial_length) { - advance_serial(gb, gb->serial_length); - cycles -= gb->serial_length; - } - - uint16_t previous_serial_cycles = gb->serial_cycles; - gb->serial_cycles += cycles; - if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { - gb->serial_count++; - if (gb->serial_count == 8) { - gb->serial_length = 0; - gb->serial_count = 0; - gb->io_registers[GB_IO_SC] &= ~0x80; - gb->io_registers[GB_IO_IF] |= 8; - } - - gb->io_registers[GB_IO_SB] <<= 1; - - if (gb->serial_transfer_bit_end_callback) { - gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); - } - else { - gb->io_registers[GB_IO_SB] |= 1; - } - - if (gb->serial_length) { - /* Still more bits to send */ - if (gb->serial_transfer_bit_start_callback) { - gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); - } - } - - } - return; - + gb->rtc_second_length = GB_get_unmultiplied_clock_rate(gb) * 2 * multiplier; } -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; + 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; switch (gb->rtc_mode) { case GB_RTC_MODE_SYNC_TO_HOST: @@ -266,8 +287,8 @@ static void GB_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; } @@ -275,10 +296,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; @@ -344,44 +365,99 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) } } +static void camera_run(GB_gameboy_t *gb, uint8_t cycles) +{ + /* Do we have a camera? */ + if (likely(gb->cartridge_type->mbc_type != GB_CAMERA)) return; + + /* The camera mapper uses the PHI pin to clock itself */ + + /* PHI does not run in halt nor stop mode */ + if (unlikely(gb->halted || gb->stopped)) return; + + /* Only every other PHI is used (as the camera wants a 512KiHz clock) */ + gb->camera_alignment += cycles; + + /* Is the camera processing an image? */ + if (likely(gb->camera_countdown == 0)) return; + + gb->camera_countdown -= cycles; + if (gb->camera_countdown <= 0) { + gb->camera_countdown = 0; + GB_camera_updated(gb); + } +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { + if (unlikely(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; + gb->dma_cycles = cycles; - GB_timers_run(gb, cycles); - if (!gb->stopped) { - advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode - } + timers_run(gb, cycles); + camera_run(gb, cycles); + 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; + 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) { + 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] & GB_LCDC_ENABLE)) { 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; 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_joypad_run(gb, cycles); + GB_apu_run(gb, false); + GB_display_run(gb, cycles, false); + if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode GB_dma_run(gb); - GB_hdma_run(gb); } - 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); } /* @@ -394,13 +470,13 @@ 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) { /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ - if (!(new_tac & 4) || gb->div_counter & new_clocks) { + if (!(new_tac & 4) || !(gb->div_counter & new_clocks)) { increase_tima(gb); } } diff --git a/thirdparty/SameBoy/Core/timing.h b/thirdparty/SameBoy/Core/timing.h index 07e04734b..8cc82e322 100644 --- a/thirdparty/SameBoy/Core/timing.h +++ b/thirdparty/SameBoy/Core/timing.h @@ -1,13 +1,25 @@ #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); +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); +internal void GB_serial_master_edge(GB_gameboy_t *gb); enum { GB_TIMA_RUNNING = 0, GB_TIMA_RELOADING = 1, @@ -17,13 +29,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; \ @@ -33,6 +55,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 diff --git a/thirdparty/SameBoy/Core/workboy.h b/thirdparty/SameBoy/Core/workboy.h index d21f27316..c99c27242 100644 --- a/thirdparty/SameBoy/Core/workboy.h +++ b/thirdparty/SameBoy/Core/workboy.h @@ -3,7 +3,7 @@ #include #include #include -#include "gb_struct_def.h" +#include "defs.h" typedef struct { diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/128x128.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/128x128.png index 6303f2373..b3259ed2a 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/128x128.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/128x128.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/16x16.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/16x16.png index 6c3f81d08..335107ec7 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/16x16.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/16x16.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/256x256.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/256x256.png index e2a6ceee4..56ea01664 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/256x256.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/256x256.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/32x32.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/32x32.png index d7f2e4eba..ea65e5a6f 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/32x32.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/32x32.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/512x512.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/512x512.png index 1608c71f5..9004db209 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/512x512.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/512x512.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/AppIcon/64x64.png b/thirdparty/SameBoy/FreeDesktop/AppIcon/64x64.png index 4a54e94eb..a5675ca2c 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/AppIcon/64x64.png and b/thirdparty/SameBoy/FreeDesktop/AppIcon/64x64.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/128x128.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/128x128.png index bc14d7922..e64a3bb77 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/128x128.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/128x128.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/16x16.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/16x16.png index 3cbd9ae7a..e3780396f 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/16x16.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/16x16.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/256x256.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/256x256.png index 14258ea86..b052b313f 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/256x256.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/256x256.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/32x32.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/32x32.png index c8ef62fdf..270de894c 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/32x32.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/32x32.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/512x512.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/512x512.png index 71314f72b..e8257c19e 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/512x512.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/512x512.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/Cartridge/64x64.png b/thirdparty/SameBoy/FreeDesktop/Cartridge/64x64.png index 8835f79c4..34d006d7a 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/Cartridge/64x64.png and b/thirdparty/SameBoy/FreeDesktop/Cartridge/64x64.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/128x128.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/128x128.png index da4757e81..978b27b32 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/128x128.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/128x128.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/16x16.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/16x16.png index 50e6b2b47..899886ef1 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/16x16.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/16x16.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/256x256.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/256x256.png index 186f5d30b..10e655837 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/256x256.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/256x256.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/32x32.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/32x32.png index 47e45b505..83f7f4b19 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/32x32.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/32x32.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/512x512.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/512x512.png index 715d68f31..417b2e53b 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/512x512.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/512x512.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/64x64.png b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/64x64.png index 403e307a0..01435cee0 100644 Binary files a/thirdparty/SameBoy/FreeDesktop/ColorCartridge/64x64.png and b/thirdparty/SameBoy/FreeDesktop/ColorCartridge/64x64.png differ diff --git a/thirdparty/SameBoy/FreeDesktop/sameboy.desktop b/thirdparty/SameBoy/FreeDesktop/sameboy.desktop index 20b1b46eb..80d0902f5 100644 --- a/thirdparty/SameBoy/FreeDesktop/sameboy.desktop +++ b/thirdparty/SameBoy/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 diff --git a/thirdparty/SameBoy/HexFiend/HFController.m b/thirdparty/SameBoy/HexFiend/HFController.m index 74e033c03..9d4b1aec0 100644 --- a/thirdparty/SameBoy/HexFiend/HFController.m +++ b/thirdparty/SameBoy/HexFiend/HFController.m @@ -279,8 +279,13 @@ - (void)setDisplayedLineRange:(HFFPRange)range { #if ! NDEBUG HFASSERT(range.location >= 0); HFASSERT(range.length >= 0); - HFASSERT(range.location + range.length <= HFULToFP([self totalLineCount])); #endif + if (range.location + range.length > HFULToFP([self totalLineCount])) { + range.location = [self totalLineCount] - range.length; + if (range.location < 0) { + return; + } + } if (! HFFPRangeEqualsRange(range, displayedLineRange)) { displayedLineRange = range; [self _addPropertyChangeBits:HFControllerDisplayedLineRange]; diff --git a/thirdparty/SameBoy/HexFiend/HFHexTextRepresenter.m b/thirdparty/SameBoy/HexFiend/HFHexTextRepresenter.m index f98382b6e..4ff9f2028 100644 --- a/thirdparty/SameBoy/HexFiend/HFHexTextRepresenter.m +++ b/thirdparty/SameBoy/HexFiend/HFHexTextRepresenter.m @@ -182,7 +182,7 @@ - (void)controllerDidChange:(HFControllerPropertyBits)bits { [[self view] setHidesNullBytes:[[self controller] shouldHideNullBytes]]; } [super controllerDidChange:bits]; - if (bits & (HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges)) { + if (bits & (HFControllerSelectedRanges)) { [self _clearOmittedNybble]; } } diff --git a/thirdparty/SameBoy/JoyKit/ControllerConfiguration.inc b/thirdparty/SameBoy/JoyKit/ControllerConfiguration.inc index ea3ba9a46..a60f2796b 100644 --- a/thirdparty/SameBoy/JoyKit/ControllerConfiguration.inc +++ b/thirdparty/SameBoy/JoyKit/ControllerConfiguration.inc @@ -4,8 +4,6 @@ hacksByManufacturer = @{ @(0x045E): @{ // Microsoft - /* Generally untested, but Microsoft goes by the book when it comes to HID report descriptors, so - it should work out of the box. The hack is only here for automatic mapping */ JOYAxisGroups: @{ @(kHIDUsage_GD_X): @(0), @@ -13,7 +11,7 @@ hacksByManufacturer = @{ @(kHIDUsage_GD_Z): @(2), @(kHIDUsage_GD_Rx): @(1), @(kHIDUsage_GD_Ry): @(1), - @(kHIDUsage_GD_Rz): @(3), + @(kHIDUsage_GD_Rz): @(2), }, JOYButtonUsageMapping: @{ @@ -37,8 +35,10 @@ hacksByManufacturer = @{ JOYAxes2DUsageMapping: @{ AXES2D(1): @(JOYAxes2DUsageLeftStick), - AXES2D(4): @(JOYAxes2DUsageRightStick), + AXES2D(3): @(JOYAxes2DUsageRightStick), }, + + JOYEmulateAxisButtons: @YES, }, @(0x054C): @{ // Sony @@ -71,14 +71,65 @@ hacksByManufacturer = @{ }, JOYAxisUsageMapping: @{ - AXIS(4): @(JOYAxisUsageL1), - AXIS(5): @(JOYAxisUsageR1), + AXIS(4): @(JOYAxisUsageL2), + AXIS(5): @(JOYAxisUsageR2), }, JOYAxes2DUsageMapping: @{ AXES2D(1): @(JOYAxes2DUsageLeftStick), AXES2D(4): @(JOYAxes2DUsageRightStick), }, + + // When DualSense mode is activated on BT, The report ID is 0x31 and there's an extra byte + JOYCustomReports: @{ + @(0x31): @[ + /* 1D and 2D axes */ + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x08, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x10, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x18, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x20, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x28, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255}, + @{@"reportID": @(0x31), @"size":@8, @"offset":@0x30, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0, @"max": @255}, + + /* Hat Switch*/ + @{@"reportID": @(0x31), @"size":@4, @"offset":@0x40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Hatswitch), @"min": @0, @"max": @7}, + + /* Buttons */ + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x44, @"usagePage":@(kHIDPage_Button), @"usage":@1}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x45, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x46, @"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x47, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x48, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x49, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4A, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4B, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4C, @"usagePage":@(kHIDPage_Button), @"usage":@9}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4D, @"usagePage":@(kHIDPage_Button), @"usage":@10}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4E, @"usagePage":@(kHIDPage_Button), @"usage":@11}, + @{@"reportID": @(0x31), @"size":@1, @"offset":@0x4F, @"usagePage":@(kHIDPage_Button), @"usage":@12}, + @{@"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)}, + ], + }, + + JOYIsSony: @YES, } }; @@ -371,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}, @@ -405,11 +454,135 @@ 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)}, + ], + }, + + JOYIgnoredReports: @[@(0x30)], // Ignore the real 0x30 report as it's broken + }, + + @"Joy-Con (L)": @{ // Switch Pro Controller + JOYIsSwitch: @YES, + JOYJoyCon: @(JOYJoyConTypeLeft), + JOYAxisGroups: @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(0), + }, + + JOYButtonUsageMapping: @{ + BUTTON(5): @(JOYButtonUsageL1), + BUTTON(7): @(JOYButtonUsageL2), + BUTTON(9): @(JOYButtonUsageSelect), + BUTTON(11): @(JOYButtonUsageLStick), + BUTTON(14): @(JOYButtonUsageMisc), + BUTTON(15): @(JOYButtonUsageL3), + BUTTON(16): @(JOYButtonUsageR3), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(1): @(JOYAxes2DUsageLeftStick), + }, + + JOYCustomReports: @{ + @(0x30): @[ + @{@"reportID": @(1), @"size":@1, @"offset":@24, @"usagePage":@(kHIDPage_Button), @"usage":@9}, + @{@"reportID": @(1), @"size":@1, @"offset":@27, @"usagePage":@(kHIDPage_Button), @"usage":@11}, + + @{@"reportID": @(1), @"size":@1, @"offset":@29, @"usagePage":@(kHIDPage_Button), @"usage":@14}, + + @{@"reportID": @(1), @"size":@1, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, + @{@"reportID": @(1), @"size":@1, @"offset":@33, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, + @{@"reportID": @(1), @"size":@1, @"offset":@34, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@35, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + + @{@"reportID": @(1), @"size":@1, @"offset":@36, @"usagePage":@(kHIDPage_Button), @"usage":@16}, + @{@"reportID": @(1), @"size":@1, @"offset":@37, @"usagePage":@(kHIDPage_Button), @"usage":@15}, + @{@"reportID": @(1), @"size":@1, @"offset":@38, @"usagePage":@(kHIDPage_Button), @"usage":@5}, + @{@"reportID": @(1), @"size":@1, @"offset":@39, @"usagePage":@(kHIDPage_Button), @"usage":@7}, + + /* Sticks */ + @{@"reportID": @(1), @"size":@12, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @0xFFF}, + @{@"reportID": @(1), @"size":@12, @"offset":@52, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"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)}, ], }, + + JOYIgnoredReports: @[@(0x30)], // Ignore the real 0x30 report as it's broken }, - JOYIgnoredReports: @(0x30), // Ignore the real 0x30 report as it's broken + @"Joy-Con (R)": @{ // Switch Pro Controller + JOYIsSwitch: @YES, + JOYJoyCon: @(JOYJoyConTypeRight), + JOYAxisGroups: @{ + @(kHIDUsage_GD_Rx): @(1), + @(kHIDUsage_GD_Ry): @(1), + @(kHIDUsage_GD_Rz): @(1), + }, + + JOYButtonUsageMapping: @{ + BUTTON(1): @(JOYButtonUsageB), + BUTTON(2): @(JOYButtonUsageA), + BUTTON(3): @(JOYButtonUsageY), + BUTTON(4): @(JOYButtonUsageX), + BUTTON(6): @(JOYButtonUsageR1), + BUTTON(8): @(JOYButtonUsageR2), + BUTTON(10): @(JOYButtonUsageStart), + BUTTON(12): @(JOYButtonUsageRStick), + BUTTON(13): @(JOYButtonUsageHome), + BUTTON(15): @(JOYButtonUsageL3), + BUTTON(16): @(JOYButtonUsageR3), + }, + + JOYAxes2DUsageMapping: @{ + AXES2D(4): @(JOYAxes2DUsageRightStick), + }, + + JOYCustomReports: @{ + @(0x30): @[ + + @{@"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}, + @{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@2}, + @{@"reportID": @(1), @"size":@1, @"offset":@20, @"usagePage":@(kHIDPage_Button), @"usage":@16}, + @{@"reportID": @(1), @"size":@1, @"offset":@21, @"usagePage":@(kHIDPage_Button), @"usage":@15}, + @{@"reportID": @(1), @"size":@1, @"offset":@22, @"usagePage":@(kHIDPage_Button), @"usage":@6}, + @{@"reportID": @(1), @"size":@1, @"offset":@23, @"usagePage":@(kHIDPage_Button), @"usage":@8}, + + @{@"reportID": @(1), @"size":@1, @"offset":@25, @"usagePage":@(kHIDPage_Button), @"usage":@10}, + @{@"reportID": @(1), @"size":@1, @"offset":@26, @"usagePage":@(kHIDPage_Button), @"usage":@12}, + @{@"reportID": @(1), @"size":@1, @"offset":@28, @"usagePage":@(kHIDPage_Button), @"usage":@13}, + + /* Sticks */ + @{@"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}, + + // The X axis is inverted on the right Joy-Con + @{@"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)}, + ], + }, + + JOYIgnoredReports: @[@(0x30)], // Ignore the real 0x30 report as it's broken + }, + @"PLAYSTATION(R)3 Controller": @{ // DualShock 3 JOYAxisGroups: @{ diff --git a/thirdparty/SameBoy/JoyKit/JOYAxes2D.h b/thirdparty/SameBoy/JoyKit/JOYAxes2D.h index b6f6d152f..71fdb422e 100644 --- a/thirdparty/SameBoy/JoyKit/JOYAxes2D.h +++ b/thirdparty/SameBoy/JoyKit/JOYAxes2D.h @@ -1,4 +1,5 @@ #import +#import "JOYInput.h" typedef enum { JOYAxes2DUsageNone, @@ -11,10 +12,8 @@ typedef enum { JOYAxes2DUsageGeneric0 = 0x10000, } JOYAxes2DUsage; -@interface JOYAxes2D : NSObject -- (NSString *)usageString; +@interface JOYAxes2D : JOYInput + (NSString *)usageToString: (JOYAxes2DUsage) usage; -- (uint64_t)uniqueID; - (double)distance; - (double)angle; - (NSPoint)value; diff --git a/thirdparty/SameBoy/JoyKit/JOYAxes2D.m b/thirdparty/SameBoy/JoyKit/JOYAxes2D.m index 272d34f9c..0d80de04d 100644 --- a/thirdparty/SameBoy/JoyKit/JOYAxes2D.m +++ b/thirdparty/SameBoy/JoyKit/JOYAxes2D.m @@ -1,14 +1,17 @@ #import "JOYAxes2D.h" #import "JOYElement.h" +@interface JOYAxes2D() +@property unsigned rotation; // in 90 degrees units, clockwise +@end + @implementation JOYAxes2D { JOYElement *_element1, *_element2; double _state1, _state2; - int32_t initialX, initialY; - int32_t minX, minY; - int32_t maxX, maxY; - + int32_t _initialX, _initialY; + int32_t _minX, _minY; + int32_t _maxX, _maxY; } + (NSString *)usageToString: (JOYAxes2DUsage) usage @@ -36,12 +39,12 @@ - (NSString *)usageString - (uint64_t)uniqueID { - return _element1.uniqueID; + return _element1.uniqueID | (uint64_t)self.combinedIndex << 32; } - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %.2f%%, %.2f degrees>", self.className, self, self.usageString, self.uniqueID, self.distance * 100, self.angle]; + return [NSString stringWithFormat:@"<%@: %p, %@ (%llx); State: %.2f%%, %.2f degrees>", self.className, self, self.usageString, self.uniqueID, self.distance * 100, self.angle]; } - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 @@ -57,12 +60,12 @@ - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYEl uint16_t usage = element1.usage; _usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1; } - initialX = 0; - initialY = 0; - minX = element1.max; - minY = element2.max; - maxX = element1.min; - maxY = element2.min; + _initialX = 0; + _initialY = 0; + _minX = element1.max; + _minY = element2.max; + _maxX = element1.min; + _maxY = element2.min; return self; } @@ -72,44 +75,44 @@ - (NSPoint)value return NSMakePoint(_state1, _state2); } --(int32_t) effectiveMinX +- (int32_t)effectiveMinX { int32_t rawMin = _element1.min; int32_t rawMax = _element1.max; - if (initialX == 0) return rawMin; - if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return minX; - if ((initialX - rawMin) < (rawMax - initialX)) return rawMin; - return initialX - (rawMax - initialX); + if (_initialX == 0) return rawMin; + if (_minX <= (rawMin * 2 + _initialX) / 3 && _maxX >= (rawMax * 2 + _initialX) / 3 ) return _minX; + if ((_initialX - rawMin) < (rawMax - _initialX)) return rawMin; + return _initialX - (rawMax - _initialX); } --(int32_t) effectiveMinY +- (int32_t)effectiveMinY { int32_t rawMin = _element2.min; int32_t rawMax = _element2.max; - if (initialY == 0) return rawMin; - if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return minY; - if ((initialY - rawMin) < (rawMax - initialY)) return rawMin; - return initialY - (rawMax - initialY); + if (_initialY == 0) return rawMin; + if (_minX <= (rawMin * 2 + _initialY) / 3 && _maxY >= (rawMax * 2 + _initialY) / 3 ) return _minY; + if ((_initialY - rawMin) < (rawMax - _initialY)) return rawMin; + return _initialY - (rawMax - _initialY); } --(int32_t) effectiveMaxX +- (int32_t)effectiveMaxX { int32_t rawMin = _element1.min; int32_t rawMax = _element1.max; - if (initialX == 0) return rawMax; - if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return maxX; - if ((initialX - rawMin) > (rawMax - initialX)) return rawMax; - return initialX + (initialX - rawMin); + if (_initialX == 0) return rawMax; + if (_minX <= (rawMin * 2 + _initialX) / 3 && _maxX >= (rawMax * 2 + _initialX) / 3 ) return _maxX; + if ((_initialX - rawMin) > (rawMax - _initialX)) return rawMax; + return _initialX + (_initialX - rawMin); } --(int32_t) effectiveMaxY +- (int32_t)effectiveMaxY { int32_t rawMin = _element2.min; int32_t rawMax = _element2.max; - if (initialY == 0) return rawMax; - if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return maxY; - if ((initialY - rawMin) > (rawMax - initialY)) return rawMax; - return initialY + (initialY - rawMin); + if (_initialY == 0) return rawMax; + if (_minX <= (rawMin * 2 + _initialY) / 3 && _maxY >= (rawMax * 2 + _initialY) / 3 ) return _maxY; + if ((_initialY - rawMin) > (rawMax - _initialY)) return rawMax; + return _initialY + (_initialY - rawMin); } - (bool)updateState @@ -118,18 +121,18 @@ - (bool)updateState int32_t y = [_element2 value]; if (x == 0 && y == 0) return false; - if (initialX == 0 && initialY == 0) { - initialX = x; - initialY = y; + if (_initialX == 0 && _initialY == 0) { + _initialX = x; + _initialY = y; } double old1 = _state1, old2 = _state2; { int32_t value = x; - if (initialX != 0) { - minX = MIN(value, minX); - maxX = MAX(value, maxX); + if (_initialX != 0) { + _minX = MIN(value, _minX); + _maxX = MAX(value, _maxX); } double min = [self effectiveMinX]; @@ -142,9 +145,9 @@ - (bool)updateState { int32_t value = y; - if (initialY != 0) { - minY = MIN(value, minY); - maxY = MAX(value, maxY); + if (_initialY != 0) { + _minY = MIN(value, _minY); + _maxY = MAX(value, _maxY); } double min = [self effectiveMinY]; @@ -158,11 +161,29 @@ - (bool)updateState _state2 < -1 || _state2 > 1) { // Makes no sense, recalibrate _state1 = _state2 = 0; - initialX = initialY = 0; - minX = _element1.max; - minY = _element2.max; - maxX = _element1.min; - maxY = _element2.min; + _initialX = _initialY = 0; + _minX = _element1.max; + _minY = _element2.max; + _maxX = _element1.min; + _maxY = _element2.min; + } + + + double temp = _state1; + switch (_rotation & 3) { + case 0: break; + case 1: + _state1 = -_state2; + _state2 = temp; + break; + case 2: + _state1 = -_state1; + _state2 = -_state2; + break; + case 3: + _state1 = _state2; + _state2 = -temp; + break; } return old1 != _state1 || old2 != _state2; @@ -173,9 +194,11 @@ - (double)distance return MIN(sqrt(_state1 * _state1 + _state2 * _state2), 1.0); } -- (double)angle { +- (double)angle +{ double temp = atan2(_state2, _state1) * 180 / M_PI; if (temp >= 0) return temp; return temp + 360; } + @end diff --git a/thirdparty/SameBoy/JoyKit/JOYAxes3D.h b/thirdparty/SameBoy/JoyKit/JOYAxes3D.h new file mode 100644 index 000000000..e30d46586 --- /dev/null +++ b/thirdparty/SameBoy/JoyKit/JOYAxes3D.h @@ -0,0 +1,26 @@ +#import +#import "JOYInput.h" + +typedef enum { + JOYAxes3DUsageNone, + JOYAxes3DUsageAcceleration, + JOYAxes3DUsageOrientation, + JOYAxes3DUsageGyroscope, + JOYAxes3DUsageNonGenericMax, + + JOYAxes3DUsageGeneric0 = 0x10000, +} JOYAxes3DUsage; + +typedef struct { + double x, y, z; +} JOYPoint3D; + +@interface JOYAxes3D : JOYInput ++ (NSString *)usageToString: (JOYAxes3DUsage) usage; +- (JOYPoint3D)rawValue; +- (JOYPoint3D)normalizedValue; // For orientation +- (JOYPoint3D)gUnitsValue; // For acceleration +@property JOYAxes3DUsage usage; +@end + + diff --git a/thirdparty/SameBoy/JoyKit/JOYAxes3D.m b/thirdparty/SameBoy/JoyKit/JOYAxes3D.m new file mode 100644 index 000000000..d14b5c68c --- /dev/null +++ b/thirdparty/SameBoy/JoyKit/JOYAxes3D.m @@ -0,0 +1,129 @@ +#import "JOYAxes3D.h" +#import "JOYElement.h" + +@interface JOYAxes3D() +@property unsigned rotation; // in 90 degrees units, clockwise +@end + +@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 | (uint64_t)self.combinedIndex << 32; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llx); 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; + } + + double temp = _state1; + switch (_rotation & 3) { + case 0: break; + case 1: + _state1 = -_state3; + _state3 = temp; + break; + case 2: + _state1 = -_state1; + _state3 = -_state3; + break; + case 3: + _state1 = _state3; + _state3 = -temp; + break; + } + + return old1 != _state1 || old2 != _state2 || old3 != _state3; +} + +@end diff --git a/thirdparty/SameBoy/JoyKit/JOYAxis.h b/thirdparty/SameBoy/JoyKit/JOYAxis.h index 5a4c1669e..06c0917c7 100644 --- a/thirdparty/SameBoy/JoyKit/JOYAxis.h +++ b/thirdparty/SameBoy/JoyKit/JOYAxis.h @@ -1,4 +1,6 @@ #import +#import "JOYButton.h" +#import "JOYInput.h" typedef enum { JOYAxisUsageNone, @@ -8,21 +10,25 @@ typedef enum { JOYAxisUsageR1, JOYAxisUsageR2, JOYAxisUsageR3, + + JOYAxisUsageSlider, + JOYAxisUsageDial, JOYAxisUsageWheel, + JOYAxisUsageRudder, JOYAxisUsageThrottle, JOYAxisUsageAccelerator, JOYAxisUsageBrake, + JOYAxisUsageNonGenericMax, JOYAxisUsageGeneric0 = 0x10000, } JOYAxisUsage; -@interface JOYAxis : NSObject -- (NSString *)usageString; +@interface JOYAxis : JOYInput + (NSString *)usageToString: (JOYAxisUsage) usage; -- (uint64_t)uniqueID; - (double)value; +- (JOYButtonUsage)equivalentButtonUsage; @property JOYAxisUsage usage; @end diff --git a/thirdparty/SameBoy/JoyKit/JOYAxis.m b/thirdparty/SameBoy/JoyKit/JOYAxis.m index 169eaee86..5e27c46e3 100644 --- a/thirdparty/SameBoy/JoyKit/JOYAxis.m +++ b/thirdparty/SameBoy/JoyKit/JOYAxis.m @@ -19,6 +19,8 @@ + (NSString *)usageToString: (JOYAxisUsage) usage @"Analog R1", @"Analog R2", @"Analog R3", + @"Slider", + @"Dial", @"Wheel", @"Rudder", @"Throttle", @@ -40,12 +42,12 @@ - (NSString *)usageString - (uint64_t)uniqueID { - return _element.uniqueID; + return _element.uniqueID | (uint64_t)self.combinedIndex << 32; } - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %f%%>", self.className, self, self.usageString, self.uniqueID, _state * 100]; + return [NSString stringWithFormat:@"<%@: %p, %@ (%llx); State: %f%%>", self.className, self, self.usageString, self.uniqueID, _state * 100]; } - (instancetype)initWithElement:(JOYElement *)element @@ -57,10 +59,23 @@ - (instancetype)initWithElement:(JOYElement *)element if (element.usagePage == kHIDPage_GenericDesktop) { - uint16_t usage = element.usage; - _usage = JOYAxisUsageGeneric0 + usage - kHIDUsage_GD_X + 1; + switch (element.usage) { + case kHIDUsage_GD_Slider: _usage = JOYAxisUsageSlider; break; + case kHIDUsage_GD_Dial: _usage = JOYAxisUsageDial; break; + case kHIDUsage_GD_Wheel: _usage = JOYAxisUsageWheel; break; + default: + _usage = JOYAxisUsageGeneric0 + element.usage - kHIDUsage_GD_X + 1; + break; + } + } + else if (element.usagePage == kHIDPage_Simulation) { + switch (element.usage) { + case kHIDUsage_Sim_Accelerator: _usage = JOYAxisUsageAccelerator; break; + case kHIDUsage_Sim_Brake: _usage = JOYAxisUsageBrake; break; + case kHIDUsage_Sim_Rudder: _usage = JOYAxisUsageRudder; break; + case kHIDUsage_Sim_Throttle: _usage = JOYAxisUsageThrottle; break; + } } - _min = 1.0; return self; @@ -87,4 +102,28 @@ - (bool)updateState return old != _state; } +- (JOYButtonUsage)equivalentButtonUsage +{ + if (self.usage >= JOYAxisUsageGeneric0) { + return self.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0; + } + switch (self.usage) { + case JOYAxisUsageL1: return JOYButtonUsageL1; + case JOYAxisUsageL2: return JOYButtonUsageL2; + case JOYAxisUsageL3: return JOYButtonUsageL3; + case JOYAxisUsageR1: return JOYButtonUsageR1; + case JOYAxisUsageR2: return JOYButtonUsageR2; + case JOYAxisUsageR3: return JOYButtonUsageR3; + case JOYAxisUsageSlider: return JOYButtonUsageSlider; + case JOYAxisUsageDial: return JOYButtonUsageDial; + case JOYAxisUsageWheel: return JOYButtonUsageWheel; + case JOYAxisUsageRudder: return JOYButtonUsageRudder; + case JOYAxisUsageThrottle: return JOYButtonUsageThrottle; + case JOYAxisUsageAccelerator: return JOYButtonUsageAccelerator; + case JOYAxisUsageBrake: return JOYButtonUsageBrake; + default: return JOYButtonUsageNone; + } +} + + @end diff --git a/thirdparty/SameBoy/JoyKit/JOYButton.h b/thirdparty/SameBoy/JoyKit/JOYButton.h index f732c8e6a..c3f13dea5 100644 --- a/thirdparty/SameBoy/JoyKit/JOYButton.h +++ b/thirdparty/SameBoy/JoyKit/JOYButton.h @@ -1,6 +1,5 @@ #import - - +#import "JOYInput.h" typedef enum { JOYButtonUsageNone, @@ -26,17 +25,33 @@ typedef enum { JOYButtonUsageDPadRight, JOYButtonUsageDPadUp, JOYButtonUsageDPadDown, + + JOYButtonUsageSlider, + JOYButtonUsageDial, + JOYButtonUsageWheel, + + JOYButtonUsageRudder, + JOYButtonUsageThrottle, + JOYButtonUsageAccelerator, + JOYButtonUsageBrake, + JOYButtonUsageNonGenericMax, JOYButtonUsageGeneric0 = 0x10000, } JOYButtonUsage; -@interface JOYButton : NSObject -- (NSString *)usageString; +typedef enum { + JOYButtonTypeNormal, + JOYButtonTypeAxisEmulated, + JOYButtonTypeAxes2DEmulated, + JOYButtonTypeHatEmulated, +} JOYButtonType; + +@interface JOYButton : JOYInput + (NSString *)usageToString: (JOYButtonUsage) usage; -- (uint64_t)uniqueID; - (bool) isPressed; @property JOYButtonUsage usage; +@property (readonly) JOYButtonType type; @end diff --git a/thirdparty/SameBoy/JoyKit/JOYButton.m b/thirdparty/SameBoy/JoyKit/JOYButton.m index 3e6026d1e..b56ead0ea 100644 --- a/thirdparty/SameBoy/JoyKit/JOYButton.m +++ b/thirdparty/SameBoy/JoyKit/JOYButton.m @@ -1,5 +1,10 @@ #import "JOYButton.h" #import "JOYElement.h" +#import + +@interface JOYButton () +@property JOYButtonUsage originalUsage; +@end @implementation JOYButton { @@ -50,12 +55,12 @@ - (NSString *)usageString - (uint64_t)uniqueID { - return _element.uniqueID; + return _element.uniqueID | (uint64_t)self.combinedIndex << 32; } - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %s>", self.className, self, self.usageString, self.uniqueID, _state? "Presssed" : "Released"]; + return [NSString stringWithFormat:@"<%@: %p, %@ (%llx); State: %s>", self.className, self, self.usageString, self.uniqueID, _state? "Presssed" : "Released"]; } - (instancetype)initWithElement:(JOYElement *)element @@ -80,6 +85,14 @@ - (instancetype)initWithElement:(JOYElement *)element case kHIDUsage_GD_SystemMainMenu: _usage = JOYButtonUsageHome; break; } } + else if (element.usagePage == kHIDPage_Consumer) { + switch (element.usage) { + case kHIDUsage_Csmr_ACHome: _usage = JOYButtonUsageHome; break; + case kHIDUsage_Csmr_ACBack: _usage = JOYButtonUsageSelect; break; + } + } + + _originalUsage = _usage; return self; } @@ -99,4 +112,8 @@ - (bool)updateState return false; } +- (JOYButtonType)type +{ + return JOYButtonTypeNormal; +} @end diff --git a/thirdparty/SameBoy/JoyKit/JOYController.h b/thirdparty/SameBoy/JoyKit/JOYController.h index 9ed7cf7b1..5bd63385b 100644 --- a/thirdparty/SameBoy/JoyKit/JOYController.h +++ b/thirdparty/SameBoy/JoyKit/JOYController.h @@ -2,9 +2,9 @@ #import "JOYButton.h" #import "JOYAxis.h" #import "JOYAxes2D.h" +#import "JOYAxes3D.h" #import "JOYHat.h" -static NSString const *JOYAxesEmulateButtonsKey = @"JOYAxesEmulateButtons"; static NSString const *JOYAxes2DEmulateButtonsKey = @"JOYAxes2DEmulateButtons"; static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; @@ -18,24 +18,51 @@ 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 +typedef enum { + JOYControllerCombinedTypeSingle, + JOYControllerCombinedTypeComponent, + JOYControllerCombinedTypeCombined, +} JOYControllerCombinedType; + +typedef enum { + JOYJoyConTypeNone, + JOYJoyConTypeLeft, + JOYJoyConTypeRight, + JOYJoyConTypeDual, +} JOYJoyConType; + @interface JOYController : NSObject + (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options; + (NSArray *) allControllers; + (void) registerListener:(id)listener; + (void) unregisterListener:(id)listener; -- (NSString *)deviceName; -- (NSString *)uniqueID; +- (JOYControllerCombinedType)combinedControllerType; - (NSArray *) buttons; - (NSArray *) axes; - (NSArray *) axes2D; +- (NSArray *) axes3D; - (NSArray *) hats; +- (NSArray *) allInputs; - (void)setRumbleAmplitude:(double)amp; - (void)setPlayerLEDs:(uint8_t)mask; +- (uint8_t)LEDMaskForPlayer:(unsigned)player; @property (readonly, getter=isConnected) bool connected; +@property (readonly) JOYJoyConType joyconType; +@property (readonly) NSString *deviceName; +@property (readonly) NSString *uniqueID; +@property (nonatomic) bool usesHorizontalJoyConGrip; @end +@interface JOYCombinedController : JOYController +- (instancetype)initWithChildren:(NSArray *)children; +- (void)breakApart; +@property (readonly) NSArray *children; +@end + + diff --git a/thirdparty/SameBoy/JoyKit/JOYController.m b/thirdparty/SameBoy/JoyKit/JOYController.m index 8ec127948..dc55c74ae 100644 --- a/thirdparty/SameBoy/JoyKit/JOYController.m +++ b/thirdparty/SameBoy/JoyKit/JOYController.m @@ -3,10 +3,13 @@ #import "JOYElement.h" #import "JOYSubElement.h" #import "JOYFullReportElement.h" - +#import "JOYButton.h" #import "JOYEmulatedButton.h" #include +#include +extern NSTextField *globalDebugField; + #define PWM_RESOLUTION 16 static NSString const *JOYAxisGroups = @"JOYAxisGroups"; @@ -16,6 +19,7 @@ static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; static NSString const *JOYCustomReports = @"JOYCustomReports"; static NSString const *JOYIsSwitch = @"JOYIsSwitch"; +static NSString const *JOYJoyCon = @"JOYJoyCon"; static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; static NSString const *JOYConnectedUsage = @"JOYConnectedUsage"; @@ -26,6 +30,8 @@ static NSString const *JOYActivationReport = @"JOYActivationReport"; static NSString const *JOYIgnoredReports = @"JOYIgnoredReports"; static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3"; +static NSString const *JOYIsSony = @"JOYIsSony"; +static NSString const *JOYEmulateAxisButtons = @"JOYEmulateAxisButtons"; static NSMutableDictionary *controllers; // Physical controllers static NSMutableArray *exposedControllers; // Logical controllers @@ -35,7 +41,6 @@ static NSMutableSet> *listeners = nil; -static bool axesEmulateButtons = false; static bool axes2DEmulateButtons = false; static bool hatsEmulateButtons = false; @@ -50,6 +55,7 @@ - (void)gotReport:(NSData *)report; @interface JOYButton () - (instancetype)initWithElement:(JOYElement *)element; - (bool)updateState; +@property JOYButtonUsage originalUsage; @end @interface JOYAxis () @@ -65,6 +71,20 @@ - (bool)updateState; @interface JOYAxes2D () - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2; - (bool)updateState; +@property unsigned rotation; // in 90 degrees units, clockwise +@end + +@interface JOYAxes3D () +{ + @public JOYElement *_element1, *_element2, *_element3; +} +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element2; +- (bool)updateState; +@property unsigned rotation; // in 90 degrees units, clockwise +@end + +@interface JOYInput () +@property unsigned combinedIndex; @end static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage) @@ -94,7 +114,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]]; } } @@ -125,21 +145,52 @@ typedef struct __attribute__((packed)) { uint8_t padding3[13]; } JOYDualShock3Output; +typedef struct __attribute__((packed)) { + uint8_t reportID; + uint8_t sequence; + union { + uint8_t tag; + uint8_t reportIDOnUSB; + }; + uint16_t flags; + uint8_t rumbleRightStrength; // Weak + uint8_t rumbleLeftStrength; // Strong + uint8_t reserved[4]; + uint8_t muteButtonLED; + uint8_t powerSaveControl; + uint8_t reserved2[28]; + uint8_t flags2; + uint8_t reserved3[2]; + uint8_t lightbarSetup; + uint8_t LEDBrightness; + uint8_t playerLEDs; + uint8_t lightbarRed; + uint8_t lightbarGreen; + uint8_t lightbarBlue; + uint8_t bluetoothSpecific[24]; + uint32_t crc32; +} JOYDualSenseOutput; + + typedef union { JOYSwitchPacket switchPacket; JOYDualShock3Output ds3Output; + JOYDualSenseOutput dualsenseOutput; } JOYVendorSpecificOutput; @implementation JOYController { + @public // Let JOYCombinedController access everything IOHIDDeviceRef _device; NSMutableDictionary *_buttons; NSMutableDictionary *_axes; NSMutableDictionary *_axes2D; + NSMutableDictionary *_axes3D; NSMutableDictionary *_hats; NSMutableDictionary *_fullReportElements; NSMutableDictionary *> *_multiElements; - + JOYAxes3D *_lastAxes3D; + // Button emulation NSMutableDictionary *_axisEmulatedButtons; NSMutableDictionary *> *_axes2DEmulatedButtons; @@ -151,6 +202,10 @@ @implementation JOYController NSString *_serialSuffix; bool _isSwitch; // Does this controller use the Switch protocol? bool _isDualShock3; // Does this controller use DS3 outputs? + bool _isSony; // Is this a DS4 or newer Sony controller? + bool _isDualSense; + bool _isUSBDualSense; + JOYVendorSpecificOutput _lastVendorSpecificOutput; volatile double _rumbleAmplitude; bool _physicallyConnected; @@ -167,6 +222,7 @@ @implementation JOYController unsigned _rumbleCounter; bool _deviceCantSendReports; dispatch_queue_t _rumbleQueue; + JOYCombinedController *_parent; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -201,29 +257,103 @@ -(void)createInputForElement:(JOYElement *)element return; } - if (element.usagePage == kHIDPage_Button) { + NSDictionary *axisGroups = @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(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 || + (element.usagePage == kHIDPage_Consumer && (element.usage == kHIDUsage_Csmr_ACHome || + element.usage == kHIDUsage_Csmr_ACBack))) { button: { JOYButton *button = [[JOYButton alloc] initWithElement: element]; [_buttons setObject:button forKey:element]; - NSNumber *replacementUsage = _hacks[JOYButtonUsageMapping][@(button.usage)]; + NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil; if (replacementUsage) { - button.usage = [replacementUsage unsignedIntValue]; + button.originalUsage = button.usage = [replacementUsage unsignedIntValue]; } return; } } + else if (element.usagePage == kHIDPage_Simulation) { + switch (element.usage) { + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: + case kHIDUsage_Sim_Rudder: + case kHIDUsage_Sim_Throttle: + goto single; + } + } else if (element.usagePage == kHIDPage_GenericDesktop) { - NSDictionary *axisGroups = @{ - @(kHIDUsage_GD_X): @(0), - @(kHIDUsage_GD_Y): @(0), - @(kHIDUsage_GD_Z): @(1), - @(kHIDUsage_GD_Rx): @(2), - @(kHIDUsage_GD_Ry): @(2), - @(kHIDUsage_GD_Rz): @(1), - }; - - axisGroups = _hacks[JOYAxisGroups] ?: axisGroups; - switch (element.usage) { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: @@ -260,55 +390,34 @@ -(void)createInputForElement:(JOYElement *)element 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], ]; } - - /* - 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; } - single: case kHIDUsage_GD_Slider: case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: { + case kHIDUsage_GD_Wheel: + { single: { JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; [_axes setObject:axis forKey:element]; - NSNumber *replacementUsage = _hacks[JOYAxisUsageMapping][@(axis.usage)]; + NSNumber *replacementUsage = element.usagePage == kHIDPage_GenericDesktop? _hacks[JOYAxisUsageMapping][@(axis.usage)] : nil; if (replacementUsage) { axis.usage = [replacementUsage unsignedIntValue]; } - if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { + if ([_hacks[JOYEmulateAxisButtons] boolValue]) { _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; + [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage type:JOYButtonTypeAxisEmulated uniqueID:axis.uniqueID]; } - if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { - _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; - } break; - } + }} case kHIDUsage_GD_DPadUp: case kHIDUsage_GD_DPadDown: case kHIDUsage_GD_DPadRight: @@ -323,10 +432,10 @@ -(void)createInputForElement:(JOYElement *)element [_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; @@ -345,6 +454,7 @@ - (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *)axes3D +{ + return [[NSSet setWithArray:[_axes3D allValues]] allObjects]; +} + - (NSArray *)hats { return [_hats allValues]; @@ -596,6 +761,7 @@ - (void)_elementChanged:(JOYElement *)element } } else if (old && !self.connected) { + [_parent breakApart]; for (id listener in listeners) { if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { [listener controllerDisconnected:self]; @@ -611,7 +777,7 @@ - (void)_elementChanged:(JOYElement *)element if ([button updateState]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { - [listener controller:self buttonChangedState:button]; + [listener controller:_parent ?: self buttonChangedState:button]; } } } @@ -626,14 +792,14 @@ - (void)_elementChanged:(JOYElement *)element if ([axis updateState]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:movedAxis:)]) { - [listener controller:self movedAxis:axis]; + [listener controller:_parent ?: self movedAxis:axis]; } } - JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)]; + JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away if ([button updateStateFromAxis:axis]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { - [listener controller:self buttonChangedState:button]; + [listener controller:_parent ?: self buttonChangedState:button]; } } } @@ -648,15 +814,15 @@ - (void)_elementChanged:(JOYElement *)element if ([axes updateState]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) { - [listener controller:self movedAxes2D:axes]; + [listener controller:_parent ?: self movedAxes2D:axes]; } } - NSArray *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)]; + NSArray *buttons = _axes2DEmulatedButtons[@(axes.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away for (JOYEmulatedButton *button in buttons) { if ([button updateStateFromAxes2D:axes]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { - [listener controller:self buttonChangedState:button]; + [listener controller:_parent ?: self buttonChangedState:button]; } } } @@ -666,22 +832,36 @@ - (void)_elementChanged:(JOYElement *)element } } + { + JOYAxes3D *axes = _axes3D[element]; + if (axes) { + if ([axes updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxes3D:)]) { + [listener controller:_parent ?: self movedAxes3D:axes]; + } + } + } + return; + } + } + { JOYHat *hat = _hats[element]; if (hat) { if ([hat updateState]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:movedHat:)]) { - [listener controller:self movedHat:hat]; + [listener controller:_parent ?: self movedHat:hat]; } } - NSArray *buttons = _hatEmulatedButtons[@(hat.uniqueID)]; + NSArray *buttons = _hatEmulatedButtons[@(hat.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away for (JOYEmulatedButton *button in buttons) { if ([button updateStateFromHat:hat]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { - [listener controller:self buttonChangedState:button]; + [listener controller:_parent ?: self buttonChangedState:button]; } } } @@ -694,6 +874,8 @@ - (void)_elementChanged:(JOYElement *)element - (void)disconnected { + _physicallyConnected = false; + [_parent breakApart]; if (_logicallyConnected && [exposedControllers containsObject:self]) { for (id listener in listeners) { if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { @@ -701,7 +883,6 @@ - (void)disconnected } } } - _physicallyConnected = false; [exposedControllers removeObject:self]; [self setRumbleAmplitude:0]; dispatch_sync(_rumbleQueue, ^{ @@ -723,9 +904,92 @@ - (void)sendReport:(NSData *)report } } +- (void) sendDualSenseOutput +{ + if (_isUSBDualSense) { + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB length:_lastVendorSpecificOutput.dualsenseOutput.bluetoothSpecific - &_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB]]; + return; + } + _lastVendorSpecificOutput.dualsenseOutput.sequence += 0x10; + static const uint32_t table[] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + const uint8_t *byte = (void *)&_lastVendorSpecificOutput.dualsenseOutput; + uint32_t size = sizeof(_lastVendorSpecificOutput.dualsenseOutput) - 4; + uint32_t ret = 0xFFFFFFFF; + ret = table[(ret ^ 0xA2) & 0xFF] ^ (ret >> 8); + + while (size--) { + ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8); + } + + _lastVendorSpecificOutput.dualsenseOutput.crc32 = ~ret; + + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput length:sizeof(_lastVendorSpecificOutput.dualsenseOutput)]]; +} + +- (uint8_t)LEDMaskForPlayer:(unsigned)player +{ + if (_isDualShock3) { + return 2 << player; + } + if (_isDualSense) { + switch (player) { + case 0: return 0x04; + case 1: return 0x0A; + case 2: return 0x15; + case 3: return 0x1B; + default: return 0; + } + } + return 1 << player; +} + - (void)setPlayerLEDs:(uint8_t)mask { - mask &= 0xF; if (mask == _playerLEDs) { return; } @@ -735,14 +999,18 @@ - (void)setPlayerLEDs:(uint8_t)mask _lastVendorSpecificOutput.switchPacket.sequence++; _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED - _lastVendorSpecificOutput.switchPacket.commandData[0] = mask; + _lastVendorSpecificOutput.switchPacket.commandData[0] = mask & 0xF; [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; } else if (_isDualShock3) { _lastVendorSpecificOutput.ds3Output.reportID = 1; - _lastVendorSpecificOutput.ds3Output.ledsEnabled = mask << 1; + _lastVendorSpecificOutput.ds3Output.ledsEnabled = (mask & 0x1F); [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; } + else if (_isDualSense) { + _lastVendorSpecificOutput.dualsenseOutput.playerLEDs = mask & 0x1F; + [self sendDualSenseOutput]; + } } - (void)updateRumble @@ -750,7 +1018,7 @@ - (void)updateRumble if (!self.connected) { return; } - if (!_rumbleElement && !_isSwitch && !_isDualShock3) { + if (!_rumbleElement && !_isSwitch && !_isDualShock3 && !_isDualSense) { return; } if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { @@ -805,10 +1073,15 @@ - (void)updateRumble } else if (_isDualShock3) { _lastVendorSpecificOutput.ds3Output.reportID = 1; - _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xff : 0; - _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xff); + _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xFF : 0; + _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xFF); [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; } + else if (_isDualSense) { + _lastVendorSpecificOutput.dualsenseOutput.rumbleLeftStrength = round(_rumbleAmplitude * _rumbleAmplitude * 0xFF); + _lastVendorSpecificOutput.dualsenseOutput.rumbleRightStrength = _rumbleAmplitude > 0.25 ? round(pow(_rumbleAmplitude - 0.25, 2) * 0xFF) : 0; + [self sendDualSenseOutput]; + } else { [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; } @@ -827,6 +1100,71 @@ - (bool)isConnected return _logicallyConnected && _physicallyConnected; } +- (NSArray *)allInputs +{ + NSMutableArray *ret = [NSMutableArray array]; + [ret addObjectsFromArray:self.buttons]; + [ret addObjectsFromArray:self.axes]; + [ret addObjectsFromArray:self.axes2D]; + [ret addObjectsFromArray:self.axes3D]; + [ret addObjectsFromArray:self.hats]; + return ret; +} + +- (void)setusesHorizontalJoyConGrip:(bool)usesHorizontalJoyConGrip +{ + if (usesHorizontalJoyConGrip == _usesHorizontalJoyConGrip) return; // Nothing to do + _usesHorizontalJoyConGrip = usesHorizontalJoyConGrip; + switch (self.joyconType) { + case JOYJoyConTypeLeft: + case JOYJoyConTypeRight: { + NSArray *buttons = _buttons.allValues; // not self.buttons to skip emulated buttons + if (!usesHorizontalJoyConGrip) { + for (JOYAxes2D *axes in self.axes2D) { + axes.rotation = 0; + } + for (JOYAxes3D *axes in self.axes3D) { + axes.rotation = 0; + } + for (JOYButton *button in buttons) { + button.usage = button.originalUsage; + } + return; + } + for (JOYAxes2D *axes in self.axes2D) { + axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1; + } + for (JOYAxes3D *axes in self.axes3D) { + axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1; + } + if (self.joyconType == JOYJoyConTypeLeft) { + for (JOYButton *button in buttons) { + switch (button.originalUsage) { + case JOYButtonUsageDPadLeft: button.usage = JOYButtonUsageB; break; + case JOYButtonUsageDPadRight: button.usage = JOYButtonUsageX; break; + case JOYButtonUsageDPadUp: button.usage = JOYButtonUsageY; break; + case JOYButtonUsageDPadDown: button.usage = JOYButtonUsageA; break; + default: button.usage = button.originalUsage; break; + } + } + } + else { + for (JOYButton *button in buttons) { + switch (button.originalUsage) { + case JOYButtonUsageY: button.usage = JOYButtonUsageX; break; + case JOYButtonUsageA: button.usage = JOYButtonUsageB; break; + case JOYButtonUsageX: button.usage = JOYButtonUsageA; break; + case JOYButtonUsageB: button.usage = JOYButtonUsageY; break; + default: button.usage = button.originalUsage; break; + } + } + } + } + default: + return; + } +} + + (void)controllerAdded:(IOHIDDeviceRef) device { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); @@ -846,8 +1184,6 @@ + (void)controllerAdded:(IOHIDDeviceRef) device } [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; - - } + (void)controllerRemoved:(IOHIDDeviceRef) device @@ -878,7 +1214,6 @@ +(void)unregisterListener:(id)listener + (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options { - axesEmulateButtons = [options[JOYAxesEmulateButtonsKey] boolValue]; axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue]; hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue]; @@ -918,3 +1253,237 @@ - (void)dealloc } } @end + + +@implementation JOYCombinedController +- (instancetype)initWithChildren:(NSArray *)children +{ + self = [super init]; + // Sorting makes the device name and unique id consistent + _children = [children sortedArrayUsingComparator:^NSComparisonResult(JOYController *a, JOYController *b) { + return [a.uniqueID compare:b.uniqueID]; + }]; + + if (_children.count == 0) return nil; + + for (JOYController *child in _children) { + if (child.combinedControllerType != JOYControllerCombinedTypeSingle) { + NSLog(@"Cannot combine non-single controller %@", child); + return nil; + } + if (![exposedControllers containsObject:child]) { + NSLog(@"Cannot combine unexposed controller %@", child); + return nil; + } + } + + unsigned index = 0; + for (JOYController *child in _children) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:child]; + } + } + child->_parent = self; + for (JOYInput *input in child.allInputs) { + input.combinedIndex = index; + } + index++; + [exposedControllers removeObject:child]; + } + + [exposedControllers addObject:self]; + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:self]; + } + } + + return self; +} + +- (void)breakApart +{ + if (![exposedControllers containsObject:self]) { + // Already broken apart + return; + } + + [exposedControllers removeObject:self]; + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { + [listener controllerDisconnected:self]; + } + } + + for (JOYController *child in _children) { + child->_parent = nil; + for (JOYInput *input in child.allInputs) { + input.combinedIndex = 0; + } + if (!child.connected) break; + [exposedControllers addObject:child]; + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controllerConnected:)]) { + [listener controllerConnected:child]; + } + } + } +} + +- (NSString *)deviceName +{ + NSString *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret stringByAppendingFormat:@" + %@", child.deviceName]; + } + else { + ret = child.deviceName; + } + } + return ret; +} + +- (NSString *)uniqueID +{ + NSString *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret stringByAppendingFormat:@"+%@", child.uniqueID]; + } + else { + ret = child.uniqueID; + } + } + return ret; +} + +- (JOYControllerCombinedType)combinedControllerType +{ + return JOYControllerCombinedTypeCombined; +} + +- (NSArray *)buttons +{ + NSArray *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret arrayByAddingObjectsFromArray:child.buttons]; + } + else { + ret = child.buttons; + } + } + return ret; +} + +- (NSArray *)axes +{ + NSArray *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret arrayByAddingObjectsFromArray:child.axes]; + } + else { + ret = child.axes; + } + } + return ret; +} + +- (NSArray *)axes2D +{ + NSArray *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret arrayByAddingObjectsFromArray:child.axes2D]; + } + else { + ret = child.axes2D; + } + } + return ret; +} + +- (NSArray *)axes3D +{ + NSArray *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret arrayByAddingObjectsFromArray:child.axes3D]; + } + else { + ret = child.axes3D; + } + } + return ret; +} + +- (NSArray *)hats +{ + NSArray *ret = nil; + for (JOYController *child in _children) { + if (ret) { + ret = [ret arrayByAddingObjectsFromArray:child.hats]; + } + else { + ret = child.hats; + } + } + return ret; +} + +- (void)setRumbleAmplitude:(double)amp +{ + for (JOYController *child in _children) { + [child setRumbleAmplitude:amp]; + } +} + +- (void)setPlayerLEDs:(uint8_t)mask +{ + // Mask is actually just the player ID in a combined controller to + // allow combining controllers with different LED layouts + for (JOYController *child in _children) { + [child setPlayerLEDs:[child LEDMaskForPlayer:mask]]; + } +} + +- (uint8_t)LEDMaskForPlayer:(unsigned int)player +{ + return player; +} + +- (bool)isConnected +{ + if (![exposedControllers containsObject:self]) { + // Controller was broken apart + return false; + } + + for (JOYController *child in _children) { + if (!child.isConnected) { + return false; // Should never happen + } + } + + return true; +} + +- (JOYJoyConType)joyconType +{ + if (_children.count != 2) return JOYJoyConTypeNone; + if (_children[0].joyconType == JOYJoyConTypeLeft && + _children[1].joyconType == JOYJoyConTypeRight) { + return JOYJoyConTypeDual; + } + + if (_children[1].joyconType == JOYJoyConTypeLeft && + _children[0].joyconType == JOYJoyConTypeRight) { + return JOYJoyConTypeDual; + } + return JOYJoyConTypeNone; +} + +@end diff --git a/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.h b/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.h index 491e0c734..05ccde829 100644 --- a/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.h +++ b/thirdparty/SameBoy/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/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.m b/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.m index 1ebed3aee..841617edd 100644 --- a/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.m +++ b/thirdparty/SameBoy/JoyKit/JOYEmulatedButton.m @@ -1,4 +1,5 @@ #import "JOYEmulatedButton.h" +#import @interface JOYButton () { @@ -9,26 +10,28 @@ @interface JOYButton () @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; } - (uint64_t)uniqueID { - return _uniqueID; + return _uniqueID | (uint64_t)self.combinedIndex << 32; } - (bool)updateStateFromAxis:(JOYAxis *)axis { bool old = _state; - _state = [axis value] > 0.5; + _state = [axis value] > 0.8; return _state != old; } @@ -88,4 +91,9 @@ - (bool)updateStateFromHat:(JOYHat *)hat return _state != old; } +- (JOYButtonType)type +{ + return _type; +} + @end diff --git a/thirdparty/SameBoy/JoyKit/JOYFullReportElement.m b/thirdparty/SameBoy/JoyKit/JOYFullReportElement.m index c8efb270b..a19a53007 100644 --- a/thirdparty/SameBoy/JoyKit/JOYFullReportElement.m +++ b/thirdparty/SameBoy/JoyKit/JOYFullReportElement.m @@ -61,9 +61,10 @@ - (NSUInteger)hash 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/thirdparty/SameBoy/JoyKit/JOYHat.h b/thirdparty/SameBoy/JoyKit/JOYHat.h index 05a582927..f430beb20 100644 --- a/thirdparty/SameBoy/JoyKit/JOYHat.h +++ b/thirdparty/SameBoy/JoyKit/JOYHat.h @@ -1,7 +1,7 @@ #import +#import "JOYInput.h" -@interface JOYHat : NSObject -- (uint64_t)uniqueID; +@interface JOYHat : JOYInput - (double)angle; - (unsigned)resolution; @property (readonly, getter=isPressed) bool pressed; diff --git a/thirdparty/SameBoy/JoyKit/JOYHat.m b/thirdparty/SameBoy/JoyKit/JOYHat.m index 743e49c3e..261721627 100644 --- a/thirdparty/SameBoy/JoyKit/JOYHat.m +++ b/thirdparty/SameBoy/JoyKit/JOYHat.m @@ -1,5 +1,6 @@ #import "JOYHat.h" #import "JOYElement.h" +#import @implementation JOYHat { @@ -9,15 +10,15 @@ @implementation JOYHat - (uint64_t)uniqueID { - return _element.uniqueID; + return _element.uniqueID | (uint64_t)self.combinedIndex << 32; } - (NSString *)description { if (self.isPressed) { - return [NSString stringWithFormat:@"<%@: %p (%llu); State: %f degrees>", self.className, self, self.uniqueID, self.angle]; + return [NSString stringWithFormat:@"<%@: %p (%llx); State: %f degrees>", self.className, self, self.uniqueID, self.angle]; } - return [NSString stringWithFormat:@"<%@: %p (%llu); State: released>", self.className, self, self.uniqueID]; + return [NSString stringWithFormat:@"<%@: %p (%llx); State: released>", self.className, self, self.uniqueID]; } @@ -27,6 +28,7 @@ - (instancetype)initWithElement:(JOYElement *)element if (!self) return self; _element = element; + _state = -1; return self; } @@ -57,4 +59,9 @@ - (bool)updateState return false; } +- (NSString *)usageString +{ + return @"Hat switch"; +} + @end diff --git a/thirdparty/SameBoy/JoyKit/JOYInput.h b/thirdparty/SameBoy/JoyKit/JOYInput.h new file mode 100644 index 000000000..ea45b59a7 --- /dev/null +++ b/thirdparty/SameBoy/JoyKit/JOYInput.h @@ -0,0 +1,8 @@ +#import + +@interface JOYInput : NSObject +@property (readonly) unsigned combinedIndex; +- (NSString *)usageString; +- (uint64_t)uniqueID; +@end + diff --git a/thirdparty/SameBoy/JoyKit/JOYInput.m b/thirdparty/SameBoy/JoyKit/JOYInput.m new file mode 100644 index 000000000..0de83bf23 --- /dev/null +++ b/thirdparty/SameBoy/JoyKit/JOYInput.m @@ -0,0 +1,21 @@ +#import "JOYInput.h" + +@interface JOYInput () +@property unsigned combinedIndex; +@end + +@implementation JOYInput + +- (uint64_t)uniqueID +{ + [self doesNotRecognizeSelector:_cmd]; + __builtin_unreachable(); +} + +- (NSString *)usageString +{ + [self doesNotRecognizeSelector:_cmd]; + __builtin_unreachable(); +} + +@end diff --git a/thirdparty/SameBoy/JoyKit/JOYSubElement.m b/thirdparty/SameBoy/JoyKit/JOYSubElement.m index c94badc7d..186caf9ee 100644 --- a/thirdparty/SameBoy/JoyKit/JOYSubElement.m +++ b/thirdparty/SameBoy/JoyKit/JOYSubElement.m @@ -57,6 +57,12 @@ - (int32_t)value 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; diff --git a/thirdparty/SameBoy/LICENSE b/thirdparty/SameBoy/LICENSE index 3303e0d7f..94f34689c 100644 --- a/thirdparty/SameBoy/LICENSE +++ b/thirdparty/SameBoy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2021 Lior Halphon +Copyright (c) 2015-2022 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/thirdparty/SameBoy/Makefile b/thirdparty/SameBoy/Makefile index fcaae75fa..e2ba5c9d6 100644 --- a/thirdparty/SameBoy/Makefile +++ b/thirdparty/SameBoy/Makefile @@ -17,15 +17,18 @@ ifeq ($(PLATFORM),windows32) _ := $(shell chcp 65001) EXESUFFIX:=.exe NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows +SDL_AUDIO_DRIVERS ?= xaudio2 xaudio2_7 sdl else EXESUFFIX:= NATIVE_CC := cc +SDL_AUDIO_DRIVERS ?= sdl endif PB12_COMPRESS := build/pb12$(EXESUFFIX) ifeq ($(PLATFORM),Darwin) DEFAULT := cocoa +ENABLE_OPENAL ?= 1 else DEFAULT := sdl endif @@ -45,9 +48,9 @@ MAKECMDGOALS := $(DEFAULT) endif include version.mk +COPYRIGHT_YEAR := $(shell grep -oE "20[2-9][0-9]" LICENSE) export VERSION CONF ?= debug -SDL_AUDIO_DRIVER ?= sdl BIN := build/bin OBJ := build/obj @@ -68,8 +71,11 @@ endif # Find libraries with pkg-config if available. ifneq (, $(shell which pkg-config)) +# But not on macOS, it's annoying +ifneq ($(PLATFORM),Darwin) PKG_CONFIG := pkg-config endif +endif ifeq ($(PLATFORM),windows32) # To force use of the Unix version instead of the Windows version @@ -121,21 +127,47 @@ endif CFLAGS += $(WARNINGS) -CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +CFLAGS += -std=gnu11 -D_GNU_SOURCE -DGB_VERSION='"$(VERSION)"' -DGB_COPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"' -I. -D_USE_MATH_DEFINES +ifneq (,$(UPDATE_SUPPORT)) +CFLAGS += -DUPDATE_SUPPORT +endif ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags) -SDL_LDFLAGS := $(shell sdl2-config --libs) +SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread + +# We cannot detect the presence of OpenAL dev headers, +# so we must do this manually +ifeq ($(ENABLE_OPENAL),1) +SDL_CFLAGS += -DENABLE_OPENAL +ifeq ($(PLATFORM),Darwin) +SDL_LDFLAGS += -framework OpenAL +else +SDL_LDFLAGS += -lopenal +endif +SDL_AUDIO_DRIVERS += openal +endif else SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) -SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) +SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread + +# Allow OpenAL to be disabled even if the development libraries are available +ifneq ($(ENABLE_OPENAL),0) +ifeq ($(shell $(PKG_CONFIG) --exists openal && echo 0),0) +SDL_CFLAGS += $(shell $(PKG_CONFIG) --cflags openal) -DENABLE_OPENAL +SDL_LDFLAGS += $(shell $(PKG_CONFIG) --libs openal) +SDL_AUDIO_DRIVERS += openal +endif +endif endif + ifeq (,$(PKG_CONFIG)) GL_LDFLAGS := -lGL else GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) 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 -lole32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows @@ -156,7 +188,7 @@ endif CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 -isysroot $(SYSROOT) OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -isysroot $(SYSROOT) +LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -framework Security -framework WebKit -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -isysroot $(SYSROOT) GL_LDFLAGS := -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations @@ -170,15 +202,18 @@ CFLAGS += -g else ifeq ($(CONF), release) CFLAGS += -O3 -DNDEBUG STRIP := strip +CODESIGN := true ifeq ($(PLATFORM),Darwin) LDFLAGS += -Wl,-exported_symbols_list,$(NULL) -STRIP := -@true +STRIP := strip -x +CODESIGN := codesign -fs - +endif +ifeq ($(PLATFORM),windows32) +LDFLAGS += -fuse-ld=lld endif -ifneq ($(PLATFORM),windows32) 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") @@ -196,15 +231,15 @@ 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 +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 $(BIN)/SDL/Palettes +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/mgb_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 CORE_SOURCES := $(shell ls Core/*.c) -SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) SDL/audio/$(SDL_AUDIO_DRIVER).c +SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS)) TESTER_SOURCES := $(shell ls Tester/*.c) ifeq ($(PLATFORM),Darwin) @@ -240,6 +275,10 @@ endif $(OBJ)/SDL/%.dep: SDL/% -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ + +$(OBJ)/OpenDialog/%.dep: OpenDialog/% + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@ $(OBJ)/%.dep: % -@$(MKDIR) -p $(dir $@) @@ -255,6 +294,11 @@ $(OBJ)/SDL/%.c.o: SDL/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ +$(OBJ)/OpenDialog/%.c.o: OpenDialog/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ + + $(OBJ)/%.c.o: %.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FAT_FLAGS) -c $< -o $@ @@ -276,6 +320,8 @@ $(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/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 \ @@ -285,12 +331,15 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ Shaders $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources cp Cocoa/*.icns Cocoa/*.png Misc/registers.sym $(BIN)/SameBoy.app/Contents/Resources/ - sed s/@VERSION/$(VERSION)/ < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist - cp Cocoa/License.html $(BIN)/SameBoy.app/Contents/Resources/Credits.html + sed "s/@VERSION/$(VERSION)/;s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist + sed "s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/License.html > $(BIN)/SameBoy.app/Contents/Resources/Credits.html $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources/Shaders cp Shaders/*.fsh Shaders/*.metal $(BIN)/SameBoy.app/Contents/Resources/Shaders $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Library/QuickLook/ cp -rf $(BIN)/SameBoy.qlgenerator $(BIN)/SameBoy.app/Contents/Library/QuickLook/ +ifeq ($(CONF), release) + $(CODESIGN) $@ +endif $(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@$(MKDIR) -p $(dir $@) @@ -310,13 +359,19 @@ $(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin $(MKDIR) -p $(BIN)/SameBoy.qlgenerator/Contents/Resources cp QuickLook/*.png $(BIN)/SameBoy.qlgenerator/Contents/Resources/ - sed s/@VERSION/$(VERSION)/ < QuickLook/Info.plist > $(BIN)/SameBoy.qlgenerator/Contents/Info.plist + sed "s/@VERSION/$(VERSION)/;s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < QuickLook/Info.plist > $(BIN)/SameBoy.qlgenerator/Contents/Info.plist +ifeq ($(CONF), release) + $(CODESIGN) $@ +endif # Currently, SameBoy.app includes two "copies" of each Core .o file once in the app itself and # once in the QL Generator. It should probably become a dylib instead. $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook +ifeq ($(CONF), release) + $(STRIP) $@ +endif # cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided # boot ROM directory. @@ -332,6 +387,7 @@ $(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS) $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS) ifeq ($(CONF), release) $(STRIP) $@ + $(CODESIGN) $@ endif # Windows version builds two, one with a conole and one without it @@ -346,11 +402,11 @@ $(BIN)/SDL/sameboy_debugger.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/r ifneq ($(USE_WINDRES),) $(OBJ)/%.o: %.rc -@$(MKDIR) -p $(dir $@) - windres --preprocessor cpp -DVERSION=\"$(VERSION)\" $^ $@ + windres --preprocessor cpp -DVERSION=\"$(VERSION)\" -DCOPYRIGHT_YEAR=\"$(COPYRIGHT_YEAR)\" $^ $@ else $(OBJ)/%.res: %.rc -@$(MKDIR) -p $(dir $@) - rc /fo $@ /dVERSION=\"$(VERSION)\" $^ + rc /fo $@ /dVERSION=\"$(VERSION)\" /dCOPYRIGHT_YEAR=\"$(COPYRIGHT_YEAR)\" $^ %.o: %.res cvtres /OUT:"$@" $^ @@ -368,6 +424,7 @@ $(BIN)/tester/sameboy_tester: $(CORE_OBJECTS) $(TESTER_OBJECTS) $(CC) $^ -o $@ $(LDFLAGS) ifeq ($(CONF), release) $(STRIP) $@ + $(CODESIGN) $@ endif $(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) @@ -401,19 +458,26 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp $(BIN)/SDL/Shaders: Shaders -@$(MKDIR) -p $@ cp -rf Shaders/*.fsh $@ + +$(BIN)/SDL/Palettes: Misc/Palettes + -@$(MKDIR) -p $@ + cp -rf Misc/Palettes/*.sbp $@ # Boot ROMs $(OBJ)/%.2bpp: %.png -@$(MKDIR) -p $(dir $@) - rgbgfx -h -u -o $@ $< + rgbgfx $(if $(filter $(shell echo 'print __RGBDS_MAJOR__ || (!__RGBDS_MAJOR__ && __RGBDS_MINOR__ > 5)' | rgbasm -), $$0), -h -u, -Z -u -c embedded) -o $@ $< $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS) + -@$(MKDIR) -p $(dir $@) $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c + -@$(MKDIR) -p $(dir $@) $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ +$(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 @@ -458,15 +522,15 @@ 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/mime/packages/sameboy.xml: FreeDesktop/sameboy.xml -@$(MKDIR) -p $(dir $@) cp -f $^ $@ diff --git a/thirdparty/SameBoy/Misc/Palettes/Desert.sbp b/thirdparty/SameBoy/Misc/Palettes/Desert.sbp new file mode 100644 index 000000000..28625ad3b Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Desert.sbp differ diff --git a/thirdparty/SameBoy/Misc/Palettes/Evening.sbp b/thirdparty/SameBoy/Misc/Palettes/Evening.sbp new file mode 100644 index 000000000..e11998ab1 --- /dev/null +++ b/thirdparty/SameBoy/Misc/Palettes/Evening.sbp @@ -0,0 +1 @@ +LPBS&6UiS䦻}^LH+ \ No newline at end of file diff --git a/thirdparty/SameBoy/Misc/Palettes/Fog.sbp b/thirdparty/SameBoy/Misc/Palettes/Fog.sbp new file mode 100644 index 000000000..a79fe00fd Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Fog.sbp differ diff --git a/thirdparty/SameBoy/Misc/Palettes/Magic Eggplant.sbp b/thirdparty/SameBoy/Misc/Palettes/Magic Eggplant.sbp new file mode 100644 index 000000000..6bd59291e Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Magic Eggplant.sbp differ diff --git a/thirdparty/SameBoy/Misc/Palettes/Radioactive Pea.sbp b/thirdparty/SameBoy/Misc/Palettes/Radioactive Pea.sbp new file mode 100644 index 000000000..57f9d6a31 Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Radioactive Pea.sbp differ diff --git a/thirdparty/SameBoy/Misc/Palettes/Seaweed.sbp b/thirdparty/SameBoy/Misc/Palettes/Seaweed.sbp new file mode 100644 index 000000000..3718efd74 Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Seaweed.sbp differ diff --git a/thirdparty/SameBoy/Misc/Palettes/Twilight.sbp b/thirdparty/SameBoy/Misc/Palettes/Twilight.sbp new file mode 100644 index 000000000..a5decc103 Binary files /dev/null and b/thirdparty/SameBoy/Misc/Palettes/Twilight.sbp differ diff --git a/thirdparty/SameBoy/Misc/registers.sym b/thirdparty/SameBoy/Misc/registers.sym index 3b31b7458..67c3266d3 100644 --- a/thirdparty/SameBoy/Misc/registers.sym +++ b/thirdparty/SameBoy/Misc/registers.sym @@ -1,67 +1,134 @@ 00:FF00 IO_JOYP +00:FF00 rJOYP 00:FF01 IO_SB +00:FF01 rSB 00:FF02 IO_SC +00:FF02 rSC 00:FF04 IO_DIV +00:FF04 rDIV 00:FF05 IO_TIMA +00:FF05 rTIMA 00:FF06 IO_TMA +00:FF06 rTMA 00:FF07 IO_TAC +00:FF07 rTAC 00:FF0F IO_IF +00:FF0F rIF 00:FF10 IO_NR10 +00:FF10 rNR10 00:FF11 IO_NR11 +00:FF11 rNR11 00:FF12 IO_NR12 +00:FF12 rNR12 00:FF13 IO_NR13 +00:FF13 rNR13 00:FF14 IO_NR14 +00:FF14 rNR14 00:FF16 IO_NR21 +00:FF16 rNR21 00:FF17 IO_NR22 +00:FF17 rNR22 00:FF18 IO_NR23 +00:FF18 rNR23 00:FF19 IO_NR24 +00:FF19 rNR24 00:FF1A IO_NR30 +00:FF1A rNR30 00:FF1B IO_NR31 +00:FF1B rNR31 00:FF1C IO_NR32 +00:FF1C rNR32 00:FF1D IO_NR33 +00:FF1D rNR33 00:FF1E IO_NR34 +00:FF1E rNR34 00:FF20 IO_NR41 +00:FF20 rNR41 00:FF21 IO_NR42 +00:FF21 rNR42 00:FF22 IO_NR43 +00:FF22 rNR43 00:FF23 IO_NR44 +00:FF23 rNR44 00:FF24 IO_NR50 +00:FF24 rNR50 00:FF25 IO_NR51 +00:FF25 rNR51 00:FF26 IO_NR52 +00:FF26 rNR52 00:FF30 IO_WAV_START +00:FF30 rWAV_START 00:FF3F IO_WAV_END +00:FF3F rWAV_END 00:FF40 IO_LCDC +00:FF40 rLCDC 00:FF41 IO_STAT +00:FF41 rSTAT 00:FF42 IO_SCY +00:FF42 rSCY 00:FF43 IO_SCX +00:FF43 rSCX 00:FF44 IO_LY +00:FF44 rLY 00:FF45 IO_LYC +00:FF45 rLYC 00:FF46 IO_DMA +00:FF46 rDMA 00:FF47 IO_BGP +00:FF47 rBGP 00:FF48 IO_OBP0 +00:FF48 rOBP0 00:FF49 IO_OBP1 +00:FF49 rOBP1 00:FF4A IO_WY +00:FF4A rWY 00:FF4B IO_WX +00:FF4B rWX 00:FF4C IO_KEY0 +00:FF4C rKEY0 00:FF4D IO_KEY1 +00:FF4D rKEY1 00:FF4F IO_VBK +00:FF4F rVBK 00:FF50 IO_BANK +00:FF50 rBANK 00:FF51 IO_HDMA1 +00:FF51 rHDMA1 00:FF52 IO_HDMA2 +00:FF52 rHDMA2 00:FF53 IO_HDMA3 +00:FF53 rHDMA3 00:FF54 IO_HDMA4 +00:FF54 rHDMA4 00:FF55 IO_HDMA5 +00:FF55 rHDMA5 00:FF56 IO_RP +00:FF56 rRP 00:FF68 IO_BGPI +00:FF68 rBGPI 00:FF69 IO_BGPD +00:FF69 rBGPD 00:FF6A IO_OBPI +00:FF6A rOBPI 00:FF6B IO_OBPD +00:FF6B rOBPD 00:FF6C IO_OPRI +00:FF6C rOPRI 00:FF70 IO_SVBK -00:FF72 IO_UNKNOWN2 -00:FF73 IO_UNKNOWN3 -00:FF74 IO_UNKNOWN4 +00:FF70 rSVBK +00:FF71 IO_PSM +00:FF71 rPSM +00:FF72 IO_PSWX +00:FF72 rPSWX +00:FF73 IO_PSWY +00:FF73 rPSWY +00:FF74 IO_PSW +00:FF74 rPSW 00:FF75 IO_UNKNOWN5 -00:FF76 IO_PCM_12 -00:FF77 IO_PCM_34 -00:FF7F IO_UNKNOWN8 +00:FF75 rUNKNOWN5 +00:FF76 IO_PCM12 +00:FF76 rPCM12 +00:FF77 IO_PCM34 +00:FF77 rPCM34 00:FFFF IO_IE +00:FFFF rIE diff --git a/thirdparty/SameBoy/OpenDialog/cocoa.m b/thirdparty/SameBoy/OpenDialog/cocoa.m index aeeb98aa9..fd9af3ca6 100644 --- a/thirdparty/SameBoy/OpenDialog/cocoa.m +++ b/thirdparty/SameBoy/OpenDialog/cocoa.m @@ -5,13 +5,16 @@ char *do_open_rom_dialog(void) { @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); NSWindow *key = [NSApp keyWindow]; NSOpenPanel *dialog = [NSOpenPanel openPanel]; dialog.title = @"Open ROM"; dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb", @"isx"]; - [dialog runModal]; + if ([dialog runModal] != NSModalResponseOK) return nil; [key makeKeyAndOrderFront:nil]; NSString *ret = [[[dialog URLs] firstObject] path]; + dup2(stderr_fd, STDERR_FILENO); if (ret) { return strdup(ret.UTF8String); } @@ -22,14 +25,39 @@ char *do_open_folder_dialog(void) { @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); NSWindow *key = [NSApp keyWindow]; NSOpenPanel *dialog = [NSOpenPanel openPanel]; dialog.title = @"Select Boot ROMs Folder"; dialog.canChooseDirectories = true; dialog.canChooseFiles = false; - [dialog runModal]; + if ([dialog runModal] != NSModalResponseOK) return nil; [key makeKeyAndOrderFront:nil]; NSString *ret = [[[dialog URLs] firstObject] path]; + dup2(stderr_fd, STDERR_FILENO); + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} + +/* The Cocoa variant of this function isn't as fully featured as the GTK and Windows ones, as Mac users would use + the fully featured Cocoa port of SameBoy anyway*/ +char *do_save_recording_dialog(unsigned frequency) +{ + @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); + NSWindow *key = [NSApp keyWindow]; + NSSavePanel *dialog = [NSSavePanel savePanel]; + dialog.title = @"Audio recording save location"; + dialog.allowedFileTypes = @[@"aiff", @"aif", @"aifc", @"wav", @"raw", @"pcm"]; + if ([dialog runModal] != NSModalResponseOK) return nil; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[dialog URL] path]; + dup2(stderr_fd, STDERR_FILENO); if (ret) { return strdup(ret.UTF8String); } diff --git a/thirdparty/SameBoy/OpenDialog/gtk.c b/thirdparty/SameBoy/OpenDialog/gtk.c index 378dcb4e5..2b08f1df8 100644 --- a/thirdparty/SameBoy/OpenDialog/gtk.c +++ b/thirdparty/SameBoy/OpenDialog/gtk.c @@ -1,4 +1,5 @@ #include "open_dialog.h" +#include #include #include #include @@ -6,6 +7,7 @@ #include #define GTK_FILE_CHOOSER_ACTION_OPEN 0 +#define GTK_FILE_CHOOSER_ACTION_SAVE 1 #define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2 #define GTK_RESPONSE_ACCEPT -3 #define GTK_RESPONSE_CANCEL -6 @@ -28,6 +30,19 @@ void _gtk_file_filter_set_name(void *filter, const char *name); void _gtk_file_chooser_add_filter(void *dialog, void *filter); void _gtk_main_iteration(void); bool _gtk_events_pending(void); +unsigned long _g_signal_connect_data(void *instance, + const char *detailed_signal, + void *c_handler, + void *data, + void *destroy_data, + unsigned connect_flags); +void _gtk_file_chooser_set_current_name(void *dialog, + const char *name); +void *_gtk_file_chooser_get_filter(void *dialog); +const char *_gtk_file_filter_get_name (void *dialog); +#define g_signal_connect(instance, detailed_signal, c_handler, data) \ +g_signal_connect_data((instance), (detailed_signal), (c_handler), (data), NULL, 0) + #define LAZY(symbol) static typeof(_##symbol) *symbol = NULL;\ @@ -35,10 +50,20 @@ if (symbol == NULL) symbol = dlsym(handle, #symbol);\ if (symbol == NULL) goto lazy_error #define TRY_DLOPEN(name) handle = handle? handle : dlopen(name, RTLD_NOW) -void nop(){} +void nop(void){} + +static void wait_mouse_up(void) +{ + while (true) { + if (!(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT))) break; + SDL_Event event; + SDL_PollEvent(&event); + } +} char *do_open_rom_dialog(void) { + wait_mouse_up(); static void *handle = NULL; TRY_DLOPEN("libgtk-3.so"); @@ -87,7 +112,7 @@ char *do_open_rom_dialog(void) gtk_file_filter_set_name(filter, "Game Boy ROMs"); gtk_file_chooser_add_filter(dialog, filter); - int res = gtk_dialog_run (dialog); + int res = gtk_dialog_run(dialog); char *ret = NULL; if (res == GTK_RESPONSE_ACCEPT) { @@ -115,6 +140,7 @@ char *do_open_rom_dialog(void) char *do_open_folder_dialog(void) { + wait_mouse_up(); static void *handle = NULL; TRY_DLOPEN("libgtk-3.so"); @@ -155,7 +181,169 @@ char *do_open_folder_dialog(void) NULL ); - int res = gtk_dialog_run (dialog); + 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; +} + +static void filter_changed(void *dialog, + void *unused, + void *unused2) +{ + 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_file_chooser_get_filename); + LAZY(gtk_file_chooser_set_current_name); + LAZY(g_free); + LAZY(gtk_file_chooser_get_filter); + LAZY(gtk_file_filter_get_name); + + char *filename = gtk_file_chooser_get_filename(dialog); + if (!filename) return; + char *temp = filename + strlen(filename); + char *basename = filename; + bool deleted_extension = false; + while (temp != filename) { + temp--; + if (*temp == '.' && !deleted_extension) { + *temp = 0; + deleted_extension = true; + } + else if (*temp == '/') { + basename = temp + 1; + break; + } + } + + char *new_filename = NULL; + + switch (gtk_file_filter_get_name(gtk_file_chooser_get_filter(dialog))[1]) { + case 'p': + default: + asprintf(&new_filename, "%s.aiff", basename); + break; + case 'I': + asprintf(&new_filename, "%s.wav", basename); + break; + case 'a': + asprintf(&new_filename, "%s.raw", basename); + break; + } + + + gtk_file_chooser_set_current_name(dialog, new_filename); + free(new_filename); + g_free(filename); + return; + +lazy_error: + fprintf(stderr, "Failed updating the file extension\n"); +} + + +char *do_save_recording_dialog(unsigned frequency) +{ + wait_mouse_up(); + 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); + LAZY(g_signal_connect_data); + LAZY(gtk_file_chooser_set_current_name); + + /* Shut up GTK */ + g_log_set_default_handler(nop, NULL); + + gtk_init_check(0, 0); + + + void *dialog = gtk_file_chooser_dialog_new("Audio recording save location", + 0, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL ); + + + void *filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.aiff"); + gtk_file_filter_add_pattern(filter, "*.aif"); + gtk_file_filter_add_pattern(filter, "*.aifc"); + gtk_file_filter_set_name(filter, "Apple AIFF"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.wav"); + gtk_file_filter_set_name(filter, "RIFF WAVE"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.raw"); + gtk_file_filter_add_pattern(filter, "*.pcm"); + static char raw_name[40]; +#ifdef GB_BIG_ENDIAN + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit BE)", frequency); +#else + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit LE)", frequency); +#endif + gtk_file_filter_set_name(filter, raw_name); + gtk_file_chooser_add_filter(dialog, filter); + + g_signal_connect(dialog, "notify::filter", filter_changed, NULL); + gtk_file_chooser_set_current_name(dialog, "Untitled.aiff"); + + int res = gtk_dialog_run(dialog); char *ret = NULL; if (res == GTK_RESPONSE_ACCEPT) { diff --git a/thirdparty/SameBoy/OpenDialog/open_dialog.h b/thirdparty/SameBoy/OpenDialog/open_dialog.h index 6d7fb5b2a..35484108d 100644 --- a/thirdparty/SameBoy/OpenDialog/open_dialog.h +++ b/thirdparty/SameBoy/OpenDialog/open_dialog.h @@ -3,4 +3,5 @@ char *do_open_rom_dialog(void); char *do_open_folder_dialog(void); +char *do_save_recording_dialog(unsigned frequency); #endif /* open_rom_h */ diff --git a/thirdparty/SameBoy/OpenDialog/windows.c b/thirdparty/SameBoy/OpenDialog/windows.c index e71103206..5a0af0e32 100644 --- a/thirdparty/SameBoy/OpenDialog/windows.c +++ b/thirdparty/SameBoy/OpenDialog/windows.c @@ -1,57 +1,104 @@ #include #include +#include #include "open_dialog.h" +static char *wc_to_utf8_alloc(const wchar_t *wide) +{ + unsigned int cb = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL); + if (cb) { + char *buffer = (char*) malloc(cb); + if (buffer) { + WideCharToMultiByte(CP_UTF8, 0, wide, -1, buffer, cb, NULL, NULL); + return buffer; + } + } + return NULL; +} + char *do_open_rom_dialog(void) { OPENFILENAMEW dialog; - static wchar_t filename[MAX_PATH] = {0}; - + wchar_t filename[MAX_PATH]; + + filename[0] = '\0'; memset(&dialog, 0, sizeof(dialog)); dialog.lStructSize = sizeof(dialog); dialog.lpstrFile = filename; - dialog.nMaxFile = sizeof(filename); + dialog.nMaxFile = MAX_PATH; dialog.lpstrFilter = L"Game Boy ROMs\0*.gb;*.gbc;*.sgb;*.isx\0All files\0*.*\0\0"; dialog.nFilterIndex = 1; dialog.lpstrFileTitle = NULL; dialog.nMaxFileTitle = 0; dialog.lpstrInitialDir = NULL; - dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - - if (GetOpenFileNameW(&dialog) == TRUE) { - char *ret = malloc(MAX_PATH * 4); - WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL); - return ret; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + + if (GetOpenFileNameW(&dialog)) { + return wc_to_utf8_alloc(filename); } - + return NULL; } char *do_open_folder_dialog(void) { - + char *ret = NULL; BROWSEINFOW dialog; + memset(&dialog, 0, sizeof(dialog)); - - dialog.ulFlags = BIF_USENEWUI; + dialog.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; dialog.lpszTitle = L"Select Boot ROMs Folder"; - - OleInitialize(NULL); - - LPITEMIDLIST list = SHBrowseForFolderW(&dialog); - static wchar_t filename[MAX_PATH] = {0}; + HRESULT hrOleInit = OleInitialize(NULL); + LPITEMIDLIST list = SHBrowseForFolderW(&dialog); if (list) { - if (!SHGetPathFromIDListW(list, filename)) { - OleUninitialize(); - return NULL; + wchar_t filename[MAX_PATH]; + if (SHGetPathFromIDListW(list, filename)) { + ret = wc_to_utf8_alloc(filename); } - 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(); + + if (SUCCEEDED(hrOleInit)) OleUninitialize(); + return ret; +} + +char *do_save_recording_dialog(unsigned frequency) +{ + OPENFILENAMEW dialog; + wchar_t filename[MAX_PATH + 5] = L"recording.wav"; + static wchar_t filter[] = L"RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo _______Hz, 16-bit LE)\0*.raw;*.pcm;\0All files\0*.*\0\0"; + + memset(&dialog, 0, sizeof(dialog)); + dialog.lStructSize = sizeof(dialog); + dialog.lpstrFile = filename; + dialog.nMaxFile = MAX_PATH; + dialog.lpstrFilter = filter; + swprintf(filter + sizeof("RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo ") - 1, + sizeof("_______Hz, 16-bit LE)"), + L"%dHz, 16-bit LE) ", + frequency); + + dialog.nFilterIndex = 1; + dialog.lpstrInitialDir = NULL; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; + + if (GetSaveFileNameW(&dialog)) { + if (dialog.nFileExtension == 0) { + switch (dialog.nFilterIndex) { + case 1: + wcscat(filename, L".wav"); + break; + case 2: + wcscat(filename, L".aiff"); + break; + case 3: + wcscat(filename, L".raw"); + break; + } + } + return wc_to_utf8_alloc(filename); + } + return NULL; } diff --git a/thirdparty/SameBoy/QuickLook/CartridgeTemplate.png b/thirdparty/SameBoy/QuickLook/CartridgeTemplate.png index 5bf1a2fb1..3d6d600ee 100644 Binary files a/thirdparty/SameBoy/QuickLook/CartridgeTemplate.png and b/thirdparty/SameBoy/QuickLook/CartridgeTemplate.png differ diff --git a/thirdparty/SameBoy/QuickLook/ColorCartridgeTemplate.png b/thirdparty/SameBoy/QuickLook/ColorCartridgeTemplate.png index 5eac5622d..356e45d29 100644 Binary files a/thirdparty/SameBoy/QuickLook/ColorCartridgeTemplate.png and b/thirdparty/SameBoy/QuickLook/ColorCartridgeTemplate.png differ diff --git a/thirdparty/SameBoy/QuickLook/Info.plist b/thirdparty/SameBoy/QuickLook/Info.plist index 9b369ec44..5fc594304 100644 --- a/thirdparty/SameBoy/QuickLook/Info.plist +++ b/thirdparty/SameBoy/QuickLook/Info.plist @@ -48,7 +48,7 @@ CFPlugInUnloadFunction NSHumanReadableCopyright - Copyright © 2015-2021 Lior Halphon + Copyright © 2015-@COPYRIGHT_YEAR Lior Halphon QLNeedsToBeRunInMainThread QLPreviewHeight diff --git a/thirdparty/SameBoy/QuickLook/UniversalCartridgeTemplate.png b/thirdparty/SameBoy/QuickLook/UniversalCartridgeTemplate.png index 1bf4903a2..7f251f4a0 100644 Binary files a/thirdparty/SameBoy/QuickLook/UniversalCartridgeTemplate.png and b/thirdparty/SameBoy/QuickLook/UniversalCartridgeTemplate.png differ diff --git a/thirdparty/SameBoy/QuickLook/generator.m b/thirdparty/SameBoy/QuickLook/generator.m index 92bb6ac10..f2651d289 100644 --- a/thirdparty/SameBoy/QuickLook/generator.m +++ b/thirdparty/SameBoy/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]; diff --git a/thirdparty/SameBoy/QuickLook/get_image_for_rom.c b/thirdparty/SameBoy/QuickLook/get_image_for_rom.c index b9f87edb1..6c9ac9198 100755 --- a/thirdparty/SameBoy/QuickLook/get_image_for_rom.c +++ b/thirdparty/SameBoy/QuickLook/get_image_for_rom.c @@ -25,7 +25,7 @@ static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes } -static void vblank(GB_gameboy_t *gb) +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) { struct local_data *local_data = (struct local_data *)GB_get_user_data(gb); @@ -59,7 +59,7 @@ int get_image_for_rom(const char *filename, const char *boot_path, uint32_t *out GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_async_input_callback(&gb, async_input_callback); GB_set_log_callback(&gb, log_callback); - GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED); size_t length = strlen(filename); char extension[4] = {0,}; diff --git a/thirdparty/SameBoy/SDL/audio.c b/thirdparty/SameBoy/SDL/audio.c new file mode 100644 index 000000000..29b3eb0f6 --- /dev/null +++ b/thirdparty/SameBoy/SDL/audio.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "audio/audio.h" +#include "configuration.h" + +#define likely(x) GB_likely(x) +#define unlikely(x) GB_unlikely(x) + +static const GB_audio_driver_t *driver = NULL; + +bool GB_audio_init(void) +{ + const GB_audio_driver_t *drivers[] = { +#ifdef _WIN32 + GB_AUDIO_DRIVER_REF(XAudio2), + GB_AUDIO_DRIVER_REF(XAudio2_7), +#endif + GB_AUDIO_DRIVER_REF(SDL), +#ifdef ENABLE_OPENAL + GB_AUDIO_DRIVER_REF(OpenAL), +#endif + }; + + // First try the preferred driver + for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) { + driver = drivers[i]; + if (strcmp(driver->name, configuration.audio_driver) != 0) { + continue; + } + if (driver->audio_init()) { + if (driver->audio_deinit) { + atexit(driver->audio_deinit); + } + return true; + } + } + + // Else go by priority + for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) { + driver = drivers[i]; + if (driver->audio_init()) { + atexit(driver->audio_deinit); + return true; + } + } + + driver = NULL; + return false; +} + +bool GB_audio_is_playing(void) +{ + if (unlikely(!driver)) return false; + return driver->audio_is_playing(); +} + +void GB_audio_set_paused(bool paused) +{ + if (unlikely(!driver)) return; + return driver->audio_set_paused(paused); +} + +void GB_audio_clear_queue(void) +{ + if (unlikely(!driver)) return; + return driver->audio_clear_queue(); +} + +unsigned GB_audio_get_frequency(void) +{ + if (unlikely(!driver)) return 0; + return driver->audio_get_frequency(); +} + +size_t GB_audio_get_queue_length(void) +{ + if (unlikely(!driver)) return 0; + return driver->audio_get_queue_length(); +} + +void GB_audio_queue_sample(GB_sample_t *sample) +{ + if (unlikely(!driver)) return; + return driver->audio_queue_sample(sample); +} + +const char *GB_audio_driver_name(void) +{ + if (unlikely(!driver)) return "None"; + return driver->name; +} + +const char *GB_audio_driver_name_at_index(unsigned index) +{ + const GB_audio_driver_t *drivers[] = { +#ifdef _WIN32 + GB_AUDIO_DRIVER_REF(XAudio2), + GB_AUDIO_DRIVER_REF(XAudio2_7), +#endif + GB_AUDIO_DRIVER_REF(SDL), +#ifdef ENABLE_OPENAL + GB_AUDIO_DRIVER_REF(OpenAL), +#endif + }; + if (index >= sizeof(drivers) / sizeof(drivers[0])) { + return ""; + } + return drivers[index]->name; +} diff --git a/thirdparty/SameBoy/SDL/audio/audio.h b/thirdparty/SameBoy/SDL/audio/audio.h index acaa011da..0de574679 100644 --- a/thirdparty/SameBoy/SDL/audio/audio.h +++ b/thirdparty/SameBoy/SDL/audio/audio.h @@ -11,6 +11,35 @@ void GB_audio_clear_queue(void); unsigned GB_audio_get_frequency(void); size_t GB_audio_get_queue_length(void); void GB_audio_queue_sample(GB_sample_t *sample); -void GB_audio_init(void); +bool GB_audio_init(void); +void GB_audio_deinit(void); +const char *GB_audio_driver_name(void); +const char *GB_audio_driver_name_at_index(unsigned index); + +typedef struct { + typeof(GB_audio_is_playing) *audio_is_playing; + typeof(GB_audio_set_paused) *audio_set_paused; + typeof(GB_audio_clear_queue) *audio_clear_queue; + typeof(GB_audio_get_frequency) *audio_get_frequency; + typeof(GB_audio_get_queue_length) *audio_get_queue_length; + typeof(GB_audio_queue_sample) *audio_queue_sample; + typeof(GB_audio_init) *audio_init; + typeof(GB_audio_deinit) *audio_deinit; + const char *name; +} GB_audio_driver_t; + +#define GB_AUDIO_DRIVER(_name) const GB_audio_driver_t _name##driver = { \ + .audio_is_playing = _audio_is_playing, \ + .audio_set_paused = _audio_set_paused, \ + .audio_clear_queue = _audio_clear_queue, \ + .audio_get_frequency = _audio_get_frequency, \ + .audio_get_queue_length = _audio_get_queue_length, \ + .audio_queue_sample = _audio_queue_sample, \ + .audio_init = _audio_init, \ + .audio_deinit = _audio_deinit, \ + .name = #_name, \ +} + +#define GB_AUDIO_DRIVER_REF(name) ({extern const GB_audio_driver_t name##driver; &name##driver;}) #endif /* sdl_audio_h */ diff --git a/thirdparty/SameBoy/SDL/audio/openal.c b/thirdparty/SameBoy/SDL/audio/openal.c new file mode 100644 index 000000000..fdcaeadea --- /dev/null +++ b/thirdparty/SameBoy/SDL/audio/openal.c @@ -0,0 +1,317 @@ +#include "audio.h" +#if defined(__APPLE__) +#include +#include + +#else +#include +#include +#endif +#include +#include +#include + +#define BUFFER_LEN_MS 5 + +static ALCdevice *al_device = NULL; +static ALCcontext *al_context = NULL; +static GB_sample_t *audio_buffer = NULL; +static ALuint al_source = 0; +static ALCint sample_rate = 0; +static unsigned buffer_size = 0; +static unsigned buffer_pos = 0; +static bool is_paused = false; + +#define AL_ERR_STRINGIFY(x) #x +#define AL_ERR_TOSTRING(x) AL_ERR_STRINGIFY(x) +#define AL_ERROR(msg) check_al_error(msg, AL_ERR_TOSTRING(__LINE__)) + +// Check if the previous OpenAL call returned an error. +// If an error occurred a message will be logged to stderr. +static bool check_al_error(const char *user_msg, const char *line) +{ + ALCenum error = alGetError(); + const char *description = ""; + + switch (error) { + case AL_NO_ERROR: + return false; + case AL_INVALID_NAME: + description = "A bad name (ID) was passed to an OpenAL function"; + break; + case AL_INVALID_ENUM: + description = "An invalid enum value was passed to an OpenAL function"; + break; + case AL_INVALID_VALUE: + description = "An invalid value was passed to an OpenAL function"; + break; + case AL_INVALID_OPERATION: + description = "The requested operation is not valid"; + break; + case AL_OUT_OF_MEMORY: + description = "The requested operation resulted in OpenAL running out of memory"; + break; + } + + if (user_msg != NULL) { + fprintf(stderr, "[OpenAL:%s] %s: %s\n", line, user_msg, description); + } + else { + fprintf(stderr, "[OpenAL:%s] %s\n", line, description); + } + + return true; +} + +static void _audio_deinit(void) +{ + // Stop the source (this should mark all queued buffers as processed) + alSourceStop(al_source); + + // Check if there are buffers that can be freed + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (!AL_ERROR("Failed to query number of processed buffers")) { + // Try to free the buffers, we do not care about potential errors here + while (processed--) { + ALuint buffer; + alSourceUnqueueBuffers(al_source, 1, &buffer); + alDeleteBuffers(1, &buffer); + } + } + + alDeleteSources(1, &al_source); + if (al_context) { + alcDestroyContext(al_context); + al_context = NULL; + } + + if (al_device) { + alcCloseDevice(al_device); + al_device = NULL; + } + + if (audio_buffer) { + free(audio_buffer); + audio_buffer = NULL; + } +} + +static void free_processed_buffers(void) +{ + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (AL_ERROR("Failed to query number of processed buffers")) { + return; + } + + while (processed--) { + ALuint buffer; + + alSourceUnqueueBuffers(al_source, 1, &buffer); + if (AL_ERROR("Failed to unqueue buffer")) { + return; + } + + alDeleteBuffers(1, &buffer); + /* Due to a limitation in Apple's OpenAL implementation, this function + can fail once in a few times. If it does, ignore the warning, and let + this buffer be freed in a later call to free_processed_buffers. */ +#if defined(__APPLE__) + if (alGetError()) return; +#else + if (AL_ERROR("Failed to delete buffer")) { + return; + } +#endif + } +} + +static bool _audio_is_playing(void) +{ + ALenum state; + alGetSourcei(al_source, AL_SOURCE_STATE, &state); + if (AL_ERROR("Failed to query source state")) { + return false; + } + + return state == AL_PLAYING; +} + +static void _audio_set_paused(bool paused) +{ + is_paused = paused; + if (paused) { + alSourcePause(al_source); + } + else { + alSourcePlay(al_source); + } +} + +static void _audio_clear_queue(void) +{ + bool is_playing = _audio_is_playing(); + + // Stopping a source clears its queue + alSourceStop(al_source); + if (AL_ERROR(NULL)) { + return; + } + + free_processed_buffers(); + buffer_pos = 0; + + if (is_playing) { + _audio_set_paused(false); + } +} + +static unsigned _audio_get_frequency(void) +{ + return sample_rate; +} + +static size_t _audio_get_queue_length(void) +{ + // Get the number of all attached buffers + ALint buffers; + alGetSourcei(al_source, AL_BUFFERS_QUEUED, &buffers); + if (AL_ERROR("Failed to query number of queued buffers")) { + buffers = 0; + } + + // Get the number of all processed buffers (ready to be detached) + ALint processed; + alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed); + if (AL_ERROR("Failed to query number of processed buffers")) { + processed = 0; + } + + return (buffers - processed) * buffer_size + buffer_pos; +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (is_paused) return; + audio_buffer[buffer_pos++] = *sample; + + if (buffer_pos == buffer_size) { + buffer_pos = 0; + + ALuint al_buffer; + alGenBuffers(1, &al_buffer); + if (AL_ERROR("Failed to create audio buffer")) { + return; + } + + alBufferData(al_buffer, AL_FORMAT_STEREO16, audio_buffer, buffer_size * sizeof(GB_sample_t), sample_rate); + if (AL_ERROR("Failed to buffer data")) { + return; + } + + alSourceQueueBuffers(al_source, 1, &al_buffer); + if (AL_ERROR("Failed to queue buffer")) { + return; + } + + // In case of an audio underrun, the source might + // have finished playing all attached buffers + // which means its status will be "AL_STOPPED". + if (!_audio_is_playing()) { + alSourcePlay(al_source); + } + + free_processed_buffers(); + } +} + +static bool _audio_init(void) +{ + // Open the default device + al_device = alcOpenDevice(NULL); + if (!al_device) { + AL_ERROR("Failed to open device"); + return false; + } + + // Create a new audio context without special attributes + al_context = alcCreateContext(al_device, NULL); + if (al_context == NULL) { + AL_ERROR("Failed to create context"); + _audio_deinit(); + return false; + } + + // Enable our audio context + if (!alcMakeContextCurrent(al_context)) { + AL_ERROR("Failed to set context"); + _audio_deinit(); + return false; + } + + // Query the sample rate of the playback device + alcGetIntegerv(al_device, ALC_FREQUENCY, 1, &sample_rate); + if (AL_ERROR("Failed to query sample rate")) { + _audio_deinit(); + return false; + } + if (sample_rate == 0) { + sample_rate = 48000; + } + + // Allocate our working buffer + buffer_size = (sample_rate * BUFFER_LEN_MS) / 1000; + audio_buffer = malloc(buffer_size * sizeof(GB_sample_t)); + if (audio_buffer == NULL) { + fprintf(stderr, "Failed to allocate audio buffer\n"); + _audio_deinit(); + return false; + } + + // Create our playback source + alGenSources(1, &al_source); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Keep the pitch as is + alSourcef(al_source, AL_PITCH, 1); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Keep the volume as is + alSourcef(al_source, AL_GAIN, 1); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Position our source at the center of the 3D space + alSource3f(al_source, AL_POSITION, 0, 0, 0); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Our source is fixed in space + alSource3f(al_source, AL_VELOCITY, 0, 0, 0); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + // Our source does not loop + alSourcei(al_source, AL_LOOPING, AL_FALSE); + if (AL_ERROR(NULL)) { + _audio_deinit(); + return false; + } + + return true; +} + +GB_AUDIO_DRIVER(OpenAL); diff --git a/thirdparty/SameBoy/SDL/audio/sdl.c b/thirdparty/SameBoy/SDL/audio/sdl.c index 12ee69ae0..9c0cd98bb 100644 --- a/thirdparty/SameBoy/SDL/audio/sdl.c +++ b/thirdparty/SameBoy/SDL/audio/sdl.c @@ -29,33 +29,33 @@ static SDL_AudioSpec want_aspec, have_aspec; static unsigned buffer_pos = 0; static GB_sample_t audio_buffer[AUDIO_BUFFER_SIZE]; -bool GB_audio_is_playing(void) +static bool _audio_is_playing(void) { return SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING; } -void GB_audio_set_paused(bool paused) +static void _audio_clear_queue(void) { - GB_audio_clear_queue(); - SDL_PauseAudioDevice(device_id, paused); + SDL_ClearQueuedAudio(device_id); } -void GB_audio_clear_queue(void) +static void _audio_set_paused(bool paused) { - SDL_ClearQueuedAudio(device_id); + _audio_clear_queue(); + SDL_PauseAudioDevice(device_id, paused); } -unsigned GB_audio_get_frequency(void) +static unsigned _audio_get_frequency(void) { return have_aspec.freq; } -size_t GB_audio_get_queue_length(void) +static size_t _audio_get_queue_length(void) { - return SDL_GetQueuedAudioSize(device_id); + return SDL_GetQueuedAudioSize(device_id) / sizeof(GB_sample_t); } -void GB_audio_queue_sample(GB_sample_t *sample) +static void _audio_queue_sample(GB_sample_t *sample) { audio_buffer[buffer_pos++] = *sample; @@ -65,8 +65,13 @@ void GB_audio_queue_sample(GB_sample_t *sample) } } -void GB_audio_init(void) +static bool _audio_init(void) { + if (SDL_Init(SDL_INIT_AUDIO) != 0) { + printf("Failed to initialize SDL audio: %s", SDL_GetError()); + return false; + } + /* Configure Audio */ memset(&want_aspec, 0, sizeof(want_aspec)); want_aspec.freq = AUDIO_FREQUENCY; @@ -93,4 +98,14 @@ void GB_audio_init(void) #endif device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + + return true; } + +static void _audio_deinit(void) +{ + _audio_set_paused(true); + SDL_CloseAudioDevice(device_id); +} + +GB_AUDIO_DRIVER(SDL); diff --git a/thirdparty/SameBoy/SDL/audio/xaudio2.c b/thirdparty/SameBoy/SDL/audio/xaudio2.c new file mode 100644 index 000000000..e2ca68b8f --- /dev/null +++ b/thirdparty/SameBoy/SDL/audio/xaudio2.c @@ -0,0 +1,159 @@ +#define COBJMACROS +#include "audio.h" +#include +#include +#include + +static unsigned audio_frequency = 48000; +static IXAudio2 *xaudio2 = NULL; +static IXAudio2MasteringVoice *master_voice = NULL; +static IXAudio2SourceVoice *source_voice = NULL; +static bool playing = false; +static GB_sample_t sample_pool[0x2000]; +static unsigned pos = 0; + +#define BATCH_SIZE 256 + +static WAVEFORMATEX wave_format = { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 2, + .nBlockAlign = 4, + .wBitsPerSample = 16, + .cbSize = 0 +}; + +static bool _audio_is_playing(void) +{ + return playing; +} + +static void _audio_clear_queue(void) +{ + pos = 0; + IXAudio2SourceVoice_FlushSourceBuffers(source_voice); +} + +static void _audio_set_paused(bool paused) +{ + if (paused) { + playing = false; + IXAudio2SourceVoice_Stop(source_voice, 0, XAUDIO2_COMMIT_NOW); + _audio_clear_queue(); + } + else { + playing = true; + IXAudio2SourceVoice_Start(source_voice, 0, XAUDIO2_COMMIT_NOW); + } + +} + + +#define _DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) static const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +_DEFINE_PROPERTYKEY(_PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0); + + +static void update_frequency(void) +{ + HRESULT hr; + IMMDevice *device = NULL; + IMMDeviceEnumerator *enumerator = NULL; + IPropertyStore *store = NULL; + PWAVEFORMATEX deviceFormatProperties; + PROPVARIANT prop; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); + if (FAILED(hr)) return; + + // get default audio endpoint + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, eRender, eMultimedia, &device); + if (FAILED(hr)) return; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &store); + if (FAILED(hr)) return; + + hr = IPropertyStore_GetValue(store, &_PKEY_AudioEngine_DeviceFormat, &prop); + if (FAILED(hr)) return; + + deviceFormatProperties = (PWAVEFORMATEX)prop.blob.pBlobData; + audio_frequency = deviceFormatProperties->nSamplesPerSec; + if (audio_frequency < 8000 || audio_frequency > 192000) { + // Bogus value, revert to 48KHz + audio_frequency = 48000; + } +} + +static unsigned _audio_get_frequency(void) +{ + return audio_frequency; +} + +static size_t _audio_get_queue_length(void) +{ + static XAUDIO2_VOICE_STATE state; + IXAudio2SourceVoice_GetState(source_voice, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED); + + return state.BuffersQueued * BATCH_SIZE + (pos & (BATCH_SIZE - 1)); +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (!playing) return; + + static XAUDIO2_BUFFER buffer = {.AudioBytes = sizeof(*sample) * BATCH_SIZE, }; + sample_pool[pos] = *sample; + buffer.pAudioData = (void *)&sample_pool[pos & ~(BATCH_SIZE - 1)]; + pos++; + pos &= 0x1fff; + if ((pos & (BATCH_SIZE - 1)) == 0) { + IXAudio2SourceVoice_SubmitSourceBuffer(source_voice, &buffer, NULL); + } +} + +static bool _audio_init(void) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + fprintf(stderr, "CoInitializeEx failed: %lx\n", hr); + return false; + } + + hr = XAudio2Create(&xaudio2, 0, XAUDIO2_DEFAULT_PROCESSOR); + if (FAILED(hr)) { + fprintf(stderr, "XAudio2Create failed: %lx\n", hr); + return false; + } + + update_frequency(); + + hr = IXAudio2_CreateMasteringVoice(xaudio2, &master_voice, + 2, // 2 channels + audio_frequency, + 0, // Flags + 0, // Device index + NULL, // Effect chain + AudioCategory_GameMedia // Category + ); + if (FAILED(hr)) { + fprintf(stderr, "CreateMasteringVoice failed: %lx\n", hr); + return false; + } + + wave_format.nSamplesPerSec = audio_frequency; + wave_format.nAvgBytesPerSec = audio_frequency * 4; + hr = IXAudio2_CreateSourceVoice(xaudio2, &source_voice, &wave_format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); + + if (FAILED(hr)) { + fprintf(stderr, "CreateSourceVoice failed: %lx\n", hr); + return false; + } + + return true; +} + +static void _audio_deinit(void) +{ + _audio_set_paused(true); +} + +GB_AUDIO_DRIVER(XAudio2); diff --git a/thirdparty/SameBoy/SDL/audio/xaudio2_7.c b/thirdparty/SameBoy/SDL/audio/xaudio2_7.c new file mode 100644 index 000000000..b8ed544af --- /dev/null +++ b/thirdparty/SameBoy/SDL/audio/xaudio2_7.c @@ -0,0 +1,179 @@ +#define COBJMACROS +#include "xaudio2_7.h" +#include "audio.h" +#include + + +static unsigned audio_frequency = 48000; +static IXAudio2 *xaudio2 = NULL; +static IXAudio2MasteringVoice *master_voice = NULL; +static IXAudio2SourceVoice *source_voice = NULL; +static bool playing = false; +static GB_sample_t sample_pool[0x2000]; +static unsigned pos = 0; + +#define BATCH_SIZE 256 + + +static WAVEFORMATEX wave_format = { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 2, + .nBlockAlign = 4, + .wBitsPerSample = 16, + .cbSize = 0 +}; + +static inline HRESULT XAudio2Create(IXAudio2 **out, + UINT32 Flags, + XAUDIO2_PROCESSOR XAudio2Processor) +{ + IXAudio2 *xaudio2; + LoadLibraryEx("xaudio2_7.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + HRESULT hr = CoCreateInstance(&CLSID_XAudio2, NULL, CLSCTX_INPROC_SERVER, &IID_IXAudio2, (void **)&xaudio2); + if (SUCCEEDED(hr)) { + hr = xaudio2->lpVtbl->Initialize(xaudio2, Flags, XAudio2Processor); + } + + if (SUCCEEDED(hr)) { + *out = xaudio2; + } + else if (xaudio2) { + xaudio2->lpVtbl->Release(xaudio2); + } + return hr; +} + +static bool _audio_is_playing(void) +{ + return playing; +} + +static void _audio_clear_queue(void) +{ + pos = 0; + IXAudio2SourceVoice_FlushSourceBuffers(source_voice); +} + +static void _audio_set_paused(bool paused) +{ + if (paused) { + playing = false; + IXAudio2SourceVoice_Stop(source_voice, 0, XAUDIO2_COMMIT_NOW); + _audio_clear_queue(); + } + else { + playing = true; + IXAudio2SourceVoice_Start(source_voice, 0, XAUDIO2_COMMIT_NOW); + } + +} + +#define _DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) static const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +_DEFINE_PROPERTYKEY(_PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0); + + +static void update_frequency(void) +{ + HRESULT hr; + IMMDevice *device = NULL; + IMMDeviceEnumerator *enumerator = NULL; + IPropertyStore *store = NULL; + PWAVEFORMATEX deviceFormatProperties; + PROPVARIANT prop; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); + if (FAILED(hr)) return; + + // get default audio endpoint + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, eRender, eMultimedia, &device); + if (FAILED(hr)) return; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &store); + if (FAILED(hr)) return; + + hr = IPropertyStore_GetValue(store, &_PKEY_AudioEngine_DeviceFormat, &prop); + if (FAILED(hr)) return; + + deviceFormatProperties = (PWAVEFORMATEX)prop.blob.pBlobData; + audio_frequency = deviceFormatProperties->nSamplesPerSec; + if (audio_frequency < 8000 || audio_frequency > 192000) { + // Bogus value, revert to 48KHz + audio_frequency = 48000; + } +} + +static unsigned _audio_get_frequency(void) +{ + return audio_frequency; +} + +static size_t _audio_get_queue_length(void) +{ + static XAUDIO2_VOICE_STATE state; + IXAudio2SourceVoice_GetState(source_voice, &state); + + return state.BuffersQueued * BATCH_SIZE + (pos & (BATCH_SIZE - 1)); +} + +static void _audio_queue_sample(GB_sample_t *sample) +{ + if (!playing) return; + + static XAUDIO2_BUFFER buffer = {.AudioBytes = sizeof(*sample) * BATCH_SIZE, }; + sample_pool[pos] = *sample; + buffer.pAudioData = (void *)&sample_pool[pos & ~(BATCH_SIZE - 1)]; + pos++; + pos &= 0x1fff; + if ((pos & (BATCH_SIZE - 1)) == 0) { + IXAudio2SourceVoice_SubmitSourceBuffer(source_voice, &buffer, NULL); + } +} + +static bool _audio_init(void) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + fprintf(stderr, "CoInitializeEx failed: %lx\n", hr); + return false; + } + + hr = XAudio2Create(&xaudio2, 0, XAUDIO2_DEFAULT_PROCESSOR); + if (FAILED(hr)) { + fprintf(stderr, "XAudio2Create failed: %lx\n", hr); + return false; + } + + update_frequency(); + + hr = IXAudio2_CreateMasteringVoice(xaudio2, &master_voice, + 2, // 2 channels + audio_frequency, + 0, // Flags + 0, // Device index + NULL // Effect chain + ); + if (FAILED(hr)) { + fprintf(stderr, "CreateMasteringVoice failed: %lx\n", hr); + return false; + } + + wave_format.nSamplesPerSec = audio_frequency; + wave_format.nAvgBytesPerSec = audio_frequency * 4; + hr = IXAudio2_CreateSourceVoice(xaudio2, &source_voice, &wave_format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); + + if (FAILED(hr)) { + fprintf(stderr, "CreateSourceVoice failed: %lx\n", hr); + return false; + } + + return true; +} + +static void _audio_deinit(void) +{ + _audio_set_paused(true); +} + +GB_AUDIO_DRIVER(XAudio2_7); diff --git a/thirdparty/SameBoy/SDL/audio/xaudio2_7.h b/thirdparty/SameBoy/SDL/audio/xaudio2_7.h new file mode 100644 index 000000000..298715650 --- /dev/null +++ b/thirdparty/SameBoy/SDL/audio/xaudio2_7.h @@ -0,0 +1,108 @@ +#define INITGUID +#include + +/* Minimal definitions for XAudio2.7 */ +typedef UINT32 XAUDIO2_PROCESSOR; + +typedef struct XAUDIO2_BUFFER { + UINT32 Flags; + UINT32 AudioBytes; + const BYTE *pAudioData; + UINT32 PlayBegin; + UINT32 PlayLength; + UINT32 LoopBegin; + UINT32 LoopLength; + UINT32 LoopCount; + void *pContext; +} XAUDIO2_BUFFER; + +typedef struct XAUDIO2_VOICE_STATE { + void *pCurrentBufferContext; + UINT32 BuffersQueued; + UINT64 SamplesPlayed; +} XAUDIO2_VOICE_STATE; + +typedef struct IXAudio2SourceVoice { + struct IXAudio2SourceVoiceVtbl *lpVtbl; +} IXAudio2SourceVoice; + +typedef struct IXAudio2SourceVoiceVtbl IXAudio2SourceVoiceVtbl; + +#undef INTERFACE +#define INTERFACE IXAudio2SourceVoice + +struct IXAudio2SourceVoiceVtbl { + void *voiceMethods[19]; // Unused inherited methods + STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet) PURE; + STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet) PURE; + STDMETHOD(SubmitSourceBuffer) (THIS_ __in const XAUDIO2_BUFFER *pBuffer, __in_opt const void *pBufferWMA) PURE; + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + STDMETHOD(Discontinuity) (THIS) PURE; + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet) PURE; + STDMETHOD_(void, GetState) (THIS_ __out XAUDIO2_VOICE_STATE *pVoiceState) PURE; +}; + +typedef struct IXAudio2 { + struct IXAudio2Vtbl *lpVtbl; +} IXAudio2; + +typedef struct IXAudio2Vtbl IXAudio2Vtbl; +typedef void *IXAudio2MasteringVoice; + +#undef INTERFACE +#define INTERFACE IXAudio2 + +struct IXAudio2Vtbl { + void *QueryInterface; + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + STDMETHOD_(ULONG, Release) (THIS) PURE; + void *GetDeviceCount; + void *GetDeviceDetails; + STDMETHOD(Initialize) (THIS_ UINT32 Flags, + XAUDIO2_PROCESSOR XAudio2Processor) PURE; + + void *RegisterForCallbacks; + void *UnregisterForCallbacks; + + STDMETHOD(CreateSourceVoice) (THIS_ __deref_out IXAudio2SourceVoice **ppSourceVoice, + __in const WAVEFORMATEX *pSourceFormat, + UINT32 Flags, + float MaxFrequencyRatio, + __in_opt void *pCallback, + __in_opt const void *pSendList, + __in_opt const void *pEffectChain) PURE; + + void *CreateSubmixVoice; + + STDMETHOD(CreateMasteringVoice) (THIS_ __deref_out IXAudio2MasteringVoice **ppMasteringVoice, + UINT32 InputChannels, + UINT32 InputSampleRate, + UINT32 Flags, UINT32 DeviceIndex, + __in_opt const void *pEffectChain) PURE; +}; + +#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ +DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ +DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +DEFINE_CLSID(XAudio2, 5a508685, a254, 4fba, 9b, 82, 9a, 24, b0, 03, 06, af); +DEFINE_IID(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + + +#define IXAudio2SourceVoice_Start(This,Flags,OperationSet) ((This)->lpVtbl->Start(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_Stop(This,Flags,OperationSet) ((This)->lpVtbl->Stop(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_SubmitSourceBuffer(This,pBuffer,pBufferWMA) ((This)->lpVtbl->SubmitSourceBuffer(This,pBuffer,pBufferWMA)) +#define IXAudio2SourceVoice_FlushSourceBuffers(This) ((This)->lpVtbl->FlushSourceBuffers(This)) +#define IXAudio2SourceVoice_GetState(This,pVoiceState) ((This)->lpVtbl->GetState(This,pVoiceState)) +#define IXAudio2_CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain) ((This)->lpVtbl->CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain)) +#define IXAudio2_CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain) ((This)->lpVtbl->CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain)) + +#define XAUDIO2_COMMIT_NOW 0 +#define XAUDIO2_DEFAULT_PROCESSOR 0xffffffff +#define XAUDIO2_DEFAULT_FREQ_RATIO 2.0f + +// WASAPI extras. This is a hack, but Windows itself is a hack so I don't care +DEFINE_CLSID(MMDeviceEnumerator, bcde0395, e52f, 467c, 8e, 3d, c4, 57, 92, 91, 69, 2e); +DEFINE_IID(IMMDeviceEnumerator, a95664d2, 9614, 4f35, a7, 46, de, 8d, b6, 36, 17, e6); diff --git a/thirdparty/SameBoy/SDL/background.bmp b/thirdparty/SameBoy/SDL/background.bmp index 0f6192d6d..d356d24e5 100644 Binary files a/thirdparty/SameBoy/SDL/background.bmp and b/thirdparty/SameBoy/SDL/background.bmp differ diff --git a/thirdparty/SameBoy/SDL/configuration.c b/thirdparty/SameBoy/SDL/configuration.c new file mode 100644 index 000000000..35ad29952 --- /dev/null +++ b/thirdparty/SameBoy/SDL/configuration.c @@ -0,0 +1,54 @@ +#include "configuration.h" + +configuration_t configuration = +{ + .keys = { + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_X, + SDL_SCANCODE_Z, + SDL_SCANCODE_BACKSPACE, + SDL_SCANCODE_RETURN, + SDL_SCANCODE_SPACE + }, + .keys_2 = { + SDL_SCANCODE_TAB, + SDL_SCANCODE_LSHIFT, + }, + .joypad_configuration = { + 13, + 14, + 11, + 12, + 0, + 1, + 9, + 8, + 10, + 4, + -1, + 5, + // The rest are unmapped by default + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }, + .joypad_axises = { + 0, + 1, + }, + .color_correction_mode = GB_COLOR_CORRECTION_MODERN_BALANCED, + .highpass_mode = GB_HIGHPASS_ACCURATE, + .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, + .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, + .rewind_length = 60 * 2, + .model = MODEL_CGB, + .volume = 100, + .rumble_mode = GB_RUMBLE_ALL_GAMES, + .default_scale = 2, + .color_temperature = 10, + .cgb_revision = GB_MODEL_CGB_E - GB_MODEL_CGB_0, + .dmg_palette = 1, // Replacing the old default (0) as of 0.15.2 + .agb_revision = GB_MODEL_AGB_A, +}; diff --git a/thirdparty/SameBoy/SDL/configuration.h b/thirdparty/SameBoy/SDL/configuration.h new file mode 100644 index 000000000..c328e3c9c --- /dev/null +++ b/thirdparty/SameBoy/SDL/configuration.h @@ -0,0 +1,140 @@ +#ifndef configuration_h +#define configuration_h + +#include +#include +#include "shader.h" + +enum scaling_mode { + GB_SDL_SCALING_ENTIRE_WINDOW, + GB_SDL_SCALING_KEEP_RATIO, + GB_SDL_SCALING_INTEGER_FACTOR, + GB_SDL_SCALING_MAX, +}; + +typedef enum { + JOYPAD_BUTTON_RIGHT, + JOYPAD_BUTTON_LEFT, + JOYPAD_BUTTON_UP, + JOYPAD_BUTTON_DOWN, + JOYPAD_BUTTON_A, + JOYPAD_BUTTON_B, + JOYPAD_BUTTON_SELECT, + JOYPAD_BUTTON_START, + JOYPAD_BUTTON_MENU, + JOYPAD_BUTTON_TURBO, + JOYPAD_BUTTON_REWIND, + JOYPAD_BUTTON_SLOW_MOTION, + JOYPAD_BUTTON_HOTKEY_1, + JOYPAD_BUTTON_HOTKEY_2, + JOYPAD_BUTTONS_MAX +} joypad_button_t; + +typedef enum { + JOYPAD_AXISES_X, + JOYPAD_AXISES_Y, + JOYPAD_AXISES_MAX +} joypad_axis_t; + +typedef enum { + HOTKEY_NONE, + HOTKEY_PAUSE, + HOTKEY_MUTE, + HOTKEY_RESET, + HOTKEY_QUIT, + HOTKEY_SAVE_STATE_1, + HOTKEY_LOAD_STATE_1, + HOTKEY_SAVE_STATE_2, + HOTKEY_LOAD_STATE_2, + HOTKEY_SAVE_STATE_3, + HOTKEY_LOAD_STATE_3, + HOTKEY_SAVE_STATE_4, + HOTKEY_LOAD_STATE_4, + HOTKEY_SAVE_STATE_5, + HOTKEY_LOAD_STATE_5, + HOTKEY_SAVE_STATE_6, + HOTKEY_LOAD_STATE_6, + HOTKEY_SAVE_STATE_7, + HOTKEY_LOAD_STATE_7, + HOTKEY_SAVE_STATE_8, + HOTKEY_LOAD_STATE_8, + HOTKEY_SAVE_STATE_9, + HOTKEY_LOAD_STATE_9, + HOTKEY_SAVE_STATE_10, + HOTKEY_LOAD_STATE_10, + HOTKEY_MAX = HOTKEY_LOAD_STATE_10, +} hotkey_action_t; + +typedef struct { + SDL_Scancode keys[9]; + GB_color_correction_mode_t color_correction_mode; + enum scaling_mode scaling_mode; + uint8_t blending_mode; + + GB_highpass_mode_t highpass_mode; + + bool _deprecated_div_joystick; + bool _deprecated_flip_joystick_bit_1; + bool _deprecated_swap_joysticks_bits_1_and_2; + + char filter[32]; + enum { + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_SGB, + MODEL_MGB, + MODEL_MAX, + } model; + + /* v0.11 */ + uint32_t rewind_length; + SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */ + uint8_t joypad_configuration[32]; /* 14 Keys + padding for the future*/; + uint8_t joypad_axises[JOYPAD_AXISES_MAX]; + + /* v0.12 */ + enum { + SGB_NTSC, + SGB_PAL, + SGB_2, + SGB_MAX + } sgb_revision; + + /* v0.13 */ + uint8_t dmg_palette; + GB_border_mode_t border_mode; + uint8_t volume; + GB_rumble_mode_t rumble_mode; + + uint8_t default_scale; + + /* v0.14 */ + unsigned padding; + uint8_t color_temperature; + char bootrom_path[4096]; + uint8_t interference_volume; + GB_rtc_mode_t rtc_mode; + + /* v0.14.4 */ + bool osd; + + struct __attribute__((packed, aligned(4))) { + + /* v0.15 */ + bool allow_mouse_controls; + uint8_t cgb_revision; + /* v0.15.1 */ + char audio_driver[16]; + /* v0.15.2 */ + bool allow_background_controllers; + bool gui_pallete_enabled; // Change the GUI palette only once the user changed the DMG palette + char dmg_palette_name[25]; + hotkey_action_t hotkey_actions[2]; + uint16_t agb_revision; + }; +} configuration_t; + +extern configuration_t configuration; + +#endif diff --git a/thirdparty/SameBoy/SDL/console.c b/thirdparty/SameBoy/SDL/console.c new file mode 100644 index 000000000..295de96ca --- /dev/null +++ b/thirdparty/SameBoy/SDL/console.c @@ -0,0 +1,1020 @@ +#include "console.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ESC(x) "\x1B" x +#define CSI(x) ESC("[" x) +#define SGR(x) CSI(x "m") + +static bool initialized = false; +typedef struct listent_s listent_t; + +struct listent_s { + listent_t *prev; + listent_t *next; + char content[]; +}; + +typedef struct { + listent_t *first; + listent_t *last; +} fifo_t; + +static fifo_t lines; +static fifo_t history; + +static void remove_entry(fifo_t *fifo, listent_t *entry) +{ + if (fifo->last == entry) { + fifo->last = entry->prev; + } + if (fifo->first == entry) { + fifo->first = entry->next; + } + if (entry->next) { + entry->next->prev = entry->prev; + } + if (entry->prev) { + entry->prev->next = entry->next; + } + free(entry); +} + +static void add_entry(fifo_t *fifo, const char *content) +{ + size_t length = strlen(content); + listent_t *entry = malloc(sizeof(*entry) + length + 1); + entry->next = NULL; + entry->prev = fifo->last; + memcpy(entry->content, content, length); + entry->content[length] = 0; + if (fifo->last) { + fifo->last->next = entry; + } + fifo->last = entry; + if (!fifo->first) { + fifo->first = entry; + } +} + +static listent_t *reverse_find(listent_t *entry, const char *string, bool exact) +{ + while (entry) { + if (exact && strcmp(entry->content, string) == 0) { + return entry; + } + if (!exact && strstr(entry->content, string)) { + return entry; + } + entry = entry->prev; + } + return NULL; +} + +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 +} + +static unsigned width, height; + +static char raw_getc(void) +{ +#ifdef _WIN32 + char c; + unsigned long ret; + ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL); +#else + ssize_t ret; + char c; + + do { + ret = read(STDIN_FILENO, &c, 1); + } while (ret == -1 && errno == EINTR); +#endif + return ret == 1? c : EOF; +} + +#ifdef _WIN32 +#pragma clang diagnostic ignored "-Wmacro-redefined" +#include + +static void update_size(void) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + width = csbi.srWindow.Right - csbi.srWindow.Left + 1; + height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +} + +static unsigned long input_mode, output_mode; + +static void cleanup(void) +{ + printf(CSI("!p")); // reset + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode); + fflush(stdout); +} + +static bool initialize(void) +{ + if (!is_term()) return false; + update_size(); + if (width == 0 || height == 0) { + return false; + } + + static bool once = false; + if (!once) { + atexit(cleanup); + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode); + once = true; + } + 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); + + printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height); + + fflush(stdout); + initialized = true; + return true; +} +#else +#include + +static void update_size(void) +{ + struct winsize winsize; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize); + width = winsize.ws_col; + height = winsize.ws_row; +} + +static void terminal_resized(int ignored) +{ + update_size(); +} + +#include +static struct termios terminal; + + +static void cleanup(void) +{ + printf(CSI("!p")); // reset + tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal); + fflush(stdout); +} + +static bool initialize(void) +{ + if (!is_term()) return false; + update_size(); + if (width == 0 || height == 0) { + return false; + } + + static bool once = false; + if (!once) { + atexit(cleanup); + signal(SIGWINCH, terminal_resized); + tcgetattr(STDIN_FILENO, &terminal); +#ifdef _WIN32 + _setmode(STDIN_FILENO, _O_TEXT); +#endif + once = true; + } + struct termios raw_terminal; + raw_terminal = terminal; + raw_terminal.c_lflag = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal); + + printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height); + + fflush(stdout); + initialized = true; + return true; +} +#endif + +static struct { + char *content; + size_t allocation_size; + size_t length; + size_t position; + size_t scroll; + bool reverse_search; + listent_t *search_line; +} line; + +#define CTL(x) ((x) - 'A' + 1) + +static const char *prompt = ""; +static size_t prompt_length = 0; +static bool repeat_empty = false; + +static bool redraw_prompt(bool force) +{ + if (line.reverse_search) { + if (!force) return false; + if (line.length == 0) { + printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1); + return true; + } + if (!line.search_line) { + printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content); + return true; + } + const char *loc = strstr(line.search_line->content, line.content); + printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"), + prompt, + (int)(loc - line.search_line->content), + line.search_line->content, + line.content, + loc + line.length, + (unsigned)(loc - line.search_line->content + line.length + prompt_length + 1)); + return true; + } + + size_t max = width - 1 - prompt_length; + + if (line.scroll && line.length <= max) { + line.scroll = 0; + force = true; + } + + if (line.scroll > line.length - max) { + line.scroll = line.length - max; + force = true; + } + + if (line.position < line.scroll + 1 && line.position) { + line.scroll = line.position - 1; + force = true; + } + + if (line.position == 0 && line.scroll) { + line.scroll = 0; + force = true; + } + + if (line.position > line.scroll + max) { + line.scroll = line.position - max; + force = true; + } + + if (!force && line.length <= max) { + return false; + } + + if (line.length <= max) { + printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1)); + return true; + } + + size_t left = max; + const char *string = line.content + line.scroll; + printf("\r" CSI("K") "%s", prompt); + if (line.scroll) { + printf(SGR("2") "%c" SGR("0"), *string); + string++; + left--; + } + if (line.scroll + max == line.length) { + printf("%s", string); + } + else { + printf("%.*s", (int)(left - 1), string); + string += left; + left = 1; + printf(SGR("2") "%c" SGR("0"), *string); + } + printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1)); + + return true; +} + +static void set_position(size_t position) +{ + if (position > line.length) { + printf("\a"); + return; + } + line.position = position; + if (!redraw_prompt(false)) { + printf(CSI("%uG"), (unsigned)(position + prompt_length + 1)); + } +} + +static void set_line(const char *content) +{ + line.length = strlen(content); + if (line.length + 1 > line.allocation_size) { + line.content = realloc(line.content, line.length + 1); + line.allocation_size = line.length + 1; + } + else if (line.allocation_size > 256 && line.length < 128) { + line.content = realloc(line.content, line.length + 1); + line.allocation_size = line.length + 1; + } + line.position = line.length; + strcpy(line.content, content); + redraw_prompt(true); +} + +static void insert(const char *string) +{ + size_t insertion_length = strlen(string); + size_t new_length = insertion_length + line.length; + bool need_realloc = false; + while (line.allocation_size < new_length + 1) { + line.allocation_size *= 2; + need_realloc = true; + } + if (need_realloc) { + line.content = realloc(line.content, line.allocation_size); + } + memmove(line.content + line.position + insertion_length, + line.content + line.position, + line.length - line.position); + memcpy(line.content + line.position, string, insertion_length); + line.position += insertion_length; + line.content[new_length] = 0; + line.length = new_length; + if (!redraw_prompt(line.position != line.length)) { + printf("%s", string); + } +} + +static void delete(size_t size, bool forward) +{ + if (line.length < size) { + printf("\a"); + return; + } + if (forward) { + if (line.position > line.length - size) { + printf("\a"); + return; + } + else { + line.position += size; + } + } + else if (line.position < size) { + printf("\a"); + return; + } + memmove(line.content + line.position - size, + line.content + line.position, + line.length - line.position); + line.length -= size; + line.content[line.length] = 0; + line.position -= size; + + if (!redraw_prompt(line.position != line.length)) { + printf(CSI("%uG") CSI("K"), + (unsigned)(line.position + prompt_length + 1)); + } +} + +static void move_word(bool forward) +{ + signed offset = forward? 1 : -1; + size_t end = forward? line.length : 0; + signed check_offset = forward? 0 : -1; + if (line.position == end) { + printf("\a"); + return; + } + line.position += offset; + while (line.position != end && isalnum(line.content[line.position + check_offset])) { + line.position += offset; + } + if (!redraw_prompt(false)) { + printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1)); + } +} + +static void delete_word(bool forward) +{ + size_t original_pos = line.position; + signed offset = forward? 1 : -1; + size_t end = forward? line.length : 0; + signed check_offset = forward? 0 : -1; + if (line.position == end) { + printf("\a"); + return; + } + line.position += offset; + while (line.position != end && isalnum(line.content[line.position + check_offset])) { + line.position += offset; + } + if (forward) { + delete(line.position - original_pos, false); + } + else { + delete(original_pos - line.position, true); + } +} + +#define MOD_ALT(x) (0x100 | x) +#define MOD_SHIFT(x) (0x200 | x) +#define MOD_CTRL(x) (0x400 | x) +#define MOD_SPECIAL(x) (0x800 | x) + +static unsigned get_extended_key(void) +{ + unsigned modifiers = 0; + char c = 0; +restart: + c = raw_getc(); + if (c == 0x1B) { + modifiers = MOD_SHIFT(MOD_ALT(0)); + goto restart; + } + else if (c != '[' && c != 'O') { + return MOD_ALT(c); + } + unsigned ret = 0; + while (true) { + c = raw_getc(); + if (c >= '0' && c <= '9') { + ret = ret * 10 + c - '0'; + } + else if (c == ';') { + if (ret == 1) { + modifiers |= MOD_ALT(0); + } + else if (ret == 2) { + modifiers |= MOD_SHIFT(0); + } + else if (ret == 5) { + modifiers |= MOD_CTRL(0); + } + ret = 0; + } + else if (c == '~') { + return MOD_SPECIAL(ret) | modifiers; + } + else { + if (ret == 1) { + modifiers |= MOD_ALT(0); + } + else if (ret == 2) { + modifiers |= MOD_SHIFT(0); + } + else if (ret == 5) { + modifiers |= MOD_CTRL(0); + } + return c | modifiers; + } + } +} + +#define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0) +static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER; + +static char reverse_search_mainloop(void) +{ + while (true) { + char c = raw_getc(); + pthread_mutex_lock(&terminal_lock); + + switch (c) { + case CTL('C'): + line.search_line = NULL; + set_line(""); + pthread_mutex_unlock(&terminal_lock); + return CTL('A'); + case CTL('R'): + line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false); + if (!line.search_line) { + printf("\a"); + } + redraw_prompt(true); + break; + case CTL('W'): + delete_word(false); + redraw_prompt(true); + break; +#ifndef _WIN32 + case CTL('Z'): + set_line(""); + raise(SIGSTOP); + initialize(); // Reinitialize + redraw_prompt(true); + break; +#endif + case CTL('H'): + case 0x7F: // Backspace + delete(1, false); + redraw_prompt(true); + break; + default: + if (c >= ' ') { + char string[2] = {c, 0}; + insert(string); + line.search_line = reverse_find(line.search_line?: history.last, line.content, false); + if (!line.search_line) { + printf("\a"); + } + redraw_prompt(true); + } + else { + pthread_mutex_unlock(&terminal_lock); + return c; + } + break; + } + pthread_mutex_unlock(&terminal_lock); + fflush(stdout); + } + +} + + +static +#ifdef _WIN32 +int __stdcall +#else +void * +#endif +mainloop(char *(*completer)(const char *substring, uintptr_t *context)) +{ + listent_t *history_line = NULL; + uintptr_t complete_context = 0; + size_t completion_length = 0; + while (true) { + char c; + if (line.reverse_search) { + c = reverse_search_mainloop(); + line.reverse_search = false; + if (line.search_line) { + size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length; + set_line(line.search_line->content); + line.search_line = NULL; + set_position(pos); + } + else { + redraw_prompt(true); + } + } + else { + c = raw_getc(); + } + pthread_mutex_lock(&terminal_lock); + + switch (c) { + case CTL('A'): + set_position(0); + complete_context = completion_length = 0; + break; + case CTL('B'): + set_position(line.position - 1); + complete_context = completion_length = 0; + break; + case CTL('C'): + if (line.length) { + set_line(""); + history_line = NULL; + complete_context = completion_length = 0; + } + else { +#ifdef _WIN32 + raise(SIGINT); +#else + kill(getpid(), SIGINT); +#endif + } + break; + case CTL('D'): + if (line.length) { + delete(1, true); + complete_context = completion_length = 0; + } + else { + pthread_mutex_lock(&lines_lock); + add_entry(&lines, CON_EOF); + pthread_cond_signal(&lines_cond); + pthread_mutex_unlock(&lines_lock); + } + break; + case CTL('E'): + set_position(line.length); + complete_context = completion_length = 0; + break; + case CTL('F'): + set_position(line.position + 1); + complete_context = completion_length = 0; + break; + case CTL('K'): + printf(CSI("K")); + if (!redraw_prompt(false)) { + line.length = line.position; + line.content[line.length] = 0; + } + complete_context = completion_length = 0; + break; + case CTL('R'): + complete_context = completion_length = 0; + line.reverse_search = true; + set_line(""); + + break; + case CTL('T'): + if (line.length < 2) { + printf("\a"); + break; + } + if (line.position && line.position == line.length) { + line.position--; + } + if (line.position == 0) { + printf("\a"); + break; + } + SWAP(line.content + line.position, + line.content + line.position - 1); + line.position++; + redraw_prompt(true); + complete_context = completion_length = 0; + break; + case CTL('W'): + delete_word(false); + complete_context = completion_length = 0; + break; +#ifndef _WIN32 + case CTL('Z'): + set_line(""); + complete_context = completion_length = 0; + raise(SIGSTOP); + initialize(); // Reinitialize + break; +#endif + case '\r': + case '\n': + pthread_mutex_lock(&lines_lock); + if (line.length == 0 && repeat_empty && history.last) { + add_entry(&lines, history.last->content); + } + else { + add_entry(&lines, line.content); + } + pthread_cond_signal(&lines_cond); + pthread_mutex_unlock(&lines_lock); + if (line.length) { + listent_t *dup = reverse_find(history.last, line.content, true); + if (dup) { + remove_entry(&history, dup); + } + add_entry(&history, line.content); + set_line(""); + history_line = NULL; + } + complete_context = completion_length = 0; + break; + case CTL('H'): + case 0x7F: // Backspace + delete(1, false); + complete_context = completion_length = 0; + break; + case 0x1B: + switch (get_extended_key()) { + case MOD_SPECIAL(1): // Home + case MOD_SPECIAL(7): + case 'H': + set_position(0); + complete_context = completion_length = 0; + break; + case MOD_SPECIAL(8): // End + case 'F': + set_position(line.length); + complete_context = completion_length = 0; + break; + case MOD_SPECIAL(3): // Delete + delete(1, true); + complete_context = completion_length = 0; + break; + case 'A': // Up + if (!history_line) { + history_line = history.last; + } + else { + history_line = history_line->prev; + } + if (history_line) { + set_line(history_line->content); + complete_context = completion_length = 0; + } + else { + history_line = history.first; + printf("\a"); + } + + break; + case 'B': // Down + if (!history_line) { + printf("\a"); + break; + } + history_line = history_line->next; + if (history_line) { + set_line(history_line->content); + complete_context = completion_length = 0; + } + else { + set_line(""); + complete_context = completion_length = 0; + } + break; + case 'C': // Right + set_position(line.position + 1); + complete_context = completion_length = 0; + break; + case 'D': // Left + set_position(line.position - 1); + complete_context = completion_length = 0; + break; + case MOD_ALT('b'): + case MOD_ALT('D'): + move_word(false); + complete_context = completion_length = 0; + break; + case MOD_ALT('f'): + case MOD_ALT('C'): + move_word(true); + complete_context = completion_length = 0; + break; + case MOD_ALT(0x7F): // ALT+Backspace + delete_word(false); + complete_context = completion_length = 0; + break; + case MOD_ALT('('): // ALT+Delete + delete_word(true); + complete_context = completion_length = 0; + break; + default: + printf("\a"); + break; + } + break; + case '\t': { + char temp = line.content[line.position - completion_length]; + line.content[line.position - completion_length] = 0; + char *completion = completer? completer(line.content, &complete_context) : NULL; + line.content[line.position - completion_length] = temp; + if (completion) { + if (completion_length) { + delete(completion_length, false); + } + insert(completion); + completion_length = strlen(completion); + free(completion); + } + else { + printf("\a"); + } + break; + } + default: + if (c >= ' ') { + char string[2] = {c, 0}; + insert(string); + complete_context = completion_length = 0; + } + else { + printf("\a"); + } + break; + } + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + } + return 0; +} + +char *CON_readline(const char *new_prompt) +{ + pthread_mutex_lock(&terminal_lock); + const char *old_prompt = prompt; + prompt = new_prompt; + prompt_length = strlen(prompt); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + + pthread_mutex_lock(&lines_lock); + while (!lines.first) { + pthread_cond_wait(&lines_cond, &lines_lock); + } + char *ret = strdup(lines.first->content); + remove_entry(&lines, lines.first); + pthread_mutex_unlock(&lines_lock); + + pthread_mutex_lock(&terminal_lock); + prompt = old_prompt; + prompt_length = strlen(prompt); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); + return ret; +} + +char *CON_readline_async(void) +{ + char *ret = NULL; + pthread_mutex_lock(&lines_lock); + if (lines.first) { + ret = strdup(lines.first->content); + remove_entry(&lines, lines.first); + } + pthread_mutex_unlock(&lines_lock); + return ret; +} + +bool CON_start(char *(*completer)(const char *substring, uintptr_t *context)) +{ + if (!initialize()) { + return false; + } + set_line(""); + pthread_t thread; + return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0; +} + +void CON_attributed_print(const char *string, CON_attributes_t *attributes) +{ + if (!initialized) { + printf("%s", string); + return; + } + static bool pending_newline = false; + pthread_mutex_lock(&terminal_lock); + printf(ESC("8")); + bool needs_reset = false; + if (attributes) { + if (attributes->color) { + if (attributes->color >= 0x10) { + printf(SGR("%d"), attributes->color - 0x11 + 90); + } + else { + printf(SGR("%d"), attributes->color - 1 + 30); + } + needs_reset = true; + } + if (attributes->background) { + if (attributes->background >= 0x10) { + printf(SGR("%d"), attributes->background - 0x11 + 100); + } + else { + printf(SGR("%d"), attributes->background - 1 + 40); + } + needs_reset = true; + } + if (attributes->bold) { + printf(SGR("1")); + needs_reset = true; + } + if (attributes->italic) { + printf(SGR("3")); + needs_reset = true; + } + if (attributes->underline) { + printf(SGR("4")); + needs_reset = true; + } + } + const char *it = string; + bool need_redraw_prompt = false; + while (*it) { + if (pending_newline) { + need_redraw_prompt = true; + printf("\n" CSI("K") "\n" CSI("A")); + pending_newline = false; + continue; + } + if (*it == '\n') { + printf("%.*s", (int)(it - string), string); + string = it + 1; + pending_newline = true; + } + it++; + } + if (*string) { + printf("%s", string); + } + if (needs_reset) { + printf(SGR("0")); + } + printf(ESC("7") CSI("B")); + if (need_redraw_prompt) { + redraw_prompt(true); + } + else { + set_position(line.position); + } + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); +} + +void CON_print(const char *string) +{ + CON_attributed_print(string, NULL); +} + +void CON_vprintf(const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + CON_attributed_print(string, NULL); + free(string); +} + +void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + CON_attributed_print(string, attributes); + free(string); +} + +void CON_printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + CON_vprintf(fmt, args); + va_end(args); +} + + +void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) +{ + va_list args; + va_start(args, attributes); + CON_attributed_vprintf(fmt, attributes, args); + va_end(args); +} + +void CON_set_async_prompt(const char *string) +{ + pthread_mutex_lock(&terminal_lock); + prompt = string; + prompt_length = strlen(string); + redraw_prompt(true); + fflush(stdout); + pthread_mutex_unlock(&terminal_lock); +} + +void CON_set_repeat_empty(bool repeat) +{ + repeat_empty = repeat; +} diff --git a/thirdparty/SameBoy/SDL/console.h b/thirdparty/SameBoy/SDL/console.h new file mode 100644 index 000000000..d15898888 --- /dev/null +++ b/thirdparty/SameBoy/SDL/console.h @@ -0,0 +1,49 @@ +#include +#include +#include + +#define CON_EOF "\x04" +bool CON_start(char *(*completer)(const char *substring, uintptr_t *context)); +char *CON_readline(const char *prompt); +char *CON_readline_async(void); + +typedef struct { + enum { + CON_COLOR_NONE = 0, + CON_COLOR_BLACK, + CON_COLOR_RED, + CON_COLOR_GREEN, + CON_COLOR_YELLOW, + CON_COLOR_BLUE, + CON_COLOR_MAGENTA, + CON_COLOR_CYAN, + CON_COLOR_LIGHT_GREY, + + CON_COLOR_DARK_GREY = CON_COLOR_BLACK + 0x10, + CON_COLOR_BRIGHT_RED = CON_COLOR_RED + 0x10, + CON_COLOR_BRIGHT_GREEN = CON_COLOR_GREEN + 0x10, + CON_COLOR_BRIGHT_YELLOW = CON_COLOR_YELLOW + 0x10, + CON_COLOR_BRIGHT_BLUE = CON_COLOR_BLUE + 0x10, + CON_COLOR_BRIGHT_MAGENTA = CON_COLOR_MAGENTA + 0x10, + CON_COLOR_BRIGHT_CYAN = CON_COLOR_CYAN + 0x10, + CON_COLOR_WHITE = CON_COLOR_LIGHT_GREY + 0x10, + } color:8, background:8; + bool bold; + bool italic; + bool underline; +} CON_attributes_t; + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void CON_print(const char *string); +void CON_attributed_print(const char *string, CON_attributes_t *attributes); +void CON_vprintf(const char *fmt, va_list args); +void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args); +void CON_printf(const char *fmt, ...) __printflike(1, 2); +void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) __printflike(1, 3); +void CON_set_async_prompt(const char *string); +void CON_set_repeat_empty(bool repeat); diff --git a/thirdparty/SameBoy/SDL/font.c b/thirdparty/SameBoy/SDL/font.c index ea2c590cb..6cc09a230 100644 --- a/thirdparty/SameBoy/SDL/font.c +++ b/thirdparty/SameBoy/SDL/font.c @@ -1119,6 +1119,36 @@ uint8_t font[] = { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + + /* Copyright symbol*/ + _, 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, _, + + /* Alt symbol */ + + _, _, 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, _, + _, _, _, _, _, _, }; const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/thirdparty/SameBoy/SDL/font.h b/thirdparty/SameBoy/SDL/font.h index f2111c3fe..ce6044fee 100644 --- a/thirdparty/SameBoy/SDL/font.h +++ b/thirdparty/SameBoy/SDL/font.h @@ -7,14 +7,16 @@ extern const uint8_t font_max; #define GLYPH_HEIGHT 8 #define GLYPH_WIDTH 6 #define LEFT_ARROW_STRING "\x86" -#define RIGHT_ARROW_STRING "\x7f" +#define RIGHT_ARROW_STRING "\x7F" #define SELECTION_STRING RIGHT_ARROW_STRING #define CTRL_STRING "\x80\x81\x82" #define SHIFT_STRING "\x83" +#define ALT_STRING "\x91\x92" #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" +#define COPYRIGHT_STRING "\x90" #endif /* font_h */ diff --git a/thirdparty/SameBoy/SDL/gui.c b/thirdparty/SameBoy/SDL/gui.c index b8e69cddd..22fa5debd 100644 --- a/thirdparty/SameBoy/SDL/gui.c +++ b/thirdparty/SameBoy/SDL/gui.c @@ -5,9 +5,11 @@ #include #include #include +#include #include "utils.h" #include "gui.h" #include "font.h" +#include "audio/audio.h" static const SDL_Color gui_palette[4] = {{8, 24, 16,}, {57, 97, 57,}, {132, 165, 99}, {198, 222, 140}}; static uint32_t gui_palette_native[4]; @@ -20,6 +22,10 @@ enum pending_command pending_command; unsigned command_parameter; char *dropped_state_file = NULL; +static char **custom_palettes; +static unsigned n_custom_palettes; + + #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING #else @@ -30,6 +36,8 @@ shader_t shader; static SDL_Rect rect; static unsigned factor; +static SDL_Surface *converted_background = NULL; + void render_texture(void *pixels, void *previous) { if (renderer) { @@ -67,57 +75,7 @@ void render_texture(void *pixels, void *previous) } } -configuration_t configuration = -{ - .keys = { - SDL_SCANCODE_RIGHT, - SDL_SCANCODE_LEFT, - SDL_SCANCODE_UP, - SDL_SCANCODE_DOWN, - SDL_SCANCODE_X, - SDL_SCANCODE_Z, - SDL_SCANCODE_BACKSPACE, - SDL_SCANCODE_RETURN, - SDL_SCANCODE_SPACE - }, - .keys_2 = { - SDL_SCANCODE_TAB, - SDL_SCANCODE_LSHIFT, - }, - .joypad_configuration = { - 13, - 14, - 11, - 12, - 0, - 1, - 9, - 8, - 10, - 4, - -1, - 5, - }, - .joypad_axises = { - 0, - 1, - }, - .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, - .highpass_mode = GB_HIGHPASS_ACCURATE, - .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, - .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, - .rewind_length = 60 * 2, - .model = MODEL_CGB, - .volume = 100, - .rumble_mode = GB_RUMBLE_ALL_GAMES, - .default_scale = 2, - .color_temperature = 10, -}; - - static const char *help[] = { -"Drop a ROM to play.\n" -"\n" "Keyboard Shortcuts:\n" " Open Menu: Escape\n" " Open ROM: " MODIFIER_NAME "+O\n" @@ -131,7 +89,16 @@ static const char *help[] = { #else " Mute/Unmute: " MODIFIER_NAME "+M\n" #endif -" Break Debugger: " CTRL_STRING "+C" +" Toggle channel: " ALT_STRING "+(1-4)\n" +" Break Debugger: " CTRL_STRING "+C", +"\n" +"SameBoy\n" +"Version " GB_VERSION "\n\n" +"Copyright " COPYRIGHT_STRING " 2015-" GB_COPYRIGHT_YEAR "\n" +"Lior Halphon\n\n" +"Licensed under the MIT\n" +"license, see LICENSE for\n" +"more details." }; void update_viewport(void) @@ -198,11 +165,13 @@ 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, signed 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, bool is_osd) { - y -= scroll; + if (!is_osd) { + y -= scroll; + } unsigned orig_x = x; - unsigned y_offset = (GB_get_screen_height(&gb) - 144) / 2; + unsigned y_offset = is_osd? 0 : (GB_get_screen_height(&gb) - 144) / 2; while (*string) { if (*string == '\n') { x = orig_x; @@ -215,21 +184,39 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned heig break; } - draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (y_offset + 144)]); + draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (is_osd? GB_get_screen_height(&gb) : y_offset + 144)]); x += GLYPH_WIDTH; string++; } } -static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border) +void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border, bool is_osd) { - draw_unbordered_text(buffer, width, height, x - 1, y, string, border); - draw_unbordered_text(buffer, width, height, x + 1, y, string, border); - draw_unbordered_text(buffer, width, height, x, y - 1, string, border); - draw_unbordered_text(buffer, width, height, x, y + 1, string, border); - draw_unbordered_text(buffer, width, height, x, y, string, color); + draw_unbordered_text(buffer, width, height, x - 1, y, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x + 1, y, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y - 1, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y + 1, string, border, is_osd); + draw_unbordered_text(buffer, width, height, x, y, string, color, is_osd); } +const char *osd_text = NULL; +unsigned osd_countdown = 0; +unsigned osd_text_lines = 1; + +void show_osd_text(const char *text) +{ + osd_text_lines = 1; + osd_text = text; + osd_countdown = 30; + while (*text++) { + if (*text == '\n') { + osd_text_lines++; + osd_countdown += 30; + } + } +} + + enum decoration { DECORATION_NONE, DECORATION_SELECTION, @@ -239,14 +226,14 @@ enum decoration { static void draw_text_centered(uint32_t *buffer, unsigned width, unsigned height, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration) { unsigned x = width / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2; - draw_text(buffer, width, height, x, y, string, color, border); + draw_text(buffer, width, height, x, y, string, color, border, false); switch (decoration) { case DECORATION_SELECTION: - draw_text(buffer, width, height, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border); + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border, false); break; case DECORATION_ARROWS: - draw_text(buffer, width, height, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border); - draw_text(buffer, width, height, width - x, y, RIGHT_ARROW_STRING, color, border); + draw_text(buffer, width, height, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border, false); + draw_text(buffer, width, height, width - x, y, RIGHT_ARROW_STRING, color, border, false); break; case DECORATION_NONE: @@ -291,11 +278,21 @@ static void item_help(unsigned index) gui_state = SHOWING_HELP; } +static void about(unsigned index) +{ + current_help_page = 1; + gui_state = SHOWING_HELP; +} + static void enter_emulation_menu(unsigned index); static void enter_graphics_menu(unsigned index); -static void enter_controls_menu(unsigned index); +static void enter_keyboard_menu(unsigned index); static void enter_joypad_menu(unsigned index); static void enter_audio_menu(unsigned index); +static void enter_controls_menu(unsigned index); +static void enter_help_menu(unsigned index); +static void enter_options_menu(unsigned index); +static void toggle_audio_recording(unsigned index); extern void set_filename(const char *new_filename, typeof(free) *new_free_function); static void open_rom(unsigned index) @@ -307,6 +304,15 @@ static void open_rom(unsigned index) } } +static void cart_swap(unsigned index) +{ + char *filename = do_open_rom_dialog(); + if (filename) { + set_filename(filename, free); + pending_command = GB_SDL_CART_SWAP_COMMAND; + } +} + static void recalculate_menu_height(void) { menu_height = 24; @@ -324,29 +330,108 @@ static void recalculate_menu_height(void) } } -static const struct menu_item paused_menu[] = { - {"Resume", NULL}, - {"Open ROM", open_rom}, +#if SDL_COMPILEDVERSION < 2014 +int SDL_OpenURL(const char *url) +{ + char *string = NULL; +#ifdef __APPLE__ + asprintf(&string, "open '%s'", url); +#else +#ifdef _WIN32 + asprintf(&string, "explorer '%s'", url); +#else + asprintf(&string, "xdg-open '%s'", url); +#endif +#endif + int ret = system(string); + free(string); + return ret; +} +#endif + +static char audio_recording_menu_item[] = "Start Audio Recording"; + +static void sponsor(unsigned index) +{ + SDL_OpenURL("https://github.com/sponsors/LIJI32"); +} + +static void debugger_help(unsigned index) +{ + SDL_OpenURL("https://sameboy.github.io/debugger/"); +} + +static void return_to_root_menu(unsigned index) +{ + current_menu = root_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static const struct menu_item options_menu[] = { {"Emulation Options", enter_emulation_menu}, {"Graphic Options", enter_graphics_menu}, {"Audio Options", enter_audio_menu}, - {"Keyboard", enter_controls_menu}, - {"Joypad", enter_joypad_menu}, - {"Help", item_help}, + {"Control Options", enter_controls_menu}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_options_menu(unsigned index) +{ + current_menu = options_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static const struct menu_item paused_menu[] = { + {"Resume", NULL}, + {"Open ROM", open_rom}, + {"Hot Swap Cartridge", cart_swap}, + {"Options", enter_options_menu}, + {audio_recording_menu_item, toggle_audio_recording}, + {"Help & About", enter_help_menu}, + {"Sponsor SameBoy", sponsor}, {"Quit SameBoy", item_exit}, {NULL,} }; -static const struct menu_item *const nonpaused_menu = &paused_menu[1]; +static struct menu_item nonpaused_menu[sizeof(paused_menu) / sizeof(paused_menu[0]) - 2]; -static void return_to_root_menu(unsigned index) +static void __attribute__((constructor)) build_nonpaused_menu(void) { - current_menu = root_menu; + const struct menu_item *in = paused_menu; + struct menu_item *out = nonpaused_menu; + while (in->string) { + if (in->handler == NULL || in->handler == cart_swap) { + in++; + continue; + } + *out = *in; + out++; + in++; + } +} + +static const struct menu_item help_menu[] = { + {"Shortcuts", item_help}, + {"Debugger Help", debugger_help}, + {"About SameBoy", about}, + {"Back", return_to_root_menu}, + {NULL,} +}; + +static void enter_help_menu(unsigned index) +{ + current_menu = help_menu; current_selection = 0; scroll = 0; recalculate_menu_height(); } + static void cycle_model(unsigned index) { @@ -366,12 +451,48 @@ static void cycle_model_backwards(unsigned index) pending_command = GB_SDL_RESET_COMMAND; } -const char *current_model_string(unsigned index) +static 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]; } +static void cycle_cgb_revision(unsigned index) +{ + + if (configuration.cgb_revision == GB_MODEL_CGB_E - GB_MODEL_CGB_0) { + configuration.cgb_revision = 0; + } + else { + configuration.cgb_revision++; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static void cycle_cgb_revision_backwards(unsigned index) +{ + if (configuration.cgb_revision == 0) { + configuration.cgb_revision = GB_MODEL_CGB_E - GB_MODEL_CGB_0; + } + else { + configuration.cgb_revision--; + } + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_cgb_revision_string(unsigned index) +{ + return (const char *[]){ + "CPU CGB 0 (Exp.)", + "CPU CGB A (Exp.)", + "CPU CGB B (Exp.)", + "CPU CGB C (Exp.)", + "CPU CGB D", + "CPU CGB E", + } + [configuration.cgb_revision]; +} + static void cycle_sgb_revision(unsigned index) { @@ -391,7 +512,7 @@ static void cycle_sgb_revision_backwards(unsigned index) pending_command = GB_SDL_RESET_COMMAND; } -const char *current_sgb_revision_string(unsigned index) +static const char *current_sgb_revision_string(unsigned index) { return (const char *[]){"Super Game Boy NTSC", "Super Game Boy PAL", "Super Game Boy 2"} [configuration.sgb_revision]; @@ -433,7 +554,7 @@ static void cycle_rewind_backwards(unsigned index) GB_set_rewind_length(&gb, configuration.rewind_length); } -const char *current_rewind_string(unsigned index) +static const char *current_rewind_string(unsigned index) { for (unsigned i = 0; i < sizeof(rewind_lengths) / sizeof(rewind_lengths[0]); i++) { if (configuration.rewind_length == rewind_lengths[i]) { @@ -443,7 +564,7 @@ const char *current_rewind_string(unsigned index) return "Custom"; } -const char *current_bootrom_string(unsigned index) +static const char *current_bootrom_string(unsigned index) { if (!configuration.bootrom_path[0]) { return "Built-in Boot ROMs"; @@ -488,7 +609,7 @@ static void toggle_rtc_mode(unsigned index) configuration.rtc_mode = !configuration.rtc_mode; } -const char *current_rtc_mode_string(unsigned index) +static const char *current_rtc_mode_string(unsigned index) { switch (configuration.rtc_mode) { case GB_RTC_MODE_SYNC_TO_HOST: return "Sync to System Clock"; @@ -497,13 +618,30 @@ const char *current_rtc_mode_string(unsigned index) return ""; } +static void cycle_agb_revision(unsigned index) +{ + + configuration.agb_revision ^= GB_MODEL_GBP_BIT; + pending_command = GB_SDL_RESET_COMMAND; +} + +static const char *current_agb_revision_string(unsigned index) +{ + if (configuration.agb_revision == GB_MODEL_GBP_A) { + return "CPU AGB A (GBP)"; + } + return "CPU AGB A (AGB)"; +} + 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}, + {"GBC Revision:", cycle_cgb_revision, current_cgb_revision_string, cycle_cgb_revision_backwards}, + {"GBA Revision:", cycle_agb_revision, current_agb_revision_string, cycle_agb_revision}, {"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}, + {"Back", enter_options_menu}, {NULL,} }; @@ -515,13 +653,13 @@ static void enter_emulation_menu(unsigned index) recalculate_menu_height(); } -const char *current_scaling_mode(unsigned index) +static const char *current_scaling_mode(unsigned index) { return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"} [configuration.scaling_mode]; } -const char *current_default_scale(unsigned index) +static const char *current_default_scale(unsigned index) { return (const char *[]){"1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x"} [configuration.default_scale - 1]; @@ -529,7 +667,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", "Modern - Balanced", "Modern - Boost Contrast", "Reduce Contrast", "Harsh Reality", "Modern - Accurate"} [configuration.color_correction_mode]; } @@ -544,6 +682,9 @@ const char *current_color_temperature(unsigned index) const char *current_palette(unsigned index) { + if (configuration.dmg_palette == 4) { + return configuration.dmg_palette_name; + } return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"} [configuration.dmg_palette]; } @@ -554,7 +695,7 @@ const char *current_border_mode(unsigned index) [configuration.border_mode]; } -void cycle_scaling(unsigned index) +static void cycle_scaling(unsigned index) { configuration.scaling_mode++; if (configuration.scaling_mode == GB_SDL_SCALING_MAX) { @@ -564,7 +705,7 @@ void cycle_scaling(unsigned index) render_texture(NULL, NULL); } -void cycle_scaling_backwards(unsigned index) +static void cycle_scaling_backwards(unsigned index) { if (configuration.scaling_mode == 0) { configuration.scaling_mode = GB_SDL_SCALING_MAX - 1; @@ -576,7 +717,7 @@ void cycle_scaling_backwards(unsigned index) render_texture(NULL, NULL); } -void cycle_default_scale(unsigned index) +static void cycle_default_scale(unsigned index) { if (configuration.default_scale == GB_SDL_DEFAULT_SCALE_MAX) { configuration.default_scale = 1; @@ -589,7 +730,7 @@ void cycle_default_scale(unsigned index) update_viewport(); } -void cycle_default_scale_backwards(unsigned index) +static void cycle_default_scale_backwards(unsigned index) { if (configuration.default_scale == 1) { configuration.default_scale = GB_SDL_DEFAULT_SCALE_MAX; @@ -604,9 +745,15 @@ 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 if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_BALANCED) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_ACCURATE; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_ACCURATE) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST; + } else { configuration.color_correction_mode++; } @@ -615,7 +762,13 @@ 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 if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_ACCURATE) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_BALANCED; + } + else if (configuration.color_correction_mode == GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST) { + configuration.color_correction_mode = GB_COLOR_CORRECTION_MODERN_ACCURATE; } else { configuration.color_correction_mode--; @@ -636,24 +789,125 @@ static void increase_color_temperature(unsigned index) } } +const GB_palette_t *current_dmg_palette(void) +{ + typedef struct __attribute__ ((packed)) { + uint32_t magic; + uint8_t flags; + struct GB_color_s colors[5]; + int32_t brightness_bias; + uint32_t hue_bias; + uint32_t hue_bias_strength; + } theme_t; + + static theme_t theme; + + if (configuration.dmg_palette == 4) { + char *path = resource_path("Palettes"); + sprintf(path + strlen(path), "/%s.sbp", configuration.dmg_palette_name); + FILE *file = fopen(path, "rb"); + if (!file) return &GB_PALETTE_GREY; + memset(&theme, 0, sizeof(theme)); + fread(&theme, sizeof(theme), 1, file); + fclose(file); +#ifdef GB_BIG_ENDIAN + theme.magic = __builtin_bswap32(theme.magic); +#endif + if (theme.magic != 'SBPL') return &GB_PALETTE_GREY; + return (GB_palette_t *)&theme.colors; + } + + switch (configuration.dmg_palette) { + case 1: return &GB_PALETTE_DMG; + case 2: return &GB_PALETTE_MGB; + case 3: return &GB_PALETTE_GBL; + default: return &GB_PALETTE_GREY; + } +} + +static void update_gui_palette(void) +{ + const GB_palette_t *palette = current_dmg_palette(); + + SDL_Color colors[4]; + for (unsigned i = 4; i--; ) { + gui_palette_native[i] = SDL_MapRGB(pixel_format, palette->colors[i].r, palette->colors[i].g, palette->colors[i].b); + colors[i].r = palette->colors[i].r; + colors[i].g = palette->colors[i].g; + colors[i].b = palette->colors[i].b; + } + + SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); + + /* Create a blank background if background.bmp could not be loaded */ + if (!background) { + background = SDL_CreateRGBSurface(0, 160, 144, 8, 0, 0, 0, 0); + } + SDL_SetPaletteColors(background->format->palette, colors, 0, 4); + converted_background = SDL_ConvertSurface(background, pixel_format, 0); + SDL_FreeSurface(background); +} + static void cycle_palette(unsigned index) { if (configuration.dmg_palette == 3) { - configuration.dmg_palette = 0; + if (n_custom_palettes == 0) { + configuration.dmg_palette = 0; + } + else { + configuration.dmg_palette = 4; + strcpy(configuration.dmg_palette_name, custom_palettes[0]); + } + } + else if (configuration.dmg_palette == 4) { + for (unsigned i = 0; i < n_custom_palettes; i++) { + if (strcmp(custom_palettes[i], configuration.dmg_palette_name) == 0) { + if (i == n_custom_palettes - 1) { + configuration.dmg_palette = 0; + } + else { + strcpy(configuration.dmg_palette_name, custom_palettes[i + 1]); + } + break; + } + } } else { configuration.dmg_palette++; } + configuration.gui_pallete_enabled = true; + update_gui_palette(); } static void cycle_palette_backwards(unsigned index) { if (configuration.dmg_palette == 0) { - configuration.dmg_palette = 3; + if (n_custom_palettes == 0) { + configuration.dmg_palette = 3; + } + else { + configuration.dmg_palette = 4; + strcpy(configuration.dmg_palette_name, custom_palettes[n_custom_palettes - 1]); + } + } + else if (configuration.dmg_palette == 4) { + for (unsigned i = 0; i < n_custom_palettes; i++) { + if (strcmp(custom_palettes[i], configuration.dmg_palette_name) == 0) { + if (i == 0) { + configuration.dmg_palette = 3; + } + else { + strcpy(configuration.dmg_palette_name, custom_palettes[i - 1]); + } + break; + } + } } else { configuration.dmg_palette--; } + configuration.gui_pallete_enabled = true; + update_gui_palette(); } static void cycle_border_mode(unsigned index) @@ -676,6 +930,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 +954,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 +977,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) { @@ -740,8 +997,9 @@ static void cycle_filter_backwards(unsigned index) } } -const char *current_filter_name(unsigned index) +static 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 +1016,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 +1027,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; } @@ -776,12 +1036,24 @@ static void cycle_blending_mode_backwards(unsigned index) } } -const char *blending_mode_string(unsigned index) +static const char *blending_mode_string(unsigned index) { + if (!uses_gl()) return "Requires OpenGL 3.2+"; return (const char *[]){"Disabled", "Simple", "Accurate"} [configuration.blending_mode]; } +static void toggle_osd(unsigned index) +{ + osd_countdown = 0; + configuration.osd = !configuration.osd; +} + +static const char *current_osd_mode(unsigned index) +{ + return configuration.osd? "Enabled" : "Disabled"; +} + static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, {"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards}, @@ -791,7 +1063,8 @@ static const struct menu_item graphics_menu[] = { {"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}, - {"Back", return_to_root_menu}, + {"On-Screen Display:", toggle_osd, current_osd_mode, toggle_osd}, + {"Back", enter_options_menu}, {NULL,} }; @@ -803,13 +1076,13 @@ static void enter_graphics_menu(unsigned index) recalculate_menu_height(); } -const char *highpass_filter_string(unsigned index) +static const char *highpass_filter_string(unsigned index) { return (const char *[]){"None (Keep DC Offset)", "Accurate", "Preserve Waveform"} [configuration.highpass_mode]; } -void cycle_highpass_filter(unsigned index) +static void cycle_highpass_filter(unsigned index) { configuration.highpass_mode++; if (configuration.highpass_mode == GB_HIGHPASS_MAX) { @@ -817,7 +1090,7 @@ void cycle_highpass_filter(unsigned index) } } -void cycle_highpass_filter_backwards(unsigned index) +static void cycle_highpass_filter_backwards(unsigned index) { if (configuration.highpass_mode == 0) { configuration.highpass_mode = GB_HIGHPASS_MAX - 1; @@ -827,14 +1100,14 @@ void cycle_highpass_filter_backwards(unsigned index) } } -const char *volume_string(unsigned index) +static const char *volume_string(unsigned index) { static char ret[5]; sprintf(ret, "%d%%", configuration.volume); return ret; } -void increase_volume(unsigned index) +static void increase_volume(unsigned index) { configuration.volume += 5; if (configuration.volume > 100) { @@ -842,7 +1115,7 @@ void increase_volume(unsigned index) } } -void decrease_volume(unsigned index) +static void decrease_volume(unsigned index) { configuration.volume -= 5; if (configuration.volume > 100) { @@ -850,14 +1123,14 @@ void decrease_volume(unsigned index) } } -const char *interference_volume_string(unsigned index) +static 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) +static void increase_interference_volume(unsigned index) { configuration.interference_volume += 5; if (configuration.interference_volume > 100) { @@ -865,7 +1138,7 @@ void increase_interference_volume(unsigned index) } } -void decrease_interference_volume(unsigned index) +static void decrease_interference_volume(unsigned index) { configuration.interference_volume -= 5; if (configuration.interference_volume > 100) { @@ -873,14 +1146,91 @@ void decrease_interference_volume(unsigned index) } } -static const struct menu_item audio_menu[] = { +static const char *audio_driver_string(unsigned index) +{ + return GB_audio_driver_name(); +} + +static const char *preferred_audio_driver_string(unsigned index) +{ + if (configuration.audio_driver[0] == 0) { + return "Auto"; + } + return configuration.audio_driver; +} + +static void audio_driver_changed(void); + +static void cycle_prefrered_audio_driver(unsigned index) +{ + audio_driver_changed(); + if (configuration.audio_driver[0] == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(0)); + return; + } + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { // Not a supported driver? Switch to auto + configuration.audio_driver[0] = 0; + return; + } + if (strcmp(configuration.audio_driver, name) == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i + 1)); + return; + } + i++; + } +} + +static void cycle_preferred_audio_driver_backwards(unsigned index) +{ + audio_driver_changed(); + if (configuration.audio_driver[0] == 0) { + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i - 1)); + return; + } + i++; + } + return; + } + unsigned i = 0; + while (true) { + const char *name = GB_audio_driver_name_at_index(i); + if (name[0] == 0) { // Not a supported driver? Switch to auto + configuration.audio_driver[0] = 0; + return; + } + if (strcmp(configuration.audio_driver, name) == 0) { + strcpy(configuration.audio_driver, GB_audio_driver_name_at_index(i - 1)); + return; + } + i++; + } +} + +static void nop(unsigned index){} + +static 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}, + {"Preferred Audio Driver:", cycle_prefrered_audio_driver, preferred_audio_driver_string, cycle_preferred_audio_driver_backwards}, + {"Active Driver:", nop, audio_driver_string}, + {"Back", enter_options_menu}, {NULL,} }; +static void audio_driver_changed(void) +{ + audio_menu[4].value_getter = NULL; + audio_menu[4].string = "Relaunch to apply"; +} + static void enter_audio_menu(unsigned index) { current_menu = audio_menu; @@ -896,7 +1246,7 @@ static void modify_key(unsigned index) static const char *key_name(unsigned index); -static const struct menu_item controls_menu[] = { +static const struct menu_item keyboard_menu[] = { {"Right:", modify_key, key_name,}, {"Left:", modify_key, key_name,}, {"Up:", modify_key, key_name,}, @@ -908,24 +1258,21 @@ static const struct menu_item controls_menu[] = { {"Turbo:", modify_key, key_name,}, {"Rewind:", modify_key, key_name,}, {"Slow-Motion:", modify_key, key_name,}, - {"Back", return_to_root_menu}, + {"Back", enter_controls_menu}, {NULL,} }; 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]); } -static void enter_controls_menu(unsigned index) +static void enter_keyboard_menu(unsigned index) { - current_menu = controls_menu; + current_menu = keyboard_menu; current_selection = 0; scroll = 0; recalculate_menu_height(); @@ -936,7 +1283,7 @@ static SDL_Joystick *joystick = NULL; static SDL_GameController *controller = NULL; SDL_Haptic *haptic = NULL; -const char *current_joypad_name(unsigned index) +static const char *current_joypad_name(unsigned index) { static char name[23] = {0,}; const char *orig_name = joystick? SDL_JoystickName(joystick) : NULL; @@ -1045,17 +1392,85 @@ static void cycle_rumble_mode_backwards(unsigned index) } } -const char *current_rumble_mode(unsigned index) +static const char *current_rumble_mode(unsigned index) { return (const char *[]){"Disabled", "Rumble Game Paks Only", "All Games"} [configuration.rumble_mode]; } +static void toggle_allow_background_controllers(unsigned index) +{ + configuration.allow_background_controllers ^= true; + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + configuration.allow_background_controllers? "1" : "0"); +} + +static const char *current_background_control_mode(unsigned index) +{ + return configuration.allow_background_controllers? "Always" : "During Window Focus Only"; +} + +static void cycle_hotkey(unsigned index) +{ + if (configuration.hotkey_actions[index - 2] == HOTKEY_MAX) { + configuration.hotkey_actions[index - 2] = 0; + } + else { + configuration.hotkey_actions[index - 2]++; + } +} + +static void cycle_hotkey_backwards(unsigned index) +{ + if (configuration.hotkey_actions[index - 2] == 0) { + configuration.hotkey_actions[index - 2] = HOTKEY_MAX; + } + else { + configuration.hotkey_actions[index - 2]--; + } +} + +static const char *current_hotkey(unsigned index) +{ + return (const char *[]){ + "None", + "Toggle Pause", + "Toggle Mute", + "Reset", + "Quit SameBoy", + "Save State Slot 1", + "Load State Slot 1", + "Save State Slot 2", + "Load State Slot 2", + "Save State Slot 3", + "Load State Slot 3", + "Save State Slot 4", + "Load State Slot 4", + "Save State Slot 5", + "Load State Slot 5", + "Save State Slot 6", + "Load State Slot 6", + "Save State Slot 7", + "Load State Slot 7", + "Save State Slot 8", + "Load State Slot 8", + "Save State Slot 9", + "Load State Slot 9", + "Save State Slot 10", + "Load State Slot 10", + } + [configuration.hotkey_actions[index - 2]]; +} + static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, {"Configure layout", detect_joypad_layout}, + {"Hotkey 1 Action:", cycle_hotkey, current_hotkey, cycle_hotkey_backwards}, + {"Hotkey 2 Action:", cycle_hotkey, current_hotkey, cycle_hotkey_backwards}, {"Rumble Mode:", cycle_rumble_mode, current_rumble_mode, cycle_rumble_mode_backwards}, - {"Back", return_to_root_menu}, + {"Enable Control:", toggle_allow_background_controllers, current_background_control_mode, toggle_allow_background_controllers}, + {"Back", enter_controls_menu}, {NULL,} }; @@ -1114,22 +1529,136 @@ void connect_joypad(void) } } +static void toggle_mouse_control(unsigned index) +{ + configuration.allow_mouse_controls = !configuration.allow_mouse_controls; +} + +static const char *mouse_control_string(unsigned index) +{ + return configuration.allow_mouse_controls? "Allow mouse control" : "Disallow mouse control"; +} + +static const struct menu_item controls_menu[] = { + {"Keyboard Options", enter_keyboard_menu}, + {"Joypad Options", enter_joypad_menu}, + {"Motion-controlled games:", toggle_mouse_control, mouse_control_string, toggle_mouse_control}, + {"Back", enter_options_menu}, + {NULL,} +}; + +static void enter_controls_menu(unsigned index) +{ + current_menu = controls_menu; + current_selection = 0; + scroll = 0; + recalculate_menu_height(); +} + +static void toggle_audio_recording(unsigned index) +{ + if (!GB_is_inited(&gb)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Cannot start audio recording, open a ROM file first.", window); + return; + } + static bool is_recording = false; + if (is_recording) { + is_recording = false; + show_osd_text("Audio recording ended"); + int error = GB_stop_audio_recording(&gb); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + } + static const char item_string[] = "Start Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + return; + } + char *filename = do_save_recording_dialog(GB_get_sample_rate(&gb)); + + /* Drop events as it SDL seems to catch several in-dialog events */ + SDL_Event event; + while (SDL_PollEvent(&event)); + + if (filename) { + GB_audio_format_t format = GB_AUDIO_FORMAT_RAW; + size_t length = strlen(filename); + if (length >= 5) { + if (strcasecmp(".aiff", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".aifc", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (length >= 4) { + if (strcasecmp(".aif", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".wav", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_WAV; + } + } + } + + int error = GB_start_audio_recording(&gb, filename, format); + free(filename); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + return; + } + + is_recording = true; + static const char item_string[] = "Stop Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + show_osd_text("Audio recording started"); + } +} + +void convert_mouse_coordinates(signed *x, signed *y) +{ + signed width = GB_get_screen_width(&gb); + signed height = GB_get_screen_height(&gb); + signed x_offset = (width - 160) / 2; + signed y_offset = (height - 144) / 2; + + *x = (signed)(*x - rect.x / factor) * width / (signed)(rect.w / factor) - x_offset; + *y = (signed)(*y - rect.y / factor) * height / (signed)(rect.h / factor) - y_offset; + + if (strcmp("CRT", configuration.filter) == 0) { + *y = *y * 8 / 7; + *y -= 144 / 16; + } +} + void run_gui(bool is_running) { SDL_ShowCursor(SDL_ENABLE); connect_joypad(); /* Draw the background screen */ - static SDL_Surface *converted_background = NULL; if (!converted_background) { - SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); - SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); - converted_background = SDL_ConvertSurface(background, pixel_format, 0); - SDL_LockSurface(converted_background); - SDL_FreeSurface(background); - - for (unsigned i = 4; i--; ) { - gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b); + if (configuration.gui_pallete_enabled) { + update_gui_palette(); + } + else { + SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); + + /* Create a blank background if background.bmp could not be loaded */ + if (!background) { + background = SDL_CreateRGBSurface(0, 160, 144, 8, 0, 0, 0, 0); + } + SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); + converted_background = SDL_ConvertSurface(background, pixel_format, 0); + SDL_FreeSurface(background); + + for (unsigned i = 4; i--; ) { + gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b); + } } } @@ -1152,13 +1681,46 @@ void run_gui(bool is_running) recalculate_menu_height(); current_selection = 0; scroll = 0; - do { + + bool scrollbar_drag = false; + signed scroll_mouse_start = 0; + signed scroll_start = 0; + while (true) { + SDL_WaitEvent(&event); /* Convert Joypad and mouse events (We only generate down events) */ if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) { switch (event.type) { + case SDL_KEYDOWN: + if (gui_state == WAITING_FOR_KEY) break; + if (event.key.keysym.mod != 0) break; + switch (event.key.keysym.scancode) { + // Do not remap these keys to prevent deadlocking + case SDL_SCANCODE_ESCAPE: + case SDL_SCANCODE_RETURN: + case SDL_SCANCODE_RIGHT: + case SDL_SCANCODE_LEFT: + case SDL_SCANCODE_UP: + case SDL_SCANCODE_DOWN: + break; + + default: + if (event.key.keysym.scancode == configuration.keys[GB_KEY_RIGHT]) event.key.keysym.scancode = SDL_SCANCODE_RIGHT; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_LEFT]) event.key.keysym.scancode = SDL_SCANCODE_LEFT; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_UP]) event.key.keysym.scancode = SDL_SCANCODE_UP; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_DOWN]) event.key.keysym.scancode = SDL_SCANCODE_DOWN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_A]) event.key.keysym.scancode = SDL_SCANCODE_RETURN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_START]) event.key.keysym.scancode = SDL_SCANCODE_RETURN; + else if (event.key.keysym.scancode == configuration.keys[GB_KEY_B]) event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + break; + } + break; + case SDL_WINDOWEVENT: should_render = true; break; + case SDL_MOUSEBUTTONUP: + scrollbar_drag = false; + break; case SDL_MOUSEBUTTONDOWN: if (gui_state == SHOWING_HELP) { event.type = SDL_KEYDOWN; @@ -1169,12 +1731,25 @@ void run_gui(bool is_running) event.key.keysym.scancode = SDL_SCANCODE_ESCAPE; } else if (gui_state == SHOWING_MENU) { - signed x = (event.button.x - rect.x / factor) * width / (rect.w / factor) - x_offset; - signed y = (event.button.y - rect.y / factor) * height / (rect.h / factor) - y_offset; - - if (strcmp("CRT", configuration.filter) == 0) { - y = y * 8 / 7; - y -= 144 / 16; + signed x = event.button.x; + signed y = event.button.y; + convert_mouse_coordinates(&x, &y); + if (x >= 160 - 6 && x < 160 && menu_height > 144) { + unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144); + if (scrollbar_offset + scrollbar_size > 140) { + scrollbar_offset = 140 - scrollbar_size; + } + + if (y < scrollbar_offset || y > scrollbar_offset + scrollbar_size) { + scroll = (menu_height - 144) * y / 143; + should_render = true; + } + + scrollbar_drag = true; + mouse_scroling = true; + scroll_mouse_start = y; + scroll_start = scroll; + break; } y += scroll; @@ -1301,7 +1876,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; @@ -1317,14 +1892,23 @@ void run_gui(bool is_running) return; } } - case SDL_JOYBUTTONDOWN: - { + case SDL_JOYBUTTONDOWN: { if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress != JOYPAD_BUTTONS_MAX) { should_render = true; configuration.joypad_configuration[joypad_configuration_progress++] = event.jbutton.button; } break; } + case SDL_JOYHATMOTION: { + if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress == JOYPAD_BUTTON_RIGHT) { + should_render = true; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + configuration.joypad_configuration[joypad_configuration_progress++] = -1; + } + break; + } case SDL_JOYAXISMOTION: { if (gui_state == WAITING_FOR_JBUTTON && @@ -1366,9 +1950,39 @@ void run_gui(bool is_running) break; } + case SDL_MOUSEMOTION: { + if (scrollbar_drag && scrollbar_size < 140 && scrollbar_size > 0) { + signed x = event.motion.x; + signed y = event.motion.y; + convert_mouse_coordinates(&x, &y); + signed delta = scroll_mouse_start - y; + scroll = scroll_start - delta * (signed)(menu_height - 144) / (signed)(140 - scrollbar_size); + if (scroll < 0) { + scroll = 0; + } + if (scroll >= menu_height - 144) { + scroll = menu_height - 144; + } + + should_render = true; + } + break; + } + case SDL_KEYDOWN: - if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { + scrollbar_drag = false; + if (gui_state == WAITING_FOR_KEY) { + if (current_selection > 8) { + 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); } @@ -1377,7 +1991,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) { @@ -1404,7 +2018,12 @@ void run_gui(bool is_running) } else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { if (gui_state == SHOWING_MENU && current_menu != root_menu) { - return_to_root_menu(0); + for (const struct menu_item *item = current_menu; item->string; item++) { + if (strcmp(item->string, "Back") == 0) { + item->handler(0); + break; + } + } should_render = true; } else if (is_running) { @@ -1464,24 +2083,6 @@ void run_gui(bool is_running) } } else if (gui_state == SHOWING_HELP) { - current_help_page++; - if (current_help_page == sizeof(help) / sizeof(help[0])) { - gui_state = SHOWING_MENU; - } - 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; } @@ -1491,6 +2092,7 @@ void run_gui(bool is_running) if (should_render) { should_render = false; rerender: + SDL_LockSurface(converted_background); if (width == 160 && height == 144) { memcpy(pixels, converted_background->pixels, sizeof(pixels)); } @@ -1499,6 +2101,7 @@ void run_gui(bool is_running) memcpy(pixels + x_offset + width * (y + y_offset), ((uint32_t *)converted_background->pixels) + 160 * y, 160 * 4); } } + SDL_UnlockSurface(converted_background); switch (gui_state) { case SHOWING_DROP_MESSAGE: @@ -1565,17 +2168,17 @@ void run_gui(bool is_running) 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]; + pixel[0] = pixel[1] = gui_palette_native[2]; } else { - pixel[0] = pixel[1]= gui_palette_native[1]; + 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]); + draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0], false); break; case WAITING_FOR_KEY: draw_text_centered(pixels, width, height, 68 + y_offset, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); @@ -1599,6 +2202,8 @@ void run_gui(bool is_running) "Turbo", "Rewind", "Slow-Motion", + "Hotkey 1", + "Hotkey 2", "", } [joypad_configuration_progress], gui_palette_native[3], gui_palette_native[0], DECORATION_NONE); @@ -1612,5 +2217,33 @@ void run_gui(bool is_running) render_texture(pixels, NULL); #endif } - } while (SDL_WaitEvent(&event)); + } +} + +static void __attribute__ ((constructor)) list_custom_palettes(void) +{ + char *path = resource_path("Palettes"); + if (!path) return; + if (strlen(path) > 1024 - 30) { + // path too long to safely concat filenames + return; + } + DIR *dir = opendir(path); + if (!dir) return; + + struct dirent *ent; + + while ((ent = readdir(dir))) { + unsigned length = strlen(ent->d_name); + if (length < 5 || length > 28) { + continue; + } + if (strcmp(ent->d_name + length - 4, ".sbp")) continue; + ent->d_name[length - 4] = 0; + custom_palettes = realloc(custom_palettes, + sizeof(custom_palettes[0]) * (n_custom_palettes + 1)); + custom_palettes[n_custom_palettes++] = strdup(ent->d_name); + } + + closedir(dir); } diff --git a/thirdparty/SameBoy/SDL/gui.h b/thirdparty/SameBoy/SDL/gui.h index baa678992..3c12c85f1 100644 --- a/thirdparty/SameBoy/SDL/gui.h +++ b/thirdparty/SameBoy/SDL/gui.h @@ -5,6 +5,7 @@ #include #include #include "shader.h" +#include "configuration.h" #define JOYSTICK_HIGH 0x4000 #define JOYSTICK_LOW 0x3800 @@ -24,14 +25,6 @@ extern SDL_PixelFormat *pixel_format; extern SDL_Haptic *haptic; extern shader_t shader; -enum scaling_mode { - GB_SDL_SCALING_ENTIRE_WINDOW, - GB_SDL_SCALING_KEEP_RATIO, - GB_SDL_SCALING_INTEGER_FACTOR, - GB_SDL_SCALING_MAX, -}; - - enum pending_command { GB_SDL_NO_COMMAND, GB_SDL_SAVE_STATE_COMMAND, @@ -40,6 +33,7 @@ enum pending_command { GB_SDL_NEW_FILE_COMMAND, GB_SDL_QUIT_COMMAND, GB_SDL_LOAD_STATE_FROM_FILE_COMMAND, + GB_SDL_CART_SWAP_COMMAND, }; #define GB_SDL_DEFAULT_SCALE_MAX 8 @@ -48,81 +42,6 @@ extern enum pending_command pending_command; extern unsigned command_parameter; extern char *dropped_state_file; -typedef enum { - JOYPAD_BUTTON_LEFT, - JOYPAD_BUTTON_RIGHT, - JOYPAD_BUTTON_UP, - JOYPAD_BUTTON_DOWN, - JOYPAD_BUTTON_A, - JOYPAD_BUTTON_B, - JOYPAD_BUTTON_SELECT, - JOYPAD_BUTTON_START, - JOYPAD_BUTTON_MENU, - JOYPAD_BUTTON_TURBO, - JOYPAD_BUTTON_REWIND, - JOYPAD_BUTTON_SLOW_MOTION, - JOYPAD_BUTTONS_MAX -} joypad_button_t; - -typedef enum { - JOYPAD_AXISES_X, - JOYPAD_AXISES_Y, - JOYPAD_AXISES_MAX -} joypad_axis_t; - -typedef struct { - SDL_Scancode keys[9]; - GB_color_correction_mode_t color_correction_mode; - enum scaling_mode scaling_mode; - uint8_t blending_mode; - - GB_highpass_mode_t highpass_mode; - - bool _deprecated_div_joystick; - bool _deprecated_flip_joystick_bit_1; - bool _deprecated_swap_joysticks_bits_1_and_2; - - char filter[32]; - enum { - MODEL_DMG, - MODEL_CGB, - MODEL_AGB, - MODEL_SGB, - MODEL_MAX, - } model; - - /* v0.11 */ - uint32_t rewind_length; - SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */ - uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/; - uint8_t joypad_axises[JOYPAD_AXISES_MAX]; - - /* v0.12 */ - enum { - SGB_NTSC, - SGB_PAL, - SGB_2, - SGB_MAX - } sgb_revision; - - /* v0.13 */ - uint8_t dmg_palette; - GB_border_mode_t border_mode; - uint8_t volume; - GB_rumble_mode_t rumble_mode; - - uint8_t default_scale; - - /* v0.14 */ - unsigned padding; - uint8_t color_temperature; - char bootrom_path[4096]; - uint8_t interference_volume; - GB_rtc_mode_t rtc_mode; -} configuration_t; - -extern configuration_t configuration; - void update_viewport(void); void run_gui(bool is_running); void render_texture(void *pixels, void *previous); @@ -140,4 +59,12 @@ static SDL_Scancode event_hotkey_code(SDL_Event *event) return event->key.keysym.scancode; } +void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border, bool is_osd); +void show_osd_text(const char *text); +extern const char *osd_text; +extern unsigned osd_countdown; +extern unsigned osd_text_lines; +void convert_mouse_coordinates(signed *x, signed *y); +const GB_palette_t *current_dmg_palette(void); + #endif diff --git a/thirdparty/SameBoy/SDL/main.c b/thirdparty/SameBoy/SDL/main.c index 29398b715..4afc5d709 100644 --- a/thirdparty/SameBoy/SDL/main.c +++ b/thirdparty/SameBoy/SDL/main.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -9,13 +11,15 @@ #include "gui.h" #include "shader.h" #include "audio/audio.h" +#include "console.h" #ifndef _WIN32 -#include +#include #else #include #endif +static bool stop_on_start = false; GB_gameboy_t gb; static bool paused = false; static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; @@ -25,8 +29,14 @@ 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; +static bool console_supported = false; +bool uses_gl(void) +{ + return gl_context; +} void set_filename(const char *new_filename, typeof(free) *new_free_function) { @@ -37,6 +47,80 @@ void set_filename(const char *new_filename, typeof(free) *new_free_function) free_function = new_free_function; } +static char *completer(const char *substring, uintptr_t *context) +{ + if (!GB_is_inited(&gb)) return NULL; + char *temp = strdup(substring); + char *ret = GB_debugger_complete_substring(&gb, temp, context); + free(temp); + return ret; +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + CON_attributes_t con_attributes = {0,}; + con_attributes.bold = attributes & GB_LOG_BOLD; + con_attributes.underline = attributes & GB_LOG_UNDERLINE; + if (attributes & GB_LOG_DASHED_UNDERLINE) { + while (*string) { + con_attributes.underline ^= true; + CON_attributed_printf("%c", &con_attributes, *string); + string++; + } + } + else { + CON_attributed_print(string, &con_attributes); + } +} + +static void handle_eof(void) +{ + CON_set_async_prompt(""); + char *line = CON_readline("Quit? [y]/n > "); + if (line[0] == 'n' || line[0] == 'N') { + free(line); + CON_set_async_prompt("> "); + } + else { + exit(0); + } +} + +static char *input_callback(GB_gameboy_t *gb) +{ +retry: { + char *ret = CON_readline("Stopped> "); + if (strcmp(ret, CON_EOF) == 0) { + handle_eof(); + free(ret); + goto retry; + } + else { + CON_attributes_t attributes = {.bold = true}; + CON_attributed_printf("> %s\n", &attributes, ret); + } + return ret; +} +} + +static char *asyc_input_callback(GB_gameboy_t *gb) +{ +retry: { + char *ret = CON_readline_async(); + if (ret && strcmp(ret, CON_EOF) == 0) { + handle_eof(); + free(ret); + goto retry; + } + else if (ret) { + CON_attributes_t attributes = {.bold = true}; + CON_attributed_printf("> %s\n", &attributes, ret); + } + return ret; +} +} + + static char *captured_log = NULL; static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -58,16 +142,16 @@ 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); + GB_set_log_callback(&gb, console_supported? log_callback : NULL); if (captured_log[0] == 0) { free(captured_log); captured_log = NULL; } 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); @@ -78,22 +162,7 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_ static void update_palette(void) { - switch (configuration.dmg_palette) { - 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); - } + GB_set_palette(&gb, current_dmg_palette()); } static void screen_size_changed(void) @@ -135,14 +204,14 @@ static void open_menu(void) static void handle_events(GB_gameboy_t *gb) { SDL_Event event; - while (SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pending_command = GB_SDL_QUIT_COMMAND; 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; } @@ -159,6 +228,25 @@ static void handle_events(GB_gameboy_t *gb) } break; } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) { + GB_set_key_state(gb, GB_KEY_A, event.type == SDL_MOUSEBUTTONDOWN); + } + break; + } + + case SDL_MOUSEMOTION: { + if (GB_has_accelerometer(gb) && configuration.allow_mouse_controls) { + signed x = event.motion.x; + signed y = event.motion.y; + convert_mouse_coordinates(&x, &y); + x = SDL_max(SDL_min(x, 160), 0); + y = SDL_max(SDL_min(y, 144), 0); + GB_set_accelerometer_values(gb, (x - 80) / -80.0, (y - 72) / -72.0); + } + break; + } case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: { @@ -184,14 +272,50 @@ static void handle_events(GB_gameboy_t *gb) else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) { open_menu(); } + else if ((button == JOYPAD_BUTTON_HOTKEY_1 || button == JOYPAD_BUTTON_HOTKEY_2) && event.type == SDL_JOYBUTTONDOWN) { + hotkey_action_t action = configuration.hotkey_actions[button - JOYPAD_BUTTON_HOTKEY_1]; + switch (action) { + case HOTKEY_NONE: + break; + case HOTKEY_PAUSE: + paused = !paused; + break; + case HOTKEY_MUTE: + GB_audio_set_paused(GB_audio_is_playing()); + break; + case HOTKEY_RESET: + pending_command = GB_SDL_RESET_COMMAND; + break; + case HOTKEY_QUIT: + pending_command = GB_SDL_QUIT_COMMAND; + break; + default: + command_parameter = (action - HOTKEY_SAVE_STATE_1) / 2 + 1; + pending_command = ((action - HOTKEY_SAVE_STATE_1) % 2)? GB_SDL_LOAD_STATE_COMMAND:GB_SDL_SAVE_STATE_COMMAND; + break; + case HOTKEY_SAVE_STATE_10: + command_parameter = 0; + pending_command = GB_SDL_SAVE_STATE_COMMAND; + break; + case HOTKEY_LOAD_STATE_10: + command_parameter = 0; + pending_command = GB_SDL_LOAD_STATE_COMMAND; + break; + } + } } - break; + break; case SDL_JOYAXISMOTION: { static bool axis_active[2] = {false, false}; + static double accel_values[2] = {0, 0}; joypad_axis_t axis = get_joypad_axis(event.jaxis.axis); if (axis == JOYPAD_AXISES_X) { - if (event.jaxis.value > JOYSTICK_HIGH) { + if (GB_has_accelerometer(gb)) { + accel_values[0] = event.jaxis.value / (double)32768.0; + GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]); + } + else if (event.jaxis.value > JOYSTICK_HIGH) { axis_active[0] = true; GB_set_key_state(gb, GB_KEY_RIGHT, true); GB_set_key_state(gb, GB_KEY_LEFT, false); @@ -208,7 +332,11 @@ static void handle_events(GB_gameboy_t *gb) } } else if (axis == JOYPAD_AXISES_Y) { - if (event.jaxis.value > JOYSTICK_HIGH) { + if (GB_has_accelerometer(gb)) { + accel_values[1] = event.jaxis.value / (double)32768.0; + GB_set_accelerometer_values(gb, -accel_values[0], -accel_values[1]); + } + else if (event.jaxis.value > JOYSTICK_HIGH) { axis_active[1] = true; GB_set_key_state(gb, GB_KEY_DOWN, true); GB_set_key_state(gb, GB_KEY_UP, false); @@ -225,22 +353,21 @@ static void handle_events(GB_gameboy_t *gb) } } } - break; - - case SDL_JOYHATMOTION: - { + break; + + case SDL_JOYHATMOTION: { uint8_t value = event.jhat.value; int8_t updown = - value == SDL_HAT_LEFTUP || value == SDL_HAT_UP || value == SDL_HAT_RIGHTUP ? -1 : (value == SDL_HAT_LEFTDOWN || value == SDL_HAT_DOWN || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + value == SDL_HAT_LEFTUP || value == SDL_HAT_UP || value == SDL_HAT_RIGHTUP ? -1 : (value == SDL_HAT_LEFTDOWN || value == SDL_HAT_DOWN || value == SDL_HAT_RIGHTDOWN ? 1 : 0); int8_t leftright = - value == SDL_HAT_LEFTUP || value == SDL_HAT_LEFT || value == SDL_HAT_LEFTDOWN ? -1 : (value == SDL_HAT_RIGHTUP || value == SDL_HAT_RIGHT || value == SDL_HAT_RIGHTDOWN ? 1 : 0); + value == SDL_HAT_LEFTUP || value == SDL_HAT_LEFT || value == SDL_HAT_LEFTDOWN ? -1 : (value == SDL_HAT_RIGHTUP || value == SDL_HAT_RIGHT || value == SDL_HAT_RIGHTDOWN ? 1 : 0); GB_set_key_state(gb, GB_KEY_LEFT, leftright == -1); GB_set_key_state(gb, GB_KEY_RIGHT, leftright == 1); GB_set_key_state(gb, GB_KEY_UP, updown == -1); GB_set_key_state(gb, GB_KEY_DOWN, updown == 1); break; - }; + }; case SDL_KEYDOWN: switch (event_hotkey_code(&event)) { @@ -250,6 +377,7 @@ static void handle_events(GB_gameboy_t *gb) } case SDL_SCANCODE_C: if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { + CON_print("^C\a\n"); GB_debugger_break(gb); } break; @@ -270,7 +398,7 @@ static void handle_events(GB_gameboy_t *gb) } break; } - + case SDL_SCANCODE_P: if (event.key.keysym.mod & MODIFIER) { paused = !paused; @@ -286,14 +414,14 @@ static void handle_events(GB_gameboy_t *gb) #endif GB_audio_set_paused(GB_audio_is_playing()); } - break; - + break; + case SDL_SCANCODE_F: if (event.key.keysym.mod & MODIFIER) { if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } - else { + else { SDL_SetWindowFullscreen(window, 0); } update_viewport(); @@ -313,6 +441,16 @@ static void handle_events(GB_gameboy_t *gb) pending_command = GB_SDL_SAVE_STATE_COMMAND; } } + else if ((event.key.keysym.mod & KMOD_ALT) && event.key.keysym.scancode <= SDL_SCANCODE_4) { + GB_channel_t channel = event.key.keysym.scancode - SDL_SCANCODE_1; + bool state = !GB_is_channel_muted(gb, channel); + + GB_set_channel_muted(gb, channel, state); + + static char message[18]; + sprintf(message, "Channel %d %smuted", channel + 1, state? "" : "un"); + show_osd_text(message); + } } break; } @@ -342,11 +480,16 @@ static void handle_events(GB_gameboy_t *gb) break; default: break; - } } } +} + +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return SDL_MapRGB(pixel_format, r, g, b); +} -static void vblank(GB_gameboy_t *gb) +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) { if (underclock_down && clock_mutliplier > 0.5) { clock_mutliplier -= 1.0/16; @@ -356,26 +499,42 @@ static void vblank(GB_gameboy_t *gb) clock_mutliplier += 1.0/16; GB_set_clock_multiplier(gb, clock_mutliplier); } - if (configuration.blending_mode) { - render_texture(active_pixel_buffer, previous_pixel_buffer); - uint32_t *temp = active_pixel_buffer; - active_pixel_buffer = previous_pixel_buffer; - previous_pixel_buffer = temp; - GB_set_pixels_output(gb, active_pixel_buffer); + + if (turbo_down) { + show_osd_text("Fast forward..."); + } + else if (underclock_down) { + show_osd_text("Slow motion..."); } - else { - render_texture(active_pixel_buffer, NULL); + else if (rewind_down) { + show_osd_text("Rewinding..."); + } + + if (osd_countdown && configuration.osd) { + unsigned width = GB_get_screen_width(gb); + unsigned height = GB_get_screen_height(gb); + draw_text(active_pixel_buffer, + width, height, 8, height - 8 - osd_text_lines * 12, osd_text, + rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0), + true); + osd_countdown--; + } + if (type != GB_VBLANK_TYPE_REPEAT) { + if (configuration.blending_mode) { + render_texture(active_pixel_buffer, previous_pixel_buffer); + uint32_t *temp = active_pixel_buffer; + active_pixel_buffer = previous_pixel_buffer; + previous_pixel_buffer = temp; + GB_set_pixels_output(gb, active_pixel_buffer); + } + else { + render_texture(active_pixel_buffer, NULL); + } } do_rewind = rewind_down; handle_events(gb); } - -static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) -{ - return SDL_MapRGB(pixel_format, r, g, b); -} - static void rumble(GB_gameboy_t *gb, double amp) { SDL_HapticRumblePlay(haptic, amp, 250); @@ -383,12 +542,15 @@ static void rumble(GB_gameboy_t *gb, double amp) static void debugger_interrupt(int ignore) { - if (!GB_is_inited(&gb)) return; + if (!GB_is_inited(&gb)) exit(0); /* ^C twice to exit */ if (GB_debugger_is_stopped(&gb)) { GB_save_battery(&gb, battery_save_path_ptr); exit(0); } + if (console_supported) { + CON_print("^C\n"); + } GB_debugger_break(&gb); } @@ -405,7 +567,7 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) } } - if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_frequency() / 4) { + if (GB_audio_get_queue_length() > GB_audio_get_frequency() / 8) { // Maximum lag of 0.125s return; } @@ -417,14 +579,14 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) GB_audio_queue_sample(sample); } - +static bool doing_hot_swap = false; 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); @@ -432,25 +594,47 @@ 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"); + if (success) { + show_osd_text(pending_command == GB_SDL_LOAD_STATE_COMMAND? "State loaded" : "State saved"); + } 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); + if (success) { + show_osd_text("State loaded"); + } return false; case GB_SDL_NO_COMMAND: return false; + case GB_SDL_CART_SWAP_COMMAND: + doing_hot_swap = true; case GB_SDL_RESET_COMMAND: case GB_SDL_NEW_FILE_COMMAND: GB_save_battery(&gb, battery_save_path_ptr); @@ -466,12 +650,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", }; @@ -484,10 +668,20 @@ 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"); } } +static bool is_path_writeable(const char *path) +{ + if (!access(path, W_OK)) return true; + int fd = creat(path, 0644); + if (fd == -1) return false; + close(fd); + unlink(path); + return true; +} + static void run(void) { SDL_ShowCursor(SDL_DISABLE); @@ -497,8 +691,9 @@ static void run(void) model = (GB_model_t []) { [MODEL_DMG] = GB_MODEL_DMG_B, - [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_CGB] = GB_MODEL_CGB_0 + configuration.cgb_revision, + [MODEL_AGB] = configuration.agb_revision, + [MODEL_MGB] = GB_MODEL_MGB, [MODEL_SGB] = (GB_model_t []) { [SGB_NTSC] = GB_MODEL_SGB_NTSC, @@ -508,7 +703,12 @@ static void run(void) }[configuration.model]; if (GB_is_inited(&gb)) { - GB_switch_model_and_reset(&gb, model); + if (doing_hot_swap) { + doing_hot_swap = false; + } + else { + GB_switch_model_and_reset(&gb, model); + } } else { GB_init(&gb, model); @@ -532,6 +732,17 @@ static void run(void) 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); + + if (console_supported) { + CON_set_async_prompt("> "); + GB_set_log_callback(&gb, log_callback); + GB_set_input_callback(&gb, input_callback); + GB_set_async_input_callback(&gb, asyc_input_callback); + } + } + if (stop_on_start) { + stop_on_start = false; + GB_debugger_break(&gb); } bool error = false; @@ -557,15 +768,26 @@ static void run(void) else { GB_load_rom(&gb, filename); } - end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING); - /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ replace_extension(filename, path_length, battery_save_path, ".sav"); battery_save_path_ptr = battery_save_path; GB_load_battery(&gb, battery_save_path); + if (GB_save_battery_size(&gb)) { + if (!is_path_writeable(battery_save_path)) { + GB_log(&gb, "The save path for this ROM is not writeable, progress will not be saved.\n"); + } + } + end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning"); + + static char start_text[64]; + static char title[17]; + GB_get_rom_title(&gb, title); + sprintf(start_text, "SameBoy v" GB_VERSION "\n%s\n%08X", title, GB_get_rom_crc32(&gb)); + show_osd_text(start_text); + /* Configure symbols */ GB_debugger_load_symbol_file(&gb, resource_path("registers.sym")); @@ -615,6 +837,11 @@ static void save_configuration(void) } } +static void stop_recording(void) +{ + GB_stop_audio_recording(&gb); +} + static bool get_arg_flag(const char *flag, int *argc, char **argv) { for (unsigned i = 1; i < *argc; i++) { @@ -627,19 +854,30 @@ static bool get_arg_flag(const char *flag, int *argc, char **argv) return false; } +#ifdef __APPLE__ +#include +static void enable_smooth_scrolling(void) +{ + CFPreferencesSetAppValue(CFSTR("AppleMomentumScrollSupported"), kCFBooleanTrue, kCFPreferencesCurrentApplication); +} +#endif + int main(int argc, char **argv) { #ifdef _WIN32 SetProcessDPIAware(); #endif -#define str(x) #x -#define xstr(x) str(x) - fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); - - bool fullscreen = get_arg_flag("--fullscreen", &argc, argv); +#ifdef __APPLE__ + enable_smooth_scrolling(); +#endif - if (argc > 2) { - fprintf(stderr, "Usage: %s [--fullscreen] [rom]\n", argv[0]); + bool fullscreen = get_arg_flag("--fullscreen", &argc, argv) || get_arg_flag("-f", &argc, argv); + bool nogl = get_arg_flag("--nogl", &argc, argv); + stop_on_start = get_arg_flag("--stop-debugger", &argc, argv) || get_arg_flag("-s", &argc, argv); + + if (argc > 2 || (argc == 2 && argv[1][0] == '-')) { + fprintf(stderr, "SameBoy v" GB_VERSION "\n"); + fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [rom]\n", argv[0]); exit(1); } @@ -649,7 +887,14 @@ int main(int argc, char **argv) signal(SIGINT, debugger_interrupt); - SDL_Init(SDL_INIT_EVERYTHING); + SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO); + if ((console_supported = CON_start(completer))) { + CON_set_repeat_empty(true); + CON_printf("SameBoy v" GB_VERSION "\n"); + } + else { + fprintf(stderr, "SameBoy v" GB_VERSION "\n"); + } strcpy(prefs_path, resource_path("prefs.bin")); if (access(prefs_path, R_OK | W_OK) != 0) { @@ -664,18 +909,31 @@ 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_MODERN_ACCURATE + 1; configuration.scaling_mode %= GB_SDL_SCALING_MAX; configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1; configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1; configuration.highpass_mode %= GB_HIGHPASS_MAX; configuration.model %= MODEL_MAX; configuration.sgb_revision %= SGB_MAX; - configuration.dmg_palette %= 3; + configuration.dmg_palette %= 5; + if (configuration.dmg_palette) { + configuration.gui_pallete_enabled = true; + } 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; + configuration.cgb_revision %= GB_MODEL_CGB_E - GB_MODEL_CGB_0 + 1; + configuration.audio_driver[15] = 0; + configuration.dmg_palette_name[24] = 0; + // Fix broken defaults, should keys 12-31 should be unmapped by default + if (configuration.joypad_configuration[31] == 0) { + memset(configuration.joypad_configuration + 12 , -1, 32 - 12); + } + if ((configuration.agb_revision & ~GB_MODEL_GBP_BIT) != GB_MODEL_AGB_A) { + configuration.agb_revision = GB_MODEL_AGB_A; + } } if (configuration.model >= MODEL_MAX) { @@ -687,12 +945,16 @@ int main(int argc, char **argv) } atexit(save_configuration); + atexit(stop_recording); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, + configuration.allow_background_controllers? "1" : "0"); - window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + window = SDL_CreateWindow("SameBoy v" GB_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); @@ -704,13 +966,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; } @@ -734,6 +998,7 @@ int main(int argc, char **argv) update_viewport(); if (filename == NULL) { + stop_on_start = false; run_gui(false); } else { diff --git a/thirdparty/SameBoy/SDL/shader.c b/thirdparty/SameBoy/SDL/shader.c index de2ba564f..44de29044 100644 --- a/thirdparty/SameBoy/SDL/shader.c +++ b/thirdparty/SameBoy/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); diff --git a/thirdparty/SameBoy/Shaders/AAScale2x.fsh b/thirdparty/SameBoy/Shaders/AAScale2x.fsh index d51a9a6a8..b1b35cefe 100644 --- a/thirdparty/SameBoy/Shaders/AAScale2x.fsh +++ b/thirdparty/SameBoy/Shaders/AAScale2x.fsh @@ -1,21 +1,15 @@ STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) -{ - // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; - +{ // texel arrangement // A B C // D E F // G H I // vec4 A = texture(image, position + vec2( -o.x, o.y)); - vec4 B = texture(image, position + vec2( 0, o.y)); - // vec4 C = texture(image, position + vec2( o.x, o.y)); - vec4 D = texture(image, position + vec2( -o.x, 0)); - vec4 E = texture(image, position + vec2( 0, 0)); - vec4 F = texture(image, position + vec2( o.x, 0)); - // vec4 G = texture(image, position + vec2( -o.x, -o.y)); - vec4 H = texture(image, position + vec2( 0, -o.y)); - // vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); diff --git a/thirdparty/SameBoy/Shaders/AAScale4x.fsh b/thirdparty/SameBoy/Shaders/AAScale4x.fsh index b59b80e97..f8237c791 100644 --- a/thirdparty/SameBoy/Shaders/AAScale4x.fsh +++ b/thirdparty/SameBoy/Shaders/AAScale4x.fsh @@ -1,20 +1,15 @@ STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; // texel arrangement // A B C // D E F // G H I - // vec4 A = texture(image, position + vec2( -o.x, o.y)); - vec4 B = texture(image, position + vec2( 0, o.y)); - // vec4 C = texture(image, position + vec2( o.x, o.y)); - vec4 D = texture(image, position + vec2( -o.x, 0)); - vec4 E = texture(image, position + vec2( 0, 0)); - vec4 F = texture(image, position + vec2( o.x, 0)); - // vec4 G = texture(image, position + vec2( -o.x, -o.y)); - vec4 H = texture(image, position + vec2( 0, -o.y)); - // vec4 I = texture(image, position + vec2( o.x, -o.y)); + + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); @@ -36,30 +31,24 @@ STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 } } } - -STATIC vec4 aaScale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +STATIC vec4 scale2x_wrapper(sampler2D t, vec2 pos, vec2 offset, vec2 input_resolution, vec2 output_resolution) { - return mix(texture(image, position), scale2x(image, position, input_resolution, output_resolution), 0.5); + vec2 origin = (floor(pos * input_resolution * 2)) + vec2(0.5, 0.5); + return scale2x(t, (origin + offset) / input_resolution / 2, input_resolution, output_resolution); } STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - // o = offset, the width of a pixel - vec2 o = 1.0 / (input_resolution * 2.); - // texel arrangement // A B C // D E F // G H I - // vec4 A = aaScale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); - vec4 B = aaScale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); - // vec4 C = aaScale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); - vec4 D = aaScale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); - vec4 E = aaScale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); - vec4 F = aaScale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); - // vec4 G = aaScale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); - vec4 H = aaScale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); - // vec4 I = aaScale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); + vec4 B = scale2x_wrapper(image, position, vec2( 0, 1), input_resolution, output_resolution); + vec4 D = scale2x_wrapper(image, position, vec2( -1, 0), input_resolution, output_resolution); + vec4 E = scale2x_wrapper(image, position, vec2( 0, 0), input_resolution, output_resolution); + vec4 F = scale2x_wrapper(image, position, vec2( 1, 0), input_resolution, output_resolution); + vec4 H = scale2x_wrapper(image, position, vec2( 0, -1), input_resolution, output_resolution); + vec4 R; vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] diff --git a/thirdparty/SameBoy/Shaders/CRT.fsh b/thirdparty/SameBoy/Shaders/CRT.fsh index 4cbab721f..25e5b37f4 100644 --- a/thirdparty/SameBoy/Shaders/CRT.fsh +++ b/thirdparty/SameBoy/Shaders/CRT.fsh @@ -25,20 +25,20 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou vec2 pos = fract(position * input_resolution); vec2 sub_pos = fract(position * input_resolution * 6); - vec4 center = texture(image, position); - vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0)); - vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0)); + vec4 center = texture_relative(image, position, vec2(0, 0)); + vec4 left = texture_relative(image, position, vec2(-1, 0)); + vec4 right = texture_relative(image, position, vec2(1, 0)); /* Vertical blurring */ if (pos.y < 1.0 / 6.0) { - center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); - left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); - right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + center = mix(center, texture_relative(image, position, vec2( 0, -1)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, -1)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, -1)), 0.5 - sub_pos.y / 2.0); } else if (pos.y > 5.0 / 6.0) { - center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0); - left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); - right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + center = mix(center, texture_relative(image, position, vec2( 0, 1)), sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, 1)), sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, 1)), sub_pos.y / 2.0); } /* Scanlines */ diff --git a/thirdparty/SameBoy/Shaders/HQ2x.fsh b/thirdparty/SameBoy/Shaders/HQ2x.fsh index 7ae806374..0baf9e146 100644 --- a/thirdparty/SameBoy/Shaders/HQ2x.fsh +++ b/thirdparty/SameBoy/Shaders/HQ2x.fsh @@ -30,7 +30,7 @@ STATIC vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; + vec2 o = vec2(1, 1); /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ @@ -40,17 +40,15 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (p.x > 0.5) o.x = -o.x; if (p.y > 0.5) o.y = -o.y; - - - vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, position + vec2( 0, -o.y)); - vec4 w2 = texture(image, position + vec2( o.x, -o.y)); - vec4 w3 = texture(image, position + vec2( -o.x, 0)); - vec4 w4 = texture(image, position + vec2( 0, 0)); - vec4 w5 = texture(image, position + vec2( o.x, 0)); - vec4 w6 = texture(image, position + vec2( -o.x, o.y)); - vec4 w7 = texture(image, position + vec2( 0, o.y)); - vec4 w8 = texture(image, position + vec2( o.x, o.y)); + vec4 w0 = texture_relative(image, position, vec2( -o.x, -o.y)); + vec4 w1 = texture_relative(image, position, vec2( 0, -o.y)); + vec4 w2 = texture_relative(image, position, vec2( o.x, -o.y)); + vec4 w3 = texture_relative(image, position, vec2( -o.x, 0)); + vec4 w4 = texture_relative(image, position, vec2( 0, 0)); + vec4 w5 = texture_relative(image, position, vec2( o.x, 0)); + vec4 w6 = texture_relative(image, position, vec2( -o.x, o.y)); + vec4 w7 = texture_relative(image, position, vec2( 0, o.y)); + vec4 w8 = texture_relative(image, position, vec2( o.x, o.y)); int pattern = 0; if (is_different(w0, w4)) pattern |= 1; @@ -62,52 +60,52 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (is_different(w7, w4)) pattern |= 64; if (is_different(w8, w4)) pattern |= 128; - if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) { + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { return interp_2px(w4, 3.0, w3, 1.0); } - if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) { + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { return interp_2px(w4, 3.0, w1, 1.0); } - if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) { + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { return w4; } - if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || - P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || - P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || - P(0xeb,0x8a)) && is_different(w3, w1)) { + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { return interp_2px(w4, 3.0, w0, 1.0); } - if (P(0x0b,0x08)) { + if (P(0x0B,0x08)) { return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); } - if (P(0x0b,0x02)) { + if (P(0x0B,0x02)) { return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); } - if (P(0x2f,0x2f)) { + if (P(0x2F,0x2F)) { return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0); } - if (P(0xbf,0x37) || P(0xdb,0x13)) { + if (P(0xBF,0x37) || P(0xDB,0x13)) { return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); } - if (P(0xdb,0x49) || P(0xef,0x6d)) { + if (P(0xDB,0x49) || P(0xEF,0x6D)) { return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); } - if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) { + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { return interp_2px(w4, 3.0, w3, 1.0); } - if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) { + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { return interp_2px(w4, 3.0, w1, 1.0); } - if (P(0x7e,0x2a) || P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e)) { + if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) { return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); } - if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || - P(0xdf,0xde) || P(0xdf,0x1e)) { + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { return interp_2px(w4, 3.0, w0, 1.0); } - if (P(0x0a,0x00) || P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || - P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || - P(0x3b,0x1b)) { + if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); } diff --git a/thirdparty/SameBoy/Shaders/LCD.fsh b/thirdparty/SameBoy/Shaders/LCD.fsh index d20a7c937..d49ac57ed 100644 --- a/thirdparty/SameBoy/Shaders/LCD.fsh +++ b/thirdparty/SameBoy/Shaders/LCD.fsh @@ -7,22 +7,22 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou vec2 pos = fract(position * input_resolution); vec2 sub_pos = fract(position * input_resolution * 6); - vec4 center = texture(image, position); - vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0)); - vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0)); + vec4 center = texture_relative(image, position, vec2(0, 0)); + vec4 left = texture_relative(image, position, vec2(-1, 0)); + vec4 right = texture_relative(image, position, vec2(1, 0)); if (pos.y < 1.0 / 6.0) { - center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); - left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); - right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + center = mix(center, texture_relative(image, position, vec2( 0, -1)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, -1)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, -1)), 0.5 - sub_pos.y / 2.0); center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); } else if (pos.y > 5.0 / 6.0) { - center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0); - left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); - right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + center = mix(center, texture_relative(image, position, vec2( 0, 1)), sub_pos.y / 2.0); + left = mix(left, texture_relative(image, position, vec2(-1, 1)), sub_pos.y / 2.0); + right = mix(right, texture_relative(image, position, vec2( 1, 1)), sub_pos.y / 2.0); center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); diff --git a/thirdparty/SameBoy/Shaders/MasterShader.fsh b/thirdparty/SameBoy/Shaders/MasterShader.fsh index 3f891d5d2..24bba3cbe 100644 --- a/thirdparty/SameBoy/Shaders/MasterShader.fsh +++ b/thirdparty/SameBoy/Shaders/MasterShader.fsh @@ -18,6 +18,12 @@ vec4 _texture(sampler2D t, vec2 pos) return pow(texture(t, pos), vec4(GAMMA)); } +vec4 texture_relative(sampler2D t, vec2 pos, vec2 offset) +{ + vec2 input_resolution = textureSize(t, 0); + return _texture(t, (floor(pos * input_resolution) + offset + vec2(0.5, 0.5)) / input_resolution); +} + #define texture _texture #line 1 diff --git a/thirdparty/SameBoy/Shaders/MasterShader.metal b/thirdparty/SameBoy/Shaders/MasterShader.metal index 2f3113e38..7ba04dda6 100644 --- a/thirdparty/SameBoy/Shaders/MasterShader.metal +++ b/thirdparty/SameBoy/Shaders/MasterShader.metal @@ -40,6 +40,13 @@ static inline float4 texture(texture2d texture, float2 pos) return pow(float4(texture.sample(texture_sampler, pos)), GAMMA); } +__attribute__((unused)) static inline float4 texture_relative(texture2d t, float2 pos, float2 offset) +{ + float2 input_resolution = float2(t.get_width(), t.get_height());; + float2 origin = (floor(pos * input_resolution)) + float2(0.5, 0.5); + return texture(t, (origin + offset) / input_resolution); +} + #line 1 {filter} diff --git a/thirdparty/SameBoy/Shaders/OmniScale.fsh b/thirdparty/SameBoy/Shaders/OmniScale.fsh index eab27ae88..28ce3211d 100644 --- a/thirdparty/SameBoy/Shaders/OmniScale.fsh +++ b/thirdparty/SameBoy/Shaders/OmniScale.fsh @@ -27,7 +27,7 @@ STATIC bool is_different(vec4 a, vec4 b) STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; + vec2 o = vec2(1, 1); /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ @@ -43,15 +43,15 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou p.y = 1.0 - p.y; } - vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, position + vec2( 0, -o.y)); - vec4 w2 = texture(image, position + vec2( o.x, -o.y)); - vec4 w3 = texture(image, position + vec2( -o.x, 0)); - vec4 w4 = texture(image, position + vec2( 0, 0)); - vec4 w5 = texture(image, position + vec2( o.x, 0)); - vec4 w6 = texture(image, position + vec2( -o.x, o.y)); - vec4 w7 = texture(image, position + vec2( 0, o.y)); - vec4 w8 = texture(image, position + vec2( o.x, o.y)); + vec4 w0 = texture_relative(image, position, vec2( -o.x, -o.y)); + vec4 w1 = texture_relative(image, position, vec2( 0, -o.y)); + vec4 w2 = texture_relative(image, position, vec2( o.x, -o.y)); + vec4 w3 = texture_relative(image, position, vec2( -o.x, 0)); + vec4 w4 = texture_relative(image, position, vec2( 0, 0)); + vec4 w5 = texture_relative(image, position, vec2( o.x, 0)); + vec4 w6 = texture_relative(image, position, vec2( -o.x, o.y)); + vec4 w7 = texture_relative(image, position, vec2( 0, o.y)); + vec4 w8 = texture_relative(image, position, vec2( o.x, o.y)); int pattern = 0; if (is_different(w0, w4)) pattern |= 1 << 0; @@ -63,28 +63,28 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (is_different(w7, w4)) pattern |= 1 << 6; if (is_different(w8, w4)) pattern |= 1 << 7; - if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5)) { + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { return mix(w4, w3, 0.5 - p.x); } - if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3)) { + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { return mix(w4, w1, 0.5 - p.y); } - if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1)) { + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { return w4; } - if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) || - P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) || - P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) || - P(0xeb,0x8a)) && is_different(w3, w1)) { + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); } - if (P(0x0b,0x08)) { + if (P(0x0B,0x08)) { return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); } - if (P(0x0b,0x02)) { + if (P(0x0B,0x02)) { return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); } - if (P(0x2f,0x2f)) { + if (P(0x2F,0x2F)) { float dist = length(p - vec2(0.5)); float pixel_size = length(1.0 / (output_resolution / input_resolution)); if (dist < 0.5 - pixel_size / 2) { @@ -103,7 +103,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); } - if (P(0xbf,0x37) || P(0xdb,0x13)) { + if (P(0xBF,0x37) || P(0xDB,0x13)) { float dist = p.x - 2.0 * p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (dist > pixel_size / 2) { @@ -115,7 +115,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } return mix(r, w1, (dist + pixel_size / 2) / pixel_size); } - if (P(0xdb,0x49) || P(0xef,0x6d)) { + if (P(0xDB,0x49) || P(0xEF,0x6D)) { float dist = p.y - 2.0 * p.x; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); if (p.y - 2.0 * p.x > pixel_size / 2) { @@ -127,7 +127,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } return mix(r, w3, (dist + pixel_size / 2) / pixel_size); } - if (P(0xbf,0x8f) || P(0x7e,0x0e)) { + if (P(0xBF,0x8F) || P(0x7E,0x0E)) { float dist = p.x + 2.0 * p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); @@ -150,7 +150,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); } - if (P(0x7e,0x2a) || P(0xef,0xab)) { + if (P(0x7E,0x2A) || P(0xEF,0xAB)) { float dist = p.y + 2.0 * p.x; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); @@ -174,22 +174,22 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); } - if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) { + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { return mix(w4, w3, 0.5 - p.x); } - if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19)) { + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { return mix(w4, w1, 0.5 - p.y); } - if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) || - P(0xdf,0xde) || P(0xdf,0x1e)) { + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); } - if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) || - P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) || - P(0x3b,0x1b)) { + if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { float dist = p.x + p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)); @@ -212,11 +212,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); } - if (P(0x0b,0x01)) { + if (P(0x0B,0x01)) { return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); } - if (P(0x0b,0x00)) { + if (P(0x0B,0x00)) { return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); } @@ -228,13 +228,13 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } /* We need more samples to "solve" this diagonal */ - vec4 x0 = texture(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); - vec4 x1 = texture(image, position + vec2( -o.x , -o.y * 2.0)); - vec4 x2 = texture(image, position + vec2( 0.0 , -o.y * 2.0)); - vec4 x3 = texture(image, position + vec2( o.x , -o.y * 2.0)); - vec4 x4 = texture(image, position + vec2( -o.x * 2.0, -o.y )); - vec4 x5 = texture(image, position + vec2( -o.x * 2.0, 0.0 )); - vec4 x6 = texture(image, position + vec2( -o.x * 2.0, o.y )); + vec4 x0 = texture_relative(image, position, vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture_relative(image, position, vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture_relative(image, position, vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture_relative(image, position, vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture_relative(image, position, vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture_relative(image, position, vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture_relative(image, position, vec2( -o.x * 2.0, o.y )); if (is_different(x0, w4)) pattern |= 1 << 8; if (is_different(x1, w4)) pattern |= 1 << 9; diff --git a/thirdparty/SameBoy/Shaders/Scale2x.fsh b/thirdparty/SameBoy/Shaders/Scale2x.fsh index 17b6edb8d..44bcfc4d6 100644 --- a/thirdparty/SameBoy/Shaders/Scale2x.fsh +++ b/thirdparty/SameBoy/Shaders/Scale2x.fsh @@ -2,22 +2,16 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; - // texel arrangement // A B C // D E F // G H I - // vec4 A = texture(image, position + vec2( -o.x, o.y)); - vec4 B = texture(image, position + vec2( 0, o.y)); - // vec4 C = texture(image, position + vec2( o.x, o.y)); - vec4 D = texture(image, position + vec2( -o.x, 0)); - vec4 E = texture(image, position + vec2( 0, 0)); - vec4 F = texture(image, position + vec2( o.x, 0)); - // vec4 G = texture(image, position + vec2( -o.x, -o.y)); - vec4 H = texture(image, position + vec2( 0, -o.y)); - // vec4 I = texture(image, position + vec2( o.x, -o.y)); + + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] p = fract(p); diff --git a/thirdparty/SameBoy/Shaders/Scale4x.fsh b/thirdparty/SameBoy/Shaders/Scale4x.fsh index da1ff1487..93c4b2159 100644 --- a/thirdparty/SameBoy/Shaders/Scale4x.fsh +++ b/thirdparty/SameBoy/Shaders/Scale4x.fsh @@ -1,23 +1,16 @@ STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - // o = offset, the width of a pixel - vec2 o = 1.0 / input_resolution; // texel arrangement // A B C // D E F // G H I - // vec4 A = texture(image, position + vec2( -o.x, o.y)); - vec4 B = texture(image, position + vec2( 0, o.y)); - // vec4 C = texture(image, position + vec2( o.x, o.y)); - vec4 D = texture(image, position + vec2( -o.x, 0)); - vec4 E = texture(image, position + vec2( 0, 0)); - vec4 F = texture(image, position + vec2( o.x, 0)); - // vec4 G = texture(image, position + vec2( -o.x, -o.y)); - vec4 H = texture(image, position + vec2( 0, -o.y)); - // vec4 I = texture(image, position + vec2( o.x, -o.y)); + vec4 B = texture_relative(image, position, vec2( 0, 1)); + vec4 D = texture_relative(image, position, vec2( -1, 0)); + vec4 E = texture_relative(image, position, vec2( 0, 0)); + vec4 F = texture_relative(image, position, vec2( 1, 0)); + vec4 H = texture_relative(image, position, vec2( 0, -1)); vec2 p = position * input_resolution; // p = the position within a pixel [0...1] - vec4 R; p = fract(p); if (p.x > .5) { if (p.y > .5) { @@ -38,24 +31,24 @@ STATIC vec4 scale2x(sampler2D image, vec2 position, vec2 input_resolution, vec2 } } +STATIC vec4 scale2x_wrapper(sampler2D t, vec2 pos, vec2 offset, vec2 input_resolution, vec2 output_resolution) +{ + vec2 origin = (floor(pos * input_resolution * 2)) + vec2(0.5, 0.5); + return scale2x(t, (origin + offset) / input_resolution / 2, input_resolution, output_resolution); +} + STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { - // o = offset, the width of a pixel - vec2 o = 1.0 / (input_resolution * 2.); - // texel arrangement // A B C // D E F // G H I - // vec4 A = scale2x(image, position + vec2( -o.x, o.y), input_resolution, output_resolution); - vec4 B = scale2x(image, position + vec2( 0, o.y), input_resolution, output_resolution); - // vec4 C = scale2x(image, position + vec2( o.x, o.y), input_resolution, output_resolution); - vec4 D = scale2x(image, position + vec2( -o.x, 0), input_resolution, output_resolution); - vec4 E = scale2x(image, position + vec2( 0, 0), input_resolution, output_resolution); - vec4 F = scale2x(image, position + vec2( o.x, 0), input_resolution, output_resolution); - // vec4 G = scale2x(image, position + vec2( -o.x, -o.y), input_resolution, output_resolution); - vec4 H = scale2x(image, position + vec2( 0, -o.y), input_resolution, output_resolution); - // vec4 I = scale2x(image, position + vec2( o.x, -o.y), input_resolution, output_resolution); + vec4 B = scale2x_wrapper(image, position, vec2( 0, 1), input_resolution, output_resolution); + vec4 D = scale2x_wrapper(image, position, vec2( -1, 0), input_resolution, output_resolution); + vec4 E = scale2x_wrapper(image, position, vec2( 0, 0), input_resolution, output_resolution); + vec4 F = scale2x_wrapper(image, position, vec2( 1, 0), input_resolution, output_resolution); + vec4 H = scale2x_wrapper(image, position, vec2( 0, -1), input_resolution, output_resolution); + vec2 p = position * input_resolution * 2.; // p = the position within a pixel [0...1] p = fract(p); diff --git a/thirdparty/SameBoy/Tester/main.c b/thirdparty/SameBoy/Tester/main.c index b0f1b3189..a1c89a52f 100755 --- a/thirdparty/SameBoy/Tester/main.c +++ b/thirdparty/SameBoy/Tester/main.c @@ -27,13 +27,13 @@ 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; static unsigned int frames = 0; static bool use_tga = false; -static const uint8_t bmp_header[] = { +static 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, @@ -45,13 +45,13 @@ static const uint8_t bmp_header[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -static const uint8_t tga_header[] = { +static 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]; +uint32_t bitmap[256*224]; static char *async_input_callback(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. */ @@ -120,7 +123,7 @@ static void handle_buttons(GB_gameboy_t *gb) } -static void vblank(GB_gameboy_t *gb) +static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) { /* Detect common crashes and stop the test early */ if (frames < test_length - 1) { @@ -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; } @@ -137,10 +140,22 @@ static void vblank(GB_gameboy_t *gb) if (frames >= test_length && !gb->disable_rendering) { bool is_screen_blank = true; - for (unsigned i = 160*144; i--;) { - if (bitmap[i] != bitmap[0]) { - is_screen_blank = false; - break; + if (!gb->sgb) { + for (unsigned i = 160 * 144; i--;) { + if (bitmap[i] != bitmap[0]) { + is_screen_blank = false; + break; + } + } + } + else { + if (gb->sgb->mask_mode == 0) { + for (unsigned i = 160 * 144; i--;) { + if (gb->sgb->screen_buffer[i] != gb->sgb->screen_buffer[0]) { + is_screen_blank = false; + break; + } + } } } @@ -148,12 +163,20 @@ static void vblank(GB_gameboy_t *gb) if (!is_screen_blank || frames >= test_length + 60 * 4) { FILE *f = fopen(bmp_filename, "wb"); if (use_tga) { + tga_header[0xC] = GB_get_screen_width(gb); + tga_header[0xD] = GB_get_screen_width(gb) >> 8; + tga_header[0xE] = GB_get_screen_height(gb); + tga_header[0xF] = GB_get_screen_height(gb) >> 8; fwrite(&tga_header, 1, sizeof(tga_header), f); } else { + (*(uint32_t *)&bmp_header[0x2]) = sizeof(bmp_header) + sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2; + (*(uint32_t *)&bmp_header[0x12]) = GB_get_screen_width(gb); + (*(int32_t *)&bmp_header[0x16]) = -GB_get_screen_height(gb); + (*(uint32_t *)&bmp_header[0x22]) = sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2; fwrite(&bmp_header, 1, sizeof(bmp_header), f); } - fwrite(&bitmap, 1, sizeof(bitmap), f); + fwrite(&bitmap, 1, sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb), f); fclose(f); if (!gb->boot_rom_finished) { GB_log(gb, "Boot ROM did not finish.\n"); @@ -265,12 +288,10 @@ 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]" + fprintf(stderr, "Usage: %s [--dmg] [--sgb] [--cgb] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" #ifndef _WIN32 " [--jobs number of tests to run simultaneously]" #endif @@ -284,6 +305,7 @@ int main(int argc, char **argv) #endif bool dmg = false; + bool sgb = false; bool sav = false; const char *boot_rom_path = NULL; @@ -293,6 +315,21 @@ int main(int argc, char **argv) if (strcmp(argv[i], "--dmg") == 0) { fprintf(stderr, "Using DMG mode\n"); dmg = true; + sgb = false; + continue; + } + + if (strcmp(argv[i], "--sgb") == 0) { + fprintf(stderr, "Using SGB mode\n"); + sgb = true; + dmg = false; + continue; + } + + if (strcmp(argv[i], "--cgb") == 0) { + fprintf(stderr, "Using CGB mode\n"); + dmg = false; + sgb = false; continue; } @@ -373,6 +410,13 @@ int main(int argc, char **argv) exit(1); } } + else if (sgb) { + GB_init(&gb, GB_MODEL_SGB2); + if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("sgb2_boot.bin"))) { + fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("sgb2_boot.bin")); + exit(1); + } + } else { GB_init(&gb, GB_MODEL_CGB_E); if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("cgb_boot.bin"))) { @@ -388,6 +432,7 @@ int main(int argc, char **argv) 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); + GB_set_emulate_joypad_bouncing(&gb, false); // Adds too much noise if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); @@ -406,7 +451,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 +491,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; diff --git a/thirdparty/SameBoy/Windows/dirent.c b/thirdparty/SameBoy/Windows/dirent.c new file mode 100644 index 000000000..f5fd8b3b2 --- /dev/null +++ b/thirdparty/SameBoy/Windows/dirent.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include "dirent.h" + +DIR *opendir(const char *filename) +{ + wchar_t w_filename[MAX_PATH + 3] = {0,}; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, MAX_PATH); + if (length) { + w_filename[length - 1] = L'/'; + w_filename[length] = L'*'; + w_filename[length + 1] = 0; + } + DIR *ret = malloc(sizeof(*ret)); + ret->handle = FindFirstFileW(w_filename, &ret->entry); + if (ret->handle == INVALID_HANDLE_VALUE) { + free(ret); + return NULL; + } + + return ret; +} + +int closedir(DIR *dir) +{ + if (dir->handle != INVALID_HANDLE_VALUE) { + FindClose(dir->handle); + } + free(dir); + return 0; +} + +struct dirent *readdir(DIR *dir) +{ + if (dir->handle == INVALID_HANDLE_VALUE) { + return NULL; + } + + WideCharToMultiByte(CP_UTF8, 0, dir->entry.cFileName, -1, + dir->out_entry.d_name, sizeof(dir->out_entry.d_name), + NULL, NULL); + + if (!FindNextFileW(dir->handle, &dir->entry)) { + FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + return &dir->out_entry; +} diff --git a/thirdparty/SameBoy/Windows/dirent.h b/thirdparty/SameBoy/Windows/dirent.h new file mode 100644 index 000000000..7102995b2 --- /dev/null +++ b/thirdparty/SameBoy/Windows/dirent.h @@ -0,0 +1,15 @@ +#include + +struct dirent { + char d_name[MAX_PATH + 1]; +}; + +typedef struct { + HANDLE handle; + WIN32_FIND_DATAW entry; + struct dirent out_entry; +} DIR; + +DIR *opendir(const char *filename); +int closedir(DIR *dir); +struct dirent *readdir(DIR *dir); diff --git a/thirdparty/SameBoy/Windows/pthread.h b/thirdparty/SameBoy/Windows/pthread.h new file mode 100644 index 000000000..7bf328bb0 --- /dev/null +++ b/thirdparty/SameBoy/Windows/pthread.h @@ -0,0 +1,86 @@ +/* Very minimal pthread implementation for Windows */ +#include +#include + +typedef HANDLE pthread_t; +typedef struct pthread_attr_s pthread_attr_t; + +static inline int pthread_create(pthread_t *pthread, + const pthread_attr_t *attrs, + LPTHREAD_START_ROUTINE function, + void *context) +{ + assert(!attrs); + *pthread = CreateThread(NULL, 0, function, + context, 0, NULL); + return *pthread? 0 : GetLastError(); +} + + +typedef struct { + unsigned status; + CRITICAL_SECTION cs; +} pthread_mutex_t; +#define PTHREAD_MUTEX_INITIALIZER {0,} + +static inline CRITICAL_SECTION *pthread_mutex_to_cs(pthread_mutex_t *mutex) +{ +retry: + if (mutex->status == 2) return &mutex->cs; + + unsigned expected = 0; + unsigned desired = 1; + if (__atomic_compare_exchange(&mutex->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + InitializeCriticalSection(&mutex->cs); + mutex->status = 2; + return &mutex->cs; + } + goto retry; +} + +static inline int pthread_mutex_lock(pthread_mutex_t *mutex) +{ + EnterCriticalSection(pthread_mutex_to_cs(mutex)); + return 0; +} + +static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + LeaveCriticalSection(pthread_mutex_to_cs(mutex)); + return 0; +} + +typedef struct { + unsigned status; + CONDITION_VARIABLE cond; +} pthread_cond_t; +#define PTHREAD_COND_INITIALIZER {0,} + +static inline CONDITION_VARIABLE *pthread_cond_to_win(pthread_cond_t *cond) +{ +retry: + if (cond->status == 2) return &cond->cond; + + unsigned expected = 0; + unsigned desired = 1; + if (__atomic_compare_exchange(&cond->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + InitializeConditionVariable(&cond->cond); + cond->status = 2; + return &cond->cond; + } + goto retry; +} + + +static inline int pthread_cond_signal(pthread_cond_t *cond) +{ + WakeConditionVariable(pthread_cond_to_win(cond)); + return 0; +} + +static inline int pthread_cond_wait(pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + // Mutex is locked therefore already initialized + return SleepConditionVariableCS(pthread_cond_to_win(cond), &mutex->cs, INFINITE); +} diff --git a/thirdparty/SameBoy/Windows/resources.rc b/thirdparty/SameBoy/Windows/resources.rc index 73c12139e..7fd16edcf 100644 Binary files a/thirdparty/SameBoy/Windows/resources.rc and b/thirdparty/SameBoy/Windows/resources.rc differ diff --git a/thirdparty/SameBoy/Windows/sameboy.ico b/thirdparty/SameBoy/Windows/sameboy.ico index bd8e372d9..17f072c14 100644 Binary files a/thirdparty/SameBoy/Windows/sameboy.ico and b/thirdparty/SameBoy/Windows/sameboy.ico differ diff --git a/thirdparty/SameBoy/Windows/stdio.h b/thirdparty/SameBoy/Windows/stdio.h index 1e6ec02f9..4a9279e3d 100755 --- a/thirdparty/SameBoy/Windows/stdio.h +++ b/thirdparty/SameBoy/Windows/stdio.h @@ -1,10 +1,11 @@ #pragma once #include_next #include +#include int access(const char *filename, int mode); -#define R_OK 2 -#define W_OK 4 +#define R_OK 4 +#define W_OK 2 #ifndef __MINGW32__ #ifndef __LIBRETRO__ @@ -20,6 +21,16 @@ static inline int vasprintf(char **str, const char *fmt, va_list args) } return ret; } + +static inline int asprintf(char **strp, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int r = vasprintf(strp, fmt, args); + va_end(args); + return r; +} + #endif #endif diff --git a/thirdparty/SameBoy/Windows/stdlib.h b/thirdparty/SameBoy/Windows/stdlib.h new file mode 100644 index 000000000..7d35615ff --- /dev/null +++ b/thirdparty/SameBoy/Windows/stdlib.h @@ -0,0 +1,3 @@ +#pragma once +#include_next +#define alloca _alloca diff --git a/thirdparty/SameBoy/Windows/string.h b/thirdparty/SameBoy/Windows/string.h index b899ca974..f1cf6b1e9 100755 --- a/thirdparty/SameBoy/Windows/string.h +++ b/thirdparty/SameBoy/Windows/string.h @@ -1,3 +1,4 @@ #pragma once #include_next -#define strdup _strdup \ No newline at end of file +#define strdup _strdup +#define strcasecmp _stricmp diff --git a/thirdparty/SameBoy/Windows/unistd.h b/thirdparty/SameBoy/Windows/unistd.h index b7aabf295..c17587e45 100644 --- a/thirdparty/SameBoy/Windows/unistd.h +++ b/thirdparty/SameBoy/Windows/unistd.h @@ -5,3 +5,6 @@ #define read(...) _read(__VA_ARGS__) #define write(...) _write(__VA_ARGS__) +#define isatty(...) _isatty(__VA_ARGS__) +#define close(...) _close(__VA_ARGS__) +#define creat(...) _creat(__VA_ARGS__) diff --git a/thirdparty/SameBoy/Windows/utf8_compat.c b/thirdparty/SameBoy/Windows/utf8_compat.c index 03472115d..9264e2e80 100755 --- a/thirdparty/SameBoy/Windows/utf8_compat.c +++ b/thirdparty/SameBoy/Windows/utf8_compat.c @@ -22,3 +22,10 @@ int access(const char *filename, int mode) return _waccess(w_filename, mode); } +int _creat(const char *filename, int mode) +{ + wchar_t w_filename[MAX_PATH] = {0,}; + MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename) / sizeof(w_filename[0])); + return _wcreat(w_filename, mode & 0700); +} + diff --git a/thirdparty/SameBoy/libretro/Makefile b/thirdparty/SameBoy/libretro/Makefile index 50ab91fdb..ada200df2 100644 --- a/thirdparty/SameBoy/libretro/Makefile +++ b/thirdparty/SameBoy/libretro/Makefile @@ -311,7 +311,7 @@ endif include Makefile.common -CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" +CFLAGS += -DGB_VERSION=\"$(VERSION)\" OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) diff --git a/thirdparty/SameBoy/libretro/Makefile.common b/thirdparty/SameBoy/libretro/Makefile.common index fabe3ad4a..2093d0d63 100644 --- a/thirdparty/SameBoy/libretro/Makefile.common +++ b/thirdparty/SameBoy/libretro/Makefile.common @@ -18,6 +18,8 @@ SOURCES_C := $(CORE_DIR)/Core/gb.c \ $(CORE_DIR)/Core/rumble.c \ $(CORE_DIR)/libretro/agb_boot.c \ $(CORE_DIR)/libretro/cgb_boot.c \ + $(CORE_DIR)/libretro/cgb0_boot.c \ + $(CORE_DIR)/libretro/mgb_boot.c \ $(CORE_DIR)/libretro/dmg_boot.c \ $(CORE_DIR)/libretro/sgb_boot.c \ $(CORE_DIR)/libretro/sgb2_boot.c \ diff --git a/thirdparty/SameBoy/libretro/jni/Android.mk b/thirdparty/SameBoy/libretro/jni/Android.mk index e0646b9af..8ac1b3bab 100644 --- a/thirdparty/SameBoy/libretro/jni/Android.mk +++ b/thirdparty/SameBoy/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 -DANDROID +COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DGB_VERSION=\"$(VERSION)\" -Wno-multichar -DANDROID GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") diff --git a/thirdparty/SameBoy/libretro/libretro.c b/thirdparty/SameBoy/libretro/libretro.c index 918a657f9..7b8c779b6 100644 --- a/thirdparty/SameBoy/libretro/libretro.c +++ b/thirdparty/SameBoy/libretro/libretro.c @@ -6,7 +6,7 @@ #include #include #include - +#include #ifndef WIIU #define AUDIO_FREQUENCY 384000 #else @@ -22,6 +22,7 @@ #include #include "libretro.h" +#include "libretro_core_options.inc" #ifdef _WIN32 static const char slash = '\\'; @@ -33,7 +34,6 @@ static const char slash = '/'; #define MAX_VIDEO_HEIGHT 224 #define MAX_VIDEO_PIXELS (MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT) - #define RETRO_MEMORY_GAMEBOY_1_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GAMEBOY_1_RTC ((2 << 8) | RETRO_MEMORY_RTC) #define RETRO_MEMORY_GAMEBOY_2_SRAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) @@ -41,26 +41,14 @@ static const char slash = '/'; #define RETRO_GAME_TYPE_GAMEBOY_LINK_2P 0x101 -char battery_save_path[512]; -char symbols_path[512]; - -enum model { - MODEL_DMG, - MODEL_CGB, - MODEL_AGB, - MODEL_SGB, - MODEL_SGB2, - MODEL_AUTO +enum rom_type { + ROM_TYPE_INVALID, + ROM_TYPE_DMG, + ROM_TYPE_SGB, + ROM_TYPE_CGB }; -static const GB_model_t libretro_to_internal_model[] = -{ - [MODEL_DMG] = GB_MODEL_DMG_B, - [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB, - [MODEL_SGB] = GB_MODEL_SGB, - [MODEL_SGB2] = GB_MODEL_SGB2 -}; +#define GB_MODEL_AUTO (-1) enum screen_layout { LAYOUT_TOP_DOWN, @@ -72,16 +60,32 @@ enum audio_out { GB_2 }; -static enum model model[2]; -static enum model auto_model = MODEL_CGB; +static GB_model_t model[2] = { + GB_MODEL_DMG_B, + GB_MODEL_DMG_B +}; +static GB_model_t auto_model[2] = { + GB_MODEL_CGB_E, + GB_MODEL_CGB_E +}; +static GB_model_t auto_sgb_model[2] = { + GB_MODEL_SGB_NTSC, + GB_MODEL_SGB_NTSC +}; +static bool auto_sgb_enabled[2] = { + false, + false +}; static uint32_t *frame_buf = NULL; static uint32_t *frame_buf_copy = NULL; +static uint32_t retained_frame_1[256 * 224]; +static uint32_t retained_frame_2[256 * 224]; static struct retro_log_callback logging; static retro_log_printf_t log_cb; static retro_video_refresh_t video_cb; -static retro_audio_sample_t audio_sample_cb; +static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; @@ -96,16 +100,18 @@ static bool geometry_updated = false; static bool link_cable_emulation = false; /*static bool infrared_emulation = false;*/ -signed short soundbuf[1024 * 2]; +static struct { + int16_t *data; + int32_t size; + int32_t capacity; +} output_audio_buffer = {NULL, 0, 0}; char retro_system_directory[4096]; -char retro_save_directory[4096]; -char retro_game_path[4096]; GB_gameboy_t gameboy[2]; -extern const unsigned char dmg_boot[], cgb_boot[], agb_boot[], sgb_boot[], sgb2_boot[]; -extern const unsigned dmg_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length; +extern const unsigned char dmg_boot[], mgb_boot[], cgb0_boot[], cgb_boot[], agb_boot[], sgb_boot[], sgb2_boot[]; +extern const unsigned dmg_boot_length, mgb_boot_length, cgb0_boot_length, cgb_boot_length, agb_boot_length, sgb_boot_length, sgb2_boot_length; bool vblank1_occurred = false, vblank2_occurred = false; static void fallback_log(enum retro_log_level level, const char *fmt, ...) @@ -131,7 +137,7 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) else { unsigned j; - for (j = 0; j < (RETRO_DEVICE_ID_JOYPAD_R3+1); 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); } @@ -160,7 +166,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); } @@ -169,24 +175,99 @@ static void rumble_callback(GB_gameboy_t *gb, double amplitude) } } +static void ensure_output_audio_buffer_capacity(int32_t capacity) +{ + if (capacity <= output_audio_buffer.capacity) { + return; + } + output_audio_buffer.data = realloc( + output_audio_buffer.data, capacity * sizeof(*output_audio_buffer.data)); + output_audio_buffer.capacity = capacity; + log_cb(RETRO_LOG_DEBUG, "Output audio buffer capacity set to %d\n", capacity); +} + +static void init_output_audio_buffer(int32_t capacity) +{ + output_audio_buffer.data = NULL; + output_audio_buffer.size = 0; + output_audio_buffer.capacity = 0; + ensure_output_audio_buffer_capacity(capacity); +} + +static void free_output_audio_buffer() +{ + free(output_audio_buffer.data); + output_audio_buffer.data = NULL; + output_audio_buffer.size = 0; + output_audio_buffer.capacity = 0; +} + +static void upload_output_audio_buffer() +{ + int32_t remaining_frames = output_audio_buffer.size / 2; + int16_t *buf_pos = output_audio_buffer.data; + + while (remaining_frames > 0) { + size_t uploaded_frames = audio_batch_cb(buf_pos, remaining_frames); + buf_pos += uploaded_frames * 2; + remaining_frames -= uploaded_frames; + } + output_audio_buffer.size = 0; +} + static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { - if ((audio_out == GB_1 && gb == &gameboy[0]) || - (audio_out == GB_2 && gb == &gameboy[1])) { - audio_sample_cb(sample->left, sample->right); + if (!(audio_out == GB_1 && gb == &gameboy[0]) && + !(audio_out == GB_2 && gb == &gameboy[1])) { + return; } + + if (output_audio_buffer.capacity - output_audio_buffer.size < 2) { + ensure_output_audio_buffer_capacity(output_audio_buffer.capacity * 1.5); + } + + output_audio_buffer.data[output_audio_buffer.size++] = sample->left; + output_audio_buffer.data[output_audio_buffer.size++] = sample->right; } -static void vblank1(GB_gameboy_t *gb) +static void vblank1(GB_gameboy_t *gb, GB_vblank_type_t type) { + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_1, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } vblank1_occurred = true; } -static void vblank2(GB_gameboy_t *gb) +static void vblank2(GB_gameboy_t *gb, GB_vblank_type_t type) { + if (type == GB_VBLANK_TYPE_REPEAT) { + memcpy(GB_get_pixels_output(gb), + retained_frame_2, + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } vblank2_occurred = true; } +static void lcd_status_change_1(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_1, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + +static void lcd_status_change_2(GB_gameboy_t *gb, bool on) +{ + if (!on) { + memcpy(retained_frame_2, + GB_get_pixels_output(gb), + GB_get_screen_width(gb) * GB_get_screen_height(gb) * sizeof(uint32_t)); + } +} + static bool bit_to_send1 = true, bit_to_send2 = true; static void serial_start1(GB_gameboy_t *gb, bool bit_received) @@ -230,33 +311,71 @@ 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|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|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" }, - { "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 + while (true) { + 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_auto_sgb_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_auto_sgb_model_1") == 0) || + (strcmp(key, "sameboy_model_2") == 0) || + (strcmp(key, "sameboy_auto_sgb_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 }, @@ -269,8 +388,8 @@ static const struct retro_subsystem_memory_info gb2_memory[] = { }; static const struct retro_subsystem_rom_info gb_roms[] = { - { "GameBoy #1", "gb|gbc", true, false, true, gb1_memory, 1 }, - { "GameBoy #2", "gb|gbc", true, false, true, gb2_memory, 1 }, + { "GameBoy #1", "gb|gbc", false, false, true, gb1_memory, 1 }, + { "GameBoy #2", "gb|gbc", false, false, true, gb2_memory, 1 }, }; static const struct retro_subsystem_info subsystems[] = { @@ -357,7 +476,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 +484,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); @@ -377,40 +496,39 @@ 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", + const char *model_name = (char *[]) { + [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 + + const uint8_t *boot_code = (const unsigned char *[]) { + [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_MGB] = mgb_boot, [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] = cgb0_boot, [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 + + unsigned boot_length = (unsigned []) { + [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_MGB] = mgb_boot_length, [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] = cgb0_boot_length, [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); @@ -491,20 +609,21 @@ static void retro_set_memory_maps(void) static void init_for_current_model(unsigned id) { unsigned i = id; - enum model effective_model; + GB_model_t effective_model; effective_model = model[i]; - if (effective_model == MODEL_AUTO) { - effective_model = auto_model; + if (effective_model == GB_MODEL_AUTO) { + effective_model = auto_model[i]; } - if (GB_is_inited(&gameboy[i])) { - GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]); + GB_switch_model_and_reset(&gameboy[i], effective_model); + retro_set_memory_maps(); } else { - GB_init(&gameboy[i], libretro_to_internal_model[effective_model]); + GB_init(&gameboy[i], effective_model); } + geometry_updated = true; GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load); @@ -519,15 +638,17 @@ static void init_for_current_model(unsigned id) /* todo: attempt to make these more generic */ GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1); + GB_set_lcd_status_callback(&gameboy[0], lcd_status_change_1); if (emulated_devices == 2) { GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2); + GB_set_lcd_status_callback(&gameboy[2], lcd_status_change_2); if (link_cable_emulation) { set_link_cable_state(true); } } /* Let's be extremely nitpicky about how devices and descriptors are set */ - if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { + if (emulated_devices == 1 && (model[0] == GB_MODEL_SGB_PAL || model[0] == GB_MODEL_SGB_NTSC || model[0] == GB_MODEL_SGB2)) { static const struct retro_controller_info ports[] = { { controllers_sgb, 1 }, { controllers_sgb, 1 }, @@ -538,7 +659,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 +667,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 }, @@ -555,16 +676,111 @@ 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_2p); } +} +static GB_model_t string_to_model(const char *string) +{ + static const struct { + const char *name; + GB_model_t model; + } models[] = { + { "Game Boy", GB_MODEL_DMG_B}, + { "Game Boy Pocket", GB_MODEL_MGB}, + { "Game Boy Color 0", GB_MODEL_CGB_0}, + { "Game Boy Color A", GB_MODEL_CGB_A}, + { "Game Boy Color B", GB_MODEL_CGB_B}, + { "Game Boy Color C", GB_MODEL_CGB_C}, + { "Game Boy Color D", GB_MODEL_CGB_D}, + { "Game Boy Color", GB_MODEL_CGB_E}, + { "Game Boy Advance", GB_MODEL_AGB_A}, + { "Game Boy Player", GB_MODEL_GBP_A}, + { "Super Game Boy", GB_MODEL_SGB_NTSC}, + { "Super Game Boy PAL", GB_MODEL_SGB_PAL}, + { "Super Game Boy 2", GB_MODEL_SGB2}, + }; + for (unsigned i = 0; i < sizeof(models) / sizeof(models[0]); i++) { + if (strcmp(models[i].name, string) == 0) { + return models[i].model; + } + } + return GB_MODEL_AUTO; } static void check_variables() { struct retro_variable var = {0}; - if (emulated_devices == 1) { + if (emulated_devices == 1) { + + var.key = "sameboy_model"; + var.value = NULL; + + model[0] = GB_MODEL_AUTO; + auto_sgb_enabled[0] = false; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = model[0]; + new_model = string_to_model(var.value); + if (new_model == GB_MODEL_AUTO) { + if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = GB_MODEL_AUTO; + auto_sgb_enabled[0] = true; + } + } + + model[0] = new_model; + } + + var.key = "sameboy_auto_sgb_model"; + var.value = NULL; + + auto_sgb_model[0] = GB_MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = auto_sgb_model[0]; + if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = GB_MODEL_SGB_PAL; + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { + new_model = GB_MODEL_SGB2; + } + else { + new_model = GB_MODEL_SGB_NTSC; + } + + auto_sgb_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); } @@ -572,16 +788,63 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); } else if (strcmp(var.value, "emulate hardware") == 0) { - GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BALANCED); } else if (strcmp(var.value, "preserve brightness") == 0) { - GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); } 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } + } + + var.key = "sameboy_light_temperature"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_light_temperature(&gameboy[0], atof(var.value)); } - + + var.key = "sameboy_border"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); + } + else if (strcmp(var.value, "Super Game Boy only") == 0) { + GB_set_border_mode(&gameboy[0], GB_BORDER_SGB); + } + 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) { + GB_set_interference_volume(&gameboy[0], atoi(var.value) / 100.0); + } + var.key = "sameboy_rumble"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { @@ -595,82 +858,172 @@ static void check_variables() GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); } } - - var.key = "sameboy_rtc"; + + } + 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) { - if (strcmp(var.value, "sync to system clock") == 0) { - GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_SYNC_TO_HOST); + bool tmp = link_cable_emulation; + if (strcmp(var.value, "enabled") == 0) { + link_cable_emulation = true; } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); + 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_high_pass_filter_mode"; + var.key = "sameboy_screen_layout"; 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); + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "top-down") == 0) { + screen_layout = LAYOUT_TOP_DOWN; } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); + else { + screen_layout = LAYOUT_LEFT_RIGHT; } - else if (strcmp(var.value, "remove dc offset") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + + 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"; + 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; + + model[0] = GB_MODEL_AUTO; + auto_sgb_enabled[0] = false; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = model[0]; + new_model = string_to_model(var.value); + if (new_model == GB_MODEL_AUTO) { + if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = GB_MODEL_AUTO; + auto_sgb_enabled[0] = true; + } } - else if (strcmp(var.value, "Game Boy Color") == 0) { - new_model = MODEL_CGB; + + model[0] = new_model; + } + + + var.key = "sameboy_auto_sgb_model_1"; + var.value = NULL; + + auto_sgb_model[0] = GB_MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = auto_sgb_model[0]; + if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = GB_MODEL_SGB_PAL; } - else if (strcmp(var.value, "Game Boy Advance") == 0) { - new_model = MODEL_AGB; + else if (strcmp(var.value, "Super Game Boy 2") == 0) { + new_model = GB_MODEL_SGB2; } - else if (strcmp(var.value, "Super Game Boy") == 0) { - new_model = MODEL_SGB; + else { + new_model = GB_MODEL_SGB_NTSC; + } + + auto_sgb_model[0] = new_model; + } + + var.key = "sameboy_model_2"; + var.value = NULL; + + model[1] = GB_MODEL_AUTO; + auto_sgb_enabled[1] = false; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = model[1]; + new_model = string_to_model(var.value); + if (new_model == GB_MODEL_AUTO) { + if (strcmp(var.value, "Auto (SGB)") == 0) { + new_model = GB_MODEL_AUTO; + auto_sgb_enabled[0] = true; + } + } + + model[1] = new_model; + } + + var.key = "sameboy_auto_sgb_model_2"; + var.value = NULL; + + auto_sgb_model[1] = GB_MODEL_SGB_NTSC; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_model_t new_model = auto_sgb_model[1]; + if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = GB_MODEL_SGB_PAL; } else if (strcmp(var.value, "Super Game Boy 2") == 0) { - new_model = MODEL_SGB2; + new_model = GB_MODEL_SGB2; } else { - new_model = MODEL_AUTO; + new_model = GB_MODEL_SGB_NTSC; } - model[0] = new_model; + auto_sgb_model[1] = new_model; } - var.key = "sameboy_border"; + var.key = "sameboy_mono_palette_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) { - GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); + if (strcmp(var.value, "greyscale") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_GREY); } - else if (strcmp(var.value, "Super Game Boy only") == 0) { - GB_set_border_mode(&gameboy[0], GB_BORDER_SGB); + else if (strcmp(var.value, "lime") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_DMG); } - else if (strcmp(var.value, "always") == 0) { - GB_set_border_mode(&gameboy[0], GB_BORDER_ALWAYS); + 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); } - - geometry_updated = true; } - } - 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_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); } @@ -678,19 +1031,25 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_CORRECT_CURVES); } else if (strcmp(var.value, "emulate hardware") == 0) { - GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BALANCED); } else if (strcmp(var.value, "preserve brightness") == 0) { - GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); } 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } } 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); } @@ -698,48 +1057,37 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_CORRECT_CURVES); } else if (strcmp(var.value, "emulate hardware") == 0) { - GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_BALANCED); } else if (strcmp(var.value, "preserve brightness") == 0) { - GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS); + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST); } 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); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_MODERN_ACCURATE); + } } - - var.key = "sameboy_rumble_1"; + + var.key = "sameboy_light_temperature_1"; 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); - } + GB_set_light_temperature(&gameboy[0], atof(var.value)); } - - var.key = "sameboy_rumble_2"; + + var.key = "sameboy_light_temperature_2"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) { - GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_DISABLED); - } - else if (strcmp(var.value, "rumble-enabled games") == 0) { - GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_CARTRIDGE_ONLY); - } - else if (strcmp(var.value, "all games") == 0) { - GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_ALL_GAMES); - } + GB_set_light_temperature(&gameboy[1], atof(var.value)); } var.key = "sameboy_high_pass_filter_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_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); } @@ -753,7 +1101,7 @@ static void check_variables() var.key = "sameboy_high_pass_filter_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_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); } @@ -765,100 +1113,48 @@ static void check_variables() } } - var.key = "sameboy_model_1"; + var.key = "sameboy_audio_interference_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; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_interference_volume(&gameboy[0], atoi(var.value) / 100.0); } - var.key = "sameboy_model_2"; + var.key = "sameboy_audio_interference_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; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + GB_set_interference_volume(&gameboy[1], atoi(var.value) / 100.0); } - var.key = "sameboy_screen_layout"; + var.key = "sameboy_rumble_1"; 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; + 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 { - screen_layout = LAYOUT_LEFT_RIGHT; + 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); } - - geometry_updated = true; } - var.key = "sameboy_link"; + var.key = "sameboy_rumble_2"; 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 (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_DISABLED); } - if (link_cable_emulation && link_cable_emulation != tmp) { - set_link_cable_state(true); + else if (strcmp(var.value, "rumble-enabled games") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_CARTRIDGE_ONLY); } - else if (!link_cable_emulation && link_cable_emulation != tmp) { - set_link_cable_state(false); + else if (strcmp(var.value, "all games") == 0) { + GB_set_rumble_mode(&gameboy[1], GB_RUMBLE_ALL_GAMES); } } - 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) @@ -872,13 +1168,6 @@ void retro_init(void) snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", "."); } - if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) { - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir); - } - else { - snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", "."); - } - if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging)) { log_cb = logging.log; } @@ -889,6 +1178,8 @@ void retro_init(void) if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) { libretro_supports_bitmasks = true; } + + init_output_audio_buffer(16384); } void retro_deinit(void) @@ -898,6 +1189,8 @@ void retro_deinit(void) frame_buf = NULL; frame_buf_copy = NULL; + free_output_audio_buffer(); + libretro_supports_bitmasks = false; } @@ -916,11 +1209,11 @@ void retro_get_system_info(struct retro_system_info *info) memset(info, 0, sizeof(*info)); info->library_name = "SameBoy"; #ifdef GIT_VERSION - info->library_version = SAMEBOY_CORE_VERSION GIT_VERSION; + info->library_version = GB_VERSION GIT_VERSION; #else - info->library_version = SAMEBOY_CORE_VERSION; + info->library_version = GB_VERSION; #endif - info->need_fullpath = true; + info->need_fullpath = false; info->valid_extensions = "gb|gbc"; } @@ -929,7 +1222,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; @@ -941,7 +1234,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]); @@ -954,21 +1247,25 @@ void retro_get_system_av_info(struct retro_system_av_info *info) info->timing = timing; } - void retro_set_environment(retro_environment_t cb) { + bool categories_supported; + environ_cb = cb; - cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); + libretro_set_core_options(environ_cb, &categories_supported); + + environ_cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); } void retro_set_audio_sample(retro_audio_sample_t cb) { - audio_sample_cb = cb; + (void)cb; } void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { + audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) @@ -1022,11 +1319,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] == GB_MODEL_SGB_PAL || model[0] == GB_MODEL_SGB_NTSC || model[0] == GB_MODEL_SGB2)) { for (unsigned i = 0; i < 4; i++) { GB_update_keys_status(&gameboy[0], i); } @@ -1037,7 +1334,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]); @@ -1047,11 +1344,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]), @@ -1079,33 +1376,80 @@ void retro_run(void) GB_get_screen_width(&gameboy[0]) * sizeof(uint32_t)); } - + upload_output_audio_buffer(); initialized = true; } +static enum rom_type check_rom_header(const uint8_t *data, size_t size) +{ + enum rom_type type; + uint8_t cgb_flag; + uint8_t sgb_flag; + + if (!data || (size < 0x146 + 1)) { + return ROM_TYPE_INVALID; + } + + type = ROM_TYPE_DMG; + cgb_flag = data[0x143]; + sgb_flag = data[0x146]; + + if ((cgb_flag == 0x80) || (cgb_flag == 0xC0)) { + type = ROM_TYPE_CGB; + } + + if ((type == ROM_TYPE_DMG) && (sgb_flag == 0x03)) { + type = ROM_TYPE_SGB; + } + + return type; +} + bool retro_load_game(const struct retro_game_info *info) { - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); + enum rom_type content_type = ROM_TYPE_INVALID; + const uint8_t *content_data = NULL; + size_t content_size; + + if (info) { + content_data = (const uint8_t *)info->data; + content_size = info->size; + content_type = check_rom_header(content_data, content_size); + } + check_variables(); + switch (content_type) { + case ROM_TYPE_DMG: + auto_model[0] = GB_MODEL_DMG_B; + auto_model[1] = GB_MODEL_DMG_B; + break; + case ROM_TYPE_SGB: + auto_model[0] = auto_sgb_enabled[0] ? auto_sgb_model[0] : GB_MODEL_DMG_B; + auto_model[1] = auto_sgb_enabled[1] ? auto_sgb_model[1] : GB_MODEL_DMG_B; + break; + case ROM_TYPE_CGB: + auto_model[0] = GB_MODEL_CGB_E; + auto_model[1] = GB_MODEL_CGB_E; + break; + case ROM_TYPE_INVALID: + default: + log_cb(RETRO_LOG_ERROR, "Invalid content\n"); + return false; + } + 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)) { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { + log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported\n"); return false; } - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; - 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)) { - log_cb(RETRO_LOG_INFO, "Failed to load ROM at %s\n", info->path); - return false; - } + GB_load_rom_from_buffer(&gameboy[i], content_data, content_size); } bool achievements = true; @@ -1140,15 +1484,13 @@ unsigned retro_get_region(void) bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num_info) { - - if (type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) { + if ((type == RETRO_GAME_TYPE_GAMEBOY_LINK_2P) && (num_info >= 2)) { emulated_devices = 2; } else { 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)); @@ -1158,20 +1500,36 @@ 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)) { - log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { + log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported\n"); return false; } - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; - snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); + for (int i = 0; i < emulated_devices; i++) { + enum rom_type content_type = ROM_TYPE_INVALID; + const uint8_t *content_data = info[i].data; + size_t content_size = info[i].size; - for (int i = 0; i < emulated_devices; i++) { - init_for_current_model(i); - if (GB_load_rom(&gameboy[i], info[i].path)) { - log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); - return false; + content_type = check_rom_header(content_data, content_size); + + switch (content_type) { + case ROM_TYPE_DMG: + auto_model[i] = GB_MODEL_DMG_B; + break; + case ROM_TYPE_SGB: + auto_model[i] = auto_sgb_enabled[i] ? auto_sgb_model[i] : GB_MODEL_DMG_B; + break; + case ROM_TYPE_CGB: + auto_model[i] = GB_MODEL_CGB_E; + break; + case ROM_TYPE_INVALID: + default: + log_cb(RETRO_LOG_ERROR, "Invalid content\n"); + return false; } + + init_for_current_model(i); + GB_load_rom_from_buffer(&gameboy[i], content_data, content_size); } bool achievements = true; @@ -1194,21 +1552,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; } @@ -1226,7 +1584,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; @@ -1237,7 +1595,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; @@ -1246,7 +1604,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; } @@ -1258,8 +1616,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; @@ -1286,8 +1644,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; @@ -1331,8 +1689,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; @@ -1359,8 +1717,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; @@ -1404,4 +1762,3 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) (void)enabled; (void)code; } - diff --git a/thirdparty/SameBoy/libretro/libretro.h b/thirdparty/SameBoy/libretro/libretro.h index 1fd2f5b79..4f4db1cf9 100644 --- a/thirdparty/SameBoy/libretro/libretro.h +++ b/thirdparty/SameBoy/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,497 @@ 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). + */ + +#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65 + /* const struct retro_system_content_info_override * -- + * Allows an implementation to override 'global' content + * info parameters reported by retro_get_system_info(). + * Overrides also affect subsystem content info parameters + * set via RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO. + * This function must be called inside retro_set_environment(). + * If callback returns false, content info overrides + * are unsupported by the frontend, and will be ignored. + * If callback returns true, extended game info may be + * retrieved by calling RETRO_ENVIRONMENT_GET_GAME_INFO_EXT + * in retro_load_game() or retro_load_game_special(). + * + * 'data' points to an array of retro_system_content_info_override + * structs terminated by a { NULL, false, false } element. + * If 'data' is NULL, no changes will be made to the frontend; + * a core may therefore pass NULL in order to test whether + * the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks are supported + * by the frontend. + * + * For struct member descriptions, see the definition of + * struct retro_system_content_info_override. + * + * Example: + * + * - struct retro_system_info: + * { + * "My Core", // library_name + * "v1.0", // library_version + * "m3u|md|cue|iso|chd|sms|gg|sg", // valid_extensions + * true, // need_fullpath + * false // block_extract + * } + * + * - Array of struct retro_system_content_info_override: + * { + * { + * "md|sms|gg", // extensions + * false, // need_fullpath + * true // persistent_data + * }, + * { + * "sg", // extensions + * false, // need_fullpath + * false // persistent_data + * }, + * { NULL, false, false } + * } + * + * Result: + * - Files of type m3u, cue, iso, chd will not be + * loaded by the frontend. Frontend will pass a + * valid path to the core, and core will handle + * loading internally + * - Files of type md, sms, gg will be loaded by + * the frontend. A valid memory buffer will be + * passed to the core. This memory buffer will + * remain valid until retro_deinit() returns + * - Files of type sg will be loaded by the frontend. + * A valid memory buffer will be passed to the core. + * This memory buffer will remain valid until + * retro_load_game() (or retro_load_game_special()) + * returns + * + * NOTE: If an extension is listed multiple times in + * an array of retro_system_content_info_override + * structs, only the first instance will be registered + */ + +#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66 + /* const struct retro_game_info_ext ** -- + * Allows an implementation to fetch extended game + * information, providing additional content path + * and memory buffer status details. + * This function may only be called inside + * retro_load_game() or retro_load_game_special(). + * If callback returns false, extended game information + * is unsupported by the frontend. In this case, only + * regular retro_game_info will be available. + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT is guaranteed + * to return true if RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE + * returns true. + * + * 'data' points to an array of retro_game_info_ext structs. + * + * For struct member descriptions, see the definition of + * struct retro_game_info_ext. + * + * - If function is called inside retro_load_game(), + * the retro_game_info_ext array is guaranteed to + * have a size of 1 - i.e. the returned pointer may + * be used to access directly the members of the + * first retro_game_info_ext struct, for example: + * + * struct retro_game_info_ext *game_info_ext; + * if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &game_info_ext)) + * printf("Content Directory: %s\n", game_info_ext->dir); + * + * - If the function is called inside retro_load_game_special(), + * the retro_game_info_ext array is guaranteed to have a + * size equal to the num_info argument passed to + * retro_load_game_special() + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67 + /* const struct retro_core_options_v2 * -- + * 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_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * 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. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * 'data' points to a retro_core_options_v2 struct, containing + * of two pointers: + * - retro_core_options_v2::categories is an array of + * retro_core_option_v2_category structs terminated by a + * { NULL, NULL, NULL } element. If retro_core_options_v2::categories + * is NULL, all core options will have no category and will be shown + * at the top level of the frontend core option interface. If frontend + * does not have core option category support, categories array will + * be ignored. + * - retro_core_options_v2::definitions is an array of + * retro_core_option_v2_definition structs terminated by a + * { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL } + * element. + * + * >> retro_core_option_v2_category notes: + * + * - retro_core_option_v2_category::key should contain string + * that uniquely identifies the core option category. Valid + * key characters are [a-z, A-Z, 0-9, _, -] + * Namespace collisions with other implementations' category + * keys are permitted. + * - retro_core_option_v2_category::desc should contain a human + * readable description of the category key. + * - retro_core_option_v2_category::info should contain any + * additional human readable information text that a typical + * user may need to understand the nature of the core option + * category. + * + * Example entry: + * { + * "advanced_settings", + * "Advanced", + * "Options affecting low-level emulation performance and accuracy." + * } + * + * >> retro_core_option_v2_definition notes: + * + * - retro_core_option_v2_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'. Valid key characters + * are [a-z, A-Z, 0-9, _, -]. + * - retro_core_option_v2_definition::desc should contain a human readable + * description of the key. Will be used when the frontend does not + * have core option category support. Examples: "Aspect Ratio" or + * "Video > Aspect Ratio". + * - retro_core_option_v2_definition::desc_categorized should contain a + * human readable description of the key, which will be used when + * frontend has core option category support. Example: "Aspect Ratio", + * where associated retro_core_option_v2_category::desc is "Video". + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::desc will be used instead. + * retro_core_option_v2_definition::desc_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_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_v2_definition::info_categorized should contain + * any additional human readable information text that a typical user + * may need to understand the functionality of the option, and will be + * used when frontend has core option category support. This is provided + * to accommodate the case where info text references an option by + * name/desc, and the desc/desc_categorized text for that option differ. + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::info will be used instead. + * retro_core_option_v2_definition::info_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::category_key should contain a + * category identifier (e.g. "video" or "audio") that will be + * assigned to the core option if frontend has core option category + * support. A categorized option will be shown in a subsection/ + * submenu of the frontend core option interface. If key is empty + * or NULL, or if key does not match one of the + * retro_core_option_v2_category::key values in the associated + * retro_core_option_v2_category array, option will have no category + * and will be shown at the top level of the frontend core option + * interface. + * - retro_core_option_v2_definition::values is an array of + * retro_core_option_value structs terminated by a { NULL, NULL } + * element. + * --> retro_core_option_v2_definition::values[index].value is an + * expected option value. + * --> retro_core_option_v2_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_v2_definition::default_value is the default + * core option setting. It must match one of the expected option + * values in the retro_core_option_v2_definition::values array. If + * it does not, or the default value is NULL, the first entry in the + * retro_core_option_v2_definition::values array is treated as the + * default. + * + * The number of possible option values 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. + * + * Example entries: + * + * - Uncategorized: + * + * { + * "foo_option", + * "Speed hack coprocessor X", + * NULL, + * "Provides increased performance at the expense of reduced accuracy.", + * NULL, + * NULL, + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * - Categorized: + * + * { + * "foo_option", + * "Advanced > Speed hack coprocessor X", + * "Speed hack coprocessor X", + * "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "advanced_settings", + * { + * { "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_V2_INTL 68 + /* const struct retro_core_options_v2_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_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2. + * 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. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_v2_intl struct. + * + * - retro_core_options_v2_intl::us is a pointer to a + * retro_core_options_v2 struct defining the US English + * core options implementation. It must point to a valid struct. + * + * - retro_core_options_v2_intl::local is a pointer to a + * retro_core_options_v2 struct defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_v2_intl::us is used by the frontend). Any items + * missing from this struct will be read from + * retro_core_options_v2_intl::us instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_v2_intl::us struct. Any default values in + * the retro_core_options_v2_intl::local struct will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69 + /* const struct retro_core_options_update_display_callback * -- + * Allows a frontend to signal that a core must update + * the visibility of any dynamically hidden core options, + * and enables the frontend to detect visibility changes. + * Used by the frontend to update the menu display status + * of core options without requiring a call of retro_run(). + * Must be called in retro_set_environment(). + */ + +#define RETRO_ENVIRONMENT_SET_VARIABLE 70 + /* const struct retro_variable * -- + * Allows an implementation to notify the frontend + * that a core option value has changed. + * + * retro_variable::key and retro_variable::value + * must match strings that have been set previously + * via one of the following: + * + * - RETRO_ENVIRONMENT_SET_VARIABLES + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL + * + * After changing a core option value via this + * callback, RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE + * will return true. + * + * If data is NULL, no changes will be registered + * and the callback will return true; an + * implementation may therefore pass NULL in order + * to test whether the callback is supported. + */ + +#define RETRO_ENVIRONMENT_GET_THROTTLE_STATE (71 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_throttle_state * -- + * Allows an implementation to get details on the actual rate + * the frontend is attempting to call retro_run(). + */ + /* VFS functionality */ /* File paths: @@ -1383,28 +1880,28 @@ typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle * 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; + /* 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 @@ -1422,13 +1919,13 @@ struct retro_vfs_interface_info 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 + 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 @@ -1924,6 +2421,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 +2433,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 +2634,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 +2818,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 +2877,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 +2937,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 +2988,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 +3106,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 +3200,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 @@ -2551,6 +3259,143 @@ struct retro_core_options_intl struct retro_core_option_definition *local; }; +struct retro_core_option_v2_category +{ + /* Variable uniquely identifying the + * option category. Valid key characters + * are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable category description + * > Used as category menu label when + * frontend has core option category + * support */ + const char *desc; + + /* Human-readable category information + * > Used as category menu sublabel when + * frontend has core option category + * support + * > Optional (may be NULL or an empty + * string) */ + const char *info; +}; + +struct retro_core_option_v2_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * Valid key characters are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable core option description + * > Used as menu label when frontend does + * not have core option category support + * e.g. "Video > Aspect Ratio" */ + const char *desc; + + /* Human-readable core option description + * > Used as menu label when frontend has + * core option category support + * e.g. "Aspect Ratio", where associated + * retro_core_option_v2_category::desc + * is "Video" + * > If empty or NULL, the string specified by + * desc will be used as the menu label + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *desc_categorized; + + /* Human-readable core option information + * > Used as menu sublabel */ + const char *info; + + /* Human-readable core option information + * > Used as menu sublabel when frontend + * has core option category support + * (e.g. may be required when info text + * references an option by name/desc, + * and the desc/desc_categorized text + * for that option differ) + * > If empty or NULL, the string specified by + * info will be used as the menu sublabel + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *info_categorized; + + /* Variable specifying category (e.g. "video", + * "audio") that will be assigned to the option + * if frontend has core option category support. + * > Categorized options will be displayed in a + * subsection/submenu of the frontend core + * option interface + * > Specified string must match one of the + * retro_core_option_v2_category::key values + * in the associated retro_core_option_v2_category + * array; If no match is not found, specified + * string will be considered as NULL + * > If specified string is empty or NULL, option will + * have no category and will be shown at the top + * level of the frontend core option interface */ + const char *category_key; + + /* 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_v2 +{ + /* Array of retro_core_option_v2_category structs, + * terminated by NULL + * > If NULL, all entries in definitions array + * will have no category and will be shown at + * the top level of the frontend core option + * interface + * > Will be ignored if frontend does not have + * core option category support */ + struct retro_core_option_v2_category *categories; + + /* Array of retro_core_option_v2_definition structs, + * terminated by NULL */ + struct retro_core_option_v2_definition *definitions; +}; + +struct retro_core_options_v2_intl +{ + /* Pointer to a retro_core_options_v2 struct + * > US English implementation + * > Must point to a valid struct */ + struct retro_core_options_v2 *us; + + /* Pointer to a retro_core_options_v2 struct + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_options_v2 *local; +}; + +/* Used by the frontend to monitor changes in core option + * visibility. May be called each time any core option + * value is set via the frontend. + * - On each invocation, the core must update the visibility + * of any dynamically hidden options using the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY environment + * callback. + * - On the first invocation, returns 'true' if the visibility + * of any core option has changed since the last call of + * retro_load_game() or retro_load_game_special(). + * - On each subsequent invocation, returns 'true' if the + * visibility of any core option has changed since the last + * time the function was called. */ +typedef bool (RETRO_CALLCONV *retro_core_options_update_display_callback_t)(void); +struct retro_core_options_update_display_callback +{ + retro_core_options_update_display_callback_t callback; +}; + struct retro_game_info { const char *path; /* Path to game, UTF-8 encoded. @@ -2597,6 +3442,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/thirdparty/SameBoy/libretro/libretro_core_options.inc b/thirdparty/SameBoy/libretro/libretro_core_options.inc new file mode 100644 index 000000000..8646b8a7e --- /dev/null +++ b/thirdparty/SameBoy/libretro/libretro_core_options.inc @@ -0,0 +1,964 @@ +#ifndef LIBRETRO_CORE_OPTIONS_H__ +#define LIBRETRO_CORE_OPTIONS_H__ + +#include +#include + +#include "libretro.h" +#include "retro_inline.h" + +/* + ******************************** + * VERSION: 2.0 + ******************************** + * + * - 2.0: Add support for core options v2 interface + * - 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_v2_category option_cats_us[] = { + { + "system", + "System", + "Configure base hardware selection." + }, + { + "video", + "Video", + "Configure display parameters: palette selection, colour correction, screen border." + }, + { + "audio", + "Audio", + "Configure audio emulation: highpass filter, electrical interference." + }, + { + "input", + "Input", + "Configure input parameters: rumble support." + }, + { NULL, NULL }, +}; + +struct retro_core_option_v2_definition option_defs_us[] = { + + /* Core options used in single cart mode */ + + { + "sameboy_model", + "System - Emulated Model (Requires Restart)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Pocket", "Game Boy Pocket/Light" }, + { "Game Boy Color 0", "Game Boy Color (CPU CGB 0) (Experimental)" }, + { "Game Boy Color A", "Game Boy Color (CPU CGB A) (Experimental)" }, + { "Game Boy Color B", "Game Boy Color (CPU CGB B) (Experimental)" }, + { "Game Boy Color C", "Game Boy Color (CPU CGB C) (Experimental)" }, + { "Game Boy Color D", "Game Boy Color (CPU CGB D)" }, + { "Game Boy Color", "Game Boy Color (CPU CGB E)" }, + { "Game Boy Advance", "Game Boy Advance (CPU AGB A)" }, + { "Game Boy Player", "Game Boy Player (CPU AGB A)" }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_auto_sgb_model", + "System - Auto Detected SGB Model (Requires Restart)", + "Auto Detected SGB Model (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_rtc", + "System - Real Time Clock Emulation", + "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.", + NULL, + "system", + { + { "sync to system clock", "Sync to System Clock" }, + { "accurate", "Accurate" }, + { NULL, NULL }, + }, + "sync to system clock" + }, + { + "sameboy_mono_palette", + "Video - GB Mono Palette", + "GB Mono Palette", + "Selects the color palette that should be used when playing Game Boy games.", + NULL, + "video", + { + { "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", + "GBC Color Correction", + "Defines which type of color correction should be applied when playing Game Boy Color games.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_border", + "Video - Display Border", + "Display Border", + "Defines when to display an on-screen border around the content.", + NULL, + "video", + { + { "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", + "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.", + NULL, + "audio", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "Disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference", + "Audio - Interference Volume", + "Interference Volume", + "Controls the volume of the buzzing effect caused by the electrical interference between the Game Boy SoC and the system speakers.", + NULL, + "audio", + { + { "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", + "Rumble Mode", + "Defines which type of content should trigger rumble effects.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + + /* Core options used in dual cart mode */ + + { + "sameboy_link", + "System - Link Cable Emulation", + "Link Cable Emulation", + "Enables the emulation of the link cable, allowing communication and exchange of data between two Game Boy systems.", + NULL, + "system", + { + { "enabled", "Enabled" }, + { "disabled", "Disabled" }, + { NULL, NULL }, + }, + "enabled" + }, + { + "sameboy_screen_layout", + "System - Screen Layout", + "Screen Layout", + "When emulating two systems at once, this option defines the respective position of the two screens.", + NULL, + "system", + { + { "top-down", "Top-Down" }, + { "left-right", "Left-Right" }, + { NULL, NULL }, + }, + "top-down" + }, + { + "sameboy_audio_output", + "System - Audio Output", + "Audio Output", + "Selects which of the two emulated Game Boy systems should output audio.", + NULL, + "system", + { + { "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)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Pocket", "Game Boy Pocket/Light" }, + { "Game Boy Color 0", "Game Boy Color (CPU CGB 0) (Experimental)" }, + { "Game Boy Color A", "Game Boy Color (CPU CGB A) (Experimental)" }, + { "Game Boy Color B", "Game Boy Color (CPU CGB B) (Experimental)" }, + { "Game Boy Color C", "Game Boy Color (CPU CGB C) (Experimental)" }, + { "Game Boy Color D", "Game Boy Color (CPU CGB D)" }, + { "Game Boy Color", "Game Boy Color (CPU CGB E)" }, + { "Game Boy Advance", "Game Boy Advance (CPU AGB A)" }, + { "Game Boy Player", "Game Boy Player (CPU AGB A)" }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_auto_sgb_model_1", + "System - Auto Detected SGB Model for Game Boy #1 (Requires Restart)", + "Auto Detected SGB Model for Game Boy #1 (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected for Game Boy #1. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_model_2", + "System - Emulated Model for Game Boy #2 (Requires Restart)", + "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.", + NULL, + "system", + { + { "Auto", "Auto Detect DMG/CGB" }, + { "Auto (SGB)", "Auto Detect DMG/SGB/CGB" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Pocket", "Game Boy Pocket/Light" }, + { "Game Boy Color 0", "Game Boy Color (CPU CGB 0) (Experimental)" }, + { "Game Boy Color A", "Game Boy Color (CPU CGB A) (Experimental)" }, + { "Game Boy Color B", "Game Boy Color (CPU CGB B) (Experimental)" }, + { "Game Boy Color C", "Game Boy Color (CPU CGB C) (Experimental)" }, + { "Game Boy Color D", "Game Boy Color (CPU CGB D)" }, + { "Game Boy Color", "Game Boy Color (CPU CGB E)" }, + { "Game Boy Advance", "Game Boy Advance (CPU AGB A)" }, + { "Game Boy Player", "Game Boy Player (CPU AGB A)" }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_auto_sgb_model_2", + "System - Auto Detected SGB Model for Game Boy #2 (Requires Restart)", + "Auto Detected SGB Model for Game Boy #2 (Requires Restart)", + "Specifies which model of Super Game Boy hardware to emulate when SGB content is automatically detected for Game Boy #2. This option requires a content restart in order to take effect.", + NULL, + "system", + { + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Super Game Boy" + }, + { + "sameboy_mono_palette_1", + "Video - GB Mono Palette for Game Boy #1", + "GB Mono Palette for Game Boy #1", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #1.", + NULL, + "video", + { + { "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", + "GB Mono Palette for Game Boy #2", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #2.", + NULL, + "video", + { + { "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", + "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.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "emulate hardware", "Modern – Balanced" }, + { "accurate", "Modern – Accurate" }, + { "preserve brightness", "Modern – Boost Contrast" }, + { "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", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_light_temperature_2", + "Video - Ambient Light Temperature for Game Boy #2", + "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.", + NULL, + "video", + { + { "1.0", "1000K (Warmest)" }, + { "0.9", "1550K" }, + { "0.8", "2100K" }, + { "0.7", "2650K" }, + { "0.6", "3200K" }, + { "0.5", "3750K" }, + { "0.4", "4300K" }, + { "0.3", "4850K" }, + { "0.2", "5400K" }, + { "0.1", "5950K" }, + { "0", "6500K (Neutral White)" }, + { "-0.1", "7050K" }, + { "-0.2", "7600K" }, + { "-0.3", "8150K" }, + { "-0.4", "8700K" }, + { "-0.5", "9250K" }, + { "-0.6", "9800K" }, + { "-0.7", "10350K" }, + { "-0.8", "10900K" }, + { "-0.9", "11450K" }, + { "-1.0", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_high_pass_filter_mode_1", + "Audio - Highpass Filter for Game Boy #1", + "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.", + NULL, + "audio", + { + { "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", + "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.", + NULL, + "audio", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference_1", + "Audio - Interference Volume for Game Boy #1", + "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.", + NULL, + "audio", + { + { "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", + "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.", + NULL, + "audio", + { + { "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", + "Rumble Mode for Game Boy #1", + "Defines which type of content should trigger rumble effects when played on Game Boy #1.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + { + "sameboy_rumble_2", + "Input - Rumble Mode for Game Boy #2", + "Rumble Mode for Game Boy #2", + "Defines which type of content should trigger rumble effects when played on Game Boy #2.", + NULL, + "input", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "Never" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + + { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }, +}; + +struct retro_core_options_v2 options_us = { + option_cats_us, + option_defs_us +}; + +/* + ******************************** + * Language Mapping + ******************************** + */ + +#ifndef HAVE_NO_LANGEXTRA +struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST] = { + &options_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, + bool *categories_supported) +{ + unsigned version = 0; +#ifndef HAVE_NO_LANGEXTRA + unsigned language = 0; +#endif + + if (!environ_cb || !categories_supported) return; + + *categories_supported = false; + + if (!environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version)) version = 0; + + if (version >= 2) { +#ifndef HAVE_NO_LANGEXTRA + struct retro_core_options_v2_intl core_options_intl; + + core_options_intl.us = &options_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 = options_intl[language]; + + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL, + &core_options_intl); +#else + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + &options_us); +#endif + } + else { + size_t i, j; + size_t option_index = 0; + size_t num_options = 0; + struct retro_core_option_definition + *option_v1_defs_us = NULL; +#ifndef HAVE_NO_LANGEXTRA + size_t num_options_intl = 0; + struct retro_core_option_v2_definition + *option_defs_intl = NULL; + struct retro_core_option_definition + *option_v1_defs_intl = NULL; + struct retro_core_options_intl + core_options_v1_intl; +#endif + struct retro_variable *variables = NULL; + char **values_buf = NULL; + + /* Determine total number of options */ + while (true) { + if (option_defs_us[num_options].key) num_options++; + else break; + } + + if (version >= 1) { + /* Allocate US array */ + option_v1_defs_us = (struct retro_core_option_definition *) calloc(num_options + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_us array */ + for (i = 0; i < num_options; i++) { + struct retro_core_option_v2_definition *option_def_us = &option_defs_us[i]; + struct retro_core_option_value *option_values = option_def_us->values; + struct retro_core_option_definition *option_v1_def_us = &option_v1_defs_us[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_us->values; + + option_v1_def_us->key = option_def_us->key; + option_v1_def_us->desc = option_def_us->desc; + option_v1_def_us->info = option_def_us->info; + option_v1_def_us->default_value = option_def_us->default_value; + + /* Values must be copied individually... */ + while (option_values->value) { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + +#ifndef HAVE_NO_LANGEXTRA + if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && + (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) && + options_intl[language]) + option_defs_intl = options_intl[language]->definitions; + + if (option_defs_intl) { + /* Determine number of intl options */ + while (true) { + if (option_defs_intl[num_options_intl].key) num_options_intl++; + else break; + } + + /* Allocate intl array */ + option_v1_defs_intl = (struct retro_core_option_definition *) + calloc(num_options_intl + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_intl array */ + for (i = 0; i < num_options_intl; i++) { + struct retro_core_option_v2_definition *option_def_intl = &option_defs_intl[i]; + struct retro_core_option_value *option_values = option_def_intl->values; + struct retro_core_option_definition *option_v1_def_intl = &option_v1_defs_intl[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_intl->values; + + option_v1_def_intl->key = option_def_intl->key; + option_v1_def_intl->desc = option_def_intl->desc; + option_v1_def_intl->info = option_def_intl->info; + option_v1_def_intl->default_value = option_def_intl->default_value; + + /* Values must be copied individually... */ + while (option_values->value) { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + } + + core_options_v1_intl.us = option_v1_defs_us; + core_options_v1_intl.local = option_v1_defs_intl; + + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_v1_intl); +#else + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, option_v1_defs_us); +#endif + } + else { + /* 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 */ + while (true) { + if (values[num_values].value) { + /* 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++; + } + } + else break; + } + + /* Build values string */ + if (num_values > 0) { + 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[option_index].key = key; + variables[option_index].value = values_buf[i]; + option_index++; + } + + /* Set variables */ + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); + } + +error: + /* Clean up */ + + if (option_v1_defs_us) { + free(option_v1_defs_us); + option_v1_defs_us = NULL; + } + +#ifndef HAVE_NO_LANGEXTRA + if (option_v1_defs_intl) { + free(option_v1_defs_intl); + option_v1_defs_intl = NULL; + } +#endif + + 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/thirdparty/SameBoy/libretro/retro_inline.h b/thirdparty/SameBoy/libretro/retro_inline.h new file mode 100644 index 000000000..14c038ccd --- /dev/null +++ b/thirdparty/SameBoy/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 diff --git a/thirdparty/SameBoy/version.mk b/thirdparty/SameBoy/version.mk index 6831f8426..6196d2412 100644 --- a/thirdparty/SameBoy/version.mk +++ b/thirdparty/SameBoy/version.mk @@ -1 +1 @@ -VERSION := 0.14.3 \ No newline at end of file +VERSION := 0.15.7 \ No newline at end of file