diff --git a/CHANGES b/CHANGES index d6593dacb..dbd23e5d6 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,10 @@ Bugfixes: Misc: - DS GX: Clean up and unify texture mapping +0.7.0: (Future) +Misc: + - GBA Timer: Use global cycles for timers + 0.6.0: (Future) Features: - GBA: Support printing debug strings from inside a game @@ -34,6 +38,7 @@ Features: - LR35902: Watchpoints - Memory search - Debugger: Execution tracing + - Qt: Italian translation (by theheroGAC) Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior @@ -78,6 +83,14 @@ Bugfixes: - GB Timer: Fix DIV batching if TAC changes - GB Video: Reset renderer when loading state - GBA BIOS: Fix INT_MIN/-1 crash + - GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658) + - OpenGL: Fix some shaders causing offset graphics + - Qt: Fix game unpausing after frame advancing and refocusing + - GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering + - Core: Fix interrupting a thread while on the thread (fixes mgba.io/i/692) + - Core: Fix directory sets crashing on close if base isn't properly detached + - Qt: Fix window icon being stretched + - Qt: Fix data directory path Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers @@ -142,6 +155,35 @@ Misc: - Core: Move savestate creation time to extdata - Debugger: Add mDebuggerRunFrame convenience function - GBA Memory: Remove unused prefetch cruft + - GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726) + - Core: Config values can now be hexadecimal + - GB: Reset with initial state of DIV register + - GB MBC: New MBC7 implementation + - Qt: Better highlight active key in control binding + - Core: Improved threading interrupted detection + - GBA Timer: Improve accuracy of timers + +0.6 beta 2: (Future) +Features: + - Qt: Italian translation (by theheroGAC) + - Qt: Updated German translation +Bugfixes: + - Qt: Fix memory search close button (fixes mgba.io/i/769) + - Qt: Fix window icon being stretched + - Qt: Fix initial window size (fixes mgba.io/i/766) + - Qt: Fix data directory path + - Qt: Fix controls not saving on non-SDL builds + - GB Video: Fix LYC regression + - Qt: Fix translation initialization (fixes mgba.io/i/776) + - PSP2: Use custom localtime_r since newlib version is broken (fixes mgba.io/i/560) +Misc: + - Qt: Add language selector + - GBA Timer: Improve accuracy of timers + - Qt: Minor test fixes + - PSP2: Update toolchain to use vita.cmake + +0.6 beta 1: (2017-06-29) + - Initial beta for 0.6 medusa alpha 2: (2017-04-26) Features: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3799f0e64..fd3a2b344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,7 +280,9 @@ endif() include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) -check_function_exists(localtime_r HAVE_LOCALTIME_R) +if(NOT DEFINED PSP2) + check_function_exists(localtime_r HAVE_LOCALTIME_R) +endif() if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -874,7 +876,6 @@ if(BUILD_EXAMPLE) endif() endif() -message(STATUS ${USE_PTHREADS}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) @@ -928,6 +929,7 @@ else() endif() if(NOT QUIET) + message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Platforms:") message(STATUS " Game Boy Advance: ${M_CORE_GBA}") message(STATUS " Game Boy: ${M_CORE_GB}") diff --git a/README.md b/README.md index d440d43a9..61eefc0e4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Features - Remappable controls for both keyboards and gamepads. - Loading from ZIP and 7z files. - IPS, UPS and BPS patch support. -- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro. +- Game debugging via a command-line interface and GDB remote support, compatible with IDA Pro. - Configurable emulation rewinding. - Support for loading and exporting GameShark and Action Replay snapshots. - Cores available for RetroArch/Libretro and OpenEmu. @@ -112,6 +112,16 @@ Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them. +If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are: + + brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit + mkdir build + cd build + cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` .. + make + +Note that you should not do a `make install` on macOS, as it will not work properly. + #### Windows developer building To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 500MiB of packages, so it will take a long time): @@ -151,7 +161,7 @@ medusa has no hard dependencies, however, the following optional dependencies ar - ImageMagick: for GIF recording. - SQLite3: for game databases. -Both libpng and zlib are included with the emulator, so they do not need to be externally compiled first. +SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. Footnotes --------- @@ -203,7 +213,7 @@ Missing features on DS are [3] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. -[4] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older. +[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.5, and may work on older. [downloads]: http://mgba.io/downloads.html [source]: https://github.com/mgba-emu/mgba/ diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index a6f7c5d10..f726cfcae 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -31,6 +31,10 @@ typedef uint32_t color_t; #define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5) #define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5) +#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) +#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) +#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) + struct blip_t; struct mCoreCallbacks { diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h index 1408f24d3..d55ad4094 100644 --- a/include/mgba/core/library.h +++ b/include/mgba/core/library.h @@ -36,6 +36,7 @@ void mLibraryDestroy(struct mLibrary*); struct VDir; struct VFile; void mLibraryLoadDirectory(struct mLibrary* library, const char* base); +void mLibraryClear(struct mLibrary* library); size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints); size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints); diff --git a/include/mgba/core/timing.h b/include/mgba/core/timing.h index 19de59dc7..634631bed 100644 --- a/include/mgba/core/timing.h +++ b/include/mgba/core/timing.h @@ -38,6 +38,7 @@ bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent* int32_t mTimingTick(struct mTiming* timing, int32_t cycles); int32_t mTimingCurrentTime(const struct mTiming* timing); int32_t mTimingNextEvent(struct mTiming* timing); +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*); CXX_GUARD_END diff --git a/include/mgba/internal/ds/timer.h b/include/mgba/internal/ds/timer.h index 738221ab5..88f4294d1 100644 --- a/include/mgba/internal/ds/timer.h +++ b/include/mgba/internal/ds/timer.h @@ -15,7 +15,7 @@ CXX_GUARD_START struct ARMCore; struct DS; void DSTimerInit(struct DS* ds); -void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control); +void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control); CXX_GUARD_END diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 2d169635a..d28338cff 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -67,21 +67,23 @@ typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_ typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address); DECL_BITFIELD(GBMBC7Field, uint8_t); -DECL_BIT(GBMBC7Field, SK, 6); DECL_BIT(GBMBC7Field, CS, 7); -DECL_BIT(GBMBC7Field, IO, 1); +DECL_BIT(GBMBC7Field, CLK, 6); +DECL_BIT(GBMBC7Field, DI, 1); +DECL_BIT(GBMBC7Field, DO, 0); enum GBMBC7MachineState { - GBMBC7_STATE_NULL = -1, GBMBC7_STATE_IDLE = 0, GBMBC7_STATE_READ_COMMAND = 1, - GBMBC7_STATE_READ_ADDRESS = 2, - GBMBC7_STATE_COMMAND_0 = 3, - GBMBC7_STATE_COMMAND_SR_WRITE = 4, - GBMBC7_STATE_COMMAND_SR_READ = 5, - GBMBC7_STATE_COMMAND_SR_FILL = 6, - GBMBC7_STATE_READ = 7, - GBMBC7_STATE_WRITE = 8, + GBMBC7_STATE_DO = 2, + + GBMBC7_STATE_EEPROM_EWDS = 0x10, + GBMBC7_STATE_EEPROM_WRAL = 0x11, + GBMBC7_STATE_EEPROM_ERAL = 0x12, + GBMBC7_STATE_EEPROM_EWEN = 0x13, + GBMBC7_STATE_EEPROM_WRITE = 0x14, + GBMBC7_STATE_EEPROM_READ = 0x18, + GBMBC7_STATE_EEPROM_ERASE = 0x1C, }; struct GBMBC1State { @@ -91,12 +93,13 @@ struct GBMBC1State { struct GBMBC7State { enum GBMBC7MachineState state; - uint32_t sr; + uint16_t sr; uint8_t address; bool writable; int srBits; - int command; - GBMBC7Field field; + uint8_t access; + uint8_t latch; + GBMBC7Field eeprom; }; struct GBPocketCamState { diff --git a/include/mgba/internal/gb/overrides.h b/include/mgba/internal/gb/overrides.h index fd787163c..44d012183 100644 --- a/include/mgba/internal/gb/overrides.h +++ b/include/mgba/internal/gb/overrides.h @@ -16,6 +16,8 @@ struct GBCartridgeOverride { int headerCrc32; enum GBModel model; enum GBMemoryBankControllerType mbc; + + uint32_t gbColors[4]; }; struct Configuration; diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 60534636b..440a90ccf 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -144,7 +144,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value); void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color); +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color); struct GBSerializedState; void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 4f24198dd..8b4e4a84b 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -264,10 +264,10 @@ struct GBASerializedState { struct { uint16_t reload; - uint16_t oldReload; + uint16_t reserved; uint32_t lastEvent; uint32_t nextEvent; - int32_t overflowInterval; + uint32_t nextIrq; GBATimerFlags flags; } timers[4]; diff --git a/include/mgba/internal/gba/timer.h b/include/mgba/internal/gba/timer.h index 17963df75..9253c409b 100644 --- a/include/mgba/internal/gba/timer.h +++ b/include/mgba/internal/gba/timer.h @@ -17,13 +17,13 @@ DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4); DECL_BIT(GBATimerFlags, CountUp, 4); DECL_BIT(GBATimerFlags, DoIrq, 5); DECL_BIT(GBATimerFlags, Enable, 6); +DECL_BIT(GBATimerFlags, IrqPending, 7); struct GBATimer { uint16_t reload; - uint16_t oldReload; - uint32_t lastEvent; + int32_t lastEvent; struct mTimingEvent event; - int32_t overflowInterval; + struct mTimingEvent irq; GBATimerFlags flags; int forcedPrescale; }; @@ -31,13 +31,13 @@ struct GBATimer { struct ARMCore; struct GBA; void GBATimerInit(struct GBA* gba); -void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload); -void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control); -void GBATimerUpdateRegister(struct GBA* gba, int timer); -void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew); -void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate); void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate); +void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate); +void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate); +void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew); +void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload); +void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control); CXX_GUARD_END diff --git a/res/patrons.txt b/res/patrons.txt index 0cf2e6e28..3ec04c2b9 100644 --- a/res/patrons.txt +++ b/res/patrons.txt @@ -3,6 +3,7 @@ Fog Reilly Grant Philip Horton Jordan Jorgensen +mars Rohit Nirmal Rhys Powell rootfather diff --git a/res/shaders/agb001.shader/manifest.ini b/res/shaders/agb001.shader/manifest.ini index 62280af9f..4c1a8f5b3 100644 --- a/res/shaders/agb001.shader/manifest.ini +++ b/res/shaders/agb001.shader/manifest.ini @@ -7,5 +7,5 @@ passes=1 [pass.0] fragmentShader=agb001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 diff --git a/res/shaders/ags001.shader/manifest.ini b/res/shaders/ags001.shader/manifest.ini index 85398cce6..2d5b7c491 100644 --- a/res/shaders/ags001.shader/manifest.ini +++ b/res/shaders/ags001.shader/manifest.ini @@ -7,13 +7,13 @@ passes=2 [pass.0] fragmentShader=ags001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 [pass.1] fragmentShader=ags001-light.fs -width=960 -height=640 +width=-4 +height=-4 [pass.1.uniform.lightBrightness] type=float diff --git a/res/shaders/fish.shader/fish.fs b/res/shaders/fish.shader/fish.fs index 7d750fd7b..96a1b5f46 100644 --- a/res/shaders/fish.shader/fish.fs +++ b/res/shaders/fish.shader/fish.fs @@ -23,19 +23,15 @@ THE SOFTWARE. */ -precision highp float; - varying vec2 texCoord; uniform sampler2D tex; uniform vec2 texSize; uniform float similarity_threshold; -#define screen_res 240,160 - vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported { - return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) ); + return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * texSize) ); } float pixel_brightness(vec4 pixel) @@ -140,7 +136,7 @@ vec4 interpolate_diagonal(vec4 a, vec4 b, vec4 c, vec4 d) void main() { - ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2); + ivec2 pixel_coords2 = ivec2(texCoord * texSize * 2); ivec2 pixel_coords = pixel_coords2 / 2; bool x_even = mod(pixel_coords2.x,2) == 0; diff --git a/res/shaders/lcd.shader/lcd.fs b/res/shaders/lcd.shader/lcd.fs new file mode 100644 index 000000000..ceedd8eb7 --- /dev/null +++ b/res/shaders/lcd.shader/lcd.fs @@ -0,0 +1,40 @@ +/* + LCD Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 3.0, 3.0)) == 0 || + int(mod(texCoord.t * texSize.y * 3.0, 3.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/lcd.shader/manifest.ini b/res/shaders/lcd.shader/manifest.ini new file mode 100644 index 000000000..81061d8b0 --- /dev/null +++ b/res/shaders/lcd.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=LCD +author=Dominus Iniquitatis +description=Simple LCD emulation. +passes=1 + +[pass.0] +fragmentShader=lcd.fs +blend=1 +width=-3 +height=-3 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.9 +min=0.0 +max=1.0 diff --git a/res/shaders/motion_blur.shader/manifest.ini b/res/shaders/motion_blur.shader/manifest.ini new file mode 100644 index 000000000..5f485a041 --- /dev/null +++ b/res/shaders/motion_blur.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Motion Blur +author=Dominus Iniquitatis +description=Simple motion blur. +passes=1 + +[pass.0] +fragmentShader=motion_blur.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.3 +min=0.0 +max=1.0 diff --git a/res/shaders/motion_blur.shader/motion_blur.fs b/res/shaders/motion_blur.shader/motion_blur.fs new file mode 100644 index 000000000..850107355 --- /dev/null +++ b/res/shaders/motion_blur.shader/motion_blur.fs @@ -0,0 +1,35 @@ +/* + Motion Blur Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color.a = 1.0 - amount; + + gl_FragColor = color; +} diff --git a/res/shaders/scanlines.shader/manifest.ini b/res/shaders/scanlines.shader/manifest.ini new file mode 100644 index 000000000..8df4604d1 --- /dev/null +++ b/res/shaders/scanlines.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Scanlines +author=Dominus Iniquitatis +description=Simple scanlines. +passes=1 + +[pass.0] +fragmentShader=scanlines.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.lineBrightness] +type=float +readableName=Line brightness +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/scanlines.shader/scanlines.fs b/res/shaders/scanlines.shader/scanlines.fs new file mode 100644 index 000000000..4f25fd0f6 --- /dev/null +++ b/res/shaders/scanlines.shader/scanlines.fs @@ -0,0 +1,39 @@ +/* + Scanlines Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float lineBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * lineBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/soften.shader/manifest.ini b/res/shaders/soften.shader/manifest.ini new file mode 100644 index 000000000..6c2312796 --- /dev/null +++ b/res/shaders/soften.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Soften +author=Dominus Iniquitatis +description=Soft image blurring. +passes=1 + +[pass.0] +fragmentShader=soften.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/soften.shader/soften.fs b/res/shaders/soften.shader/soften.fs new file mode 100644 index 000000000..41cfa1083 --- /dev/null +++ b/res/shaders/soften.shader/soften.fs @@ -0,0 +1,64 @@ +/* + Soften Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +vec2 GetTexelSize() +{ + return vec2(1.0 / texSize.x, 1.0 / texSize.y); +} + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + vec4 northColor = texture2D(tex, texCoord + vec2(0.0, GetTexelSize().y)); + vec4 southColor = texture2D(tex, texCoord - vec2(0.0, GetTexelSize().y)); + vec4 eastColor = texture2D(tex, texCoord + vec2(GetTexelSize().x, 0.0)); + vec4 westColor = texture2D(tex, texCoord - vec2(GetTexelSize().x, 0.0)); + + if (abs(length(color) - length(northColor)) > 0.0) + { + color = mix(color, northColor, amount / 4.0); + } + + if (abs(length(color) - length(southColor)) > 0.0) + { + color = mix(color, southColor, amount / 4.0); + } + + if (abs(length(color) - length(eastColor)) > 0.0) + { + color = mix(color, eastColor, amount / 4.0); + } + + if (abs(length(color) - length(westColor)) > 0.0) + { + color = mix(color, westColor, amount / 4.0); + } + + gl_FragColor = color; +} diff --git a/res/shaders/vba_pixelate.shader/manifest.ini b/res/shaders/vba_pixelate.shader/manifest.ini new file mode 100644 index 000000000..ae5d58d1b --- /dev/null +++ b/res/shaders/vba_pixelate.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=VBA Pixelate +author=Dominus Iniquitatis +description=VisualBoyAdvance-style pixelation. +passes=1 + +[pass.0] +fragmentShader=vba_pixelate.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/vba_pixelate.shader/vba_pixelate.fs b/res/shaders/vba_pixelate.shader/vba_pixelate.fs new file mode 100644 index 000000000..c676a3afd --- /dev/null +++ b/res/shaders/vba_pixelate.shader/vba_pixelate.fs @@ -0,0 +1,40 @@ +/* + VBA Pixelate Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 2.0, 2.0)) == 0 || + int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/vignette.shader/manifest.ini b/res/shaders/vignette.shader/manifest.ini new file mode 100644 index 000000000..f486dbc44 --- /dev/null +++ b/res/shaders/vignette.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Vignette +author=Dominus Iniquitatis +description=Configurable vignette effect. +passes=1 + +[pass.0] +fragmentShader=vignette.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.intensity] +type=float +readableName=Intensity +default=1.0 +min=0.0 +max=1.0 diff --git a/res/shaders/vignette.shader/vignette.fs b/res/shaders/vignette.shader/vignette.fs new file mode 100644 index 000000000..fb6e11c3c --- /dev/null +++ b/res/shaders/vignette.shader/vignette.fs @@ -0,0 +1,35 @@ +/* + Vignette Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float intensity; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), length(texCoord - 0.5) * intensity); + + gl_FragColor = color; +} diff --git a/res/shaders/wiiu.shader/manifest.ini b/res/shaders/wiiu.shader/manifest.ini index 2b92c1d74..e69751ed3 100644 --- a/res/shaders/wiiu.shader/manifest.ini +++ b/res/shaders/wiiu.shader/manifest.ini @@ -7,5 +7,5 @@ passes=1 [pass.0] fragmentShader=wiiu.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 diff --git a/src/core/config.c b/src/core/config.c index cbb6f32ee..950607469 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -86,6 +86,9 @@ static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, i } char* end; long value = strtol(charValue, &end, 10); + if (end == &charValue[1] && *end == 'x') { + value = strtol(charValue, &end, 16); + } if (*end) { return false; } diff --git a/src/core/core.c b/src/core/core.c index bd899bea6..4cf17d849 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -26,7 +26,7 @@ #include #endif -const static struct mCoreFilter { +static const struct mCoreFilter { bool (*filter)(struct VFile*); struct mCore* (*open)(void); enum mPlatform platform; diff --git a/src/core/directories.c b/src/core/directories.c index ca261dbd9..4d52bec43 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -22,28 +22,58 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) { mDirectorySetDetachBase(dirs); if (dirs->archive) { + if (dirs->archive == dirs->save) { + dirs->save = NULL; + } + if (dirs->archive == dirs->patch) { + dirs->patch = NULL; + } + if (dirs->archive == dirs->state) { + dirs->state = NULL; + } + if (dirs->archive == dirs->screenshot) { + dirs->screenshot = NULL; + } dirs->archive->close(dirs->archive); - dirs->archive = 0; + dirs->archive = NULL; } if (dirs->save) { + if (dirs->save == dirs->patch) { + dirs->patch = NULL; + } + if (dirs->save == dirs->state) { + dirs->state = NULL; + } + if (dirs->save == dirs->screenshot) { + dirs->screenshot = NULL; + } dirs->save->close(dirs->save); - dirs->save = 0; + dirs->save = NULL; } if (dirs->patch) { + if (dirs->patch == dirs->state) { + dirs->state = NULL; + } + if (dirs->patch == dirs->screenshot) { + dirs->screenshot = NULL; + } dirs->patch->close(dirs->patch); - dirs->patch = 0; + dirs->patch = NULL; } if (dirs->state) { + if (dirs->state == dirs->screenshot) { + dirs->state = NULL; + } dirs->state->close(dirs->state); - dirs->state = 0; + dirs->state = NULL; } if (dirs->screenshot) { dirs->screenshot->close(dirs->screenshot); - dirs->screenshot = 0; + dirs->screenshot = NULL; } } @@ -65,21 +95,21 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) { void mDirectorySetDetachBase(struct mDirectorySet* dirs) { if (dirs->save == dirs->base) { - dirs->save = 0; + dirs->save = NULL; } if (dirs->patch == dirs->base) { - dirs->patch = 0; + dirs->patch = NULL; } if (dirs->state == dirs->base) { - dirs->state = 0; + dirs->state = NULL; } if (dirs->screenshot == dirs->base) { - dirs->screenshot = 0; + dirs->screenshot = NULL; } if (dirs->base) { dirs->base->close(dirs->base); - dirs->base = 0; + dirs->base = NULL; } } diff --git a/src/core/library.c b/src/core/library.c index d230f60a6..b16afe3ce 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -339,6 +339,16 @@ static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry* sqlite3_step(library->insertPath); } +void mLibraryClear(struct mLibrary* library) { + int result = sqlite3_exec(library->db, + " BEGIN TRANSACTION;" + "\n DELETE FROM roots;" + "\n DELETE FROM roms;" + "\n DELETE FROM paths;" + "\n COMMIT;" + "\n VACUUM;", NULL, NULL, NULL); +} + size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) { sqlite3_clear_bindings(library->count); sqlite3_reset(library->count); diff --git a/src/core/mem-search.c b/src/core/mem-search.c index 5209f2b54..5cf74140f 100644 --- a/src/core/mem-search.c +++ b/src/core/mem-search.c @@ -272,7 +272,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor // Decimal: value = strtoull(valueStr, &end, 10); - if (end) { + if (end && !end[0]) { if (value > 0x10000) { found += _search32(mem, size, block, value, out, limit ? limit - found : 0); } else if (value > 0x100) { @@ -305,7 +305,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor // Hex: value = strtoull(valueStr, &end, 16); - if (end) { + if (end && !end[0]) { if (value > 0x10000) { found += _search32(mem, size, block, value, out, limit ? limit - found : 0); } else if (value > 0x100) { diff --git a/src/core/thread.c b/src/core/thread.c index 424260b7e..5ed87e9ca 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -401,11 +401,14 @@ void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) { MutexLock(&threadContext->stateMutex); ++threadContext->interruptDepth; if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) { + if (threadContext->state == THREAD_INTERRUPTING) { + threadContext->state = THREAD_INTERRUPTED; + } MutexUnlock(&threadContext->stateMutex); return; } threadContext->savedState = threadContext->state; - threadContext->state = THREAD_INTERRUPTING; + threadContext->state = THREAD_INTERRUPTED; ConditionWake(&threadContext->stateCond); MutexUnlock(&threadContext->stateMutex); } @@ -465,7 +468,7 @@ void mCoreThreadUnpause(struct mCoreThread* threadContext) { bool mCoreThreadIsPaused(struct mCoreThread* threadContext) { bool isPaused; MutexLock(&threadContext->stateMutex); - if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) { + if (threadContext->interruptDepth) { isPaused = threadContext->savedState == THREAD_PAUSED; } else { isPaused = threadContext->state == THREAD_PAUSED; @@ -495,7 +498,7 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) { void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) { bool frameOn = true; MutexLock(&threadContext->stateMutex); - if (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING)) { + if (threadContext->state == THREAD_RUNNING || (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING)) { threadContext->state = THREAD_PAUSING; frameOn = false; } @@ -506,11 +509,11 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) { void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) { MutexLock(&threadContext->stateMutex); - if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_REWINDING))) { + if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->interruptDepth && threadContext->savedState == THREAD_REWINDING))) { MutexUnlock(&threadContext->stateMutex); return; } - if (!rewinding && (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING))) { + if (!rewinding && ((!threadContext->interruptDepth && threadContext->state != THREAD_REWINDING) || (threadContext->interruptDepth && threadContext->savedState != THREAD_REWINDING))) { MutexUnlock(&threadContext->stateMutex); return; } @@ -526,7 +529,7 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) { MutexLock(&threadContext->stateMutex); - if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_RUNNING) { + if (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING) { threadContext->savedState = THREAD_WAITING; } else if (threadContext->state == THREAD_RUNNING) { threadContext->state = THREAD_WAITING; @@ -536,7 +539,7 @@ void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) { void mCoreThreadStopWaiting(struct mCoreThread* threadContext) { MutexLock(&threadContext->stateMutex); - if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_WAITING) { + if (threadContext->interruptDepth && threadContext->savedState == THREAD_WAITING) { threadContext->savedState = THREAD_RUNNING; } else if (threadContext->state == THREAD_WAITING) { threadContext->state = THREAD_RUNNING; diff --git a/src/core/timing.c b/src/core/timing.c index 11b808a93..1d22a93cb 100644 --- a/src/core/timing.c +++ b/src/core/timing.c @@ -89,5 +89,9 @@ int32_t mTimingNextEvent(struct mTiming* timing) { if (!next) { return INT_MAX; } - return next->when - timing->masterCycles; + return next->when - timing->masterCycles - *timing->relativeCycles; +} + +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent* event) { + return event->when - timing->masterCycles - *timing->relativeCycles; } diff --git a/src/ds/io.c b/src/ds/io.c index 99b7838ec..7e82059ed 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -78,19 +78,19 @@ static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t va case DS_REG_TM0CNT_HI: value &= 0x00C7; - DSTimerWriteTMCNT_HI(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], value); + DSTimerWriteTMCNT_HI(&dscore->timers[0], &dscore->timing, &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], value); break; case DS_REG_TM1CNT_HI: value &= 0x00C7; - DSTimerWriteTMCNT_HI(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], value); + DSTimerWriteTMCNT_HI(&dscore->timers[1], &dscore->timing, &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], value); break; case DS_REG_TM2CNT_HI: value &= 0x00C7; - DSTimerWriteTMCNT_HI(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], value); + DSTimerWriteTMCNT_HI(&dscore->timers[2], &dscore->timing, &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], value); break; case DS_REG_TM3CNT_HI: value &= 0x00C7; - DSTimerWriteTMCNT_HI(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], value); + DSTimerWriteTMCNT_HI(&dscore->timers[3], &dscore->timing, &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], value); break; // IPC @@ -243,16 +243,16 @@ static uint16_t DSIOReadKeyInput(struct DS* ds) { static void DSIOUpdateTimer(struct DSCommon* dscore, uint32_t address) { switch (address) { case DS_REG_TM0CNT_LO: - GBATimerUpdateRegisterInternal(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0); + GBATimerUpdateRegisterInternal(&dscore->timers[0], &dscore->timing, &dscore->memory.io[address >> 1], 0); break; case DS_REG_TM1CNT_LO: - GBATimerUpdateRegisterInternal(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0); + GBATimerUpdateRegisterInternal(&dscore->timers[1], &dscore->timing, &dscore->memory.io[address >> 1], 0); break; case DS_REG_TM2CNT_LO: - GBATimerUpdateRegisterInternal(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0); + GBATimerUpdateRegisterInternal(&dscore->timers[2], &dscore->timing, &dscore->memory.io[address >> 1], 0); break; case DS_REG_TM3CNT_LO: - GBATimerUpdateRegisterInternal(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0); + GBATimerUpdateRegisterInternal(&dscore->timers[3], &dscore->timing, &dscore->memory.io[address >> 1], 0); break; } } diff --git a/src/ds/timer.c b/src/ds/timer.c index d142dfbd1..6e2797b64 100644 --- a/src/ds/timer.c +++ b/src/ds/timer.c @@ -8,42 +8,58 @@ #include #include +static void DSTimerIrq(struct DSCommon* dscore, int timerId) { + struct GBATimer* timer = &dscore->timers[timerId]; + if (GBATimerFlagsIsIrqPending(timer->flags)) { + timer->flags = GBATimerFlagsClearIrqPending(timer->flags); + DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0 + timerId); + } +} + +static void DSTimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + DSTimerIrq(context, 0); +} + +static void DSTimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + DSTimerIrq(context, 1); +} + +static void DSTimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + DSTimerIrq(context, 2); +} + +static void DSTimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + DSTimerIrq(context, 3); +} + static void DSTimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; - struct GBATimer* timer = &dscore->timers[0]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0); - } GBATimerUpdate(timing, &dscore->timers[0], &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate); } static void DSTimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; - struct GBATimer* timer = &dscore->timers[1]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER1); - } GBATimerUpdate(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate); } static void DSTimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; - struct GBATimer* timer = &dscore->timers[2]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER2); - } GBATimerUpdate(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate); } static void DSTimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; - struct GBATimer* timer = &dscore->timers[3]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER3); - } GBATimerUpdate(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate); } @@ -65,6 +81,22 @@ void DSTimerInit(struct DS* ds) { ds->ds7.timers[3].event.callback = DSTimerUpdate3; ds->ds7.timers[3].event.context = &ds->ds7; ds->ds7.timers[3].event.priority = 0x23; + ds->ds7.timers[0].irq.name = "DS7 Timer 0 IRQ"; + ds->ds7.timers[0].irq.callback = DSTimerIrq0; + ds->ds7.timers[0].irq.context = &ds->ds7; + ds->ds7.timers[0].irq.priority = 0x28; + ds->ds7.timers[1].irq.name = "DS7 Timer 1 IRQ"; + ds->ds7.timers[1].irq.callback = DSTimerIrq1; + ds->ds7.timers[1].irq.context = &ds->ds7; + ds->ds7.timers[1].irq.priority = 0x29; + ds->ds7.timers[2].irq.name = "DS7 Timer 2 IRQ"; + ds->ds7.timers[2].irq.callback = DSTimerIrq2; + ds->ds7.timers[2].irq.context = &ds->ds7; + ds->ds7.timers[2].irq.priority = 0x2A; + ds->ds7.timers[3].irq.name = "DS7 Timer 3 IRQ"; + ds->ds7.timers[3].irq.callback = DSTimerIrq3; + ds->ds7.timers[3].irq.context = &ds->ds7; + ds->ds7.timers[3].irq.priority = 0x2B; memset(ds->ds9.timers, 0, sizeof(ds->ds9.timers)); ds->ds9.timers[0].event.name = "DS9 Timer 0"; @@ -87,9 +119,24 @@ void DSTimerInit(struct DS* ds) { ds->ds9.timers[3].event.context = &ds->ds9; ds->ds9.timers[3].event.priority = 0x23; ds->ds9.timers[3].forcedPrescale = 1; + ds->ds9.timers[0].irq.name = "DS9 Timer 0 IRQ"; + ds->ds9.timers[0].irq.callback = DSTimerIrq0; + ds->ds9.timers[0].irq.context = &ds->ds9; + ds->ds9.timers[0].irq.priority = 0x28; + ds->ds9.timers[1].irq.name = "DS9 Timer 1 IRQ"; + ds->ds9.timers[1].irq.callback = DSTimerIrq1; + ds->ds9.timers[1].irq.context = &ds->ds9; + ds->ds9.timers[1].irq.priority = 0x29; + ds->ds9.timers[2].irq.name = "DS9 Timer 2 IRQ"; + ds->ds9.timers[2].irq.callback = DSTimerIrq2; + ds->ds9.timers[2].irq.context = &ds->ds9; + ds->ds9.timers[2].irq.priority = 0x2A; + ds->ds9.timers[3].irq.name = "DS9 Timer 3 IRQ"; + ds->ds9.timers[3].irq.callback = DSTimerIrq3; + ds->ds9.timers[3].irq.context = &ds->ds9; + ds->ds9.timers[3].irq.priority = 0x2B; } -void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t value) { - GBATimerUpdateRegisterInternal(timer, timing, cpu, io, 0); - GBATimerWriteTMCNT_HI(timer, timing, cpu, io, value); +void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t value) { + GBATimerWriteTMCNT_HI(timer, timing, io, value); } diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index 865bd24ea..ab071512d 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -26,7 +26,7 @@ const char mVL_MAGIC[] = "mVL\0"; -const static struct mVLDescriptor { +static const struct mVLDescriptor { enum mPlatform platform; struct mCore* (*open)(void); } _descriptors[] = { diff --git a/src/gb/audio.c b/src/gb/audio.c index c8f1338fb..3b384cdf8 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -555,7 +555,7 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) { if (audio->ch2.envelope.dead == 2) { mTimingDeschedule(timing, &audio->ch2Event); } - _updateSquareSample(&audio->ch1); + _updateSquareSample(&audio->ch2); } } @@ -709,7 +709,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) { } static void _updateSquareSample(struct GBAudioSquareChannel* ch) { - ch->sample = (ch->control.hi * ch->envelope.currentVolume - 8) * 0x10; + ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8; } static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) { diff --git a/src/gb/core.c b/src/gb/core.c index 8cdd29b3d..397448fa9 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -29,26 +29,26 @@ #include #endif -const static struct mCoreChannelInfo _GBVideoLayers[] = { +static const struct mCoreChannelInfo _GBVideoLayers[] = { { 0, "bg", "Background", NULL }, { 1, "obj", "Objects", NULL }, { 2, "win", "Window", NULL }, }; -const static struct mCoreChannelInfo _GBAudioChannels[] = { - { 0, "ch0", "Channel 0", "Square/Sweep" }, - { 1, "ch1", "Channel 1", "Square" }, - { 2, "ch2", "Channel 2", "PCM" }, - { 3, "ch3", "Channel 3", "Noise" }, +static const struct mCoreChannelInfo _GBAudioChannels[] = { + { 0, "ch1", "Channel 1", "Square/Sweep" }, + { 1, "ch2", "Channel 2", "Square" }, + { 2, "ch3", "Channel 3", "PCM" }, + { 3, "ch4", "Channel 4", "Noise" }, }; -const static struct LR35902Segment _GBSegments[] = { +static const struct LR35902Segment _GBSegments[] = { { .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM }, { .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 }, { 0 } }; -const static struct LR35902Segment _GBCSegments[] = { +static const struct LR35902Segment _GBCSegments[] = { { .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM }, { .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 }, { .name = "WRAM", .start = GB_BASE_WORKING_RAM_BANK1, .end = 0xE000 }, @@ -56,7 +56,7 @@ const static struct LR35902Segment _GBCSegments[] = { { 0 } }; -const static struct mCoreMemoryBlock _GBMemoryBlocks[] = { +static const struct mCoreMemoryBlock _GBMemoryBlocks[] = { { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -67,7 +67,7 @@ const static struct mCoreMemoryBlock _GBMemoryBlocks[] = { { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, }; -const static struct mCoreMemoryBlock _GBCMemoryBlocks[] = { +static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = { { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, diff --git a/src/gb/gb.c b/src/gb/gb.c index 9b513d311..7338701cc 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) { cpu->e = 0xD8; cpu->h = 1; cpu->l = 0x4D; + gb->timer.internalDiv = 0x2AF3; break; case GB_MODEL_AGB: cpu->b = 1; @@ -444,6 +445,7 @@ void GBReset(struct LR35902Core* cpu) { cpu->e = 0x08; cpu->h = 0; cpu->l = 0x7C; + gb->timer.internalDiv = 0x7A8; break; } diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 544f01af8..4c8bee9a8 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -79,7 +79,11 @@ static bool _isMulticart(const uint8_t* mem) { void GBMBCSwitchSramBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; - GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM); + if (bankStart + GB_SIZE_EXTERNAL_RAM > gb->sramSize) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank); + bankStart &= (gb->sramSize - 1); + bank = bankStart / GB_SIZE_EXTERNAL_RAM; + } gb->memory.sramBank = &gb->memory.sram[bankStart]; gb->memory.sramCurrentBank = bank; } @@ -194,7 +198,7 @@ void GBMBCInit(struct GB* gb) { case GB_MBC7: gb->memory.mbcWrite = _GBMBC7; gb->memory.mbcRead = _GBMBC7Read; - gb->sramSize = GB_SIZE_EXTERNAL_RAM; + gb->sramSize = 0x100; break; case GB_MMM01: mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01"); @@ -468,12 +472,25 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) { void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { + case 0x0: + switch (value) { + default: + case 0: + gb->memory.mbcState.mbc7.access = 0; + break; + case 0xA: + gb->memory.mbcState.mbc7.access |= 1; + break; + } + break; case 0x1: GBMBCSwitchBank(gb, bank); break; case 0x2: - if (value < 0x10) { - GBMBCSwitchSramBank(gb, value); + if (value == 0x40) { + gb->memory.mbcState.mbc7.access |= 2; + } else { + gb->memory.mbcState.mbc7.access &= ~2; } break; default: @@ -485,17 +502,15 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { + return 0xFF; + } switch (address & 0xF0) { - case 0x00: - case 0x10: - case 0x60: - case 0x70: - return 0; case 0x20: if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x; } return 0xFF; @@ -503,7 +518,7 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x >> 8; } return 7; @@ -511,7 +526,7 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y; } return 0xFF; @@ -519,144 +534,142 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y >> 8; } return 7; + case 0x60: + return 0; case 0x80: - return (mbc7->sr >> 16) & 1; + return mbc7->eeprom; default: return 0xFF; } } void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { - if ((address & 0xF0) != 0x80) { + struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { return; } - struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; - GBMBC7Field old = memory->mbcState.mbc7.field; - mbc7->field = GBMBC7FieldClearIO(value); - if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) { - if (mbc7->state == GBMBC7_STATE_WRITE) { - if (mbc7->writable) { - memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8; - memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr; - } - mbc7->sr = 0x1FFFF; - mbc7->state = GBMBC7_STATE_NULL; - } else { - mbc7->state = GBMBC7_STATE_IDLE; + switch (address & 0xF0) { + case 0x00: + mbc7->latch = (value & 0x55) == 0x55; + return; + case 0x10: + mbc7->latch |= (value & 0xAA); + if (mbc7->latch == 0xAB && memory->rotation && memory->rotation->sample) { + memory->rotation->sample(memory->rotation); } + mbc7->latch = 0; + return; + default: + mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value); + return; + case 0x80: + break; } - if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) { - if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) { + GBMBC7Field old = memory->mbcState.mbc7.eeprom; + value = GBMBC7FieldFillDO(value); // Hi-Z + if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) { + mbc7->state = GBMBC7_STATE_IDLE; + } + if (!GBMBC7FieldIsCLK(old) && GBMBC7FieldIsCLK(value)) { + if (mbc7->state == GBMBC7_STATE_READ_COMMAND || mbc7->state == GBMBC7_STATE_EEPROM_WRITE || mbc7->state == GBMBC7_STATE_EEPROM_WRAL) { mbc7->sr <<= 1; - mbc7->sr |= GBMBC7FieldGetIO(value); + mbc7->sr |= GBMBC7FieldGetDI(value); ++mbc7->srBits; } switch (mbc7->state) { case GBMBC7_STATE_IDLE: - if (GBMBC7FieldIsIO(value)) { + if (GBMBC7FieldIsDI(value)) { mbc7->state = GBMBC7_STATE_READ_COMMAND; mbc7->srBits = 0; mbc7->sr = 0; } break; case GBMBC7_STATE_READ_COMMAND: - if (mbc7->srBits == 2) { - mbc7->state = GBMBC7_STATE_READ_ADDRESS; - mbc7->srBits = 0; - mbc7->command = mbc7->sr; - } - break; - case GBMBC7_STATE_READ_ADDRESS: - if (mbc7->srBits == 8) { - mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command; - mbc7->srBits = 0; - mbc7->address = mbc7->sr; - if (mbc7->state == GBMBC7_STATE_COMMAND_0) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; - } + if (mbc7->srBits == 10) { + mbc7->state = 0x10 | (mbc7->sr >> 6); + if (mbc7->state & 0xC) { + mbc7->state &= ~0x3; } - } - break; - case GBMBC7_STATE_COMMAND_0: - if (mbc7->srBits == 16) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 1: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = mbc7->sr >> 8; - memory->sramBank[i * 2 + 1] = mbc7->sr; - } - } - break; - case 2: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = 0xFF; - memory->sramBank[i * 2 + 1] = 0xFF; - } - } - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; - } - } - break; - case GBMBC7_STATE_COMMAND_SR_WRITE: - if (mbc7->srBits == 16) { mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + mbc7->address = mbc7->sr & 0x7F; } break; - case GBMBC7_STATE_COMMAND_SR_READ: - if (mbc7->srBits == 1) { - mbc7->sr = memory->sramBank[mbc7->address * 2] << 8; - mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1]; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_READ; - } - break; - case GBMBC7_STATE_COMMAND_SR_FILL: - if (mbc7->srBits == 16) { - mbc7->sr = 0xFFFF; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + case GBMBC7_STATE_DO: + value = GBMBC7FieldSetDO(value, mbc7->sr >> 15); + mbc7->sr <<= 1; + --mbc7->srBits; + if (!mbc7->srBits) { + mbc7->state = GBMBC7_STATE_IDLE; } break; default: break; } - } else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) { - if (mbc7->state == GBMBC7_STATE_READ) { - mbc7->sr <<= 1; - ++mbc7->srBits; + switch (mbc7->state) { + case GBMBC7_STATE_EEPROM_EWEN: + mbc7->writable = true; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_EWDS: + mbc7->writable = false; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_WRITE: if (mbc7->srBits == 16) { - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_NULL; + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = mbc7->sr >> 8; + memory->sram[mbc7->address * 2 + 1] = mbc7->sr; + } + mbc7->state = GBMBC7_STATE_IDLE; } + break; + case GBMBC7_STATE_EEPROM_ERASE: + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = 0xFF; + memory->sram[mbc7->address * 2 + 1] = 0xFF; + } + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_READ: + mbc7->srBits = 16; + mbc7->sr = memory->sram[mbc7->address * 2] << 8; + mbc7->sr |= memory->sram[mbc7->address * 2 + 1]; + mbc7->state = GBMBC7_STATE_DO; + value = GBMBC7FieldClearDO(value); + break; + case GBMBC7_STATE_EEPROM_WRAL: + if (mbc7->srBits == 16) { + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = mbc7->sr >> 8; + memory->sram[i * 2 + 1] = mbc7->sr; + } + } + mbc7->state = GBMBC7_STATE_IDLE; + } + break; + case GBMBC7_STATE_EEPROM_ERAL: + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = 0xFF; + memory->sram[i * 2 + 1] = 0xFF; + } + } + mbc7->state = GBMBC7_STATE_IDLE; + break; + default: + break; } + } else if (GBMBC7FieldIsCS(value) && GBMBC7FieldIsCLK(old) && !GBMBC7FieldIsCLK(value)) { + value = GBMBC7FieldSetDO(value, GBMBC7FieldGetDO(old)); } + mbc7->eeprom = value; } void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { diff --git a/src/gb/memory.c b/src/gb/memory.c index eafa0c911..c424f5a92 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -437,6 +437,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) { if (base > 0xF100) { return; } + mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent); mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8); if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) { gb->cpu->nextEvent = gb->cpu->cycles + 8; diff --git a/src/gb/overrides.c b/src/gb/overrides.c index 70d11cd22..d8265800b 100644 --- a/src/gb/overrides.c +++ b/src/gb/overrides.c @@ -13,7 +13,7 @@ static const struct GBCartridgeOverride _overrides[] = { // None yet - { 0, 0, 0 } + { 0, 0, 0, { 0 } } }; bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverride* override) { @@ -35,6 +35,12 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri snprintf(sectionName, sizeof(sectionName), "gb.override.%08X", override->headerCrc32); const char* model = ConfigurationGetValue(config, sectionName, "model"); const char* mbc = ConfigurationGetValue(config, sectionName, "mbc"); + const char* pal[4] = { + ConfigurationGetValue(config, sectionName, "pal[0]"), + ConfigurationGetValue(config, sectionName, "pal[1]"), + ConfigurationGetValue(config, sectionName, "pal[2]"), + ConfigurationGetValue(config, sectionName, "pal[3]") + }; if (model) { if (strcasecmp(model, "DMG") == 0) { @@ -63,6 +69,21 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri found = true; } } + + if (pal[0] && pal[1] && pal[2] && pal[3]) { + int i; + for (i = 0; i < 4; ++i) { + char* end; + unsigned long value = strtoul(pal[i], &end, 10); + if (end == &pal[i][1] && *end == 'x') { + value = strtoul(pal[i], &end, 16); + } + if (*end) { + continue; + } + override->gbColors[i] = value; + } + } } return found; } @@ -89,6 +110,12 @@ void GBOverrideSave(struct Configuration* config, const struct GBCartridgeOverri } ConfigurationSetValue(config, sectionName, "model", model); + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + ConfigurationSetIntValue(config, sectionName, "pal[0]", override->gbColors[0]); + ConfigurationSetIntValue(config, sectionName, "pal[1]", override->gbColors[1]); + ConfigurationSetIntValue(config, sectionName, "pal[2]", override->gbColors[2]); + ConfigurationSetIntValue(config, sectionName, "pal[3]", override->gbColors[3]); + } if (override->mbc != GB_MBC_AUTODETECT) { ConfigurationSetIntValue(config, sectionName, "mbc", override->mbc); } else { @@ -105,6 +132,13 @@ void GBOverrideApply(struct GB* gb, const struct GBCartridgeOverride* override) gb->memory.mbcType = override->mbc; GBMBCInit(gb); } + + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + GBVideoSetPalette(&gb->video, 0, override->gbColors[0]); + GBVideoSetPalette(&gb->video, 1, override->gbColors[1]); + GBVideoSetPalette(&gb->video, 2, override->gbColors[2]); + GBVideoSetPalette(&gb->video, 3, override->gbColors[3]); + } } void GBOverrideApplyDefaults(struct GB* gb) { diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 1474a86a5..2d2dbe787 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -67,8 +67,6 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G softwareRenderer->currentWy = 0; softwareRenderer->wx = 0; softwareRenderer->model = model; - - _clearScreen(softwareRenderer); } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) { diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 9eab0fea2..d9f192092 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -169,8 +169,8 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { } GBMemoryDeserialize(gb, state); - GBIODeserialize(gb, state); GBVideoDeserialize(&gb->video, state); + GBIODeserialize(gb, state); GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); diff --git a/src/gb/timer.c b/src/gb/timer.c index 48383f652..ad9e6c788 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -5,12 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include #include -static void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate); - void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); UNUSED(cyclesLate); @@ -20,28 +19,28 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBUpdateIRQs(timer->p); } -void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) { - struct GBTimer* timer = context; - timer->nextDiv += cyclesLate; - while (timer->nextDiv > 0) { +static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) { + while (timer->nextDiv >= GB_DMG_DIV_PERIOD) { timer->nextDiv -= GB_DMG_DIV_PERIOD; // Make sure to trigger when the correct bit is a falling edge if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { ++timer->p->memory.io[REG_TIMA]; if (!timer->p->memory.io[REG_TIMA]) { - mTimingSchedule(timing, &timer->irq, 4 - cyclesLate); + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - cyclesLate); } } ++timer->internalDiv; timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } - _GBTimerUpdateDIV(timer, cyclesLate); } -void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate) { +void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBTimer* timer = context; + timer->nextDiv += cyclesLate; + _GBTimerDivIncrement(timer, cyclesLate); // Batch div increments - int divsToGo = 16 - (timer->internalDiv & 15) + (timer->nextDiv / GB_DMG_DIV_PERIOD); + int divsToGo = 16 - (timer->internalDiv & 15); int timaToGo = INT_MAX; if (timer->timaPeriod) { timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1)); @@ -49,18 +48,14 @@ void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate) { if (timaToGo < divsToGo) { divsToGo = timaToGo; } - if (divsToGo > 16) { - divsToGo = 16; - } - timer->nextDiv &= GB_DMG_DIV_PERIOD - 1; - timer->nextDiv += GB_DMG_DIV_PERIOD * divsToGo; - mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - cyclesLate); + timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo; + mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate); } void GBTimerReset(struct GBTimer* timer) { timer->event.context = timer; timer->event.name = "GB Timer"; - timer->event.callback = _GBTimerIncrement; + timer->event.callback = _GBTimerUpdate; timer->event.priority = 0x20; timer->irq.context = timer; timer->irq.name = "GB Timer IRQ"; @@ -73,11 +68,19 @@ void GBTimerReset(struct GBTimer* timer) { } void GBTimerDivReset(struct GBTimer* timer) { + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + if (timer->internalDiv & (timer->timaPeriod >> 1)) { + ++timer->p->memory.io[REG_TIMA]; + if (!timer->p->memory.io[REG_TIMA]) { + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - ((timer->p->cpu->executionState + 1) & 3)); + } + } timer->p->memory.io[REG_DIV] = 0; timer->internalDiv = 0; timer->nextDiv = GB_DMG_DIV_PERIOD; - mTimingDeschedule(&timer->p->timing, &timer->event); - mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3)); } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { @@ -96,11 +99,15 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { timer->timaPeriod = 256 >> 4; break; } + + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + timer->nextDiv += GB_DMG_DIV_PERIOD; + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); } else { timer->timaPeriod = 0; } - mTimingDeschedule(&timer->p->timing, &timer->event); - _GBTimerUpdateDIV(timer, 0); return tac; } diff --git a/src/gb/video.c b/src/gb/video.c index e8a1fd015..605fcdd7f 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -185,9 +185,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { - video->p->memory.rotation->sample(video->p->memory.rotation); - } } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; next = GB_VIDEO_HORIZONTAL_LENGTH - 8; @@ -389,6 +386,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } + video->p->memory.io[REG_STAT] = video->stat; } void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) { @@ -471,11 +469,11 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) { video->vramCurrentBank = value; } -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) { +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color) { if (index >= 4) { return; } - video->dmgPalette[index] = color; + video->dmgPalette[index] = M_RGB8_TO_RGB5(color); } static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { diff --git a/src/gba/core.c b/src/gba/core.c index a20ae3ed8..b30f39162 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -28,7 +28,7 @@ #include #endif -const static struct mCoreChannelInfo _GBAVideoLayers[] = { +static const struct mCoreChannelInfo _GBAVideoLayers[] = { { 0, "bg0", "Background 0", NULL }, { 1, "bg1", "Background 1", NULL }, { 2, "bg2", "Background 2", NULL }, @@ -36,16 +36,16 @@ const static struct mCoreChannelInfo _GBAVideoLayers[] = { { 4, "obj", "Objects", NULL }, }; -const static struct mCoreChannelInfo _GBAAudioChannels[] = { - { 0, "ch0", "PSG Channel 0", "Square/Sweep" }, - { 1, "ch1", "PSG Channel 1", "Square" }, - { 2, "ch2", "PSG Channel 2", "PCM" }, - { 3, "ch3", "PSG Channel 3", "Noise" }, +static const struct mCoreChannelInfo _GBAAudioChannels[] = { + { 0, "ch1", "PSG Channel 1", "Square/Sweep" }, + { 1, "ch2", "PSG Channel 2", "Square" }, + { 2, "ch3", "PSG Channel 3", "PCM" }, + { 3, "ch4", "PSG Channel 4", "Noise" }, { 4, "chA", "FIFO Channel A", NULL }, { 5, "chB", "FIFO Channel B", NULL }, }; -const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = { +static const struct mCoreMemoryBlock _GBAMemoryBlocks[] = { { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -59,7 +59,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = { { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, }; -const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = { +static const struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = { { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -74,7 +74,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = { { REGION_CART_SRAM, "sram", "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_SRAM, SIZE_CART_SRAM, true }, }; -const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = { +static const struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = { { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -89,7 +89,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = { { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH512, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, }; -const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = { +static const struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = { { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -104,7 +104,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = { { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, }; -const static struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { +static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, diff --git a/src/gba/io.c b/src/gba/io.c index 9f67b296f..7ec88c399 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -501,23 +501,19 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { case REG_TM0CNT_HI: value &= 0x00C7; - GBATimerUpdateRegister(gba, 0); - GBATimerWriteTMCNT_HI(&gba->timers[0], &gba->timing, gba->cpu, &gba->memory.io[REG_TM0CNT_LO >> 1], value); + GBATimerWriteTMCNT_HI(&gba->timers[0], &gba->timing, &gba->memory.io[REG_TM0CNT_LO >> 1], value); break; case REG_TM1CNT_HI: value &= 0x00C7; - GBATimerUpdateRegister(gba, 1); - GBATimerWriteTMCNT_HI(&gba->timers[1], &gba->timing, gba->cpu, &gba->memory.io[REG_TM1CNT_LO >> 1], value); + GBATimerWriteTMCNT_HI(&gba->timers[1], &gba->timing, &gba->memory.io[REG_TM1CNT_LO >> 1], value); break; case REG_TM2CNT_HI: value &= 0x00C7; - GBATimerUpdateRegister(gba, 2); - GBATimerWriteTMCNT_HI(&gba->timers[2], &gba->timing, gba->cpu, &gba->memory.io[REG_TM2CNT_LO >> 1], value); + GBATimerWriteTMCNT_HI(&gba->timers[2], &gba->timing, &gba->memory.io[REG_TM2CNT_LO >> 1], value); break; case REG_TM3CNT_HI: value &= 0x00C7; - GBATimerUpdateRegister(gba, 3); - GBATimerWriteTMCNT_HI(&gba->timers[3], &gba->timing, gba->cpu, &gba->memory.io[REG_TM3CNT_LO >> 1], value); + GBATimerWriteTMCNT_HI(&gba->timers[3], &gba->timing, &gba->memory.io[REG_TM3CNT_LO >> 1], value); break; // SIO @@ -711,17 +707,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { } switch (address) { + // Reading this takes two cycles (1N+1I), so let's remove them preemptively case REG_TM0CNT_LO: - GBATimerUpdateRegister(gba, 0); + GBATimerUpdateRegister(gba, 0, 2); break; case REG_TM1CNT_LO: - GBATimerUpdateRegister(gba, 1); + GBATimerUpdateRegister(gba, 1, 2); break; case REG_TM2CNT_LO: - GBATimerUpdateRegister(gba, 2); + GBATimerUpdateRegister(gba, 2, 2); break; case REG_TM3CNT_LO: - GBATimerUpdateRegister(gba, 3); + GBATimerUpdateRegister(gba, 3, 2); break; case REG_KEYINPUT: @@ -929,10 +926,9 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) { for (i = 0; i < 4; ++i) { STORE_16(gba->memory.io[(REG_DMA0CNT_LO + i * 12) >> 1], (REG_DMA0CNT_LO + i * 12), state->io); STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload); - STORE_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload); STORE_32(gba->timers[i].lastEvent - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].lastEvent); STORE_32(gba->timers[i].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent); - STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval); + STORE_32(gba->timers[i].irq.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextIrq); STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags); STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); @@ -958,8 +954,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { uint32_t when; for (i = 0; i < 4; ++i) { LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload); - LOAD_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload); - LOAD_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval); LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags); if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) { // Overwrite invalid values in savestate @@ -972,6 +966,10 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { if (GBATimerFlagsIsEnable(gba->timers[i].flags)) { mTimingSchedule(&gba->timing, &gba->timers[i].event, when); } + LOAD_32(when, 0, &state->timers[i].nextIrq); + if (GBATimerFlagsIsIrqPending(gba->timers[i].flags)) { + mTimingSchedule(&gba->timing, &gba->timers[i].irq, when); + } LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io); LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index 4ada2360c..d9ca483c9 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -115,24 +115,14 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec uint32_t copySize = size - 0x1C; switch (gba->memory.savedata.type) { - case SAVEDATA_SRAM: - if (copySize > SIZE_CART_SRAM) { - copySize = SIZE_CART_SRAM; - } - break; case SAVEDATA_FLASH512: if (copySize > SIZE_CART_FLASH512) { GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); } // Fall through - case SAVEDATA_FLASH1M: - if (copySize > SIZE_CART_FLASH1M) { - copySize = SIZE_CART_FLASH1M; - } - break; - case SAVEDATA_EEPROM: - if (copySize > SIZE_CART_EEPROM) { - copySize = SAVEDATA_EEPROM; + default: + if (copySize > GBASavedataSize(&gba->memory.savedata)) { + copySize = GBASavedataSize(&gba->memory.savedata); } break; case SAVEDATA_FORCE_NONE: @@ -141,6 +131,7 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec } memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + gba->memory.savedata.vf && gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size); free(payload); return true; diff --git a/src/gba/timer.c b/src/gba/timer.c index 97feef1ce..c8b5e77b9 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -8,6 +8,58 @@ #include #include +#define TIMER_IRQ_DELAY 7 +#define TIMER_RELOAD_DELAY 0 +#define TIMER_STARTUP_DELAY 2 + +static void GBATimerIrq(struct GBA* gba, int timerId) { + struct GBATimer* timer = &gba->timers[timerId]; + if (GBATimerFlagsIsIrqPending(timer->flags)) { + timer->flags = GBATimerFlagsClearIrqPending(timer->flags); + GBARaiseIRQ(gba, IRQ_TIMER0 + timerId); + } +} + +static void GBATimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + GBATimerIrq(context, 0); +} + +static void GBATimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + GBATimerIrq(context, 1); +} + +static void GBATimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + GBATimerIrq(context, 2); +} + +static void GBATimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + GBATimerIrq(context, 3); +} + +void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) { + *io = timer->reload; + int32_t currentTime = mTimingCurrentTime(timing) - cyclesLate; + int32_t tickMask = (1 << GBATimerFlagsGetPrescaleBits(timer->flags)) - 1; + currentTime &= ~tickMask; + timer->lastEvent = currentTime; + GBATimerUpdateRegisterInternal(timer, timing, io, 0); + + if (GBATimerFlagsIsDoIrq(timer->flags)) { + timer->flags = GBATimerFlagsFillIrqPending(timer->flags); + if (!mTimingIsScheduled(timing, &timer->irq)) { + mTimingSchedule(timing, &timer->irq, TIMER_IRQ_DELAY - cyclesLate); + } + } +} + static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLate) { if (!gba->audio.enable) { return; @@ -25,28 +77,13 @@ void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, u if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? ++*io; if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) { - mTimingSchedule(timing, &nextTimer->event, -cyclesLate); + GBATimerUpdate(timing, nextTimer, io, cyclesLate); } } } -void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) { - *io = timer->reload; - timer->oldReload = timer->reload; - timer->lastEvent = timing->masterCycles - cyclesLate; - - if (!GBATimerFlagsIsCountUp(timer->flags)) { - uint32_t nextEvent = timer->overflowInterval - cyclesLate; - mTimingSchedule(timing, &timer->event, nextEvent); - } -} - static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; - struct GBATimer* timer = &gba->timers[0]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - GBARaiseIRQ(gba, IRQ_TIMER0); - } GBATimerUpdateAudio(gba, 0, cyclesLate); GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate); @@ -54,10 +91,6 @@ static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cycl static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; - struct GBATimer* timer = &gba->timers[1]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - GBARaiseIRQ(gba, IRQ_TIMER1); - } GBATimerUpdateAudio(gba, 1, cyclesLate); GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate); @@ -65,20 +98,12 @@ static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cycl static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; - struct GBATimer* timer = &gba->timers[2]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - GBARaiseIRQ(gba, IRQ_TIMER2); - } GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate); GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate); } static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; - struct GBATimer* timer = &gba->timers[3]; - if (GBATimerFlagsIsDoIrq(timer->flags)) { - GBARaiseIRQ(gba, IRQ_TIMER3); - } GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate); } @@ -100,63 +125,97 @@ void GBATimerInit(struct GBA* gba) { gba->timers[3].event.callback = GBATimerUpdate3; gba->timers[3].event.context = gba; gba->timers[3].event.priority = 0x23; + gba->timers[0].irq.name = "GBA Timer 0 IRQ"; + gba->timers[0].irq.callback = GBATimerIrq0; + gba->timers[0].irq.context = gba; + gba->timers[0].irq.priority = 0x28; + gba->timers[1].irq.name = "GBA Timer 1 IRQ"; + gba->timers[1].irq.callback = GBATimerIrq1; + gba->timers[1].irq.context = gba; + gba->timers[1].irq.priority = 0x29; + gba->timers[2].irq.name = "GBA Timer 2 IRQ"; + gba->timers[2].irq.callback = GBATimerIrq2; + gba->timers[2].irq.context = gba; + gba->timers[2].irq.priority = 0x2A; + gba->timers[3].irq.name = "GBA Timer 3 IRQ"; + gba->timers[3].irq.callback = GBATimerIrq3; + gba->timers[3].irq.context = gba; + gba->timers[3].irq.priority = 0x2B; } -void GBATimerUpdateRegister(struct GBA* gba, int timer) { +void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) { struct GBATimer* currentTimer = &gba->timers[timer]; if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) { - // Reading this takes two cycles (1N+1I), so let's remove them preemptively - int32_t prefetchSkew = -2; + int32_t prefetchSkew = cyclesLate; if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; } - GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, gba->cpu, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew); + GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew); } } -void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew) { - int32_t diff = cpu->cycles - (timer->lastEvent - timing->masterCycles); - *io = timer->oldReload + ((diff + skew) >> GBATimerFlagsGetPrescaleBits(timer->flags)); +void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew) { + if (!GBATimerFlagsIsEnable(timer->flags) || GBATimerFlagsIsCountUp(timer->flags)) { + return; + } + + int prescaleBits = GBATimerFlagsGetPrescaleBits(timer->flags); + int32_t currentTime = mTimingCurrentTime(timing) - skew; + int32_t tickMask = (1 << prescaleBits) - 1; + currentTime &= ~tickMask; + int32_t tickIncrement = currentTime - timer->lastEvent; + timer->lastEvent = currentTime; + tickIncrement >>= prescaleBits; + tickIncrement += *io; + *io = tickIncrement; + if (!mTimingIsScheduled(timing, &timer->event)) { + tickIncrement = (0x10000 - tickIncrement) << prescaleBits; + currentTime -= mTimingCurrentTime(timing) - skew; + mTimingSchedule(timing, &timer->event, TIMER_RELOAD_DELAY + tickIncrement + currentTime); + } } void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) { timer->reload = reload; - timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags); } -void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control) { +void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control) { + GBATimerUpdateRegisterInternal(timer, timing, io, 0); + unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(timer->flags); + unsigned prescaleBits; switch (control & 0x0003) { case 0x0000: - timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, timer->forcedPrescale); + prescaleBits = 0; break; case 0x0001: - timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 6 + timer->forcedPrescale); + prescaleBits = 6; break; case 0x0002: - timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 8 + timer->forcedPrescale); + prescaleBits = 8; break; case 0x0003: - timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 10 + timer->forcedPrescale); + prescaleBits = 10; break; } + prescaleBits += timer->forcedPrescale; + timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, prescaleBits); timer->flags = GBATimerFlagsTestFillCountUp(timer->flags, timer > 0 && (control & 0x0004)); timer->flags = GBATimerFlagsTestFillDoIrq(timer->flags, control & 0x0040); - timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags); bool wasEnabled = GBATimerFlagsIsEnable(timer->flags); timer->flags = GBATimerFlagsTestFillEnable(timer->flags, control & 0x0080); if (!wasEnabled && GBATimerFlagsIsEnable(timer->flags)) { mTimingDeschedule(timing, &timer->event); - if (!GBATimerFlagsIsCountUp(timer->flags)) { - mTimingSchedule(timing, &timer->event, timer->overflowInterval); - } *io = timer->reload; - timer->oldReload = timer->reload; - timer->lastEvent = timing->masterCycles + cpu->cycles; + int32_t tickMask = (1 << prescaleBits) - 1; + timer->lastEvent = (mTimingCurrentTime(timing) - TIMER_STARTUP_DELAY) & ~tickMask; + GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_STARTUP_DELAY); } else if (wasEnabled && !GBATimerFlagsIsEnable(timer->flags)) { mTimingDeschedule(timing, &timer->event); } else if (GBATimerFlagsIsEnable(timer->flags) && GBATimerFlagsGetPrescaleBits(timer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(timer->flags)) { mTimingDeschedule(timing, &timer->event); - mTimingSchedule(timing, &timer->event, timer->overflowInterval - timer->lastEvent); + int32_t tickMask = (1 << prescaleBits) - 1; + timer->lastEvent = (mTimingCurrentTime(timing) - TIMER_STARTUP_DELAY) & ~tickMask; + GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_STARTUP_DELAY); } } diff --git a/src/lr35902/decoder.c b/src/lr35902/decoder.c index 796a50b17..61832acf1 100644 --- a/src/lr35902/decoder.c +++ b/src/lr35902/decoder.c @@ -315,24 +315,24 @@ DEFINE_POPPUSH_DECODER_LR35902(HL); DEFINE_POPPUSH_DECODER_LR35902(AF); #define DEFINE_CB_2_DECODER_LR35902(NAME, BODY) \ - DEFINE_DECODER_LR35902(NAME ## B, info->op1.reg = LR35902_REG_B; BODY) \ - DEFINE_DECODER_LR35902(NAME ## C, info->op1.reg = LR35902_REG_C; BODY) \ - DEFINE_DECODER_LR35902(NAME ## D, info->op1.reg = LR35902_REG_D; BODY) \ - DEFINE_DECODER_LR35902(NAME ## E, info->op1.reg = LR35902_REG_E; BODY) \ - DEFINE_DECODER_LR35902(NAME ## H, info->op1.reg = LR35902_REG_H; BODY) \ - DEFINE_DECODER_LR35902(NAME ## L, info->op1.reg = LR35902_REG_L; BODY) \ - DEFINE_DECODER_LR35902(NAME ## HL, info->op1.reg = LR35902_REG_HL; BODY) \ - DEFINE_DECODER_LR35902(NAME ## A, info->op1.reg = LR35902_REG_A; BODY) + DEFINE_DECODER_LR35902(NAME ## B, info->op2.reg = LR35902_REG_B; BODY) \ + DEFINE_DECODER_LR35902(NAME ## C, info->op2.reg = LR35902_REG_C; BODY) \ + DEFINE_DECODER_LR35902(NAME ## D, info->op2.reg = LR35902_REG_D; BODY) \ + DEFINE_DECODER_LR35902(NAME ## E, info->op2.reg = LR35902_REG_E; BODY) \ + DEFINE_DECODER_LR35902(NAME ## H, info->op2.reg = LR35902_REG_H; BODY) \ + DEFINE_DECODER_LR35902(NAME ## L, info->op2.reg = LR35902_REG_L; BODY) \ + DEFINE_DECODER_LR35902(NAME ## HL, info->op2.reg = LR35902_REG_HL; info->op2.flags = LR35902_OP_FLAG_MEMORY; BODY) \ + DEFINE_DECODER_LR35902(NAME ## A, info->op2.reg = LR35902_REG_A; BODY) #define DEFINE_CB_DECODER_LR35902(NAME, BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 0, info->op2.immediate = 1; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 1, info->op2.immediate = 2; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 2, info->op2.immediate = 4; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 3, info->op2.immediate = 8; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 4, info->op2.immediate = 16; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 5, info->op2.immediate = 32; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 6, info->op2.immediate = 64; BODY) \ - DEFINE_CB_2_DECODER_LR35902(NAME ## 7, info->op2.immediate = 128; BODY) + DEFINE_CB_2_DECODER_LR35902(NAME ## 0, info->op1.immediate = 0; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 1, info->op1.immediate = 1; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 2, info->op1.immediate = 2; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 3, info->op1.immediate = 3; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 4, info->op1.immediate = 4; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 5, info->op1.immediate = 5; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 6, info->op1.immediate = 6; BODY) \ + DEFINE_CB_2_DECODER_LR35902(NAME ## 7, info->op1.immediate = 7; BODY) DEFINE_CB_DECODER_LR35902(BIT, info->mnemonic = LR35902_MN_BIT) DEFINE_CB_DECODER_LR35902(RES, info->mnemonic = LR35902_MN_RES) @@ -496,7 +496,7 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) { } if (op.flags & LR35902_OP_FLAG_MEMORY) { - strncpy(buffer, "(", blen - 1); + strncpy(buffer, "[", blen - 1); ADVANCE(1); } if (op.reg) { @@ -519,7 +519,7 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) { ADVANCE(1); } if (op.flags & LR35902_OP_FLAG_MEMORY) { - strncpy(buffer, ")", blen - 1); + strncpy(buffer, "]", blen - 1); ADVANCE(1); } return total; @@ -544,7 +544,7 @@ int LR35902Disassemble(struct LR35902InstructionInfo* info, char* buffer, int bl } } - if (info->op1.reg || info->op1.immediate) { + if (info->op1.reg || info->op1.immediate || info->op2.reg || info->op2.immediate) { written = _decodeOperand(info->op1, buffer, blen); ADVANCE(written); } diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 47842273e..2dfa7448b 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -175,7 +175,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) drawW -= drawW % v->width; drawH -= drawH % v->height; } - glViewport(0, 0, v->width, v->height); + glViewport(0, 0, w, h); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); @@ -203,13 +203,13 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { int drawH = shader->height; int padW = 0; int padH = 0; - if (!shader->width) { + if (!drawW) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { drawW = context->d.width * -shader->width; } - if (!shader->height) { + if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { @@ -234,7 +234,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glEnableVertexAttribArray(shader->positionLocation); size_t u; @@ -290,7 +290,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); } void mGLES2ContextDrawFrame(struct VideoBackend* v) { @@ -298,12 +297,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, context->tex); + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + context->finalShader.filter = v->filter; _drawShader(context, &context->initialShader); size_t n; for (n = 0; n < context->nShaders; ++n) { + glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->shaders[n]); } + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _drawShader(context, &context->finalShader); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index a6fb17017..8c127f735 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,9 +1,7 @@ -find_program(FIXUP vita-elf-create) -find_program(MAKE_FSELF vita-make-fself) -find_program(MAKE_SFO vita-mksfoex) +include("${VITASDK}/share/vita.cmake" REQUIRED) + find_program(OBJCOPY ${cross_prefix}objcopy) find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share) -find_program(STRIP ${cross_prefix}strip) set(OS_DEFINES IOAPI_NO_64) set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) @@ -37,34 +35,16 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o COMMAND ${OBJCOPY_CMD} backdrop.png ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -add_custom_target(${BINARY_NAME}.velf ALL - ${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf - COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} - DEPENDS ${BINARY_NAME}.elf) - -add_custom_target(sce_sys ${CMAKE_COMMAND} -E make_directory sce_sys) - -add_custom_target(param.sfo - ${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA00001 sce_sys/param.sfo - DEPENDS sce_sys) - -add_custom_target(eboot.bin - ${MAKE_FSELF} -s ${BINARY_NAME}.velf eboot.bin - DEPENDS ${BINARY_NAME}.velf) +vita_create_self(${BINARY_NAME}.self ${BINARY_NAME}.elf) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/template.xml.in ${CMAKE_CURRENT_BINARY_DIR}/template.xml) -add_custom_target(livearea - ${CMAKE_COMMAND} -E make_directory sce_sys/livearea/contents - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/icon0.png sce_sys - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/pic0.png sce_sys - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bg.png sce_sys/livearea/contents - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents - DEPENDS sce_sys) - -add_custom_target(${BINARY_NAME}.vpk ALL - zip -qr ${BINARY_NAME}.vpk sce_sys eboot.bin - DEPENDS livearea eboot.bin param.sfo) +vita_create_vpk(${BINARY_NAME}.vpk MGBA00001 ${BINARY_NAME}.self + NAME ${PROJECT_NAME} + FILE ${CMAKE_CURRENT_SOURCE_DIR}/icon0.png sce_sys/icon0.png + FILE ${CMAKE_CURRENT_SOURCE_DIR}/pic0.png sce_sys/pic0.png + FILE ${CMAKE_CURRENT_SOURCE_DIR}/bg.png sce_sys/livearea/contents/bg.png + FILE ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents/startup.png + FILE ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents/template.xml) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/CMakeToolchain.vitasdk b/src/platform/psp2/CMakeToolchain.vitasdk index fce2bd5f1..4334701e7 100644 --- a/src/platform/psp2/CMakeToolchain.vitasdk +++ b/src/platform/psp2/CMakeToolchain.vitasdk @@ -14,7 +14,7 @@ set(CMAKE_PROGRAM_PATH ${toolchain_dir}/bin) set(cross_prefix arm-vita-eabi-) set(inc_flags -I${toolchain_dir}/include) -set(link_flags "-L${toolchain_dir}/lib -Wl,-q") +set(link_flags "-L${toolchain_dir}/lib -Wl,-z,nocopyreloc") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR armv7-a CACHE INTERNAL "processor") @@ -26,9 +26,9 @@ find_program(CMAKE_C_COMPILER ${cross_prefix}gcc${extension}) find_program(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension}) find_program(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension}) find_program(CMAKE_LINKER ${cross_prefix}ld${extension}) -set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_C_FLAGS "${inc_flags} -Wl,-q" CACHE INTERNAL "c compiler flags") set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "assembler flags") -set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") +set(CMAKE_CXX_FLAGS "${inc_flags} -Wl,-q" CACHE INTERNAL "cxx compiler flags") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") @@ -41,6 +41,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE INTERNAL "") set(ENV{PKG_CONFIG_PATH} ${VITASDK}/arm-vita-eabi/lib/pkgconfig) set(ENV{PKG_CONFIG_LIBDIR} ${VITASDK}/arm-vita-eabi/lib/pkgconfig) +set(VITA_ELF_CREATE "${VITASDK}/bin/vita-elf-create${TOOL_OS_SUFFIX}" CACHE PATH "vita-elf-create") +set(VITA_ELF_EXPORT "${VITASDK}/bin/vita-elf-export${TOOL_OS_SUFFIX}" CACHE PATH "vita-elf-export") +set(VITA_LIBS_GEN "${VITASDK}/bin/vita-libs-gen${TOOL_OS_SUFFIX}" CACHE PATH "vita-libs-gen") +set(VITA_MAKE_FSELF "${VITASDK}/bin/vita-make-fself${TOOL_OS_SUFFIX}" CACHE PATH "vita-make-fself") +set(VITA_MKSFOEX "${VITASDK}/bin/vita-mksfoex${TOOL_OS_SUFFIX}" CACHE PATH "vita-mksfoex") +set(VITA_PACK_VPK "${VITASDK}/bin/vita-pack-vpk${TOOL_OS_SUFFIX}" CACHE PATH "vita-pack-vpk") + set(PSP2 ON) add_definitions(-DPSP2) set(M_LIBRARY m) diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 9721e14b6..7d7703bb5 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -30,6 +30,7 @@ void free(void*); #include #include #include +#include #define PYEXPORT extern "Python+C" #include "platform/python/log.h" diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 0c50ff0e5..2065dec40 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -23,6 +23,7 @@ ffi.set_source("mgba._pylib", """ #include #include #include +#include #include #include #include diff --git a/src/platform/python/mgba/__init__.py b/src/platform/python/mgba/__init__.py index aab19b4d2..a8c0ddce4 100644 --- a/src/platform/python/mgba/__init__.py +++ b/src/platform/python/mgba/__init__.py @@ -5,6 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from ._pylib import ffi, lib +from collections import namedtuple + def createCallback(structName, cbName, funcName=None): funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) fullStruct = "struct {}*".format(structName) @@ -13,3 +15,19 @@ def createCallback(structName, cbName, funcName=None): return getattr(ffi.from_handle(h.pyobj), cbName)(*args) return ffi.def_extern(name=funcName)(cb) + +version = ffi.string(lib.projectVersion).decode('utf-8') + +GitInfo = namedtuple("GitInfo", "commit commitShort branch revision") + +git = {} +if lib.gitCommit and lib.gitCommit != "(unknown)": + git['commit'] = ffi.string(lib.gitCommit).decode('utf-8') +if lib.gitCommitShort and lib.gitCommitShort != "(unknown)": + git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') +if lib.gitBranch and lib.gitBranch != "(unknown)": + git['branch'] = ffi.string(lib.gitBranch).decode('utf-8') +if lib.gitRevision > 0: + git['revision'] = lib.gitRevision + +git = GitInfo(**git) diff --git a/src/platform/python/mgba/gb.py b/src/platform/python/mgba/gb.py index 2b490e008..fdf7f78bd 100644 --- a/src/platform/python/mgba/gb.py +++ b/src/platform/python/mgba/gb.py @@ -35,6 +35,10 @@ class GB(Core): self._native.video.renderer.cache = ffi.NULL lib.mTileCacheDeinit(cache) + def reset(self): + super(GB, self).reset() + self.memory = GBMemory(self._core) + def attachSIO(self, link): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) @@ -61,21 +65,46 @@ class GBSIODriver(object): return value class GBSIOSimpleDriver(GBSIODriver): - def __init__(self): + def __init__(self, period=0x100): super(GBSIOSimpleDriver, self).__init__() - self.tx = 0xFF - self.rx = 0xFF + self.rx = 0x00 + self._period = period + + def init(self): + self._native.p.period = self._period + return True def writeSB(self, value): self.rx = value - def schedule(self, period=0x100, when=0): + def writeSC(self, value): + self._native.p.period = self._period + if value & 0x80: + lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) + lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period) + return value + + def isReady(self): + return not self._native.p.remainingBits + + @property + def tx(self): + self._native.p.pendingSB + + @property + def period(self): + return self._native.p.period + + @tx.setter + def tx(self, newTx): + self._native.p.pendingSB = newTx self._native.p.remainingBits = 8 - self._native.p.period = period - self._native.p.pendingSB = self.tx - self.tx = 0xFF - lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) - lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), when) + + @period.setter + def period(self, newPeriod): + self._period = newPeriod + if self._native.p: + self._native.p.period = newPeriod class GBMemory(Memory): def __init__(self, core): diff --git a/src/platform/python/mgba/image.py b/src/platform/python/mgba/image.py index f76945b32..948edeeb4 100644 --- a/src/platform/python/mgba/image.py +++ b/src/platform/python/mgba/image.py @@ -6,64 +6,74 @@ from ._pylib import ffi, lib from . import png +try: + import PIL.Image as PImage +except ImportError: + pass + class Image: - def __init__(self, width, height, stride=0): - self.width = width - self.height = height - self.stride = stride - self.constitute() + def __init__(self, width, height, stride=0): + self.width = width + self.height = height + self.stride = stride + self.constitute() - def constitute(self): - if self.stride <= 0: - self.stride = self.width - self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) + def constitute(self): + if self.stride <= 0: + self.stride = self.width + self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) - def savePNG(self, f): - p = png.PNG(f) - success = p.writeHeader(self) - success = success and p.writePixels(self) - p.writeClose() - return success + def savePNG(self, f): + p = png.PNG(f) + success = p.writeHeader(self) + success = success and p.writePixels(self) + p.writeClose() + return success + + if 'PImage' in globals(): + def toPIL(self): + return PImage.frombytes("RGBX", (self.width, self.height), ffi.buffer(self.buffer), "raw", + "RGBX", self.stride * 4) def u16ToU32(c): - r = c & 0x1F - g = (c >> 5) & 0x1F - b = (c >> 10) & 0x1F - a = (c >> 15) & 1 - abgr = r << 3 - abgr |= g << 11 - abgr |= b << 19 - abgr |= (a * 0xFF) << 24 - return abgr + r = c & 0x1F + g = (c >> 5) & 0x1F + b = (c >> 10) & 0x1F + a = (c >> 15) & 1 + abgr = r << 3 + abgr |= g << 11 + abgr |= b << 19 + abgr |= (a * 0xFF) << 24 + return abgr def u32ToU16(c): - r = (c >> 3) & 0x1F - g = (c >> 11) & 0x1F - b = (c >> 19) & 0x1F - a = c >> 31 - abgr = r - abgr |= g << 5 - abgr |= b << 10 - abgr |= a << 15 - return abgr + r = (c >> 3) & 0x1F + g = (c >> 11) & 0x1F + b = (c >> 19) & 0x1F + a = c >> 31 + abgr = r + abgr |= g << 5 + abgr |= b << 10 + abgr |= a << 15 + return abgr if ffi.sizeof("color_t") == 2: - def colorToU16(c): - return c + def colorToU16(c): + return c - colorToU32 = u16ToU32 + colorToU32 = u16ToU32 - def u16ToColor(c): - return c + def u16ToColor(c): + return c - u32ToColor = u32ToU16 + u32ToColor = u32ToU16 else: - def colorToU32(c): - return c + def colorToU32(c): + return c - colorToU16 = u32ToU16 + colorToU16 = u32ToU16 - def u32ToColor(c): - return c + def u32ToColor(c): + return c - u16ToColor = u16ToU32 + u16ToColor = u16ToU32 diff --git a/src/platform/python/mgba/log.py b/src/platform/python/mgba/log.py index 4514e67a9..168627a0e 100644 --- a/src/platform/python/mgba/log.py +++ b/src/platform/python/mgba/log.py @@ -8,29 +8,33 @@ from . import createCallback createCallback("mLoggerPy", "log", "_pyLog") +defaultLogger = None + def installDefault(logger): - lib.mLogSetDefaultLogger(logger._native) + global defaultLogger + defaultLogger = logger + lib.mLogSetDefaultLogger(logger._native) class Logger(object): - FATAL = lib.mLOG_FATAL - DEBUG = lib.mLOG_DEBUG - INFO = lib.mLOG_INFO - WARN = lib.mLOG_WARN - ERROR = lib.mLOG_ERROR - STUB = lib.mLOG_STUB - GAME_ERROR = lib.mLOG_GAME_ERROR + FATAL = lib.mLOG_FATAL + DEBUG = lib.mLOG_DEBUG + INFO = lib.mLOG_INFO + WARN = lib.mLOG_WARN + ERROR = lib.mLOG_ERROR + STUB = lib.mLOG_STUB + GAME_ERROR = lib.mLOG_GAME_ERROR - def __init__(self): - self._handle = ffi.new_handle(self) - self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) - @staticmethod - def categoryName(category): - return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + @staticmethod + def categoryName(category): + return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') - def log(self, category, level, message): - print("{}: {}".format(self.categoryName(category), message)) + def log(self, category, level, message): + print("{}: {}".format(self.categoryName(category), message)) class NullLogger(Logger): - def log(self, category, level, message): - pass + def log(self, category, level, message): + pass diff --git a/src/platform/python/setup.py.in b/src/platform/python/setup.py.in index 027fa1aaa..04c1c0c29 100644 --- a/src/platform/python/setup.py.in +++ b/src/platform/python/setup.py.in @@ -23,6 +23,7 @@ setup(name="${BINARY_NAME}", packages=["mgba"], setup_requires=['cffi>=1.6'], install_requires=['cffi>=1.6', 'cached-property'], + extras_require={'pil': ['Pillow>=2.3']}, cffi_modules=["_builder.py:ffi"], license="MPL 2.0", classifiers=classifiers diff --git a/src/platform/qt/AboutScreen.ui b/src/platform/qt/AboutScreen.ui index ac886e13c..eaaab43d4 100644 --- a/src/platform/qt/AboutScreen.ui +++ b/src/platform/qt/AboutScreen.ui @@ -83,7 +83,7 @@ - © 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 + © 2013 – 2017 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 Game Boy and Game Boy Advance are registered trademarks of Nintendo Co., Ltd. diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 1d7664891..fb4292b27 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -26,6 +26,7 @@ endif() set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/input) find_package(Qt5Multimedia) find_package(Qt5OpenGL) @@ -76,10 +77,6 @@ set(SOURCE_FILES GamepadButtonEvent.cpp GamepadHatEvent.cpp IOViewer.cpp - InputController.cpp - InputItem.cpp - InputModel.cpp - InputProfile.cpp KeyEditor.cpp LoadSaveState.cpp LogController.cpp @@ -104,7 +101,12 @@ set(SOURCE_FILES utils.cpp Window.cpp VFileDevice.cpp - VideoView.cpp) + VideoView.cpp + input/InputController.cpp + input/InputIndex.cpp + input/InputItem.cpp + input/InputModel.cpp + input/InputProfile.cpp) set(UI_FILES AboutScreen.ui @@ -215,7 +217,7 @@ endif() install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) if(NOT WIN32 AND NOT APPLE) - list(APPEND QT_DEFINES DATADIR="${DATADIR}") + list(APPEND QT_DEFINES DATADIR="${CMAKE_INSTALL_PREFIX}/${DATADIR}") endif() find_package(Qt5LinguistTools) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 8abb6a21b..c92cc402f 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -6,6 +6,7 @@ #include "GBAApp.h" #include "AudioProcessor.h" +#include "ConfigController.h" #include "Display.h" #include "GameController.h" #include "Window.h" @@ -14,10 +15,8 @@ #include #include #include -#include #include -#include #include #include @@ -31,8 +30,9 @@ static GBAApp* g_app = nullptr; mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt"); -GBAApp::GBAApp(int& argc, char* argv[]) +GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config) : QApplication(argc, argv) + , m_configController(config) { g_app = this; @@ -41,15 +41,9 @@ GBAApp::GBAApp(int& argc, char* argv[]) #endif #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/res/mgba-1024.png")); + setWindowIcon(QIcon(":/res/mgba-512.png")); #endif - QTranslator* translator = new QTranslator(this); - if (translator->load(QLocale(), QLatin1String(binaryName), QLatin1String("-"), QLatin1String(":/translations"))) { - installTranslator(translator); - } - - SocketSubsystemInit(); qRegisterMetaType("const uint32_t*"); qRegisterMetaType("mCoreThread*"); @@ -57,50 +51,15 @@ GBAApp::GBAApp(int& argc, char* argv[]) QApplication::setApplicationName(projectName); QApplication::setApplicationVersion(projectVersion); - if (!m_configController.getQtOption("displayDriver").isNull()) { - Display::setDriver(static_cast(m_configController.getQtOption("displayDriver").toInt())); - } - - mArguments args; - mGraphicsOpts graphicsOpts; - mSubParser subparser; - initParserForGraphics(&subparser, &graphicsOpts); - bool loaded = m_configController.parseArguments(&args, argc, argv, &subparser); - if (loaded && args.showHelp) { - usage(argv[0], subparser.usage); - ::exit(0); - return; + if (!m_configController->getQtOption("displayDriver").isNull()) { + Display::setDriver(static_cast(m_configController->getQtOption("displayDriver").toInt())); } reloadGameDB(); - if (!m_configController.getQtOption("audioDriver").isNull()) { - AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); + if (!m_configController->getQtOption("audioDriver").isNull()) { + AudioProcessor::setDriver(static_cast(m_configController->getQtOption("audioDriver").toInt())); } - Window* w = new Window(&m_configController); - connect(w, &Window::destroyed, [this, w]() { - m_windows.removeAll(w); - }); - m_windows.append(w); - - if (loaded) { - w->argumentsPassed(&args); - } else { - w->loadConfig(); - } - freeArguments(&args); - - if (graphicsOpts.multiplier) { - w->resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * graphicsOpts.multiplier, VIDEO_VERTICAL_PIXELS * graphicsOpts.multiplier)); - } - if (graphicsOpts.fullscreen) { - w->enterFullScreen(); - } - - w->show(); - - w->controller()->setMultiplayerController(&m_multiplayer); - w->multiplayerChanged(); } GBAApp::~GBAApp() { @@ -122,7 +81,7 @@ Window* GBAApp::newWindow() { if (m_windows.count() >= MAX_GBAS) { return nullptr; } - Window* w = new Window(&m_configController, m_multiplayer.attached()); + Window* w = new Window(m_configController, m_multiplayer.attached()); int windowId = m_multiplayer.attached(); connect(w, &Window::destroyed, [this, w]() { m_windows.removeAll(w); @@ -165,10 +124,10 @@ void GBAApp::continueAll(const QList& paused) { QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) { QList paused; pauseAll(&paused); - QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getOption("lastDirectory"), filter); + QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); + m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } @@ -176,10 +135,10 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) { QList paused; pauseAll(&paused); - QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getOption("lastDirectory"), filter); + QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); + m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } @@ -187,10 +146,10 @@ QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QStr QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) { QList paused; pauseAll(&paused); - QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController.getOption("lastDirectory")); + QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController->getOption("lastDirectory")); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); + m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index fec6056f3..2b7c2e5cc 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -10,7 +10,6 @@ #include #include -#include "ConfigController.h" #include "MultiplayerController.h" struct NoIntroDB; @@ -21,6 +20,7 @@ mLOG_DECLARE_CATEGORY(QT); namespace QGBA { +class ConfigController; class GameController; class Window; @@ -43,7 +43,7 @@ class GBAApp : public QApplication { Q_OBJECT public: - GBAApp(int& argc, char* argv[]); + GBAApp(int& argc, char* argv[], ConfigController*); ~GBAApp(); static GBAApp* app(); @@ -67,7 +67,7 @@ private: void pauseAll(QList* paused); void continueAll(const QList& paused); - ConfigController m_configController; + ConfigController* m_configController; QList m_windows; MultiplayerController m_multiplayer; diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index 0b58760eb..064bd0f58 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GBAKeyEditor.h" +#include #include #include #include @@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) { } bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) { + KeyEditor* keyEditor = static_cast(obj); + if (event->type() == QEvent::FocusOut) { + keyEditor->setPalette(QApplication::palette(keyEditor)); + } if (event->type() != QEvent::FocusIn) { return false; } - findFocus(static_cast(obj)); + + QPalette palette = keyEditor->palette(); + palette.setBrush(keyEditor->backgroundRole(), palette.highlight()); + palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText()); + keyEditor->setPalette(palette); + + findFocus(keyEditor); return true; } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index a22fb919a..6628468a2 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -9,6 +9,7 @@ #include "InputController.h" #include "LogController.h" #include "MultiplayerController.h" +#include "Override.h" #include "VFileDevice.h" #include @@ -162,6 +163,7 @@ GameController::GameController(QObject* parent) default: break; } + mTileCacheDeinit(controller->m_tileCache.get()); controller->m_tileCache.reset(); } @@ -703,6 +705,7 @@ void GameController::threadContinue() { void GameController::frameAdvance() { if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { setPaused(false); + m_wasPaused = true; } } diff --git a/src/platform/qt/InputItem.cpp b/src/platform/qt/InputItem.cpp deleted file mode 100644 index f68a66763..000000000 --- a/src/platform/qt/InputItem.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) 2013-2017 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "InputItem.h" - -#include - -using namespace QGBA; - -InputItem::InputItem(QAction* action, const QString& name, InputItem* parent) - : m_action(action) - , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0]) - , m_keys(-1) - , m_menu(nullptr) - , m_name(name) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_platform(PLATFORM_NONE) - , m_parent(parent) -{ - m_visibleName = action->text() - .remove(QRegExp("&(?!&)")) - .remove("..."); -} - -InputItem::InputItem(InputItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, InputItem* parent) - : m_action(nullptr) - , m_shortcut(shortcut) - , m_functions(functions) - , m_keys(-1) - , m_menu(nullptr) - , m_name(name) - , m_visibleName(visibleName) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_platform(PLATFORM_NONE) - , m_parent(parent) -{ -} - -InputItem::InputItem(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name, InputItem* parent) - : m_action(nullptr) - , m_shortcut(shortcut) - , m_keys(key) - , m_menu(nullptr) - , m_name(name) - , m_visibleName(visibleName) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_platform(platform) - , m_parent(parent) -{ -} - -InputItem::InputItem(QMenu* menu, InputItem* parent) - : m_action(nullptr) - , m_shortcut(0) - , m_menu(menu) - , m_button(-1) - , m_axis(-1) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ - if (menu) { - m_visibleName = menu->title() - .remove(QRegExp("&(?!&)")) - .remove("..."); - } -} - -void InputItem::addAction(QAction* action, const QString& name) { - m_items.append(InputItem(action, name, this)); -} - -void InputItem::addFunctions(InputItem::Functions functions, - int shortcut, const QString& visibleName, - const QString& name) { - m_items.append(InputItem(functions, shortcut, visibleName, name, this)); -} - -void InputItem::addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) { - m_items.append(InputItem(platform, key, shortcut, visibleName, name, this)); -} - -void InputItem::addSubmenu(QMenu* menu) { - m_items.append(InputItem(menu, this)); -} - -void InputItem::setShortcut(int shortcut) { - m_shortcut = shortcut; - if (m_action) { - m_action->setShortcut(QKeySequence(shortcut)); - } -} - -void InputItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { - m_axis = axis; - m_direction = direction; -} diff --git a/src/platform/qt/InputItem.h b/src/platform/qt/InputItem.h deleted file mode 100644 index d52de02a6..000000000 --- a/src/platform/qt/InputItem.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2013-2017 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_INPUT_ITEM -#define QGBA_INPUT_ITEM - -#include "GamepadAxisEvent.h" -#include "GamepadHatEvent.h" - -#include - -#include - -#include - -namespace QGBA { - -class InputItem { -public: - typedef QPair, std::function> Functions; - - InputItem(QAction* action, const QString& name, InputItem* parent = nullptr); - InputItem(Functions functions, int shortcut, const QString& visibleName, - const QString& name, InputItem* parent = nullptr); - InputItem(mPlatform platform, int key, int shortcut, const QString& name, const QString& visibleName, InputItem* parent = nullptr); - InputItem(QMenu* action, InputItem* parent = nullptr); - - QAction* action() { return m_action; } - const QAction* action() const { return m_action; } - int shortcut() const { return m_shortcut; } - mPlatform platform() const { return m_platform; } - Functions functions() const { return m_functions; } - int key() const { return m_keys; } - QMenu* menu() { return m_menu; } - const QMenu* menu() const { return m_menu; } - const QString& visibleName() const { return m_visibleName; } - const QString& name() const { return m_name; } - QList& items() { return m_items; } - const QList& items() const { return m_items; } - InputItem* parent() { return m_parent; } - const InputItem* parent() const { return m_parent; } - void addAction(QAction* action, const QString& name); - void addFunctions(Functions functions, int shortcut, const QString& visibleName, - const QString& name); - void addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name); - void addSubmenu(QMenu* menu); - int button() const { return m_button; } - void setShortcut(int sequence); - void setButton(int button) { m_button = button; } - int axis() const { return m_axis; } - GamepadAxisEvent::Direction direction() const { return m_direction; } - void setAxis(int axis, GamepadAxisEvent::Direction direction); - - bool operator==(const InputItem& other) const { - return m_menu == other.m_menu && m_action == other.m_action; - } - -private: - QAction* m_action; - int m_shortcut; - QMenu* m_menu; - Functions m_functions; - QString m_name; - QString m_visibleName; - int m_button; - int m_axis; - int m_keys; - GamepadAxisEvent::Direction m_direction; - mPlatform m_platform; - QList m_items; - InputItem* m_parent; -}; - -} - -#endif diff --git a/src/platform/qt/InputModel.cpp b/src/platform/qt/InputModel.cpp deleted file mode 100644 index 18bbb9f8d..000000000 --- a/src/platform/qt/InputModel.cpp +++ /dev/null @@ -1,577 +0,0 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "InputModel.h" - -#include "ConfigController.h" -#include "GamepadButtonEvent.h" -#include "InputProfile.h" - -#include -#include -#include - -using namespace QGBA; - -InputModel::InputModel(QObject* parent) - : QAbstractItemModel(parent) - , m_rootMenu(nullptr) -{ -} - -void InputModel::setConfigController(ConfigController* controller) { - m_config = controller; -} - -QVariant InputModel::data(const QModelIndex& index, int role) const { - if (role != Qt::DisplayRole || !index.isValid()) { - return QVariant(); - } - int row = index.row(); - const InputItem* item = static_cast(index.internalPointer()); - switch (index.column()) { - case 0: - return item->visibleName(); - case 1: - return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText); - case 2: - if (item->button() >= 0) { - return item->button(); - } - if (item->axis() >= 0) { - char d = '\0'; - if (item->direction() == GamepadAxisEvent::POSITIVE) { - d = '+'; - } - if (item->direction() == GamepadAxisEvent::NEGATIVE) { - d = '-'; - } - return QString("%1%2").arg(d).arg(item->axis()); - } - break; - } - return QVariant(); -} - -QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) { - return QAbstractItemModel::headerData(section, orientation, role); - } - if (orientation == Qt::Horizontal) { - switch (section) { - case 0: - return tr("Action"); - case 1: - return tr("Keyboard"); - case 2: - return tr("Gamepad"); - } - } - return section; -} - -QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const { - const InputItem* pmenu = &m_rootMenu; - if (parent.isValid()) { - pmenu = static_cast(parent.internalPointer()); - } - return createIndex(row, column, const_cast(&pmenu->items()[row])); -} - -QModelIndex InputModel::parent(const QModelIndex& index) const { - if (!index.isValid() || !index.internalPointer()) { - return QModelIndex(); - } - InputItem* item = static_cast(index.internalPointer()); - return this->index(item->parent()); -} - -QModelIndex InputModel::index(InputItem* item) const { - if (!item || !item->parent()) { - return QModelIndex(); - } - return createIndex(item->parent()->items().indexOf(*item), 0, item); -} - -int InputModel::columnCount(const QModelIndex& index) const { - return 3; -} - -int InputModel::rowCount(const QModelIndex& index) const { - if (!index.isValid()) { - return m_rootMenu.items().count(); - } - const InputItem* item = static_cast(index.internalPointer()); - return item->items().count(); -} - -InputItem* InputModel::add(QMenu* menu, std::function callback) { - InputItem* smenu = m_menuMap[menu]; - if (!smenu) { - return nullptr; - } - QModelIndex parent = index(smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - callback(smenu); - endInsertRows(); - InputItem* item = &smenu->items().last(); - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); - return item; -} - -void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) { - InputItem* item = add(menu, [&](InputItem* smenu) { - smenu->addAction(action, name); - }); - if (!item) { - return; - } - if (m_config) { - loadShortcuts(item); - } -} - -void InputModel::addFunctions(QMenu* menu, std::function press, std::function release, - int shortcut, const QString& visibleName, const QString& name) { - InputItem* item = add(menu, [&](InputItem* smenu) { - smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); - }); - if (!item) { - return; - } - - bool loadedShortcut = false; - if (m_config) { - loadedShortcut = loadShortcuts(item); - } - if (!loadedShortcut && !m_heldKeys.contains(shortcut)) { - m_heldKeys[shortcut] = item; - } -} - -void InputModel::addFunctions(QMenu* menu, std::function press, std::function release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name) { - addFunctions(menu, press, release, shortcut[0], visibleName, name); -} - -void InputModel::addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) { - InputItem* item = add(menu, [&](InputItem* smenu) { - smenu->addKey(platform, key, shortcut, visibleName, name); - }); - if (!item) { - return; - } - bool loadedShortcut = false; - if (m_config) { - loadedShortcut = loadShortcuts(item); - } - if (!loadedShortcut && !m_keys.contains(qMakePair(platform, shortcut))) { - m_keys[qMakePair(platform, shortcut)] = item; - } -} - -QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) { - InputItem* smenu = m_menuMap[parentMenu]; - if (!smenu) { - smenu = &m_rootMenu; - } - QModelIndex parent = index(smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addSubmenu(menu); - endInsertRows(); - InputItem* item = &smenu->items().last(); - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); - m_menuMap[menu] = item; - return index(item); -} - -InputItem* InputModel::itemAt(const QModelIndex& index) { - if (!index.isValid()) { - return nullptr; - } - if (index.internalPointer()) { - return static_cast(index.internalPointer()); - } - if (!index.parent().isValid()) { - return nullptr; - } - InputItem* pmenu = static_cast(index.parent().internalPointer()); - return &pmenu->items()[index.row()]; -} - -const InputItem* InputModel::itemAt(const QModelIndex& index) const { - if (!index.isValid()) { - return nullptr; - } - if (index.internalPointer()) { - return static_cast(index.internalPointer()); - } - if (!index.parent().isValid()) { - return nullptr; - } - InputItem* pmenu = static_cast(index.parent().internalPointer()); - return &pmenu->items()[index.row()]; -} - -int InputModel::shortcutAt(const QModelIndex& index) const { - const InputItem* item = itemAt(index); - if (!item) { - return 0; - } - return item->shortcut(); -} - -int InputModel::keyAt(const QModelIndex& index) const { - const InputItem* item = itemAt(index); - if (!item) { - return -1; - } - return item->key(); -} - -bool InputModel::isMenuAt(const QModelIndex& index) const { - const InputItem* item = itemAt(index); - if (!item) { - return false; - } - return item->menu(); -} - -void InputModel::updateKey(const QModelIndex& index, int keySequence) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - InputItem* item = itemAt(index); - updateKey(item, keySequence); - if (m_config) { - m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); - } -} - -void InputModel::updateKey(InputItem* item, int keySequence) { - int oldShortcut = item->shortcut(); - if (item->functions().first) { - if (oldShortcut > 0) { - m_heldKeys.take(oldShortcut); - } - if (keySequence > 0) { - m_heldKeys[keySequence] = item; - } - } - - if (item->key() >= 0) { - if (oldShortcut > 0) { - m_keys.take(qMakePair(item->platform(), oldShortcut)); - } - if (keySequence > 0) { - m_keys[qMakePair(item->platform(), keySequence)] = item; - } - } - - item->setShortcut(keySequence); - - emit dataChanged(createIndex(index(item).row(), 0, item), - createIndex(index(item).row(), 2, item)); - - emit keyRebound(index(item), keySequence); -} - -void InputModel::updateButton(const QModelIndex& index, int button) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - InputItem* item = itemAt(index); - int oldButton = item->button(); - if (oldButton >= 0) { - m_buttons.take(oldButton); - } - updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); - item->setButton(button); - if (button >= 0) { - m_buttons[button] = item; - } - if (m_config) { - m_config->setQtOption(item->name(), button, BUTTON_SECTION); - if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); - } - } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); - - emit buttonRebound(index, button); -} - -void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - InputItem* item = itemAt(index); - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - } - if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { - updateButton(index, -1); - m_axes[qMakePair(axis, direction)] = item; - } - item->setAxis(axis, direction); - if (m_config) { - char d = '\0'; - if (direction == GamepadAxisEvent::POSITIVE) { - d = '+'; - } - if (direction == GamepadAxisEvent::NEGATIVE) { - d = '-'; - } - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); - if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); - } - } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); - - emit axisRebound(index, axis, direction); -} - -void InputModel::clearKey(const QModelIndex& index) { - updateKey(index, 0); -} - -void InputModel::clearButton(const QModelIndex& index) { - updateButton(index, -1); -} - -bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) { - auto key = m_keys.find(qMakePair(platform, keySequence)); - if (key != m_keys.end()) { - m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down); - return true; - } - auto heldKey = m_heldKeys.find(keySequence); - if (heldKey != m_heldKeys.end()) { - auto pair = heldKey.value()->functions(); - if (down) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } - } - return true; - } - return false; -} - -bool InputModel::triggerButton(int button, bool down) { - auto item = m_buttons.find(button); - if (item == m_buttons.end()) { - return false; - } - if (down) { - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { - action->trigger(); - } - auto pair = item.value()->functions(); - if (pair.first) { - pair.first(); - } - } else { - auto pair = item.value()->functions(); - if (pair.second) { - pair.second(); - } - } - return true; -} - -bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) { - auto item = m_axes.find(qMakePair(axis, direction)); - if (item == m_axes.end()) { - return false; - } - if (isNew) { - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { - action->trigger(); - } - } - auto pair = item.value()->functions(); - if (isNew) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } - } - return true; -} - -bool InputModel::loadShortcuts(InputItem* item) { - if (item->name().isNull()) { - return false; - } - loadGamepadShortcuts(item); - QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); - if (!shortcut.isNull()) { - if (shortcut.toString().endsWith("+")) { - updateKey(item, toModifierShortcut(shortcut.toString())); - } else { - updateKey(item, QKeySequence(shortcut.toString())[0]); - } - return true; - } - return false; -} - -void InputModel::loadGamepadShortcuts(InputItem* item) { - if (item->name().isNull()) { - return; - } - QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); - int oldButton = item->button(); - if (oldButton >= 0) { - m_buttons.take(oldButton); - item->setButton(-1); - } - if (button.isNull() && m_profile) { - int buttonInt; - if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { - button = buttonInt; - } - } - if (!button.isNull()) { - item->setButton(button.toInt()); - m_buttons[button.toInt()] = item; - } - - QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - item->setAxis(-1, GamepadAxisEvent::NEUTRAL); - } - if (axis.isNull() && m_profile) { - int axisInt; - GamepadAxisEvent::Direction direction; - if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { - axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); - } - } - if (!axis.isNull()) { - QString axisDesc = axis.toString(); - if (axisDesc.size() >= 2) { - GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; - if (axisDesc[0] == '-') { - direction = GamepadAxisEvent::NEGATIVE; - } - if (axisDesc[0] == '+') { - direction = GamepadAxisEvent::POSITIVE; - } - bool ok; - int axis = axisDesc.mid(1).toInt(&ok); - if (ok) { - item->setAxis(axis, direction); - m_axes[qMakePair(axis, direction)] = item; - } - } - } -} - -void InputModel::loadProfile(mPlatform platform, const QString& profile) { - m_profileName = profile; - m_profile = InputProfile::findProfile(platform, profile); - onSubitems(&m_rootMenu, [this](InputItem* item) { - loadGamepadShortcuts(item); - }); -} - -void InputModel::onSubitems(InputItem* item, std::function func) { - for (InputItem& subitem : item->items()) { - func(&subitem); - onSubitems(&subitem, func); - } -} - -int InputModel::toModifierShortcut(const QString& shortcut) { - // Qt doesn't seem to work with raw modifier shortcuts! - QStringList modifiers = shortcut.split('+'); - int value = 0; - for (const auto& mod : modifiers) { - if (mod == QLatin1String("Shift")) { - value |= Qt::ShiftModifier; - continue; - } - if (mod == QLatin1String("Ctrl")) { - value |= Qt::ControlModifier; - continue; - } - if (mod == QLatin1String("Alt")) { - value |= Qt::AltModifier; - continue; - } - if (mod == QLatin1String("Meta")) { - value |= Qt::MetaModifier; - continue; - } - } - return value; -} - -bool InputModel::isModifierKey(int key) { - switch (key) { - case Qt::Key_Shift: - case Qt::Key_Control: - case Qt::Key_Alt: - case Qt::Key_Meta: - return true; - default: - return false; - } -} - -int InputModel::toModifierKey(int key) { - int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); - key ^= modifiers; - switch (key) { - case Qt::Key_Shift: - modifiers |= Qt::ShiftModifier; - break; - case Qt::Key_Control: - modifiers |= Qt::ControlModifier; - break; - case Qt::Key_Alt: - modifiers |= Qt::AltModifier; - break; - case Qt::Key_Meta: - modifiers |= Qt::MetaModifier; - break; - default: - break; - } - return modifiers; -} diff --git a/src/platform/qt/InputModel.h b/src/platform/qt/InputModel.h deleted file mode 100644 index 2d1044d2c..000000000 --- a/src/platform/qt/InputModel.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Copyright (c) 2013-2017 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_INPUT_MODEL -#define QGBA_INPUT_MODEL - -#include - -#include "GamepadAxisEvent.h" -#include "InputItem.h" - -#include - -#include - -class QAction; -class QKeyEvent; -class QMenu; -class QString; - -namespace QGBA { - -class ConfigController; -class InputProfile; - -class InputModel : public QAbstractItemModel { -Q_OBJECT - -private: - constexpr static const char* const KEY_SECTION = "shortcutKey"; - constexpr static const char* const BUTTON_SECTION = "shortcutButton"; - constexpr static const char* const AXIS_SECTION = "shortcutAxis"; - constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; - constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; - -public: - InputModel(QObject* parent = nullptr); - - void setConfigController(ConfigController* controller); - void setProfile(const QString& profile); - void setKeyCallback(std::function callback) { m_keyCallback = callback; } - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; - - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; - - void addAction(QMenu* menu, QAction* action, const QString& name); - void addFunctions(QMenu* menu, std::function press, std::function release, - int shortcut, const QString& visibleName, const QString& name); - void addFunctions(QMenu* menu, std::function press, std::function release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name); - void addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name); - QModelIndex addMenu(QMenu* menu, QMenu* parent = nullptr); - - QAction* getAction(const QString& name); - int shortcutAt(const QModelIndex& index) const; - int keyAt(const QModelIndex& index) const; - bool isMenuAt(const QModelIndex& index) const; - - void updateKey(const QModelIndex& index, int keySequence); - void updateButton(const QModelIndex& index, int button); - void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction); - void updateHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction); - - void clearKey(const QModelIndex& index); - void clearButton(const QModelIndex& index); - - static int toModifierShortcut(const QString& shortcut); - static bool isModifierKey(int key); - static int toModifierKey(int key); - - void loadProfile(mPlatform platform, const QString& profile); - - bool triggerKey(int keySequence, bool down, mPlatform platform = PLATFORM_NONE); - bool triggerButton(int button, bool down); - bool triggerAxis(int axis, GamepadAxisEvent::Direction, bool isNew); - bool triggerHat(int hat, GamepadHatEvent::Direction); - -signals: - void keyRebound(const QModelIndex&, int keySequence); - void buttonRebound(const QModelIndex&, int button); - void axisRebound(const QModelIndex& index, int axis, GamepadAxisEvent::Direction); - void hatRebound(const QModelIndex& index, int hat, GamepadHatEvent::Direction); - -private: - InputItem* add(QMenu* menu, std::function); - InputItem* itemAt(const QModelIndex& index); - const InputItem* itemAt(const QModelIndex& index) const; - bool loadShortcuts(InputItem*); - void loadGamepadShortcuts(InputItem*); - void onSubitems(InputItem*, std::function func); - void updateKey(InputItem* item, int keySequence); - - QModelIndex index(InputItem* item) const; - - InputItem m_rootMenu; - QMap m_menuMap; - QMap m_buttons; - QMap, InputItem*> m_axes; - QMap m_heldKeys; - QMap, InputItem*> m_keys; - ConfigController* m_config; - std::function m_keyCallback; - QString m_profileName; - const InputProfile* m_profile; -}; - -} - -#endif diff --git a/src/platform/qt/InputProfile.cpp b/src/platform/qt/InputProfile.cpp deleted file mode 100644 index 92e1c0cc9..000000000 --- a/src/platform/qt/InputProfile.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "InputProfile.h" - -#include "InputController.h" - -#include - -using namespace QGBA; - -const InputProfile InputProfile::s_defaultMaps[] = { - { - "XInput Controller #\\d+", // XInput (Windows) - { - /*keyA */ 11, - /*keyB */ 10, - /*keySelect */ 5, - /*keyStart */ 4, - /*keyRight */ 3, - /*keyLeft */ 2, - /*keyUp */ 0, - /*keyDown */ 1, - /*keyR */ 9, - /*keyL */ 8 - }, - { - /*loadState */ 12, - /*saveState */ 13, - /*holdFastForward */ -1, - /*holdRewind */ -1, - }, - { - /*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5}, - /*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 4}, - } - }, - { - "(Microsoft X-Box 360 pad|Xbox Gamepad \\(userspace driver\\))", // Linux - { - /*keyA */ 1, - /*keyB */ 0, - /*keySelect */ 6, - /*keyStart */ 7, - /*keyRight */ -1, - /*keyLeft */ -1, - /*keyUp */ -1, - /*keyDown */ -1, - /*keyR */ 5, - /*keyL */ 4 - }, - { - /*loadState */ 2, - /*saveState */ 3, - /*holdFastForward */ -1, - /*holdRewind */ -1, - }, - { - /*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5}, - /*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 2}, - } - }, - { - "Xbox 360 Wired Controller", // OS X - { - /*keyA */ 1, - /*keyB */ 0, - /*keySelect */ 9, - /*keyStart */ 8, - /*keyRight */ 14, - /*keyLeft */ 13, - /*keyUp */ 11, - /*keyDown */ 12, - /*keyR */ 5, - /*keyL */ 4 - }, - { - /*loadState */ 2, - /*saveState */ 3, - /*holdFastForward */ -1, - /*holdRewind */ -1, - }, - { - /*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1}, - /*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5}, - /*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 2}, - } - }, - { - "(Sony Computer Entertainment )?Wireless Controller", // The DualShock 4 device ID is cut off on Windows - { - /*keyA */ 1, - /*keyB */ 2, - /*keySelect */ 8, - /*keyStart */ 9, - /*keyRight */ -1, - /*keyLeft */ -1, - /*keyUp */ -1, - /*keyDown */ -1, - /*keyR */ 5, - /*keyL */ 4 - }, - { - /*loadState */ 0, - /*saveState */ 3, - /*holdFastForward */ 7, - /*holdRewind */ 6, - }, - }, - { - "PLAYSTATION\\(R\\)3 Controller", // DualShock 3 (OS X) - { - /*keyA */ 13, - /*keyB */ 14, - /*keySelect */ 0, - /*keyStart */ 3, - /*keyRight */ 5, - /*keyLeft */ 7, - /*keyUp */ 4, - /*keyDown */ 6, - /*keyR */ 11, - /*keyL */ 10 - }, - { - /*loadState */ 15, - /*saveState */ 12, - /*holdFastForward */ 9, - /*holdRewind */ 8, - }, - }, - { - "Wiimote \\(..-..-..-..-..-..\\)", // WJoy (OS X) - { - /*keyA */ 15, - /*keyB */ 16, - /*keySelect */ 7, - /*keyStart */ 6, - /*keyRight */ 14, - /*keyLeft */ 13, - /*keyUp */ 11, - /*keyDown */ 12, - /*keyR */ 20, - /*keyL */ 19 - }, - { - /*loadState */ 18, - /*saveState */ 17, - /*holdFastForward */ 22, - /*holdRewind */ 21, - }, - }, -}; - -constexpr InputProfile::InputProfile(const char* name, - const KeyList keys, - const Shortcuts shortcutButtons, - const Shortcuts shortcutAxes, - const KeyList axes, - const struct Coord& tiltAxis, - const struct Coord& gyroAxis, - float gyroSensitivity) - : m_profileName(name) - , m_keys { - keys.keyA, - keys.keyB, - keys.keySelect, - keys.keyStart, - keys.keyRight, - keys.keyLeft, - keys.keyUp, - keys.keyDown, - keys.keyR, - keys.keyL, - } - , m_axes { - axes.keyA, - axes.keyB, - axes.keySelect, - axes.keyStart, - axes.keyRight, - axes.keyLeft, - axes.keyUp, - axes.keyDown, - axes.keyR, - axes.keyL, - } - , m_shortcutButtons(shortcutButtons) - , m_shortcutAxes(shortcutAxes) - , m_tiltAxis(tiltAxis) - , m_gyroAxis(gyroAxis) - , m_gyroSensitivity(gyroSensitivity) -{ -} - -const InputProfile* InputProfile::findProfile(mPlatform platform, const QString& name) { - // TODO: Use platform - for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) { - QRegExp re(s_defaultMaps[i].m_profileName); - if (re.exactMatch(name)) { - return &s_defaultMaps[i]; - } - } - return nullptr; -} - -void InputProfile::apply(mPlatform platform, InputController* controller) const { - for (size_t i = 0; i < GBA_KEY_MAX; ++i) { -#ifdef BUILD_SDL - controller->bindKey(platform, SDL_BINDING_BUTTON, m_keys[i], static_cast(i)); - controller->bindAxis(platform, SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast(i)); -#endif - } - controller->registerTiltAxisX(m_tiltAxis.x); - controller->registerTiltAxisY(m_tiltAxis.y); - controller->registerGyroAxisX(m_gyroAxis.x); - controller->registerGyroAxisY(m_gyroAxis.y); - controller->setGyroSensitivity(m_gyroSensitivity); -} - -bool InputProfile::lookupShortcutButton(const QString& shortcutName, int* button) const { - if (shortcutName == QLatin1String("loadState")) { - *button = m_shortcutButtons.loadState; - return true; - } - if (shortcutName == QLatin1String("saveState")) { - *button = m_shortcutButtons.saveState; - return true; - } - if (shortcutName == QLatin1String("holdFastForward")) { - *button = m_shortcutButtons.holdFastForward; - return true; - } - if (shortcutName == QLatin1String("holdRewind")) { - *button = m_shortcutButtons.holdRewind; - return true; - } - return false; -} - -bool InputProfile::lookupShortcutAxis(const QString& shortcutName, int* axis, GamepadAxisEvent::Direction* direction) const { - if (shortcutName == QLatin1String("loadState")) { - *axis = m_shortcutAxes.loadState.axis; - *direction = m_shortcutAxes.loadState.direction; - return true; - } - if (shortcutName == QLatin1String("saveState")) { - *axis = m_shortcutAxes.saveState.axis; - *direction = m_shortcutAxes.saveState.direction; - return true; - } - if (shortcutName == QLatin1String("holdFastForward")) { - *axis = m_shortcutAxes.holdFastForward.axis; - *direction = m_shortcutAxes.holdFastForward.direction; - return true; - } - if (shortcutName == QLatin1String("holdRewind")) { - *axis = m_shortcutAxes.holdRewind.axis; - *direction = m_shortcutAxes.holdRewind.direction; - return true; - } - return false; -} diff --git a/src/platform/qt/InputProfile.h b/src/platform/qt/InputProfile.h deleted file mode 100644 index 37e608cd6..000000000 --- a/src/platform/qt/InputProfile.h +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_INPUT_PROFILE -#define QGBA_INPUT_PROFILE - -#include "GamepadAxisEvent.h" - -#include -#include - -namespace QGBA { - -class InputController; - -class InputProfile { -public: - static const InputProfile* findProfile(mPlatform platform, const QString& name); - - void apply(mPlatform platform, InputController*) const; - bool lookupShortcutButton(const QString& shortcut, int* button) const; - bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const; - -private: - struct Coord { - int x; - int y; - }; - - struct AxisValue { - GamepadAxisEvent::Direction direction; - int axis; - }; - - template struct Shortcuts { - T loadState; - T saveState; - T holdFastForward; - T holdRewind; - }; - - struct Axis { - GamepadAxisEvent::Direction direction; - int axis; - }; - - template struct KeyList { - T keyA; - T keyB; - T keySelect; - T keyStart; - T keyRight; - T keyLeft; - T keyUp; - T keyDown; - T keyR; - T keyL; - }; - - constexpr InputProfile(const char* name, - const KeyList keys, - const Shortcuts shortcutButtons = { -1, -1, -1, -1}, - const Shortcuts shortcutAxes = { - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}}, - const KeyList axes = { - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::POSITIVE, 0 }, - { GamepadAxisEvent::Direction::NEGATIVE, 0 }, - { GamepadAxisEvent::Direction::NEGATIVE, 1 }, - { GamepadAxisEvent::Direction::POSITIVE, 1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }}, - const struct Coord& tiltAxis = { 2, 3 }, - const struct Coord& gyroAxis = { 0, 1 }, - float gyroSensitivity = 2e+09f); - - static const InputProfile s_defaultMaps[]; - - const char* m_profileName; - const int m_keys[GBA_KEY_MAX]; - const AxisValue m_axes[GBA_KEY_MAX]; - const Shortcuts m_shortcutButtons; - const Shortcuts m_shortcutAxes; - Coord m_tiltAxis; - Coord m_gyroAxis; - float m_gyroSensitivity; -}; - -} - -#endif diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index 761abed14..80471f8e1 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -7,7 +7,7 @@ #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" -#include "InputModel.h" +#include "InputIndex.h" #include #include @@ -99,7 +99,7 @@ void KeyEditor::keyPressEvent(QKeyEvent* event) { } m_lastKey.start(KEY_TIME); if (m_key) { - if (InputModel::isModifierKey(m_key)) { + if (InputIndex::isModifierKey(m_key)) { switch (event->key()) { case Qt::Key_Shift: setValue(Qt::ShiftModifier); @@ -115,7 +115,7 @@ void KeyEditor::keyPressEvent(QKeyEvent* event) { break; } } - if (InputModel::isModifierKey(event->key())) { + if (InputIndex::isModifierKey(event->key())) { switch (event->key()) { case Qt::Key_Shift: setValue(m_key | Qt::ShiftModifier); diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 82dae23be..60d4b095b 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -9,6 +9,7 @@ #include #include "GameController.h" +#include "MemoryView.h" using namespace QGBA; @@ -24,6 +25,7 @@ MemorySearch::MemorySearch(GameController* controller, QWidget* parent) connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory); } MemorySearch::~MemorySearch() { @@ -47,7 +49,6 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { params->type = mCORE_MEMORY_SEARCH_32; } if (m_ui.numHex->isChecked()) { - bool ok; uint32_t v = m_ui.value->text().toUInt(&ok, 16); if (ok) { switch (params->type) { @@ -150,6 +151,7 @@ void MemorySearch::refresh() { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); m_ui.results->setItem(i, 0, item); + QTableWidgetItem* type; if (m_ui.numHex->isChecked()) { switch (result->type) { case mCORE_MEMORY_SEARCH_8: @@ -181,7 +183,44 @@ void MemorySearch::refresh() { item = new QTableWidgetItem("?"); // TODO } } + QString divisor; + if (result->guessDivisor > 1) { + divisor = tr(" (⅟%0×)").arg(result->guessDivisor); + } + switch (result->type) { + case mCORE_MEMORY_SEARCH_8: + type = new QTableWidgetItem(tr("1 byte%0").arg(divisor)); + break; + case mCORE_MEMORY_SEARCH_16: + type = new QTableWidgetItem(tr("2 bytes%0").arg(divisor)); + break; + case mCORE_MEMORY_SEARCH_GUESS: + case mCORE_MEMORY_SEARCH_32: + type = new QTableWidgetItem(tr("4 bytes%0").arg(divisor)); + break; + case mCORE_MEMORY_SEARCH_STRING: + item = new QTableWidgetItem("?"); // TODO + } m_ui.results->setItem(i, 1, item); + m_ui.results->setItem(i, 2, type); } m_ui.results->sortItems(0); + m_ui.results->resizeColumnsToContents(); + m_ui.results->resizeRowsToContents(); +} + +void MemorySearch::openMemory() { + auto items = m_ui.results->selectedItems(); + if (items.empty()) { + return; + } + QTableWidgetItem* item = items[0]; + uint32_t address = item->text().toUInt(nullptr, 16); + + MemoryView* memView = new MemoryView(m_controller); + memView->jumpToAddress(address); + + connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); + memView->setAttribute(Qt::WA_DeleteOnClose); + memView->show(); } diff --git a/src/platform/qt/MemorySearch.h b/src/platform/qt/MemorySearch.h index 3ca37c34c..65f365f44 100644 --- a/src/platform/qt/MemorySearch.h +++ b/src/platform/qt/MemorySearch.h @@ -28,6 +28,9 @@ public slots: void search(); void searchWithin(); +private slots: + void openMemory(); + private: bool createParams(mCoreMemorySearchParams*); diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui index 9b8e57460..ccbe07076 100644 --- a/src/platform/qt/MemorySearch.ui +++ b/src/platform/qt/MemorySearch.ui @@ -17,7 +17,7 @@ - Form + Memory Search @@ -28,6 +28,9 @@ 0 + + QAbstractItemView::NoEditTriggers + QAbstractItemView::SelectRows @@ -194,11 +197,8 @@ - - false - - View in Memory View + Open in Memory Viewer @@ -214,10 +214,26 @@ - + + + buttonBox + rejected() + MemorySearch + close() + + + 315 + 357 + + + 315 + 188 + + + + - - + diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h index 9882ce32b..04a492ca9 100644 --- a/src/platform/qt/MemoryView.h +++ b/src/platform/qt/MemoryView.h @@ -22,6 +22,7 @@ public: public slots: void update(); + void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); } private slots: void setIndex(int); diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 0fa19df8c..35fb9be0b 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -68,6 +68,7 @@ void ObjView::translateIndex(int index) { #ifdef M_CORE_GBA void ObjView::updateTilesGBA(bool force) { + m_ui.objId->setMaximum(127); const GBA* gba = static_cast(m_controller->thread()->core->board); const GBAObj* obj = &gba->video.oam.obj[m_objId]; @@ -172,6 +173,7 @@ void ObjView::updateTilesGBA(bool force) { #ifdef M_CORE_GB void ObjView::updateTilesGB(bool force) { + m_ui.objId->setMaximum(39); const GB* gb = static_cast(m_controller->thread()->core->board); const GBObj* obj = &gb->video.oam.obj[m_objId]; diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 42f90f5eb..6b7652f81 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "OverrideView.h" +#include #include #include "ConfigController.h" @@ -79,6 +80,21 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); + QPalette palette = m_ui.color0->palette(); + palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8)); + m_ui.color0->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8)); + m_ui.color1->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50)); + m_ui.color2->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00)); + m_ui.color3->setPalette(palette); + + m_ui.color0->installEventFilter(this); + m_ui.color1->installEventFilter(this); + m_ui.color2->installEventFilter(this); + m_ui.color3->installEventFilter(this); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides); #ifndef M_CORE_GBA m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA)); @@ -96,6 +112,42 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, } } +bool OverrideView::eventFilter(QObject* obj, QEvent* event) { +#ifdef M_CORE_GB + if (event->type() != QEvent::MouseButtonRelease) { + return false; + } + int colorId; + if (obj == m_ui.color0) { + colorId = 0; + } else if (obj == m_ui.color1) { + colorId = 1; + } else if (obj == m_ui.color2) { + colorId = 2; + } else if (obj == m_ui.color3) { + colorId = 3; + } else { + return false; + } + + QWidget* swatch = static_cast(obj); + + QColorDialog* colorPicker = new QColorDialog; + colorPicker->setAttribute(Qt::WA_DeleteOnClose); + colorPicker->open(); + connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) { + QPalette palette = swatch->palette(); + palette.setColor(backgroundRole(), color); + swatch->setPalette(palette); + m_gbColors[colorId] = color.rgb(); + updateOverrides(); + }); + return true; +#else + return false; +#endif +} + void OverrideView::saveOverride() { if (!m_config) { return; @@ -155,7 +207,13 @@ void OverrideView::updateOverrides() { GBOverride* gb = new GBOverride; gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()]; gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()]; - if (gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT) { + gb->override.gbColors[0] = m_gbColors[0]; + gb->override.gbColors[1] = m_gbColors[1]; + gb->override.gbColors[2] = m_gbColors[2]; + gb->override.gbColors[3] = m_gbColors[3]; + bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT; + hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]); + if (hasOverride) { m_controller->setOverride(gb); } else { m_controller->clearOverride(); diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index c7f3fad99..3d1c8a3aa 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -36,6 +36,9 @@ private slots: void gameStarted(mCoreThread*); void gameStopped(); +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + private: Ui::OverrideView m_ui; @@ -43,6 +46,8 @@ private: ConfigController* m_config; #ifdef M_CORE_GB + uint32_t m_gbColors[4]{}; + static QList s_gbModelList; static QList s_mbcList; #endif diff --git a/src/platform/qt/OverrideView.ui b/src/platform/qt/OverrideView.ui index 2d383ad47..3d8238cd8 100644 --- a/src/platform/qt/OverrideView.ui +++ b/src/platform/qt/OverrideView.ui @@ -6,8 +6,8 @@ 0 0 - 443 - 282 + 444 + 284 @@ -326,6 +326,93 @@ + + + + Colors + + + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 0c1cd2583..666d8988d 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -13,13 +13,15 @@ #include "ShortcutView.h" #include +#include #include using namespace QGBA; -SettingsView::SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent) +SettingsView::SettingsView(ConfigController* controller, InputController* inputController, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) , m_controller(controller) + , m_input(inputController) { m_ui.setupUi(this); @@ -88,6 +90,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC m_ui.patchPath->setText(path); } }); + connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() QVariant audioDriver = m_controller->getQtOption("audioDriver"); @@ -152,11 +155,29 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC } }); - ShortcutView* shortcutView = new ShortcutView(); - shortcutView->setModel(inputModel); - shortcutView->setInputController(inputController); - m_ui.stackedWidget->addWidget(shortcutView); - m_ui.tabs->addItem(tr("Bindings")); + m_ui.languages->setItemData(0, QLocale("en")); + QDir ts(":/translations/"); + for (auto name : ts.entryList()) { + if (!name.endsWith(".qm")) { + continue; + } + QLocale locale(name.remove(QString("%0-").arg(binaryName)).remove(".qm")); + m_ui.languages->addItem(locale.nativeLanguageName(), locale); + if (locale == QLocale()) { + m_ui.languages->setCurrentIndex(m_ui.languages->count() - 1); + } + } + + m_keyView = new ShortcutView(); + m_keyView->setModel(inputController->keyIndex()); + m_keyView->setInputController(inputController); + m_shortcutView = new ShortcutView(); + m_shortcutView->setModel(inputController->inputIndex()); + m_shortcutView->setInputController(inputController); + m_ui.stackedWidget->addWidget(m_keyView); + m_ui.tabs->addItem(tr("Controls")); + m_ui.stackedWidget->addWidget(m_shortcutView); + m_ui.tabs->addItem(tr("Shortcuts")); } void SettingsView::selectBios(QLineEdit* bios) { @@ -244,8 +265,18 @@ void SettingsView::updateConfig() { emit displayDriverChanged(); } + QLocale language = m_ui.languages->itemData(m_ui.languages->currentIndex()).toLocale(); + if (language != m_controller->getQtOption("language").toLocale() && !(language.bcp47Name() == QLocale::system().bcp47Name() && m_controller->getQtOption("language").isNull())) { + m_controller->setQtOption("language", language.bcp47Name()); + emit languageChanged(); + } + m_controller->write(); + m_input->rebuildIndex(m_shortcutView->root()); + m_input->rebuildKeyIndex(m_keyView->root()); + m_input->saveConfiguration(); + emit pathsChanged(); emit biosLoaded(PLATFORM_GBA, m_ui.gbaBios->text()); } diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 511e2346c..b71d84796 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -16,19 +16,22 @@ namespace QGBA { class ConfigController; class InputController; -class InputModel; +class InputIndex; +class ShortcutView; class SettingsView : public QDialog { Q_OBJECT public: - SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent = nullptr); + SettingsView(ConfigController* controller, InputController* inputController, QWidget* parent = nullptr); signals: void biosLoaded(int platform, const QString&); void audioDriverChanged(); void displayDriverChanged(); void pathsChanged(); + void languageChanged(); + void libraryCleared(); private slots: void selectBios(QLineEdit*); @@ -40,6 +43,8 @@ private: ConfigController* m_controller; InputController* m_input; + ShortcutView* m_shortcutView; + ShortcutView* m_keyView; void saveSetting(const char* key, const QAbstractButton*); void saveSetting(const char* key, const QComboBox*); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 120a2dcb3..321cd143d 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -398,17 +398,30 @@ - - + + - Show when no game open - - - true + Language + + + + English + + + + + + + + Library: + + + + @@ -422,38 +435,38 @@ - - + + - Library: + Show when no game open + + + true - + - - false - Clear cache - + Qt::Horizontal - + Allow opposing input directions - + Suspend screensaver @@ -463,13 +476,20 @@ - + Pause when inactive + + + + Qt::Horizontal + + + diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index 71376a2c8..eab54a0b0 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/platform/qt/ShortcutView.cpp @@ -15,9 +15,11 @@ using namespace QGBA; ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) + , m_model() { m_ui.setupUi(this); m_ui.keyEdit->setValueKey(0); + m_ui.shortcutTable->setModel(&m_model); connect(m_ui.gamepadButton, &QAbstractButton::pressed, [this]() { bool signalsBlocked = m_ui.keyEdit->blockSignals(true); @@ -33,15 +35,19 @@ ShortcutView::ShortcutView(QWidget* parent) connect(m_ui.keyEdit, &KeyEditor::axisChanged, this, &ShortcutView::updateAxis); connect(m_ui.shortcutTable, &QAbstractItemView::doubleClicked, this, &ShortcutView::load); connect(m_ui.clearButton, &QAbstractButton::clicked, this, &ShortcutView::clear); +#ifdef BUILD_SDL + connect(m_ui.gamepadName, static_cast(&QComboBox::currentIndexChanged), [this](int index) { + m_input->setGamepad(SDL_BINDING_BUTTON, index); + }); +#endif } ShortcutView::~ShortcutView() { m_input->releaseFocus(this); } -void ShortcutView::setModel(InputModel* model) { - m_controller = model; - m_ui.shortcutTable->setModel(model); +void ShortcutView::setModel(InputIndex* model) { + m_model.clone(*model); } void ShortcutView::setInputController(InputController* controller) { @@ -50,16 +56,33 @@ void ShortcutView::setInputController(InputController* controller) { } m_input = controller; m_input->stealFocus(this); + updateGamepads(); +} + +void ShortcutView::updateGamepads() { + if (!m_input) { + return; + } +#ifdef BUILD_SDL + m_ui.gamepadName->clear(); + + QStringList gamepads = m_input->connectedGamepads(SDL_BINDING_BUTTON); + int activeGamepad = m_input->gamepad(SDL_BINDING_BUTTON); + + for (const auto& gamepad : gamepads) { + m_ui.gamepadName->addItem(gamepad); + } + m_ui.gamepadName->setCurrentIndex(activeGamepad); +#endif + } void ShortcutView::load(const QModelIndex& index) { - if (!m_controller) { + InputItem* item = m_model.itemAt(index); + if (!item) { return; } - if (m_controller->isMenuAt(index)) { - return; - } - int shortcut = m_controller->shortcutAt(index); + int shortcut = item->shortcut(); if (index.column() == 1) { m_ui.keyboardButton->click(); } else if (index.column() == 2) { @@ -76,39 +99,38 @@ void ShortcutView::load(const QModelIndex& index) { } void ShortcutView::clear() { - if (!m_controller) { - return; - } QModelIndex index = m_ui.shortcutTable->selectionModel()->currentIndex(); - if (m_controller->isMenuAt(index)) { + InputItem* item = m_model.itemAt(index); + if (!item) { return; } if (m_ui.gamepadButton->isChecked()) { - m_controller->clearButton(index); + item->clearButton(); m_ui.keyEdit->setValueButton(-1); } else { - m_controller->clearKey(index); + item->clearShortcut(); m_ui.keyEdit->setValueKey(-1); } } void ShortcutView::updateButton(int button) { - if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + InputItem* item = m_model.itemAt(m_ui.shortcutTable->selectionModel()->currentIndex()); + if (!item) { return; } if (m_ui.gamepadButton->isChecked()) { - m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + item->setButton(button); } else { - m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + item->setShortcut(button); } } void ShortcutView::updateAxis(int axis, int direction) { - if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + InputItem* item = m_model.itemAt(m_ui.shortcutTable->selectionModel()->currentIndex()); + if (!item) { return; } - m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, - static_cast(direction)); + item->setAxis(axis, static_cast(direction)); } void ShortcutView::closeEvent(QCloseEvent*) { diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h index 824950560..fc35822c0 100644 --- a/src/platform/qt/ShortcutView.h +++ b/src/platform/qt/ShortcutView.h @@ -7,6 +7,8 @@ #define QGBA_SHORTCUT_VIEW #include "GamepadAxisEvent.h" +#include "InputIndex.h" +#include "InputModel.h" #include @@ -15,7 +17,6 @@ namespace QGBA { class InputController; -class InputModel; class ShortcutView : public QWidget { Q_OBJECT @@ -24,9 +25,11 @@ public: ShortcutView(QWidget* parent = nullptr); ~ShortcutView(); - void setModel(InputModel* controller); + void setModel(InputIndex* model); void setInputController(InputController* input); + const InputIndex* root() { return m_model.inputIndex(); } + protected: virtual bool event(QEvent*) override; virtual void closeEvent(QCloseEvent*) override; @@ -36,11 +39,12 @@ private slots: void clear(); void updateButton(int button); void updateAxis(int axis, int direction); + void updateGamepads(); private: Ui::ShortcutView m_ui; - InputModel* m_controller = nullptr; + InputModel m_model; InputController* m_input = nullptr; }; diff --git a/src/platform/qt/ShortcutView.ui b/src/platform/qt/ShortcutView.ui index 8b28e4d52..b77d46e98 100644 --- a/src/platform/qt/ShortcutView.ui +++ b/src/platform/qt/ShortcutView.ui @@ -72,6 +72,27 @@ + + + + + + Current Gamepad + + + + + + + + 1 + 0 + + + + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 764511108..58f4a949d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -90,8 +90,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) , m_logView(new LogView(&m_log)) , m_screenWidget(new WindowBackground()) , m_config(config) - , m_inputModel(new InputModel(this)) - , m_inputController(m_inputModel, playerId, this) + , m_inputController(playerId, this) { setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); @@ -108,7 +107,11 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_screenWidget->setMinimumSize(m_display->minimumSize()); m_screenWidget->setSizePolicy(m_display->sizePolicy()); - int i = 2; +#if defined(M_CORE_GBA) + float i = 2; +#elif defined(M_CORE_GB) + float i = 3; +#endif QVariant multiplier = m_config->getOption("scaleMultiplier"); if (!multiplier.isNull()) { m_savedScale = multiplier.toInt(); @@ -142,8 +145,11 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_controller->loadGame(output, path.second, path.first); } }); -#elif defined(M_CORE_GBA) - m_screenWidget->setSizeHint(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i)); +#endif +#if defined(M_CORE_GBA) + resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i)); +#elif defined(M_CORE_GB) + resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i)); #endif m_screenWidget->setPixmap(m_logo); m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height()); @@ -197,6 +203,9 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples); connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate); connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget); + connect(&m_inputController, &InputController::keyPressed, m_controller, &GameController::keyPressed); + connect(&m_inputController, &InputController::keyReleased, m_controller, &GameController::keyReleased); + connect(&m_inputController, &InputController::keyAutofire, m_controller, &GameController::setAutofire); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); connect(m_display, &Display::hideCursor, [this]() { @@ -212,19 +221,17 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_focusCheck.setInterval(200); - m_inputModel->setConfigController(m_config); setupMenu(menuBar()); #ifdef M_CORE_GBA - m_inputController.addPlatform(PLATFORM_GBA, tr("Game Boy Advance"), &GBAInputInfo); + m_inputController.addPlatform(PLATFORM_GBA, &GBAInputInfo); #endif #ifdef M_CORE_GB - m_inputController.addPlatform(PLATFORM_GB, tr("Game Boy"), &GBInputInfo); + m_inputController.addPlatform(PLATFORM_GB, &GBInputInfo); #endif #ifdef M_CORE_DS - m_inputController.addPlatform(PLATFORM_DS, tr("DS"), &DSInputInfo); + m_inputController.addPlatform(PLATFORM_DS, &DSInputInfo); #endif - m_inputController.setupCallback(m_controller); } Window::~Window() { @@ -266,9 +273,6 @@ void Window::argumentsPassed(mArguments* args) { void Window::resizeFrame(const QSize& size) { QSize newSize(size); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - newSize /= m_screenWidget->devicePixelRatioF(); -#endif m_screenWidget->setSizeHint(newSize); newSize -= m_screenWidget->size(); newSize += this->size(); @@ -504,11 +508,13 @@ void Window::exportSharkport() { } void Window::openSettingsWindow() { - SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_inputModel); + SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController); connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS); connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); + connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); + connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); openView(settingsWindow); } @@ -806,14 +812,14 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { } #endif - m_inputController.setPlatform(m_controller->platform()); - m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); m_focusCheck.start(); m_controller->threadInterrupt(); if (m_controller->isLoaded()) { + m_inputController.setPlatform(m_controller->platform()); + mCore* core = m_controller->thread()->core; const mCoreChannelInfo* videoLayers; const mCoreChannelInfo* audioChannels; @@ -1010,9 +1016,9 @@ void Window::openStateWindow(LoadSave ls) { void Window::setupMenu(QMenuBar* menubar) { menubar->clear(); - QMenu* fileMenu = menubar->addMenu(tr("&File")); - m_inputModel->addMenu(fileMenu); installEventFilter(&m_inputController); + + QMenu* fileMenu = menubar->addMenu(tr("&File")); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); #ifdef USE_SQLITE3 @@ -1069,8 +1075,6 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); - m_inputModel->addMenu(quickLoadMenu); - m_inputModel->addMenu(quickSaveMenu); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState); @@ -1162,7 +1166,6 @@ void Window::setupMenu(QMenuBar* menubar) { #endif QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); - m_inputModel->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); connect(reset, &QAction::triggered, m_controller, &GameController::reset); @@ -1203,11 +1206,11 @@ void Window::setupMenu(QMenuBar* menubar) { emulationMenu->addSeparator(); - m_inputModel->addFunctions(emulationMenu, [this]() { + m_inputController.inputIndex()->addItem(qMakePair([this]() { m_controller->setTurbo(true, false); }, [this]() { m_controller->setTurbo(false, false); - }, QKeySequence(Qt::Key_Tab), tr("Fast forward (held)"), "holdFastForward"); + }), tr("Fast forward (held)"), "holdFastForward", emulationMenu)->setShortcut(QKeySequence(Qt::Key_Tab)[0]); QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); turbo->setCheckable(true); @@ -1229,11 +1232,11 @@ void Window::setupMenu(QMenuBar* menubar) { } m_config->updateOption("fastForwardRatio"); - m_inputModel->addFunctions(emulationMenu, [this]() { + m_inputController.inputIndex()->addItem(qMakePair([this]() { m_controller->startRewinding(); }, [this]() { m_controller->stopRewinding(); - }, QKeySequence("`"), tr("Rewind (held)"), "holdRewind"); + }), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]); QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); rewind->setShortcut(tr("~")); @@ -1270,7 +1273,6 @@ void Window::setupMenu(QMenuBar* menubar) { emulationMenu->addSeparator(); QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); - m_inputModel->addMenu(solarMenu); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); connect(solarIncrease, &QAction::triggered, m_controller, &GameController::increaseLuminanceLevel); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); @@ -1297,9 +1299,7 @@ void Window::setupMenu(QMenuBar* menubar) { } QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); - m_inputModel->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); - m_inputModel->addMenu(frameMenu, avMenu); for (int i = 1; i <= 6; ++i) { QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); setSize->setCheckable(true); @@ -1434,13 +1434,9 @@ void Window::setupMenu(QMenuBar* menubar) { avMenu->addSeparator(); m_videoLayers = avMenu->addMenu(tr("Video layers")); - m_inputModel->addMenu(m_videoLayers, avMenu); - m_audioChannels = avMenu->addMenu(tr("Audio channels")); - m_inputModel->addMenu(m_audioChannels, avMenu); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); - m_inputModel->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show); addControlledAction(toolsMenu, viewLogs, "viewLogs"); @@ -1583,72 +1579,11 @@ void Window::setupMenu(QMenuBar* menubar) { exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); - QMenu* autofireMenu = new QMenu(tr("Autofire"), this); - m_inputModel->addMenu(autofireMenu); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_A, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_A, false); - }, QKeySequence(), tr("Autofire A"), "autofireA"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_B, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_B, false); - }, QKeySequence(), tr("Autofire B"), "autofireB"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_L, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_L, false); - }, QKeySequence(), tr("Autofire L"), "autofireL"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_R, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_R, false); - }, QKeySequence(), tr("Autofire R"), "autofireR"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_START, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_START, false); - }, QKeySequence(), tr("Autofire Start"), "autofireStart"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, false); - }, QKeySequence(), tr("Autofire Select"), "autofireSelect"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_UP, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_UP, false); - }, QKeySequence(), tr("Autofire Up"), "autofireUp"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, false); - }, QKeySequence(), tr("Autofire Right"), "autofireRight"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, false); - }, QKeySequence(), tr("Autofire Down"), "autofireDown"); - - m_inputModel->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, false); - }, QKeySequence(), tr("Autofire Left"), "autofireLeft"); - for (QAction* action : m_gameActions) { action->setDisabled(true); } + + m_inputController.rebuildIndex(); } void Window::attachWidget(QWidget* widget) { @@ -1701,7 +1636,7 @@ QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString } QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) { - m_inputModel->addAction(menu, action, name); + m_inputController.inputIndex()->addItem(action, name, menu); action->setShortcutContext(Qt::WidgetShortcut); addAction(action); return action; diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 89b842866..6ec33eb0a 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -28,7 +28,6 @@ class Display; class GameController; class GDBController; class GIFView; -class InputModel; class LibraryController; class LogView; class ShaderSelector; @@ -171,7 +170,6 @@ private: WindowBackground* m_screenWidget; QPixmap m_logo{":/res/medusa-bg.png"}; ConfigController* m_config; - InputModel* m_inputModel; InputController m_inputController; QList m_frameList; QTimer m_fpsTimer; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/input/InputController.cpp similarity index 59% rename from src/platform/qt/InputController.cpp rename to src/platform/qt/input/InputController.cpp index 4185f9d65..437dda9ed 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/input/InputController.cpp @@ -9,6 +9,7 @@ #include "GameController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" +#include "InputItem.h" #include "InputModel.h" #include "InputProfile.h" @@ -30,6 +31,7 @@ #ifdef M_CORE_DS #include #endif +#include using namespace QGBA; @@ -38,13 +40,13 @@ int InputController::s_sdlInited = 0; mSDLEvents InputController::s_sdlEvents; #endif -InputController::InputController(InputModel* model, int playerId, QWidget* topLevel, QObject* parent) +InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) : QObject(parent) - , m_inputModel(model) - , m_platform(PLATFORM_NONE) , m_playerId(playerId) , m_topLevel(topLevel) , m_focusParent(topLevel) + , m_bindings(new QMenu(tr("Controls"))) + , m_autofire(new QMenu(tr("Autofire"))) { #ifdef BUILD_SDL if (s_sdlInited == 0) { @@ -65,20 +67,74 @@ InputController::InputController(InputModel* model, int playerId, QWidget* topLe m_gamepadTimer.setInterval(50); m_gamepadTimer.start(); - m_autofireMenu = std::unique_ptr(new QMenu(tr("Autofire"))); - m_inputModel->addMenu(m_autofireMenu.get()); + static QList> defaultBindings({ + qMakePair(QLatin1String("A"), Qt::Key_Z), + qMakePair(QLatin1String("B"), Qt::Key_X), + qMakePair(QLatin1String("L"), Qt::Key_A), + qMakePair(QLatin1String("R"), Qt::Key_S), + qMakePair(QLatin1String("Start"), Qt::Key_Return), + qMakePair(QLatin1String("Select"), Qt::Key_Backspace), + qMakePair(QLatin1String("Up"), Qt::Key_Up), + qMakePair(QLatin1String("Down"), Qt::Key_Down), + qMakePair(QLatin1String("Left"), Qt::Key_Left), + qMakePair(QLatin1String("Right"), Qt::Key_Right) + }); - m_inputMenu = std::unique_ptr(new QMenu(tr("Bindings"))); - m_inputModel->addMenu(m_inputMenu.get()); + for (auto k : defaultBindings) { + addKey(k.first); + } + m_keyIndex.rebuild(); + for (auto k : defaultBindings) { + bindKey(KEYBOARD, k.second, k.first); + } +} - connect(m_inputModel, SIGNAL(keyRebound(const QModelIndex&, int)), this, SLOT(bindKey(const QModelIndex&, int))); - connect(m_inputModel, SIGNAL(buttonRebound(const QModelIndex&, int)), this, SLOT(bindButton(const QModelIndex&, int))); - connect(m_inputModel, SIGNAL(axisRebound(const QModelIndex&, int, GamepadAxisEvent::Direction)), this, SLOT(bindAxis(const QModelIndex&, int, GamepadAxisEvent::Direction))); +void InputController::addKey(const QString& name) { + if (itemForKey(name)) { + return; + } + m_keyIndex.addItem(qMakePair([this, name]() { + emit keyPressed(keyId(name)); + }, [this, name]() { + emit keyReleased(keyId(name)); + }), name, QString("key%0").arg(name), m_bindings.get()); + + m_keyIndex.addItem(qMakePair([this, name]() { + emit keyAutofire(keyId(name), true); + }, [this, name]() { + emit keyAutofire(keyId(name), false); + }), name, QString("autofire%1").arg(name), m_autofire.get()); +} + +void InputController::addPlatform(mPlatform platform, const mInputPlatformInfo* info) { + m_keyInfo[platform] = info; + for (size_t i = 0; i < info->nKeys; ++i) { + addKey(info->keyId[i]); + } +} + +void InputController::setPlatform(mPlatform platform) { + if (m_activeKeyInfo) { + mInputMapDeinit(&m_inputMap); + } + + m_sdlPlayer.bindings = &m_inputMap; + m_activeKeyInfo = m_keyInfo[platform]; + mInputMapInit(&m_inputMap, m_activeKeyInfo); + + loadConfiguration(KEYBOARD); +#ifdef BUILD_SDL + mSDLInitBindingsGBA(&m_inputMap); + loadConfiguration(SDL_BINDING_BUTTON); +#endif + + rebuildKeyIndex(); + restoreModel(); } InputController::~InputController() { - for (auto& inputMap : m_inputMaps) { - mInputMapDeinit(&inputMap); + if (m_activeKeyInfo) { + mInputMapDeinit(&m_inputMap); } #ifdef BUILD_SDL @@ -93,82 +149,27 @@ InputController::~InputController() { #endif } -void InputController::addPlatform(mPlatform platform, const QString& visibleName, const mInputPlatformInfo* info) { - mInputMap* inputMap = &m_inputMaps[platform]; - mInputMapInit(inputMap, info); - - QMenu* input = m_inputMenu->addMenu(visibleName); - QMenu* autofire = m_autofireMenu->addMenu(visibleName); - m_inputMenuIndices[platform] = m_inputModel->addMenu(input, m_inputMenu.get()); - m_inputModel->addMenu(autofire, m_autofireMenu.get()); - - for (size_t i = 0; i < info->nKeys; ++i) { - m_inputModel->addKey(input, platform, i, 0, info->keyId[i], QString("%1.%2").arg(info->platformName).arg(info->keyId[i])); - m_inputModel->addKey(autofire, platform, i, 0, info->keyId[i], QString("%1.autofire.%2").arg(info->platformName).arg(info->keyId[i])); - } - -#ifdef BUILD_SDL - mSDLInitBindingsGBA(inputMap); -#endif - switch (platform) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT); - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GB_KEY_A); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GB_KEY_B); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GB_KEY_START); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GB_KEY_SELECT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GB_KEY_UP); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GB_KEY_DOWN); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GB_KEY_LEFT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GB_KEY_RIGHT); - break; -#endif -#ifdef M_CORE_DS - case PLATFORM_DS: - mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, DS_KEY_A); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, DS_KEY_B); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, DS_KEY_X); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, DS_KEY_Y); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Q, DS_KEY_L); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_W, DS_KEY_R); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, DS_KEY_START); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, DS_KEY_SELECT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, DS_KEY_UP); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, DS_KEY_DOWN); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, DS_KEY_LEFT); - mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, DS_KEY_RIGHT); - break; -#endif - default: - break; - } +void InputController::rebuildIndex(const InputIndex* index) { + m_inputIndex.rebuild(index); } -void InputController::setPlatform(mPlatform platform) { -#ifdef BUILD_SDL - m_sdlPlayer.bindings = &m_inputMaps[platform]; -#endif - m_platform = platform; +void InputController::rebuildKeyIndex(const InputIndex* index) { + m_keyIndex.rebuild(index); + + for (const InputItem* item : m_keyIndex.items()) { + if (!item->name().startsWith(QLatin1String("key"))) { + rebindKey(item->visibleName()); + } + } } void InputController::setConfiguration(ConfigController* config) { m_config = config; + m_inputIndex.setConfigController(config); + m_keyIndex.setConfigController(config); setAllowOpposing(config->getOption("allowOpposingDirections").toInt()); loadConfiguration(KEYBOARD); + loadProfile(KEYBOARD, profileForType(KEYBOARD)); #ifdef BUILD_SDL mSDLEventsLoadConfig(&s_sdlEvents, config->input()); if (!m_playerAttached) { @@ -181,31 +182,23 @@ void InputController::setConfiguration(ConfigController* config) { } void InputController::loadConfiguration(uint32_t type) { - for (auto& inputMap : m_inputMaps) { - mInputMapLoad(&inputMap, type, m_config->input()); -#ifdef BUILD_SDL - if (m_playerAttached) { - mInputMap* bindings = m_sdlPlayer.bindings; - m_sdlPlayer.bindings = &inputMap; - mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input()); - m_sdlPlayer.bindings = bindings; - } -#endif + if (!m_activeKeyInfo) { + return; } + mInputMapLoad(&m_inputMap, type, m_config->input()); +#ifdef BUILD_SDL + if (m_playerAttached) { + mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input()); + } +#endif } void InputController::loadProfile(uint32_t type, const QString& profile) { - for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) { - bool loaded = mInputProfileLoad(&iter.value(), type, m_config->input(), profile.toUtf8().constData()); - if (!loaded) { - const InputProfile* ip = InputProfile::findProfile(iter.key(), profile); - if (ip) { - ip->apply(iter.key(), this); - } - } + const InputProfile* ip = InputProfile::findProfile(profile); + if (ip) { + ip->apply(this); } recalibrateAxes(); - m_inputModel->loadProfile(PLATFORM_NONE, profile); // TODO emit profileLoaded(profile); } @@ -217,23 +210,22 @@ void InputController::saveConfiguration() { if (m_playerAttached) { mSDLPlayerSaveConfig(&m_sdlPlayer, m_config->input()); } - m_config->write(); #endif + m_inputIndex.saveConfig(); + m_keyIndex.saveConfig(); + m_config->write(); } void InputController::saveConfiguration(uint32_t type) { - for (auto& inputMap : m_inputMaps) { - if (!inputMap.info) { - continue; - } - mInputMapSave(&inputMap, type, m_config->input()); + if (m_activeKeyInfo) { + mInputMapSave(&m_inputMap, type, m_config->input()); } m_config->write(); } void InputController::saveProfile(uint32_t type, const QString& profile) { - for (auto& inputMap : m_inputMaps) { - mInputProfileSave(&inputMap, type, m_config->input(), profile.toUtf8().constData()); + if (m_activeKeyInfo) { + mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData()); } m_config->write(); } @@ -383,7 +375,7 @@ void InputController::updateJoysticks() { } const mInputMap* InputController::map() { - return &m_inputMaps[m_platform]; + return &m_inputMap; } int InputController::pollEvents() { @@ -395,7 +387,7 @@ int InputController::pollEvents() { int numButtons = SDL_JoystickNumButtons(joystick); int i; for (i = 0; i < numButtons; ++i) { - GBAKey key = static_cast(mInputMapKey(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i)); + GBAKey key = static_cast(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i)); if (key == GBA_KEY_NONE) { continue; } @@ -409,14 +401,14 @@ int InputController::pollEvents() { int numHats = SDL_JoystickNumHats(joystick); for (i = 0; i < numHats; ++i) { int hat = SDL_JoystickGetHat(joystick, i); - activeButtons |= mInputMapHat(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, hat); + activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat); } int numAxes = SDL_JoystickNumAxes(joystick); for (i = 0; i < numAxes; ++i) { int value = SDL_JoystickGetAxis(joystick, i); - enum GBAKey key = static_cast(mInputMapAxis(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, value)); + enum GBAKey key = static_cast(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value)); if (key != GBA_KEY_NONE) { activeButtons |= 1 << key; } @@ -486,31 +478,28 @@ QSet> InputController::activeGamepadAxes return activeAxes; } -void InputController::bindKey(mPlatform platform, uint32_t type, int key, int coreKey) { - if (m_inputMaps.find(platform) == m_inputMaps.end() || coreKey >= m_inputMaps[platform].info->nKeys) { - return; - } - QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]); - bool signalsBlocked = m_inputModel->blockSignals(true); +void InputController::bindKey(uint32_t type, int key, const QString& keyName) { + InputItem* item = itemForKey(keyName); if (type != KEYBOARD) { - m_inputModel->updateButton(index, key); + item->setButton(key); } else { - m_inputModel->updateKey(index, key); + item->setShortcut(key); + } + if (m_activeKeyInfo) { + int coreKey = keyId(keyName); + mInputBindKey(&m_inputMap, type, key, coreKey); } - m_inputModel->blockSignals(signalsBlocked); - mInputBindKey(&m_inputMaps[platform], type, key, coreKey); } -void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction direction, int key) { - if (m_inputMaps.find(platform) == m_inputMaps.end() || key >= m_inputMaps[platform].info->nKeys) { +void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, const QString& key) { + InputItem* item = itemForKey(key); + item->setAxis(axis, direction); + + if (!m_activeKeyInfo) { return; } - QModelIndex index = m_inputModel->index(key, 0, m_inputMenuIndices[platform]); - bool signalsBlocked = m_inputModel->blockSignals(true); - m_inputModel->updateAxis(index, axis, direction); - m_inputModel->blockSignals(signalsBlocked); - const mInputAxis* old = mInputQueryAxis(&m_inputMaps[platform], type, axis); + const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis); mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD }; if (old) { description = *old; @@ -521,18 +510,18 @@ void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, Game } switch (direction) { case GamepadAxisEvent::NEGATIVE: - description.lowDirection = key; + description.lowDirection = keyId(key); description.deadLow = deadzone - AXIS_THRESHOLD; break; case GamepadAxisEvent::POSITIVE: - description.highDirection = key; + description.highDirection = keyId(key); description.deadHigh = deadzone + AXIS_THRESHOLD; break; default: return; } - mInputBindAxis(&m_inputMaps[platform], type, axis, &description); + mInputBindAxis(&m_inputMap, type, axis, &description); } QSet> InputController::activeGamepadHats(int type) { @@ -567,32 +556,30 @@ QSet> InputController::activeGamepadHats( return activeHats; } -void InputController::bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction direction, int coreKey) { - if (m_inputMaps.find(platform) == m_inputMaps.end() || coreKey >= m_inputMaps[platform].info->nKeys) { +void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, const QString& key) { + if (!m_activeKeyInfo) { return; } - QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]); - //m_inputModel->updateHat(index, hat, direction); mInputHatBindings bindings{ -1, -1, -1, -1 }; - mInputQueryHat(&m_inputMaps[platform], type, hat, &bindings); + mInputQueryHat(&m_inputMap, type, hat, &bindings); switch (direction) { case GamepadHatEvent::UP: - bindings.up = coreKey; + bindings.up = keyId(key); break; case GamepadHatEvent::RIGHT: - bindings.right = coreKey; + bindings.right = keyId(key); break; case GamepadHatEvent::DOWN: - bindings.down = coreKey; + bindings.down = keyId(key); break; case GamepadHatEvent::LEFT: - bindings.left = coreKey; + bindings.left = keyId(key); break; default: return; } - mInputBindHat(&m_inputMaps[platform], type, hat, &bindings); + mInputBindHat(&m_inputMap, type, hat, &bindings); } void InputController::testGamepad(int type) { @@ -730,70 +717,15 @@ void InputController::releaseFocus(QWidget* focus) { } } -void InputController::setupCallback(GameController* controller) { - m_inputModel->setKeyCallback([this, controller](QMenu* menu, int key, bool down) { - if (menu->parent() == m_autofireMenu.get()) { - controller->setAutofire(key, down); - } else { - if (down) { - controller->keyPressed(key); - } else { - controller->keyReleased(key); - } - } - }); -} - -void InputController::bindKey(const QModelIndex& index, int key) { - int coreKey = m_inputModel->keyAt(index); - if (coreKey < 0) { - return; - } - mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); - bindKey(platform, KEYBOARD, key, coreKey); -} - -#ifdef BUILD_SDL -void InputController::bindButton(const QModelIndex& index, int key) { - int coreKey = m_inputModel->keyAt(index); - if (coreKey < 0) { - return; - } - mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); - bindKey(platform, SDL_BINDING_BUTTON, key, coreKey); -} - -void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { - int coreKey = m_inputModel->keyAt(index); - if (coreKey < 0) { - return; - } - mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); - bindAxis(platform, SDL_BINDING_BUTTON, axis, direction, coreKey); -} - -void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction direction) { - int coreKey = m_inputModel->keyAt(index); - if (coreKey < 0) { - return; - } - mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE); - bindHat(platform, SDL_BINDING_BUTTON, hat, direction, coreKey); -} -#else -void InputController::bindButton(const QModelIndex& index, int key) {} -void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction) {} -void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction) {} -#endif - bool InputController::eventFilter(QObject*, QEvent* event) { + event->ignore(); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); int key = keyEvent->key(); - if (!InputModel::isModifierKey(key)) { + if (!InputIndex::isModifierKey(key)) { key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); } else { - key = InputModel::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); + key = InputIndex::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); } if (keyEvent->isAutoRepeat()) { @@ -801,67 +733,120 @@ bool InputController::eventFilter(QObject*, QEvent* event) { return true; } - if (m_inputModel->triggerKey(key, event->type() == QEvent::KeyPress, m_platform)) { + event->ignore(); + InputItem* item = m_inputIndex.itemForShortcut(key); + if (item) { + item->trigger(event->type() == QEvent::KeyPress); + event->accept(); + } + item = m_keyIndex.itemForShortcut(key); + if (item) { + item->trigger(event->type() == QEvent::KeyPress); event->accept(); - return true; } } if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadButtonEvent::Up()) { GamepadButtonEvent* gbe = static_cast(event); - if (m_inputModel->triggerButton(gbe->value(), event->type() == GamepadButtonEvent::Down())) { + InputItem* item = m_inputIndex.itemForButton(gbe->value()); + if (item) { + item->trigger(event->type() == GamepadButtonEvent::Down()); + event->accept(); + } + item = m_keyIndex.itemForButton(gbe->value()); + if (item) { + item->trigger(event->type() == GamepadButtonEvent::Down()); event->accept(); - return true; } } if (event->type() == GamepadAxisEvent::Type()) { GamepadAxisEvent* gae = static_cast(event); - if (m_inputModel->triggerAxis(gae->axis(), gae->direction(), gae->isNew())) { + InputItem* item = m_inputIndex.itemForAxis(gae->axis(), gae->direction()); + if (item) { + item->trigger(event->type() == gae->isNew()); + event->accept(); + } + item = m_keyIndex.itemForAxis(gae->axis(), gae->direction()); + if (item) { + item->trigger(event->type() == gae->isNew()); event->accept(); - return true; } } - return false; + return event->isAccepted(); +} + +InputItem* InputController::itemForKey(const QString& key) { + return m_keyIndex.itemAt(QString("key%0").arg(key)); +} + +int InputController::keyId(const QString& key) { + for (int i = 0; i < m_activeKeyInfo->nKeys; ++i) { + if (m_activeKeyInfo->keyId[i] == key) { + return i; + } + } + return -1; } void InputController::restoreModel() { - bool signalsBlocked = m_inputModel->blockSignals(true); - for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) { - mPlatform platform = iter.key(); - QModelIndex parent = m_inputMenuIndices[platform]; - int nKeys = iter->info->nKeys; - for (int i = 0; i < nKeys; ++i) { - int key = mInputQueryBinding(&iter.value(), KEYBOARD, i); + if (!m_activeKeyInfo) { + return; + } + int nKeys = m_inputMap.info->nKeys; + for (int i = 0; i < nKeys; ++i) { + const QString& keyName = m_inputMap.info->keyId[i]; + InputItem* item = itemForKey(keyName); + if (item) { + int key = mInputQueryBinding(&m_inputMap, KEYBOARD, i); if (key >= 0) { - m_inputModel->updateKey(m_inputModel->index(i, 0, parent), key); + item->setShortcut(key); } else { - m_inputModel->clearKey(m_inputModel->index(i, 0, parent)); + item->clearShortcut(); } #ifdef BUILD_SDL - key = mInputQueryBinding(&iter.value(), SDL_BINDING_BUTTON, i); + key = mInputQueryBinding(&m_inputMap, SDL_BINDING_BUTTON, i); if (key >= 0) { - m_inputModel->updateButton(m_inputModel->index(i, 0, parent), key); + item->setButton(key); } else { - m_inputModel->clearButton(m_inputModel->index(i, 0, parent)); + item->clearButton(); } #endif } -#ifdef BUILD_SDL - struct Context { - InputModel* model; - QModelIndex parent; - } context{ m_inputModel, parent }; - mInputEnumerateAxes(&iter.value(), SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) { - Context* context = static_cast(user); - if (description->highDirection >= 0) { - context->model->updateAxis(context->model->index(description->highDirection, 0, context->parent), axis, GamepadAxisEvent::POSITIVE); - } - if (description->lowDirection >= 0) { - context->model->updateAxis(context->model->index(description->lowDirection, 0, context->parent), axis, GamepadAxisEvent::NEGATIVE); - } - }, &context); -#endif } - m_inputModel->blockSignals(signalsBlocked); +#ifdef BUILD_SDL + mInputEnumerateAxes(&m_inputMap, SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) { + InputController* controller = static_cast(user); + InputItem* item; + const mInputPlatformInfo* inputMap = controller->m_inputMap.info; + if (description->highDirection >= 0 && description->highDirection < controller->m_inputMap.info->nKeys) { + int id = description->lowDirection; + if (id >= 0 && id < inputMap->nKeys) { + item = controller->itemForKey(inputMap->keyId[id]); + if (item) { + item->setAxis(axis, GamepadAxisEvent::POSITIVE); + } + } + } + if (description->lowDirection >= 0 && description->lowDirection < controller->m_inputMap.info->nKeys) { + int id = description->highDirection; + if (id >= 0 && id < inputMap->nKeys) { + item = controller->itemForKey(inputMap->keyId[id]); + if (item) { + item->setAxis(axis, GamepadAxisEvent::NEGATIVE); + } + } + } + }, this); +#endif + rebuildKeyIndex(); +} + +void InputController::rebindKey(const QString& key) { + InputItem* item = itemForKey(key); + bindKey(KEYBOARD, item->shortcut(), key); +#ifdef BUILD_SDL + bindKey(SDL_BINDING_BUTTON, item->button(), key); + bindAxis(SDL_BINDING_BUTTON, item->axis(), item->direction(), key); +#endif } diff --git a/src/platform/qt/InputController.h b/src/platform/qt/input/InputController.h similarity index 74% rename from src/platform/qt/InputController.h rename to src/platform/qt/input/InputController.h index 916f3b3fe..a6b531d93 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/input/InputController.h @@ -8,6 +8,7 @@ #include "GamepadAxisEvent.h" #include "GamepadHatEvent.h" +#include "InputIndex.h" #include @@ -33,7 +34,7 @@ namespace QGBA { class ConfigController; class GameController; -class InputModel; +class InputItem; class InputController : public QObject { Q_OBJECT @@ -41,11 +42,17 @@ Q_OBJECT public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(InputModel* model, int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); + InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); - void addPlatform(mPlatform, const QString& visibleName, const mInputPlatformInfo*); + InputIndex* inputIndex() { return &m_inputIndex; } + InputIndex* keyIndex() { return &m_keyIndex; } + void rebuildIndex(const InputIndex* = nullptr); + void rebuildKeyIndex(const InputIndex* = nullptr); + + void addPlatform(mPlatform, const mInputPlatformInfo*); void setPlatform(mPlatform); + void addKey(const QString& name); void setConfiguration(ConfigController* config); void saveConfiguration(); @@ -68,9 +75,9 @@ public: QSet> activeGamepadHats(int type); void recalibrateAxes(); - void bindKey(mPlatform platform, uint32_t type, int key, int); - void bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction, int); - void bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction, int); + void bindKey(uint32_t type, int key, const QString&); + void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, const QString&); + void bindHat(uint32_t type, int hat, GamepadHatEvent::Direction, const QString&); QStringList connectedGamepads(uint32_t type) const; int gamepad(uint32_t type) const; @@ -91,10 +98,11 @@ public: mRumble* rumble(); mRotationSource* rotationSource(); - void setupCallback(GameController* controller); - signals: void profileLoaded(const QString& profile); + void keyPressed(int); + void keyReleased(int); + void keyAutofire(int, bool enabled); public slots: void testGamepad(int type); @@ -105,12 +113,6 @@ public slots: void resumeScreensaver(); void setScreensaverSuspendable(bool); -private slots: - void bindKey(const QModelIndex&, int key); - void bindButton(const QModelIndex&, int key); - void bindAxis(const QModelIndex&, int axis, GamepadAxisEvent::Direction); - void bindHat(const QModelIndex&, int hat, GamepadHatEvent::Direction); - protected: bool eventFilter(QObject*, QEvent*) override; @@ -120,15 +122,24 @@ private: bool hasPendingEvent(int key) const; void sendGamepadEvent(QEvent*); void restoreModel(); + void rebindKey(const QString& key); - InputModel* m_inputModel; - mPlatform m_platform; - QMap m_inputMaps; + InputItem* itemForKey(const QString& key); + int keyId(const QString& key); + + InputIndex m_inputIndex; + InputIndex m_keyIndex; + mInputMap m_inputMap; ConfigController* m_config = nullptr; int m_playerId; bool m_allowOpposing = false; QWidget* m_topLevel; QWidget* m_focusParent; + QMap m_keyInfo; + const mInputPlatformInfo* m_activeKeyInfo = nullptr; + + std::unique_ptr m_bindings; + std::unique_ptr m_autofire; #ifdef BUILD_SDL static int s_sdlInited; @@ -139,10 +150,6 @@ private: QVector m_deadzones; - std::unique_ptr m_inputMenu; - std::unique_ptr m_autofireMenu; - QMap m_inputMenuIndices; - QSet m_activeButtons; QSet> m_activeAxes; QSet> m_activeHats; diff --git a/src/platform/qt/input/InputIndex.cpp b/src/platform/qt/input/InputIndex.cpp new file mode 100644 index 000000000..182653dd5 --- /dev/null +++ b/src/platform/qt/input/InputIndex.cpp @@ -0,0 +1,250 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputIndex.h" + +#include "ConfigController.h" + +using namespace QGBA; + +void InputIndex::setConfigController(ConfigController* controller) { + m_config = controller; + for (auto& item : m_items) { + loadShortcuts(item); + } +} + +void InputIndex::clone(InputIndex* root, bool actions) { + if (!actions) { + clone(const_cast(root)); + } else { + qDeleteAll(m_items); + m_items.clear(); + for (auto& item : root->m_items) { + InputItem* newItem = new InputItem(*item); + m_items.append(newItem); + itemAdded(newItem); + } + } + rebuild(); +} + +void InputIndex::clone(const InputIndex* root) { + qDeleteAll(m_items); + m_items.clear(); + for (auto& item : root->m_items) { + InputItem* newItem = new InputItem(*item); + m_items.append(newItem); + itemAdded(newItem); + } + rebuild(); +} + +void InputIndex::rebuild(const InputIndex* root) { + if (!root) { + root = this; + } + + m_names.clear(); + m_menus.clear(); + m_shortcuts.clear(); + m_buttons.clear(); + m_axes.clear(); + + for (auto& item : root->m_items) { + InputItem* newItem = nullptr; + for (auto &iter : m_items) { + if (*iter == *item) { + newItem = iter; + break; + } + } + if (!newItem) { + continue; + } + if (item->hasShortcut()) { + newItem->setShortcut(item->shortcut()); + } + if (item->hasButton()) { + newItem->setButton(item->button()); + } + if (item->hasAxis()) { + newItem->setAxis(item->axis(), item->direction()); + } + + itemAdded(newItem); + } +} + +InputItem* InputIndex::itemAt(const QString& name) { + return m_names[name]; +} + +const InputItem* InputIndex::itemAt(const QString& name) const { + return m_names[name]; +} + +InputItem* InputIndex::itemForMenu(const QMenu* menu) { + InputItem* item = m_menus[menu]; + return item; +} + +const InputItem* InputIndex::itemForMenu(const QMenu* menu) const { + const InputItem* item = m_menus[menu]; + return item; +} + +InputItem* InputIndex::itemForShortcut(int shortcut) { + return m_shortcuts[shortcut]; +} + +InputItem* InputIndex::itemForButton(int button) { + return m_buttons[button]; +} + +InputItem* InputIndex::itemForAxis(int axis, GamepadAxisEvent::Direction direction) { + return m_axes[qMakePair(axis, direction)]; +} + +bool InputIndex::loadShortcuts(InputItem* item) { + if (item->name().isNull()) { + return false; + } + loadGamepadShortcuts(item); + QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); + if (!shortcut.isNull()) { + if (shortcut.toString().endsWith("+")) { + item->setShortcut(toModifierShortcut(shortcut.toString())); + } else { + item->setShortcut(QKeySequence(shortcut.toString())[0]); + } + return true; + } + return false; +} + +void InputIndex::loadGamepadShortcuts(InputItem* item) { + if (item->name().isNull()) { + return; + } + QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION); + if (!button.isNull()) { + item->setButton(button.toInt()); + } + + QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION); + int oldAxis = item->axis(); + if (oldAxis >= 0) { + item->setAxis(-1, GamepadAxisEvent::NEUTRAL); + } + if (!axis.isNull()) { + QString axisDesc = axis.toString(); + if (axisDesc.size() >= 2) { + GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; + if (axisDesc[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + } + if (axisDesc[0] == '+') { + direction = GamepadAxisEvent::POSITIVE; + } + bool ok; + int axis = axisDesc.mid(1).toInt(&ok); + if (ok) { + item->setAxis(axis, direction); + } + } + } +} + +int InputIndex::toModifierShortcut(const QString& shortcut) { + // Qt doesn't seem to work with raw modifier shortcuts! + QStringList modifiers = shortcut.split('+'); + int value = 0; + for (const auto& mod : modifiers) { + if (mod == QLatin1String("Shift")) { + value |= Qt::ShiftModifier; + continue; + } + if (mod == QLatin1String("Ctrl")) { + value |= Qt::ControlModifier; + continue; + } + if (mod == QLatin1String("Alt")) { + value |= Qt::AltModifier; + continue; + } + if (mod == QLatin1String("Meta")) { + value |= Qt::MetaModifier; + continue; + } + } + return value; +} + +void InputIndex::itemAdded(InputItem* child) { + const QMenu* menu = child->menu(); + if (menu) { + m_menus[menu] = child; + } + m_names[child->name()] = child; + + if (child->shortcut() > 0) { + m_shortcuts[child->shortcut()] = child; + } + if (child->button() >= 0) { + m_buttons[child->button()] = child; + } + if (child->direction() != GamepadAxisEvent::NEUTRAL) { + m_axes[qMakePair(child->axis(), child->direction())] = child; + } +} + +bool InputIndex::isModifierKey(int key) { + switch (key) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + return true; + default: + return false; + } +} + +int InputIndex::toModifierKey(int key) { + int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); + key ^= modifiers; + switch (key) { + case Qt::Key_Shift: + modifiers |= Qt::ShiftModifier; + break; + case Qt::Key_Control: + modifiers |= Qt::ControlModifier; + break; + case Qt::Key_Alt: + modifiers |= Qt::AltModifier; + break; + case Qt::Key_Meta: + modifiers |= Qt::MetaModifier; + break; + default: + break; + } + return modifiers; +} + +void InputIndex::saveConfig() { + for (auto& item : m_items) { + if (item->hasShortcut()) { + m_config->setQtOption(item->name(), QKeySequence(item->shortcut()).toString(), KEY_SECTION); + } + if (item->hasButton()) { + m_config->setQtOption(item->name(), item->button(), BUTTON_SECTION); + } + if (item->hasAxis()) { + m_config->setQtOption(item->name(), QString("%1%2").arg(GamepadAxisEvent::POSITIVE ? '+' : '-').arg(item->axis()), AXIS_SECTION); + } + } +} diff --git a/src/platform/qt/input/InputIndex.h b/src/platform/qt/input/InputIndex.h new file mode 100644 index 000000000..2d5190851 --- /dev/null +++ b/src/platform/qt/input/InputIndex.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_INDEX +#define QGBA_INPUT_INDEX + +#include "GamepadAxisEvent.h" +#include "InputItem.h" + +#include +#include + +class QMenu; + +namespace QGBA { + +class ConfigController; + +class InputIndex { +private: + constexpr static const char* const KEY_SECTION = "shortcutKey"; + constexpr static const char* const BUTTON_SECTION = "shortcutButton"; + constexpr static const char* const AXIS_SECTION = "shortcutAxis"; + constexpr static const char* const HAT_SECTION = "shortcutHat"; + +public: + void setConfigController(ConfigController* controller); + + void clone(InputIndex* root, bool actions = false); + void clone(const InputIndex* root); + void rebuild(const InputIndex* root = nullptr); + + const QList& items() const { return m_items; } + + template InputItem* addItem(Args... params) { + InputItem* newItem = new InputItem(params...); + m_items.append(newItem); + itemAdded(newItem); + return newItem; + } + + InputItem* itemAt(const QString& name); + const InputItem* itemAt(const QString& name) const; + + InputItem* itemForMenu(const QMenu* menu); + const InputItem* itemForMenu(const QMenu* menu) const; + + InputItem* itemForShortcut(int shortcut); + InputItem* itemForButton(int button); + InputItem* itemForAxis(int axis, GamepadAxisEvent::Direction); + + static int toModifierShortcut(const QString& shortcut); + static bool isModifierKey(int key); + static int toModifierKey(int key); + + void saveConfig(); + +private: + bool loadShortcuts(InputItem*); + void loadGamepadShortcuts(InputItem*); + + void itemAdded(InputItem*); + + QList m_items; + + QMap m_names; + QMap m_menus; + QMap m_shortcuts; + QMap m_buttons; + QMap, InputItem*> m_axes; + + ConfigController* m_config = nullptr; +}; + +} + +#endif diff --git a/src/platform/qt/input/InputItem.cpp b/src/platform/qt/input/InputItem.cpp new file mode 100644 index 000000000..f5a050757 --- /dev/null +++ b/src/platform/qt/input/InputItem.cpp @@ -0,0 +1,108 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputItem.h" + +#include + +using namespace QGBA; + +InputItem::InputItem() +{ +} + +InputItem::InputItem(QAction* action, const QString& name, QMenu* parent) + : QObject(parent) + , m_action(action) + , m_shortcut(action->shortcut().isEmpty() ? -2 : action->shortcut()[0]) + , m_name(name) + , m_menu(parent) +{ + m_visibleName = action->text() + .remove(QRegExp("&(?!&)")) + .remove("..."); +} + +InputItem::InputItem(InputItem::Functions functions, const QString& visibleName, const QString& name, QMenu* parent) + : QObject(parent) + , m_functions(functions) + , m_name(name) + , m_visibleName(visibleName) + , m_menu(parent) +{ +} + +InputItem::InputItem(const QString& visibleName, const QString& name, QMenu* parent) + : QObject(parent) + , m_name(name) + , m_visibleName(visibleName) + , m_menu(parent) +{ +} + +InputItem::InputItem(const InputItem& other) + : QObject(other.m_menu) + , m_name(other.m_name) + , m_visibleName(other.m_visibleName) + , m_shortcut(other.m_shortcut) + , m_button(other.m_button) + , m_axis(other.m_axis) + , m_direction(other.m_direction) + , m_menu(other.m_menu) +{ +} + +InputItem::InputItem(InputItem& other) + : QObject(other.m_menu) + , m_action(other.m_action) + , m_functions(other.m_functions) + , m_name(other.m_name) + , m_visibleName(other.m_visibleName) + , m_shortcut(other.m_shortcut) + , m_button(other.m_button) + , m_axis(other.m_axis) + , m_direction(other.m_direction) + , m_menu(other.m_menu) +{ +} + +void InputItem::setShortcut(int shortcut) { + m_shortcut = shortcut; + if (m_action) { + m_action->setShortcut(QKeySequence(shortcut)); + } + emit shortcutBound(this, shortcut); +} + +void InputItem::clearShortcut() { + setShortcut(0); +} + +void InputItem::setButton(int button) { + m_button = button; + emit buttonBound(this, button); +} + +void InputItem::clearButton() { + setButton(-1); +} + +void InputItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { + m_axis = axis; + m_direction = direction; + emit axisBound(this, axis, direction); +} + +void InputItem::trigger(bool active) { + if (active) { + if (m_functions.first) { + m_functions.first(); + } + } else { + if (m_functions.second) { + m_functions.second(); + } + } +} diff --git a/src/platform/qt/input/InputItem.h b/src/platform/qt/input/InputItem.h new file mode 100644 index 000000000..ffd582868 --- /dev/null +++ b/src/platform/qt/input/InputItem.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_ITEM +#define QGBA_INPUT_ITEM + +#include "GamepadAxisEvent.h" +#include "GamepadHatEvent.h" + +#include + +#include + +namespace QGBA { + +class InputItem : public QObject { +Q_OBJECT + +public: + typedef QPair, std::function> Functions; + + InputItem(QAction* action, const QString& name, QMenu* parent = nullptr); + InputItem(Functions functions, const QString& visibleName, const QString& name, + QMenu* parent = nullptr); + InputItem(const QString& visibleName, const QString& name, QMenu* parent = nullptr); + + InputItem(); + InputItem(InputItem&); + InputItem(const InputItem&); + + QAction* action() { return m_action; } + const QAction* action() const { return m_action; } + Functions functions() const { return m_functions; } + + QMenu* menu() { return m_menu; } + const QMenu* menu() const { return m_menu; } + + const QString& visibleName() const { return m_visibleName; } + const QString& name() const { return m_name; } + + int shortcut() const { return m_shortcut; } + void setShortcut(int sequence); + void clearShortcut(); + bool hasShortcut() { return m_shortcut > -2; } + + int button() const { return m_button; } + void setButton(int button); + void clearButton(); + bool hasButton() { return m_button > -2; } + + int axis() const { return m_axis; } + GamepadAxisEvent::Direction direction() const { return m_direction; } + void setAxis(int axis, GamepadAxisEvent::Direction direction); + bool hasAxis() { return m_axis > -2; } + + bool operator==(const InputItem& other) const { + return m_name == other.m_name; + } + +public slots: + void trigger(bool = true); + +signals: + void shortcutBound(InputItem*, int shortcut); + void buttonBound(InputItem*, int button); + void axisBound(InputItem*, int axis, GamepadAxisEvent::Direction); + void childAdded(InputItem* parent, InputItem* child); + +private: + QAction* m_action = nullptr; + Functions m_functions; + + QMenu* m_menu = nullptr; + QString m_name; + QString m_visibleName; + + int m_shortcut = -2; + int m_button = -2; + int m_axis = -2; + GamepadAxisEvent::Direction m_direction = GamepadAxisEvent::NEUTRAL; +}; + +} + +Q_DECLARE_METATYPE(QGBA::InputItem) + +#endif diff --git a/src/platform/qt/input/InputModel.cpp b/src/platform/qt/input/InputModel.cpp new file mode 100644 index 000000000..ccfecdeb2 --- /dev/null +++ b/src/platform/qt/input/InputModel.cpp @@ -0,0 +1,184 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputModel.h" + +#include "GamepadButtonEvent.h" +#include "InputIndex.h" + +#include +#include +#include + +using namespace QGBA; + +QString InputModel::InputModelItem::visibleName() const { + if (item) { + return item->visibleName(); + } + if (menu) { + return menu->title() + .remove(QRegExp("&(?!&)")) + .remove("..."); + } + return QString(); +} + +InputModel::InputModel(const InputIndex& index, QObject* parent) + : QAbstractItemModel(parent) +{ + clone(index); +} + +InputModel::InputModel(QObject* parent) + : QAbstractItemModel(parent) +{ +} + +void InputModel::clone(const InputIndex& index) { + emit beginResetModel(); + m_index.clone(&index); + m_menus.clear(); + m_topLevelMenus.clear(); + QList menus; + for (auto& item : m_index.items()) { + const QMenu* menu = item->menu(); + if (!menus.contains(menu)) { + menus.append(menu); + m_menus.insert(menu); + } + } + for (auto& menu : menus) { + if (m_menus.contains(menu->parent())) { + m_tree[menu->parent()].append(menu); + } else { + m_topLevelMenus.append(menu); + } + } + for (auto& item : m_index.items()) { + const QMenu* menu = item->menu(); + m_tree[menu].append(item); + } + emit endResetModel(); +} + +QVariant InputModel::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + int row = index.row(); + const InputModelItem* item = static_cast(index.internalPointer()); + switch (index.column()) { + case 0: + return item->visibleName(); + case 1: + if (item->item && item->item->shortcut() > 0) { + return QKeySequence(item->item->shortcut()).toString(QKeySequence::NativeText); + } + break; + case 2: + if (!item->item) { + break; + } + if (item->item->button() >= 0) { + return item->item->button(); + } + if (item->item->axis() >= 0) { + char d = '\0'; + if (item->item->direction() == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (item->item->direction() == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + return QString("%1%2").arg(d).arg(item->item->axis()); + } + break; + } + return QVariant(); +} + +QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QAbstractItemModel::headerData(section, orientation, role); + } + if (orientation == Qt::Horizontal) { + switch (section) { + case 0: + return tr("Action"); + case 1: + return tr("Keyboard"); + case 2: + return tr("Gamepad"); + } + } + return section; +} + +QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const { + if (parent.isValid()) { + InputModelItem* p = static_cast(parent.internalPointer()); + return createIndex(row, column, const_cast(&m_tree[p->obj][row])); + } + return createIndex(row, column, const_cast(&m_topLevelMenus[row])); +} + +QModelIndex InputModel::parent(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return QModelIndex(); + } + const QObject* obj = static_cast(index.internalPointer())->obj; + if (m_menus.contains(obj->parent())) { + return this->index(obj->parent()); + } else { + return QModelIndex(); + } +} + +QModelIndex InputModel::index(const QObject* item, int column) const { + if (!item) { + return QModelIndex(); + } + const QObject* parent = item->parent(); + if (parent && m_tree.contains(parent)) { + int index = m_tree[parent].indexOf(item); + return createIndex(index, column, const_cast(&m_tree[parent][index])); + } + if (m_topLevelMenus.contains(item)) { + int index = m_topLevelMenus.indexOf(item); + return createIndex(index, column, const_cast(&m_topLevelMenus[index])); + } + return QModelIndex(); +} + +int InputModel::columnCount(const QModelIndex& index) const { + return 3; +} + +int InputModel::rowCount(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return m_topLevelMenus.count(); + } + const QObject* obj = static_cast(index.internalPointer())->obj; + if (!m_tree.contains(obj)) { + return 0; + } + return m_tree[obj].count(); +} + +InputItem* InputModel::itemAt(const QModelIndex& index) { + if (!index.isValid() || !index.internalPointer()) { + return nullptr; + } + return static_cast(index.internalPointer())->item; +} + +const InputItem* InputModel::itemAt(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return nullptr; + } + return static_cast(index.internalPointer())->item; + +} diff --git a/src/platform/qt/input/InputModel.h b/src/platform/qt/input/InputModel.h new file mode 100644 index 000000000..ab4c418e6 --- /dev/null +++ b/src/platform/qt/input/InputModel.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_MODEL +#define QGBA_INPUT_MODEL + +#include + +#include "InputIndex.h" + +#include +#include +#include + +#include + +class QAction; +class QKeyEvent; +class QMenu; +class QString; + +namespace QGBA { + +class ConfigController; +class InputIndex; +class InputProfile; + +class InputModel : public QAbstractItemModel { +Q_OBJECT + +public: + InputModel(const InputIndex& index, QObject* parent = nullptr); + InputModel(QObject* parent = nullptr); + + void clone(const InputIndex& index); + + void setProfile(const QString& profile); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + InputItem* itemAt(const QModelIndex& index); + const InputItem* itemAt(const QModelIndex& index) const; + + InputIndex* inputIndex() { return &m_index; } + +private: + struct InputModelItem { + InputModelItem(InputItem* i) : item(i), obj(i) {} + InputModelItem(const QMenu* i) : menu(i), obj(i) {} + InputModelItem(const QObject* i) : obj(i) {} + + InputItem* item = nullptr; + const QMenu* menu = nullptr; + const QObject* obj; + + QString visibleName() const; + bool operator==(const InputModelItem& other) { return obj == other.obj; } + }; + + QModelIndex index(const QObject* item, int column = 0) const; + + InputIndex m_index; + QList m_topLevelMenus; + QSet m_menus; + QMap> m_tree; + QString m_profileName; +}; + +} + +#endif diff --git a/src/platform/qt/input/InputProfile.cpp b/src/platform/qt/input/InputProfile.cpp new file mode 100644 index 000000000..3cb1f80ba --- /dev/null +++ b/src/platform/qt/input/InputProfile.cpp @@ -0,0 +1,99 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "InputProfile.h" + +#include "InputController.h" + +#include + +using namespace QGBA; + +QList InputProfile::s_profiles; + +InputProfile::InputProfile(const QString& name) + : m_profileName(name) +{ +} + +void InputProfile::loadDefaultProfiles() { + loadProfiles(":/input/default-profiles.ini"); +} + +void InputProfile::loadProfiles(const QString& path) { + QSettings profileIni(path, QSettings::IniFormat); + + for (const auto& group : profileIni.childGroups()) { + } + profileIni.beginGroup(PROFILE_SECTION); + for (const auto& group : profileIni.childGroups()) { + loadProfile(profileIni, group); + } + profileIni.endGroup(); +} + +void InputProfile::loadProfile(QSettings& profileIni, const QString& name) { + profileIni.beginGroup(name); + s_profiles.append(name); + InputProfile& profile = s_profiles.last(); + for (const auto& group : profileIni.childGroups()) { + profileIni.beginGroup(group); + if (group == MATCH_SECTION) { + for (const auto& key : profileIni.childKeys()) { + profile.m_match.append(QRegExp(profileIni.value(key).toString())); + } + } + for (const auto& key : profileIni.childKeys()) { + InputItem* item = profile.m_inputIndex.itemAt(key); + if (!item) { + item = profile.m_inputIndex.addItem(QString(), key); + } + if (group == BUTTON_SECTION) { + item->setButton(profileIni.value(key).toInt()); + } + if (group == AXIS_SECTION) { + QString axisDescription = profileIni.value(key).toString(); + GamepadAxisEvent::Direction direction = GamepadAxisEvent::POSITIVE; + int axis = profileIni.value(key).toInt(); + if (axisDescription[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + axis = -axis; + } + + item->setAxis(axis, direction); + } + if (group == KEY_SECTION) { + item->setShortcut(profileIni.value(key).toInt()); + } + } + profileIni.endGroup(); + } + profile.m_inputIndex.rebuild(); + profileIni.endGroup(); +} + +const InputProfile* InputProfile::findProfile(const QString& name) { + if (s_profiles.isEmpty()) { + loadDefaultProfiles(); + } + for (const InputProfile& profile : s_profiles) { + for (const auto& match : profile.m_match) { + if (match.exactMatch(name)) { + return &profile; + } + } + } + return nullptr; +} + +void InputProfile::apply(InputController* controller) const { + controller->rebuildIndex(&m_inputIndex); + controller->rebuildKeyIndex(&m_inputIndex); + controller->registerTiltAxisX(m_tiltAxis.x); + controller->registerTiltAxisY(m_tiltAxis.y); + controller->registerGyroAxisX(m_gyroAxis.x); + controller->registerGyroAxisY(m_gyroAxis.y); + controller->setGyroSensitivity(m_gyroSensitivity); +} diff --git a/src/platform/qt/input/InputProfile.h b/src/platform/qt/input/InputProfile.h new file mode 100644 index 000000000..8a7270e10 --- /dev/null +++ b/src/platform/qt/input/InputProfile.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_INPUT_PROFILE +#define QGBA_INPUT_PROFILE + +#include "GamepadAxisEvent.h" +#include "InputIndex.h" + +#include +#include + +#include + +class QSettings; + +namespace QGBA { + +class InputController; + +class InputProfile { +public: + constexpr static const char* const PROFILE_SECTION = "profiles"; + constexpr static const char* const MATCH_SECTION = "match"; + constexpr static const char* const KEY_SECTION = "keys"; + constexpr static const char* const BUTTON_SECTION = "buttons"; + constexpr static const char* const AXIS_SECTION = "axes"; + constexpr static const char* const HAT_SECTION = "hats"; + + static const InputProfile* findProfile(const QString& name); + static void loadProfiles(const QString& path); + + void apply(InputController*) const; +private: + InputProfile(const QString&); + + struct Coord { + int x; + int y; + }; + + static void loadDefaultProfiles(); + static void loadProfile(QSettings&, const QString& name); + + static QList s_profiles; + + QString m_profileName; + QList m_match; + + Coord m_tiltAxis = { 2, 3 }; + Coord m_gyroAxis = { 0, 1 }; + float m_gyroSensitivity = 2e+09f; + InputIndex m_inputIndex; +}; + +} + +#endif diff --git a/src/platform/qt/input/default-profiles.ini b/src/platform/qt/input/default-profiles.ini new file mode 100644 index 000000000..5504cabbc --- /dev/null +++ b/src/platform/qt/input/default-profiles.ini @@ -0,0 +1,114 @@ +[profiles/XInput (Windows)/match] +0=XInput Controller #\\d+ + +[profiles/XInput (Windows)/buttons] +keyA=11 +keyB=10 +keySelect=5 +keyStart=4 +keyRight=3 +keyLeft=2 +keyUp=0 +keyDown=1 +keyR=9 +keyL=8 +loadState=12 +saveState=13 + +[profiles/XInput (Windows)/axes] +holdFastForward=+5 +holdRewind=+4 + +[profiles/XInput (Linux)/match] +0=Microsoft X-Box 360 pad +1=Xbox Gamepad \\(userspace driver\\) + +[profiles/XInput (Linux)/buttons] +keyA=1 +keyB=0 +keySelect=6 +keyStart=7 +keyR=5 +keyL=4 +loadState=2 +saveState=3 + +[profiles/XInput (Linux)/axes] +holdFastForward=+5 +holdRewind=+2 + +[profiles/XInput (macOS)/match] +0=Xbox 360 Wired Controller + +[profiles/XInput (macOS)/buttons] +keyA=1 +keyB=0 +keySelect=9 +keyStart=8 +keyRight=14 +keyLeft=13 +keyUp=11 +keyDown=12 +keyR=5 +keyL=4 +loadState=2 +saveState=3 + +[profiles/XInput (macOS)/axes] +holdFastForward=+5 +holdRewind=+2 + + +[profiles/DualShock 4/match] +0=Sony Computer Entertainment Wireless Controller +1=Wireless Controller + +[profiles/DualShock 4/buttons] +keyA=1 +keyB=2 +keySelect=8 +keyStart=9 +keyR=5 +keyL=4 +loadState=0 +saveState=3 +holdFastForward=7 +holdRewind=6 + +[profiles/DualShock 3/match] +0=PLAYSTATION\\(R\\)3 Controller + +[profiles/DualShock 3/buttons] +keyA=13 +keyB=14 +keySelect=0 +keyStart=3 +keyRight=5 +keyLeft=7 +keyUp=4 +keyDown=6 +keyR=11 +keyL=10 +loadState=15 +saveState=12 +holdFastForward=9 +holdRewind=8 + +[profiles/Wii Remote/match] +0=Wiimote \\(..-..-..-..-..-..\\) + +[profiles/Wii Remote/buttons] +keyA=15 +keyB=16 +keySelect=7 +keyStart=6 +keyRight=14 +keyLeft=13 +keyUp=11 +keyDown=12 +keyR=20 +keyL=19 +loadState=18 +saveState=17 +holdFastForward=22 +holdRewind=21 diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 7446cb752..fc558f226 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LibraryController.h" -#include "../GBAApp.h" +#include "ConfigController.h" +#include "GBAApp.h" #include "LibraryGrid.h" #include "LibraryTree.h" @@ -24,7 +25,7 @@ void AbstractGameList::addEntries(QList items) { } void AbstractGameList::removeEntries(QList items) { for (LibraryEntryRef o : items) { - addEntry(o); + removeEntry(o); } } @@ -45,8 +46,10 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi mLibraryListingInit(&m_listing, 0); if (!path.isNull()) { + // This can return NULL if the library is already open m_library = mLibraryLoad(path.toUtf8().constData()); - } else { + } + if (!m_library) { m_library = mLibraryCreateEmpty(); } @@ -130,6 +133,20 @@ void LibraryController::addDirectory(const QString& dir) { m_loaderThread.start(); } +void LibraryController::clear() { + if (!m_library) { + if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { + m_library = m_loaderThread.m_library; + m_loaderThread.m_library = nullptr; + } else { + return; + } + } + + mLibraryClear(m_library); + refresh(); +} + void LibraryController::refresh() { if (!m_library) { if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 3cc82c8b5..7a7799b5b 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -100,6 +100,9 @@ public: void addDirectory(const QString& dir); +public slots: + void clear(); + signals: void startGame(); void doneLoading(); diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 631040d82..8b6de713d 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -7,14 +7,16 @@ // This must be defined before anything else is included. #define SDL_MAIN_HANDLED +#include "ConfigController.h" #include "GBAApp.h" #include "Window.h" +#include +#include + #include #include -#include - #ifdef QT_STATIC #include #ifdef _WIN32 @@ -25,13 +27,32 @@ Q_IMPORT_PLUGIN(QWindowsAudioPlugin); #endif #endif +using namespace QGBA; + int main(int argc, char* argv[]) { #ifdef BUILD_SDL SDL_SetMainReady(); #endif - QGBA::GBAApp application(argc, argv); - QLocale locale = QLocale::system(); + ConfigController configController; + + QLocale locale; + if (!configController.getQtOption("language").isNull()) { + locale = QLocale(configController.getQtOption("language").toString()); + QLocale::setDefault(locale); + } + + mArguments args; + mGraphicsOpts graphicsOpts; + mSubParser subparser; + initParserForGraphics(&subparser, &graphicsOpts); + bool loaded = configController.parseArguments(&args, argc, argv, &subparser); + if (loaded && args.showHelp) { + usage(argv[0], subparser.usage); + return 0; + } + + GBAApp application(argc, argv, &configController); QTranslator qtTranslator; qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); @@ -41,5 +62,22 @@ int main(int argc, char* argv[]) { langTranslator.load(locale, binaryName, "-", ":/translations/"); application.installTranslator(&langTranslator); + Window* w = application.newWindow(); + if (loaded) { + w->argumentsPassed(&args); + } else { + w->loadConfig(); + } + freeArguments(&args); + + if (graphicsOpts.multiplier) { + w->resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * graphicsOpts.multiplier, VIDEO_VERTICAL_PIXELS * graphicsOpts.multiplier)); + } + if (graphicsOpts.fullscreen) { + w->enterFullScreen(); + } + + w->show(); + return application.exec(); } diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index 1f5683da9..730d51352 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -2,5 +2,6 @@ ../../../res/medusa-bg.jpg ../../../res/patrons.txt + input/default-profiles.ini diff --git a/src/platform/qt/ts/medusa-emu-de.ts b/src/platform/qt/ts/medusa-emu-de.ts index 7bc5df1ff..e8caa5546 100644 --- a/src/platform/qt/ts/medusa-emu-de.ts +++ b/src/platform/qt/ts/medusa-emu-de.ts @@ -454,6 +454,105 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Max. Zeilen + + MemorySearch + + + Form + Eingabemaske + + + + Address + Adresse + + + + Current Value + Aktueller Wert + + + + + Type + Typ + + + + Value + Wert + + + + Numeric + Numerisch + + + + Text + Text + + + + Width + Breite + + + + 1 Byte (8-bit) + 1 Byte (8-bit) + + + + 2 Bytes (16-bit) + 2 Bytes (16-bit) + + + + 4 Bytes (32-bit) + 4 Bytes (32-bit) + + + + Number type + Zahlensystem + + + + Hexadecimal + Hexadezimal + + + + Decimal + Dezimal + + + + Guess + automatisch + + + + Search + Suchen + + + + Search Within + Suchen innerhalb + + + + Open in Memory Viewer + Im Speicher-Monitor öffnen + + + + Refresh + Aktualisieren + + MemoryView @@ -834,6 +933,11 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L HuC-3 HuC-3 + + + Colors + Farben + PaletteView @@ -1042,28 +1146,28 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::GameController - - + + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen - + Failed to start audio processor Fehler beim Starten des Audio-Prozessors @@ -2499,7 +2603,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Defekt - + Slot %1 Speicherplatz %1 @@ -2606,57 +2710,80 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L ISO-8859-1 + + QGBA::MemorySearch + + + (⅟%0×) + (⅟%0×) + + + + 1 byte%0 + 1 Byte%0 + + + + 2 bytes%0 + 2 Bytes%0 + + + + 4 bytes%0 + 4 Bytes%0 + + QGBA::ObjView - - + + 0x%0 0x%0 - + Off Aus - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Ungültig - - + + N/A N/A - + Export sprite Sprite exportieren - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 Fehler beim Öffnen der Ausgabe-PNG-Datei: %1 @@ -2716,7 +2843,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L bytes - Bytes + Bytes @@ -2727,37 +2854,47 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::SettingsView - + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (erzwinge Version 1.x) - - Bindings - Tastenbelegung + + Keyboard + Tastatur - + + Controllers + Gamepads + + + + Shortcuts + Tastenkürzel + + + Select BIOS BIOS auswählen @@ -2821,67 +2958,66 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::Window - + Game Boy Advance ROMs (%1) Game Boy Advance-ROMs (%1) - DS ROMs (%1) DS-ROMs (%1) - + Game Boy ROMs (%1) Game Boy-ROMs (%1) - + All ROMs (%1) Alle ROMs (%1) - + %1 Video Logs (*.mvl) %1 Video-Logs (*.mvl) - + Archives (%1) Archive (%1) - - - + + + Select ROM ROM auswählen - + Game Boy Advance save files (%1) Game Boy Advance-Speicherdateien (%1) - - - + + + Select save Speicherdatei wählen - + Select patch Patch wählen - + Patches (*.ips *.ups *.bps) Patches (*.ips *.ups *.bps) - - + + GameShark saves (*.sps *.xps) GameShark-Speicherdaten (*.sps *.xps) @@ -2896,21 +3032,22 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L DS-Unterstützung erfordert ein Abbild des BIOS und der Firmware. + Select video log Video-Log auswählen - + Video logs (*.mvl) Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -2919,523 +3056,523 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L %1 - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate &laden - + F10 F10 - + &Save state Savestate &speichern - + Shift+F10 Umschalt+F10 - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Stand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown B&eenden - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + Frame&skip Frame&skip - + Shader options... Shader-Optionen... - + Mute Stummschalten - + FPS target Bildwiederholrate - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativ (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + Record video log... Video-Log aufzeichnen... - + Stop video log Video-Log beenden - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole äffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... @@ -3455,37 +3592,37 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L DS - + Select folder Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View memory... Speicher betrachten... @@ -3495,62 +3632,72 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L &I/O-Register betrachten... - + + Search memory... + Speicher durchsuchen... + + + + View &I/O registers... + &I/O-Register betrachten... + + + Exit fullscreen Vollbildmodus beenden - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links @@ -3842,7 +3989,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L - + frames Bilder @@ -3883,55 +4030,65 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L (Integer scaling) - + + Language + Sprache + + + + English + Englisch + + + List view Listenansicht - + Tree view Baumansicht - + Library: Bibliothek: - + Show when no game open Anzeigen, wenn kein Spiel geöffnet ist - + Clear cache Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Rewind affects save data Rücklauf beeinflusst Speicherdaten - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - + + + + + + + Browse Durchsuchen @@ -3946,28 +4103,28 @@ in Arbeitsspeicher vorladen DS-BIOS 9: - + Use BIOS file if found BIOS-Datei verwenden, wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -3977,50 +4134,50 @@ wenn vorhanden BIOS - + Pause when inactive Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen - + Allow opposing input directions Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -4030,37 +4187,37 @@ wenn vorhanden Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: @@ -4070,30 +4227,30 @@ wenn vorhanden DS-Firmware: - + Save games Spielstände - - - - + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches diff --git a/src/platform/qt/ts/mgba-it.ts b/src/platform/qt/ts/mgba-it.ts new file mode 100644 index 000000000..e66db7da8 --- /dev/null +++ b/src/platform/qt/ts/mgba-it.ts @@ -0,0 +1,4305 @@ + + + + + AboutScreen + + + About + About + + + + <a href="http://mgba.io/">Website</a> • <a href="https://forums.mgba.io/">Forums / Support</a> • <a href="https://patreon.com/mgba">Donate</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> + <a href="http://mgba.io/">Sitio web</a> • <a href="https://forums.mgba.io/">Foros / Supporto</a> • <a href="https://patreon.com/mgba">Donar</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Codice sorgente</a> + + + + {projectName} + {projectName} + + + + {projectName} would like to thank the following patrons from Patreon: + {projectName} desidera ringraziare i seguenti sponsor di Patreon: + + + + © 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 +Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + © 2013 - 2016 Jeffrey Pfau, sotto licenza Mozilla Public License, versione 2.0 +Game Boy Advance è un marchio registrato di Nintendo Co., Ltd. + + + + {patrons} + {patrons} + + + + {projectVersion} + {projectVersion} + + + + {logo} + {logo} + + + + {projectName} is an open-source Game Boy Advance emulator + {projectName} è un emulatore open-source del Game Boy Advance + + + + Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt> + Ramo Git: <tt>{gitBranch}</tt><br/>Revisione: <tt>{gitCommit}</tt> + + + + ArchiveInspector + + + Open in archive... + Apri il file in ... + + + + Loading... + Caricamento in corso... + + + + AssetTile + + + AssetTile + AssetTile + + + + Tile # + Tile Nº + + + + 0 + 0 + + + + Address + Indirizzo + + + + 0x06000000 + 0x06000000 + + + + Red + Rosso + + + + Green + Verde + + + + Blue + Blue + + + + + + 0x00 (00) + 0x00 (00) + + + + CheatsView + + + Cheats + Trucchi + + + + Remove + Rimuovere + + + + Save + Salva + + + + Load + Carica + + + + Add New Set + Aggiungere Nuovo Set + + + + Add + Aggiungi + + + + DebuggerConsole + + + Debugger + Debugger + + + + Enter command (try `help` for more info) + Inserire un comando (premere `help` per maggiori informazioni) + + + + Break + Pausa + + + + GIFView + + + Record GIF + Registra GIF + + + + Start + Avvia + + + + Stop + Stop + + + + Select File + Seleziona File + + + + Frameskip + Salta Frame + + + + Frame delay (ms) + Ritardo Frame (ms) + + + + Automatic + Automatico + + + + IOViewer + + + I/O Viewer + Visualizzatore I/O + + + + 0x0000 + 0x0000 + + + + 2 + 2 + + + + 5 + 5 + + + + 4 + 4 + + + + 7 + 7 + + + + 0 + 0 + + + + 9 + 9 + + + + 1 + 1 + + + + 3 + 3 + + + + 8 + 8 + + + + C + C + + + + E + E + + + + 6 + 6 + + + + D + D + + + + F + F + + + + A + A + + + + B + B + + + + LibraryView + + + Library + Biblioteca + + + + LoadSaveState + + + + %1 State + %1 cattura stato + + + + + + + + + + + + No Save + Senza Salvare + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + 9 + 9 + + + + LogView + + + Logs + Logs + + + + Enabled Levels + Abilita Livelli + + + + Debug + Debug + + + + Stub + Stub + + + + Info + Informazioni + + + + Warning + Avvertenze + + + + Error + Errore + + + + Fatal + Fatale + + + + Game Error + Errore del gioco + + + + Clear + Pulisci + + + + Max Lines + Linee di massima + + + + MemoryView + + + Memory + Memoria + + + + Inspect Address: + Ispezionare indirizzo: + + + + 0x + 0x + + + + Set Alignment: + Set di allignamento: + + + + 1 Byte + 1 byte + + + + 2 Bytes + 2 bytes + + + + 4 Bytes + 4 bytes + + + + Signed Integer: + Integer Signato: + + + + String: + Stringa: + + + + Load TBL + Carica TBL + + + + Copy Selection + Copia la selezione + + + + Paste + Incolla + + + + Save Selection + Salva Selezione + + + + Load + Carica + + + + Unsigned Integer: + Integer non signato: + + + + ObjView + + + Sprites + Sprites + + + + + × + × + + + + Magnification + Magnification + + + + Attributes + Attributi + + + + Transform + Transformazione + + + + Off + No + + + + Palette + Palette + + + + + + + 0 + 0 + + + + Double Size + Doppia Dimensione + + + + + + + Return, Ctrl+R + Return, Ctrl+R + + + + Flipped + Flippato + + + + H + H + + + + V + V + + + + Mode + Modalità + + + + Normal + Normale + + + + Mosaic + Mosaico + + + + Enabled + Abilitato + + + + Priority + Priorità + + + + Tile + Tile + + + + Geometry + Geometria + + + + Position + Posizione + + + + , + , + + + + Dimensions + Dimensione + + + + + 8 + 8 + + + + Address + Indirizzo + + + + 0x07000000 + 0x07000000 + + + + OverrideView + + + Game Overrides + Valori specifici per gioco + + + + Game Boy Advance + Game Boy Advance + + + + + + + Autodetect + Rilevamento automatico + + + + Realtime clock + RealTime clock + + + + Gyroscope + Giroscopio + + + + Tilt + Inclinazione + + + + Light sensor + Sensore di luce + + + + Rumble + Vibrazione + + + + Save type + Tipo di salvataggio + + + + + None + Nessuno + + + + SRAM + SRAM + + + + Flash 512kb + Flash 512kb + + + + Flash 1Mb + Flash 1Mb + + + + EEPROM + EEPROM + + + + Idle loop + Idle loop + + + + Game Boy Player features + Caratteristiche Game Boy Player + + + + Game Boy + Game Boy + + + + Game Boy model + Modello del Game Boy + + + + Game Boy (DMG) + Game Boy (DMG) + + + + Game Boy Color (CGB) + Game Boy Color (CGB) + + + + Game Boy Advance (AGB) + Game Boy Advance (AGB) + + + + Memory bank controller + Controller del banco di memoria + + + + MBC1 + MBC1 + + + + MBC2 + MBC2 + + + + MBC3 + MBC3 + + + + MBC3 + RTC + MBC3 + Reloj + + + + MBC5 + MBC5 + + + + MBC5 + Rumble + MBC5 + Vibrazione + + + + MBC7 + MBC7 + + + + HuC-3 + HuC-3 + + + + PaletteView + + + Palette + Palette + + + + Background + SFondo (BG) + + + + Objects + Oggetti (OBJ) + + + + Selection + Selezione + + + + Red + Rosso + + + + Green + Verde + + + + Blue + Blue + + + + + + 0x00 (00) + 0x00 (00) + + + + 16-bit value + Valore in 16 bits + + + + Hex code + Codice esadecimale + + + + Palette index + Indice palette + + + + 0x0000 + 0x0000 + + + + #000000 + #000000 + + + + 000 + 000 + + + + Export BG + Esporta BG + + + + Export OBJ + Esporta OBJ + + + + QGBA::AssetTile + + + %0%1%2 + %0%1%2 + + + + + + 0x%0 (%1) + 0x%0 (%1) + + + + QGBA::CheatsModel + + + (untitled) + (senza titolo) + + + + Failed to open cheats file: %1 + Impossibile aprire il file cheats: %1 + + + + QGBA::CheatsView + + + + Add GameShark + Aggiungi GameShark + + + + Add Pro Action Replay + Aggiungi Pro Action Replay + + + + Add CodeBreaker + Aggiungi CodeBreaker + + + + Add GameGenie + Aggiungi GameGenie + + + + + Select cheats file + Seleziona il file cheats + + + + QGBA::GBAKeyEditor + + + Clear Button + Pulisci bottoni + + + + Clear Analog + Pulisci analogici + + + + Refresh + Aggiornare + + + + Set all + Impostare tutti + + + + QGBA::GDBWindow + + + Server settings + Impostazioni Server + + + + Local port + Porta locale + + + + Bind address + Indirizzo Bind + + + + Break + Break + + + + Stop + Stop + + + + Start + Avvia + + + + Crash + Errore + + + + Could not start GDB server + Impossibile avviare il server GDB + + + + QGBA::GIFView + + + Failed to open output GIF file: %1 + Impossibile aprire il file GIF di output: %1 + + + + Select output file + Seleziona file di output + + + + Graphics Interchange Format (*.gif) + Formato di interconnessione grafica (*.gif) + + + + QGBA::GameController + + + + Failed to open game file: %1 + Impossibile aprire il file di gioco: %1 + + + + Failed to open save file: %1 + Impossibile aprire il file di salvataggio: %1 + + + + Failed to open snapshot file for reading: %1 + Impossibile aprire il file snapshot per la lettura: %1 + + + + Failed to open snapshot file for writing: %1 + Impossibile aprire il file snapshot per la scrittura: %1 + + + + Failed to start audio processor + Impossibile avviare il processore audio + + + + QGBA::IOViewer + + + Background mode + Modalità Background (BG) + + + + Mode 0: 4 tile layers + Modalità 0: 4 tiles layers + + + + Mode 1: 2 tile layers + 1 rotated/scaled tile layer + Modalità 1: 2 tile layers + 1 ruotato/scalato tile layer + + + + Mode 2: 2 rotated/scaled tile layers + Modalità 2: 2 ruotato/scalato tile layers + + + + Mode 3: Full 15-bit bitmap + Modalità 3: completo 15 bitmap + + + + Mode 4: Full 8-bit bitmap + Modalità 4: completo 8 bits + + + + Mode 5: Small 15-bit bitmap + Modalità 5: basso 15-bit bitmap + + + + CGB Mode + Modalità CGB + + + + Frame select + Seleziona Frame + + + + Unlocked HBlank + HBlank sbloccato + + + + Linear OBJ tile mapping + Mappatura lineare tile OBJ + + + + Force blank screen + Forza schermo bianco + + + + Enable background 0 + Abilitare sfondo 0 + + + + Enable background 1 + Abilitare sfondo 1 + + + + Enable background 2 + Abilitare sfondo 2 + + + + Enable background 3 + Abilitare sfondo 3 + + + + Enable OBJ + Abilitare OBJ + + + + Enable Window 0 + Abilitare Window 0 + + + + Enable Window 1 + Abilitare Window 1 + + + + Enable OBJ Window + Abilitare Window OBJ + + + + Currently in VBlank + Attualmente in VBlank + + + + Currently in HBlank + Attualmente in HBlank + + + + Currently in VCounter + Attualmente in VCounter + + + + Enable VBlank IRQ generation + Abilita VBlank + + + + Enable HBlank IRQ generation + Abilita HBlank generazione IRQ + + + + Enable VCounter IRQ generation + Abilita generazione IRQ VCounter + + + + VCounter scanline + VCounter scanline + + + + Current scanline + Scanline corrente + + + + + + + Priority + Priorità + + + + + + + Tile data base (* 16kB) + Tile data base (* 16kB) + + + + + + + Enable mosaic + Abilita Mosaico + + + + + + + Enable 256-color + Abilita 256 colori + + + + + + + Tile map base (* 2kB) + Tile map base (* 2kB) + + + + + + + Background dimensions + Dimensioni dello sfondo + + + + + Overflow wraps + Overflow wraps + + + + + + + Horizontal offset + Compensazione orizzontale + + + + + + + Vertical offset + Compensazione verticale + + + + + + + + + + + + + + + Fractional part + Parte frazionale + + + + + + + + + + + Integer part + Parte Integer + + + + + + + Integer part (bottom) + Parte Integer (inferiore) + + + + + + + Integer part (top) + Parte Integer (superiore) + + + + + End x + Fine x + + + + + Start x + Inizio x + + + + + End y + Fine y + + + + + Start y + Inizio y + + + + Window 0 enable BG 0 + Window 0 BG 0 + + + + Window 0 enable BG 1 + Window 0 BG 1 + + + + Window 0 enable BG 2 + Window 0 BG 2 + + + + Window 0 enable BG 3 + Window 0 BG 3 + + + + Window 0 enable OBJ + Window 0 OBJ + + + + Window 0 enable blend + Abilita Window 0 miscelato + + + + Window 1 enable BG 0 + Abilita Window 1 BG 0 + + + + Window 1 enable BG 1 + Abilita Window 1 BG 1 + + + + Window 1 enable BG 2 + Abilita Window 1 BG 2 + + + + Window 1 enable BG 3 + AbilitaWindow 1 BG 3 + + + + Window 1 enable OBJ + Abilita Window 1 OBJ + + + + Window 1 enable blend + Abilita Window 1 miscelato + + + + Outside window enable BG 0 + Abilita Outside window BG 0 + + + + Outside window enable BG 1 + Abilita Outside window BG 1 + + + + Outside window enable BG 2 + Abilita Outside window BG 2 + + + + Outside window enable BG 3 + Abilita Outside window BG 3 + + + + Outside window enable OBJ + Abilita Outside window OBJ + + + + Outside window enable blend + Outside window mezcla + + + + OBJ window enable BG 0 + Abilita OBJ window BG 0 + + + + OBJ window enable BG 1 + Abilita OBJ window BG 1 + + + + OBJ window enable BG 2 + Abilita OBJ window BG 2 + + + + OBJ window enable BG 3 + Abilita OBJ window BG 3 + + + + OBJ window enable OBJ + Abilita OBJ window OBJ + + + + OBJ window enable blend + Abilita OBJ window blend + + + + Background mosaic size vertical + Sfondo mosaico verticale + + + + Background mosaic size horizontal + Sfondo mosaico orizzontale + + + + Object mosaic size vertical + Sfondo mosaico oggetto verticale + + + + Object mosaic size horizontal + Sfondo mosaico oggetto orizzontale + + + + BG 0 target 1 + BG 0 target 1 + + + + BG 1 target 1 + BG 1 target 1 + + + + BG 2 target 1 + BG 2 target 1 + + + + BG 3 target 1 + BG 3 target 1 + + + + OBJ target 1 + OBJ target 1 + + + + Backdrop target 1 + Backdrop target 1 + + + + Blend mode + Modalità miscelato + + + + Disabled + Disabilitato + + + + Additive blending + Miscelazione dell'additivo + + + + Brighten + Schiarire + + + + Darken + Oscurire + + + + BG 0 target 2 + BG 0 target 2 + + + + BG 1 target 2 + BG 1 target 2 + + + + BG 2 target 2 + BG 2 target 2 + + + + BG 3 target 2 + BG 3 target 2 + + + + OBJ target 2 + OBJ target 2 + + + + Backdrop target 2 + Backdrop target 2 + + + + Blend A (target 1) + Miscelato A (target 1) + + + + Blend B (target 2) + Miscelato B (target 2) + + + + Blend Y + Miscelato Y + + + + Sweep shifts + Sweep shifts + + + + Sweep subtract + Sposta subastratto + + + + Sweep time (in 1/128s) + Tempo di sweep (in 1/128seg) + + + + + + + Sound length + Lunghezza del suono + + + + + Duty cycle + Ciclo di lavoro + + + + + + Envelope step time + Envelope step time + + + + + + Envelope increase + Aumento envelope + + + + + + Initial volume + Volume iniziale + + + + + + Sound frequency + Frequenza del suono + + + + + + + Timed + Temporizzato + + + + + + + Reset + Reset + + + + Double-size wave table + Tavola delle onde a doppia dimensione + + + + Active wave table + Tavola delle onde attive + + + + Enable channel 3 + Canale 3 attivo + + + + Volume + Volume + + + + 0% + 0% + + + + + 100% + 100% + + + + + 50% + 50% + + + + + 25% + 25% + + + + + + + 75% + 75% + + + + Clock divider + Divisore dell'orologio + + + + Register stages + Stadi di registrazione + + + + 15 + 15 + + + + 7 + 7 + + + + Shifter frequency + Cambio di frequenza + + + + PSG volume right + PSG volume destro + + + + PSG volume left + PSG volume sinistro + + + + Enable channel 1 right + Abilita Canale 1 destro + + + + Enable channel 2 right + Abilita Canale 2 destro + + + + Enable channel 3 right + Abilita Canale 3 destro + + + + Enable channel 4 right + Abilita Canale 4 destro + + + + Enable channel 1 left + Abilita Canale 1 sinistro + + + + Enable channel 2 left + Abilita Canale 2 sinistro + + + + Enable channel 3 left + Abilita Canale 3 sinistro + + + + Enable channel 4 left + Abilita Canale 4 sinistro + + + + PSG master volume + PSG volume master + + + + Loud channel A + Canale A forte + + + + Loud channel B + Canale B forte + + + + Enable channel A right + Abilita Canale A destro + + + + Enable channel A left + Abilta Canale A sinistro + + + + Channel A timer + Canale A temporizzato + + + + + 0 + 0 + + + + + + + + + + + + 1 + 1 + + + + Channel A reset + Resetta canale A + + + + Enable channel B right + Abilita Canale B destro + + + + Enable channel B left + Abilita Canale B sinistro + + + + Channel B timer + Canale B temporizzato + + + + Channel B reset + Resetta canale B + + + + Active channel 1 + Canale 1 attivo + + + + Active channel 2 + Canale 2 attivo + + + + Active channel 3 + Canale 3 attivo + + + + Active channel 4 + Canale 4 attivo + + + + Enable audio + Abilitare audio + + + + Bias + Polarizzazione + + + + Resolution + Risoluzione + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sample + Mostra + + + + + + + + + + + Address (bottom) + Indirizzo (inferiore) + + + + + + + + + + + Address (top) + Indirizzo (superiore) + + + + + + + Word count + Contatore di parole + + + + + + + Destination offset + Compensazione offset + + + + + + + + + + + Increment + Incremento + + + + + + + + + + + Decrement + Decremento + + + + + + + + + + + Fixed + Fissato + + + + + + + Increment and reload + Incremento e ricarica + + + + + + + Source offset + Compensazione di origine + + + + + + + Repeat + Ripeti + + + + + + + 32-bit + 32 bit + + + + + + + Start timing + Avvia temporizzatore + + + + + + + Immediate + Immediato + + + + + + + + + VBlank + VBlank + + + + + + + + + HBlank + HBlank + + + + + + + + + + + + IRQ + IRQ + + + + + + + + + + + Enable + Abilitare + + + + + + Audio FIFO + Audio FIFO + + + + Video Capture + Cattura video + + + + DRQ + DRQ + + + + + + + Value + Valore + + + + + + + Scale + Scala + + + + + + + 1/64 + 1/64 + + + + + + + 1/256 + 1/256 + + + + + + + 1/1024 + 1/1024 + + + + + + Cascade + Cascata + + + + + A + A + + + + + B + B + + + + + Select + Seleziona + + + + + Start + Avvia + + + + + Right + Destro + + + + + Left + Sinistro + + + + + Up + + + + + + Down + Giù + + + + + R + R + + + + + L + L + + + + Condition + Condizione + + + + SC + SC + + + + SD + SD + + + + SI + SI + + + + SO + SO + + + + + VCounter + VCounter + + + + + Timer 0 + Timer 0 + + + + + Timer 1 + Timer 1 + + + + + Timer 2 + Timer 2 + + + + + Timer 3 + Timer 3 + + + + + SIO + SIO + + + + + DMA 0 + DMA 0 + + + + + DMA 1 + DMA 1 + + + + + DMA 2 + DMA 2 + + + + + DMA 3 + DMA 3 + + + + + Keypad + Tastiera + + + + + Gamepak + Gamepak + + + + SRAM wait + Attesa SRAM + + + + + + + + 4 + 4 + + + + + + + 3 + 3 + + + + + + + + 2 + 2 + + + + + + + + 8 + 8 + + + + Cart 0 non-sequential + Cart 0 non sequenziale + + + + Cart 0 sequential + Cart 0 sequenziale + + + + Cart 1 non-sequential + Cart 1 non sequenziale + + + + Cart 1 sequential + Cart 1 sequenziale + + + + Cart 2 non-sequential + Cart 2 non sequenziale + + + + Cart 2 sequential + Cart 2 sequenziale + + + + PHI terminal + Terminale PHI + + + + Disable + Disabilita + + + + 4.19MHz + 4.19MHz + + + + 8.38MHz + 8.38MHz + + + + 16.78MHz + 16.78MHz + + + + Gamepak prefetch + Gamepak prefetch + + + + Enable IRQs + Abilitare IRQs + + + + QGBA::KeyEditor + + + + --- + --- + + + + QGBA::LibraryModel + + + Name + Nome + + + + Filename + Nome del file + + + + Size + Dimensione + + + + Platform + Piattaforma + + + + GBA + GBA + + + + GB + GB + + + + ? + ? + + + + Location + Posizione + + + + CRC32 + CRC32 + + + + QGBA::LoadSaveState + + + Load State + Carica stato + + + + Save State + Salva stato + + + + Empty + Vuoto + + + + Corrupted + Corrotto + + + + Slot %1 + Slot %1 + + + + QGBA::LogController + + + DEBUG + DEBUG + + + + STUB + STUB + + + + INFO + INFORMAZIONI + + + + WARN + AVVERTENZA + + + + ERROR + ERRORE + + + + FATAL + FATALE + + + + GAME ERROR + ERRORE NEL GIOCO + + + + QGBA::MemoryModel + + + Copy selection + Copia selezionato + + + + Save selection + Salva selezionato + + + + Paste + Incolla + + + + Load + Carica + + + + + All + Tutto + + + + Load TBL + Carica TBL + + + + Save selected memory + Salva la memoria selezionate + + + + Failed to open output file: %1 + Impossibile aprire il file di output: %1 + + + + Load memory + Carica memoria + + + + Failed to open input file: %1 + Impossibile aprire il file di input: %1 + + + + TBL + TBL + + + + ISO-8859-1 + ISO-8859-1 + + + + QGBA::ObjView + + + + 0x%0 + 0x%0 + + + + Off + No + + + + Normal + Normale + + + + Trans + Trans + + + + OBJWIN + OBJWIN + + + + Invalid + Invalido + + + + + N/A + n/d + + + + QGBA::PaletteView + + + #%0 + #%0 + + + + 0x%0 + 0x%0 + + + + %0 + %0 + + + + + + 0x%0 (%1) + 0x%0 (%1) + + + + Export palette + Esporta palette + + + + Windows PAL (*.pal);;Adobe Color Table (*.act) + WIndows PAL (*.pal);;Tabella dei colori Adobe (*.act) + + + + Failed to open output palette file: %1 + Errore nell'aprire il file palette di output : %1 + + + + QGBA::ROMInfo + + + + + + + (unknown) + (sconosciuto) + + + + + bytes + bytes + + + + (no database present) + (nessun database presente) + + + + QGBA::SettingsView + + + Qt Multimedia + Qt Multimedia + + + + SDL + SDL + + + + Software (Qt) + Software (Qt) + + + + OpenGL + OpenGL + + + + OpenGL (force version 1.x) + OpenGL (forza la versione 1.x) + + + + Keyboard + Tastiera + + + + Controllers + Controllers + + + + Shortcuts + Tasti di scelta rapida + + + + Select BIOS + Seleziona BIOS + + + + QGBA::ShaderSelector + + + No shader active + Nessun shader attivo + + + + Load shader + Carica shader + + + + %1 Shader (%.shader) + %1 Shader (%.shader) + + + + No shader loaded + Nessun shader caricato + + + + by %1 + por %1 + + + + Preprocessing + Preprocesso + + + + Pass %1 + Pass %1 + + + + QGBA::ShortcutController + + + Action + Azione + + + + Keyboard + Tastiera + + + + Gamepad + Gamepad + + + + QGBA::VideoView + + + Failed to open output video file: %1 + Errore durante l'archiviazione del video: %1 + + + + Native (%0x%1) + Nativo (%0x%1) + + + + Select output file + Seleziona file di output + + + + QGBA::Window + + + Game Boy Advance ROMs (%1) + ROM di Game Boy Advance (%1) + + + + Game Boy ROMs (%1) + ROMs del Game Boy (%1) + + + + All ROMs (%1) + Tutte le ROM (%1) + + + + Archives (%1) + Archivio (%1) + + + + + + Select ROM + Seleziona ROM + + + + Game Boy Advance save files (%1) + Game Boy Advance file di salvataggio (%1) + + + + + + Select save + Seleziona salvataggio + + + + Select patch + Seleziona patch + + + + Patches (*.ips *.ups *.bps) + Patches (*.ips *.ups *.bps) + + + + + GameShark saves (*.sps *.xps) + Salvataggi GameShark (*.sps *.xps) + + + + Crash + Errore fatale + + + + The game has crashed with the following error: + +%1 + Il gioco è andato in crash con il seguente errore:: + +%1 + + + + Couldn't Load + Non è possibile caricare + + + + Could not load game. Are you sure it's in the correct format? + Impossibile caricare il gioco. Sei sicuro che sia nel formato corretto? + + + + Unimplemented BIOS call + BIOS non implementato + + + + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. + Questo gioco utilizza una chiamata BIOS non implementata. Utilizza il BIOS ufficiale per una migliore esperienza + + + + Really make portable? + Davvero rendere portatile? + + + + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? + In questo modo l'emulatore carica la propria configurazione dalla stessa directory dell'eseguibile. Vuoi continuare? + + + + Restart needed + È necessario riavviare + + + + Some changes will not take effect until the emulator is restarted. + Alcune modifiche non avranno effetto finché l'emulatore non viene riavviato. + + + + - Player %1 of %2 + - Giocatore %1 di %2 + + + + %1 - %2 + %1 - %2 + + + + %1 - %2 - %3 + %1 - %2 - %3 + + + + %1 - %2 (%3 fps) - %4 + %1 - %2 (%3 fps) - %4 + + + + &File + &File + + + + Load &ROM... + Carica &ROM... + + + + Load ROM in archive... + Carica la ROM in archivio... + + + + Load temporary save... + Carica il salvataggio temporaneo.. + + + + Load &patch... + Carica &patch... + + + + Boot BIOS + Boot BIOS + + + + Replace ROM... + Sostituire la ROM... + + + + ROM &info... + ROM &info... + + + + Recent + Recente + + + + Make portable + Rendi portatile + + + + &Load state + &Carica stato + + + + F10 + F10 + + + + &Save state + &Salva stato + + + + Shift+F10 + DO NOT TRANSLATE + Shift+F10 + + + + Quick load + Caricamento rapido + + + + Quick save + Salvataggio rapido + + + + Load recent + Carica recente + + + + Save recent + Salva recente + + + + Undo load state + Annulla il caricamento dello stato + + + + F11 + DO NOT TRANSLATE + F11 + + + + Undo save state + Annulla salva stato + + + + Shift+F11 + DO NOT TRANSLATE + Shift+F11 + + + + + State &%1 + Stato &%1 + + + + F%1 + F%1 + + + + Shift+F%1 + DO NOT TRANSLATE + Shift+F%1 + + + + Import GameShark Save + Importa il salvataggio del GameShark + + + + Export GameShark Save + Esporta salvataggio dal GameShark + + + + New multiplayer window + Nuova finestra multigiocatore + + + + About + About + + + + E&xit + Uscire (&X) + + + + &Emulation + &Emulazione + + + + &Reset + &Reset + + + + Ctrl+R + Ctrl+R + + + + Sh&utdown + Spegni (&U) + + + + Yank game pak + Yank game pak + + + + &Pause + &Pausa + + + + Ctrl+P + Ctrl+P + + + + &Next frame + Salta il prossimo frame (&N) + + + + Ctrl+N + Ctrl+N + + + + Fast forward (held) + Avanzamento rapido (sostenuto) + + + + &Fast forward + Avanzamento rapido (&F) + + + + Shift+Tab + Shift+Tab + + + + Fast forward speed + Velocità di avanzamento rapido + + + + Unbounded + Illimitato + + + + %0x + %0x + + + + Rewind (held) + Riavvolgi (sostenuto) + + + + Re&wind + Riavvolgi (&W) + + + + ~ + ~ + + + + Step backwards + Torna indietro + + + + Ctrl+B + Ctrl+B + + + + Sync to &video + Sincronizzare su &video + + + + Sync to &audio + Sincronizzare su &audio + + + + Solar sensor + Sensore solare + + + + Increase solar level + Aumenta il livello solare + + + + Decrease solar level + Riduce il livello solare + + + + Brightest solar level + Livello solare brillante + + + + Darkest solar level + Livello solare più scuro + + + + Brightness %1 + Luminosità %1 + + + + Audio/&Video + Audio/&Video + + + + Frame size + Dimensione Frame + + + + %1x + %1x + + + + Toggle fullscreen + Abilita schermo intero + + + + Lock aspect ratio + Blocca aspect ratio + + + + Resample video + Rimostra video + + + + Frame&skip + Frame&skip + + + + Shader options... + Opzioni shader... + + + + Mute + Muto + + + + FPS target + FPS mirato + + + + 15 + 15 + + + + 30 + 30 + + + + 45 + 45 + + + + Native (59.7) + Nativo (59.7) + + + + 60 + 60 + + + + 90 + 90 + + + + 120 + 120 + + + + 240 + 240 + + + + Take &screenshot + Effettua &screenshot + + + + F12 + F12 + + + + Record output... + Registra salida... + + + + Record GIF... + Registra GIF... + + + + Video layers + Layers video + + + + Background %0 + Sfondo %0 + + + + OBJ (sprites) + OBJ (sprites) + + + + Audio channels + Canali audio + + + + Channel %0 + Canale %0 + + + + Channel A + Canale A + + + + Channel B + Canale B + + + + &Tools + &Strumenti + + + + View &logs... + Visualizza registri... (&L) + + + + Game &overrides... + Val&specifico per il gioco... + + + + Game &Pak sensors... + Sensori di gioco &Pak... + + + + &Cheats... + &Trucchi... + + + + Open debugger console... + Apri debugger console... + + + + Start &GDB server... + Avvia server &GDB... + + + + Settings... + Impostazioni... + + + + Select folder + Seleziona cartella + + + + Add folder to library... + Aggiungi cartella alla libreria... + + + + View &palette... + Ver &palette... + + + + View &sprites... + Ver &sprites... + + + + View &tiles... + Ver &tiles... + + + + View memory... + Ver memoria... + + + + View &I/O registers... + Ver reg&registri I/O... + + + + Exit fullscreen + Esci da schermo intero + + + + Autofire + Pulsanti Auto fuoco + + + + Autofire A + Auto fuoco A + + + + Autofire B + Auto fuoco B + + + + Autofire L + Auto fuoco L + + + + Autofire R + Auto fuoco R + + + + Autofire Start + Avvia Auto fuoco + + + + Autofire Select + Seleziona Auto fuoco + + + + Autofire Up + Auto fuoco sù + + + + Autofire Right + Auto fuoco destro + + + + Autofire Down + Auto fuoco giù + + + + Autofire Left + Auto fuoco sinistro + + + + ROMInfo + + + ROM Info + Informazioni della ROM + + + + Game name: + Nome del gioco: + + + + {NAME} + {NAME} + + + + Internal name: + Nome interno: + + + + {TITLE} + {TITLE} + + + + Game ID: + ID del gioco: + + + + {ID} + {ID} + + + + File size: + Dimensione del file: + + + + {SIZE} + {SIZE} + + + + CRC32: + CRC32: + + + + {CRC} + {CRC} + + + + SensorView + + + Sensors + Sensori + + + + Realtime clock + Realtime clock + + + + Fixed time + Ora fissa + + + + System time + Ora del sistema + + + + Start time at + Avvia tempo a + + + + Now + Adesso + + + + MM/dd/yy hh:mm:ss AP + dd/MM/yy HH:mm:ss + + + + Light sensor + Sensore di luce + + + + Brightness + Luminosità + + + + Tilt sensor + Sensore di inclinazione + + + + + Set Y + Config. Y + + + + + Set X + Config. X + + + + Gyroscope + Giroscopio + + + + Sensitivity + Sensibilità + + + + SettingsView + + + Settings + Impostazioni + + + + Audio/Video + Audio/Video + + + + Interface + Interfaccia + + + + Emulation + Emulazione + + + + Paths + Paths + + + + Audio driver: + Audio driver: + + + + Audio buffer: + Buffer audio: + + + + + 1536 + 1536 + + + + 512 + 512 + + + + 768 + 768 + + + + 1024 + 1024 + + + + 2048 + 2048 + + + + 3072 + 3072 + + + + 4096 + 4096 + + + + samples + samples + + + + Sample rate: + Sample rate: + + + + + 44100 + 44100 + + + + 22050 + 22050 + + + + 32000 + 32000 + + + + 48000 + 48000 + + + + Hz + Hz + + + + Volume: + Volume: + + + + Mute + Muto + + + + Display driver: + Visualizza driver: + + + + Frameskip: + Frameskip: + + + + Skip every + Salta ognuno + + + + + frames + frames + + + + FPS target: + FPS mirato: + + + + frames per second + frame per secondo + + + + Sync: + Sincronizzare: + + + + Video + Video + + + + Audio + Audio + + + + Lock aspect ratio + Blocca aspect ratio + + + + Resample video + Rimostrare video + + + + Library: + Biblioteca: + + + + Show when no game open + Mostra quando nessun gioco è aperto + + + + Clear cache + Cancella cache + + + + Fast forward speed: + Velocità di avanzamento rapido: + + + + + + + + + + Browse + Sfoglia + + + + Use BIOS file if found + Utilizzare il file del BIOS se è stato trovato + + + + Skip BIOS intro + Salta BIOS intro + + + + × + × + + + + Unbounded + Illimitato + + + + Suspend screensaver + Sospendi screensaver + + + + BIOS + BIOS + + + + Pause when inactive + Pausa se inattivo + + + + Run all + Avviare tutto + + + + Remove known + Rimuovi conosciuto + + + + Detect and remove + Rileva e rimuovi + + + + Allow opposing input directions + Consenti direzioni opposte + + + + + Screenshot + Screenshot + + + + + Save data + Salva dati + + + + + Cheat codes + Trucchi + + + + Enable rewind + Abilita riavvolgi + + + + Rewind history: + Riavvolgi storia: + + + + Idle loops: + Idle loops: + + + + Savestate extra data: + Salva dati extra: + + + + Load extra data: + Carica dati extra: + + + + GB BIOS file: + File GB BIOS: + + + + GBA BIOS file: + File GBA BIOS: + + + + GBC BIOS file: + File GBC BIOS: + + + + Save games + Salva il gioco + + + + + + + Same directory as the ROM + Stessa directory della ROM + + + + Save states + Salva Stato + + + + Screenshots + Screenshots + + + + Patches + Patches + + + + ShaderSelector + + + Shaders + Shaders + + + + Active Shader: + Attiva Shader: + + + + Name + Nome + + + + Author + Autore + + + + Description + Descrizione + + + + Unload Shader + Non caricare shader + + + + Load New Shader + Carica Nuovo shader + + + + ShortcutView + + + Edit Shortcuts + Edita Shortcuts + + + + Keyboard + Tastiera + + + + Gamepad + Gamepad + + + + Clear + Cancella + + + + TileView + + + Tiles + Tiles + + + + 256 colors + 256 colori + + + + × + × + + + + Magnification + Magnification + + + + VideoView + + + Record Video + Registra video + + + + Start + Avvia + + + + Stop + Stop + + + + Select File + Seleziona File + + + + Presets + Presets + + + + High Quality + Alta Qualità + + + + YouTube + YouTube + + + + + WebM + WebM + + + + Lossless + Senza perdite + + + + 1080p + 1080p + + + + 720p + 720p + + + + 480p + 480p + + + + Native + Nativo + + + + Format + Formato + + + + MKV + MKV + + + + AVI + AVI + + + + MP4 + MP4 + + + + PNG + PNG + + + + h.264 + h.264 + + + + VP8 + + + + + Xvid + Xvid + + + + FFV1 + FFV1 + + + + FLAC + FLAC + + + + Opus + Opus + + + + Vorbis + Vorbis + + + + MP3 + MP3 + + + + AAC + AAC + + + + Uncompressed + Senza compressione + + + + Bitrate (kbps) + Bitrate (kbps) + + + + VBR + VBR + + + + ABR + ABR + + + + Dimensions + Dimensioni + + + + : + : + + + + × + × + + + + Lock aspect ratio + Blocca aspect ratio + + + + Show advanced + Mostra avanzato + + + diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index d39a46b97..197c8443e 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -189,11 +189,8 @@ bool mSDLAttachPlayer(struct mSDLEvents* events, struct mSDLPlayer* player) { break; } } - if (claimed) { - continue; - } - if (firstUnclaimed == SIZE_MAX) { + if (!claimed && firstUnclaimed == SIZE_MAX) { firstUnclaimed = i; } @@ -353,7 +350,7 @@ void mSDLUpdateJoysticks(struct mSDLEvents* events, const struct Configuration* } if (events->preferredJoysticks[i] && strcmp(events->preferredJoysticks[i], joystickName) == 0) { events->players[i]->joystick = joystick; - if (config) { + if (config && events->players[i]->bindings) { mInputProfileLoad(events->players[i]->bindings, SDL_BINDING_BUTTON, config, joystickName); } return; @@ -364,7 +361,7 @@ void mSDLUpdateJoysticks(struct mSDLEvents* events, const struct Configuration* continue; } events->players[i]->joystick = joystick; - if (config) { + if (config && events->players[i]->bindings) { mInputProfileLoad(events->players[i]->bindings, SDL_BINDING_BUTTON, config, joystickName); } break;