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
+
+
+
+ Eingabemaske
+
+
+
+
+ Adresse
+
+
+
+
+ Aktueller Wert
+
+
+
+
+
+ Typ
+
+
+
+
+ Wert
+
+
+
+
+ Numerisch
+
+
+
+
+ Text
+
+
+
+
+ Breite
+
+
+
+
+ 1 Byte (8-bit)
+
+
+
+
+ 2 Bytes (16-bit)
+
+
+
+
+ 4 Bytes (32-bit)
+
+
+
+
+ Zahlensystem
+
+
+
+
+ Hexadezimal
+
+
+
+
+ Dezimal
+
+
+
+
+ automatisch
+
+
+
+
+ Suchen
+
+
+
+
+ Suchen innerhalb
+
+
+
+
+ Im Speicher-Monitor öffnen
+
+
+
+
+ Aktualisieren
+
+
MemoryView
@@ -834,6 +933,11 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
HuC-3
+
+
+
+ Farben
+
PaletteView
@@ -1042,28 +1146,28 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
QGBA::GameController
-
-
+
+
Fehler beim Öffnen der Spieldatei: %1
-
+
Fehler beim Öffnen der Speicherdatei: %1
-
+
Konnte Snapshot-Datei %1 nicht zum Lesen öffnen
-
+
Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen
-
+
Fehler beim Starten des Audio-Prozessors
@@ -2499,7 +2603,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
Defekt
-
+
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×)
+
+
+
+
+ 1 Byte%0
+
+
+
+
+ 2 Bytes%0
+
+
+
+
+ 4 Bytes%0
+
+
QGBA::ObjView
-
-
+
+
0x%0
-
+
Aus
-
+
Normal
-
+
Trans
-
+
OBJWIN
-
+
Ungültig
-
-
+
+
N/A
-
+
Sprite exportieren
-
+
Portable Network Graphics (*.png)
-
+
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
@@ -2727,37 +2854,47 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
QGBA::SettingsView
-
+
Qt Multimedia
-
+
SDL
-
+
Software (Qt)
-
+
OpenGL
-
+
OpenGL (erzwinge Version 1.x)
-
-
- Tastenbelegung
+
+
+ Tastatur
-
+
+
+ Gamepads
+
+
+
+
+ Tastenkürzel
+
+
+
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)
-
DS-ROMs (%1)
-
+
Game Boy-ROMs (%1)
-
+
Alle ROMs (%1)
-
+
%1 Video-Logs (*.mvl)
-
+
Archive (%1)
-
-
-
+
+
+
ROM auswählen
-
+
Game Boy Advance-Speicherdateien (%1)
-
-
-
+
+
+
Speicherdatei wählen
-
+
Patch wählen
-
+
Patches (*.ips *.ups *.bps)
-
-
+
+
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.
+
Video-Log auswählen
-
+
Video-Logs (*.mvl)
-
+
Absturz
-
+
@@ -2919,523 +3056,523 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
%1
-
+
Konnte nicht geladen werden
-
+
Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt?
-
+
Nicht implementierter BIOS-Aufruf
-
+
Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS.
-
+
Portablen Modus wirklich aktivieren?
-
+
Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren?
-
+
Neustart benötigt
-
+
Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde.
-
+
- Spieler %1 von %2
-
+
%1 - %2
-
+
%1 - %2 - %3
-
+
%1 - %2 (%3 Bilder/Sekunde) - %4
-
+
&Datei
-
+
&ROM laden...
-
+
ROM aus Archiv laden...
-
+
Temporäre Speicherdatei laden...
-
+
&Patch laden...
-
+
BIOS booten
-
+
ROM ersetzen...
-
+
ROM-&Informationen...
-
+
Zuletzt verwendet
-
+
Portablen Modus aktivieren
-
+
Savestate &laden
-
+
F10
-
+
Savestate &speichern
-
+
Umschalt+F10
-
+
Schnell laden
-
+
Schnell speichern
-
+
Lade zuletzt gespeicherten Savestate
-
+
Speichere aktuellen Stand
-
+
Laden des Savestate rückgängig machen
-
+
F11
-
+
Speichern des Savestate rückgängig machen
-
+
Umschalt+F11
-
-
+
+
Savestate &%1
-
+
F%1
-
+
Umschalt+F%1
-
+
Importiere GameShark-Speicherstand
-
+
Exportiere GameShark-Speicherstand
-
+
Neues Multiplayer-Fenster
-
+
Über
-
+
&Beenden
-
+
&Emulation
-
+
Zu&rücksetzen
-
+
Strg+R
-
+
B&eenden
-
+
Spielmodul herausziehen
-
+
&Pause
-
+
Strg+P
-
+
&Nächstes Bild
-
+
Strg+N
-
+
Schneller Vorlauf (gehalten)
-
+
Schneller &Vorlauf
-
+
Umschalt+Tab
-
+
Vorlauf-Geschwindigkeit
-
+
Unbegrenzt
-
+
%0x
-
+
Zurückspulen (gehalten)
-
+
Zur&ückspulen
-
+
~
-
+
Schrittweiser Rücklauf
-
+
Strg+B
-
+
Mit &Video synchronisieren
-
+
Mit &Audio synchronisieren
-
+
Solar-Sensor
-
+
Sonnen-Level erhöhen
-
+
Sonnen-Level verringern
-
+
Hellster Sonnen-Level
-
+
Dunkelster Sonnen-Level
-
+
Helligkeit %1
-
+
Audio/&Video
-
+
Bildgröße
-
+
%1x
-
+
Vollbildmodus umschalten
-
+
Seitenverhältnis korrigieren
-
+
Pixelgenaue Skalierung (Integer scaling)
-
+
Frame&skip
-
+
Shader-Optionen...
-
+
Stummschalten
-
+
Bildwiederholrate
-
+
15
-
+
30
-
+
45
-
+
Nativ (59.7)
-
+
60
-
+
90
-
+
120
-
+
240
-
+
&Screenshot erstellen
-
+
F12
-
+
Ausgabe aufzeichen...
-
+
GIF aufzeichen...
-
+
Video-Log aufzeichnen...
-
+
Video-Log beenden
-
+
Video-Ebenen
-
+
Audio-Kanäle
-
+
&Werkzeuge
-
+
&Logs ansehen...
-
+
Spiel-&Überschreibungen...
-
+
Game &Pak-Sensoren...
-
+
&Cheats...
-
+
Debugger-Konsole äffnen...
-
+
&GDB-Server starten...
-
+
Einstellungen...
@@ -3455,37 +3592,37 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
DS
-
+
Ordner auswählen
-
+
Ordner zur Bibliothek hinzufügen...
-
+
Bilineare Filterung
-
+
&Palette betrachten...
-
+
&Sprites betrachten...
-
+
&Tiles betrachten...
-
+
Speicher betrachten...
@@ -3495,62 +3632,72 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
&I/O-Register betrachten...
-
+
+
+ Speicher durchsuchen...
+
+
+
+
+ &I/O-Register betrachten...
+
+
+
Vollbildmodus beenden
-
+
Autofeuer
-
+
Autofeuer A
-
+
Autofeuer B
-
+
Autofeuer L
-
+
Autofeuer R
-
+
Autofeuer Start
-
+
Autofeuer Select
-
+
Autofeuer nach oben
-
+
Autofeuer rechts
-
+
Autofeuer nach unten
-
+
Autofeuer links
@@ -3842,7 +3989,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
-
+
Bilder
@@ -3883,55 +4030,65 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
(Integer scaling)
-
+
+
+ Sprache
+
+
+
+
+ Englisch
+
+
+
Listenansicht
-
+
Baumansicht
-
+
Bibliothek:
-
+
Anzeigen, wenn kein Spiel geöffnet ist
-
+
Cache leeren
-
+
Vorlauf-Geschwindigkeit:
-
+
Rücklauf beeinflusst
Speicherdaten
-
+
ROM-Datei vollständig
in Arbeitsspeicher vorladen
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Durchsuchen
@@ -3946,28 +4103,28 @@ in Arbeitsspeicher vorladen
DS-BIOS 9:
-
+
BIOS-Datei verwenden,
wenn vorhanden
-
+
BIOS-Intro überspringen
-
+
×
-
+
unbegrenzt
-
+
Bildschirmschoner deaktivieren
@@ -3977,50 +4134,50 @@ wenn vorhanden
BIOS
-
+
Pause, wenn inaktiv
-
+
Alle ausführen
-
+
Bekannte entfernen
-
+
Erkennen und entfernen
-
+
Gegensätzliche Eingaberichtungen erlauben
-
-
+
+
Screenshot
-
-
+
+
Speicherdaten
-
-
+
+
Cheat-Codes
-
+
Rücklauf aktivieren
@@ -4030,37 +4187,37 @@ wenn vorhanden
Bilineare Filterung
-
+
Rücklauf-Verlauf:
-
+
Leerlaufprozesse:
-
+
Zusätzliche Savestate-Daten:
-
+
Lade zusätzliche Daten:
-
+
Datei mit GB-BIOS:
-
+
Datei mit GBA-BIOS:
-
+
Datei mit GBC-BIOS:
@@ -4070,30 +4227,30 @@ wenn vorhanden
DS-Firmware:
-
+
Spielstände
-
-
-
-
+
+
+
+
Verzeichnis der ROM-Datei
-
+
Savestates
-
+
Screenshots
-
+
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
+
+
+
+
+ <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} desidera ringraziare i seguenti sponsor di Patreon:
+
+
+
+
+ © 2013 - 2016 Jeffrey Pfau, sotto licenza Mozilla Public License, versione 2.0
+Game Boy Advance è un marchio registrato di Nintendo Co., Ltd.
+
+
+
+
+ {patrons}
+
+
+
+
+ {projectVersion}
+
+
+
+
+ {logo}
+
+
+
+
+ {projectName} è un emulatore open-source del Game Boy Advance
+
+
+
+
+ Ramo Git: <tt>{gitBranch}</tt><br/>Revisione: <tt>{gitCommit}</tt>
+
+
+
+ ArchiveInspector
+
+
+
+ Apri il file in ...
+
+
+
+
+ Caricamento in corso...
+
+
+
+ AssetTile
+
+
+
+ AssetTile
+
+
+
+
+ Tile Nº
+
+
+
+
+ 0
+
+
+
+
+ Indirizzo
+
+
+
+
+ 0x06000000
+
+
+
+
+ Rosso
+
+
+
+
+ Verde
+
+
+
+
+ Blue
+
+
+
+
+
+
+ 0x00 (00)
+
+
+
+ CheatsView
+
+
+
+ Trucchi
+
+
+
+
+ Rimuovere
+
+
+
+
+ Salva
+
+
+
+
+ Carica
+
+
+
+
+ Aggiungere Nuovo Set
+
+
+
+
+ Aggiungi
+
+
+
+ DebuggerConsole
+
+
+
+ Debugger
+
+
+
+
+ Inserire un comando (premere `help` per maggiori informazioni)
+
+
+
+
+ Pausa
+
+
+
+ GIFView
+
+
+
+ Registra GIF
+
+
+
+
+ Avvia
+
+
+
+
+ Stop
+
+
+
+
+ Seleziona File
+
+
+
+
+ Salta Frame
+
+
+
+
+ Ritardo Frame (ms)
+
+
+
+
+ Automatico
+
+
+
+ IOViewer
+
+
+
+ Visualizzatore I/O
+
+
+
+
+ 0x0000
+
+
+
+
+ 2
+
+
+
+
+ 5
+
+
+
+
+ 4
+
+
+
+
+ 7
+
+
+
+
+ 0
+
+
+
+
+ 9
+
+
+
+
+ 1
+
+
+
+
+ 3
+
+
+
+
+ 8
+
+
+
+
+ C
+
+
+
+
+ E
+
+
+
+
+ 6
+
+
+
+
+ D
+
+
+
+
+ F
+
+
+
+
+ A
+
+
+
+
+ B
+
+
+
+ LibraryView
+
+
+
+ Biblioteca
+
+
+
+ LoadSaveState
+
+
+
+
+ %1 cattura stato
+
+
+
+
+
+
+
+
+
+
+
+
+ Senza Salvare
+
+
+
+
+ 1
+
+
+
+
+ 2
+
+
+
+
+ 3
+
+
+
+
+ 4
+
+
+
+
+ 5
+
+
+
+
+ 6
+
+
+
+
+ 7
+
+
+
+
+ 8
+
+
+
+
+ 9
+
+
+
+ LogView
+
+
+
+ Logs
+
+
+
+
+ Abilita Livelli
+
+
+
+
+ Debug
+
+
+
+
+ Stub
+
+
+
+
+ Informazioni
+
+
+
+
+ Avvertenze
+
+
+
+
+ Errore
+
+
+
+
+ Fatale
+
+
+
+
+ Errore del gioco
+
+
+
+
+ Pulisci
+
+
+
+
+ Linee di massima
+
+
+
+ MemoryView
+
+
+
+ Memoria
+
+
+
+
+ Ispezionare indirizzo:
+
+
+
+
+ 0x
+
+
+
+
+ Set di allignamento:
+
+
+
+
+ 1 byte
+
+
+
+
+ 2 bytes
+
+
+
+
+ 4 bytes
+
+
+
+
+ Integer Signato:
+
+
+
+
+ Stringa:
+
+
+
+
+ Carica TBL
+
+
+
+
+ Copia la selezione
+
+
+
+
+ Incolla
+
+
+
+
+ Salva Selezione
+
+
+
+
+ Carica
+
+
+
+
+ Integer non signato:
+
+
+
+ ObjView
+
+
+
+ Sprites
+
+
+
+
+
+ ×
+
+
+
+
+ Magnification
+
+
+
+
+ Attributi
+
+
+
+
+ Transformazione
+
+
+
+
+ No
+
+
+
+
+ Palette
+
+
+
+
+
+
+
+ 0
+
+
+
+
+ Doppia Dimensione
+
+
+
+
+
+
+
+ Return, Ctrl+R
+
+
+
+
+ Flippato
+
+
+
+
+ H
+
+
+
+
+ V
+
+
+
+
+ Modalità
+
+
+
+
+ Normale
+
+
+
+
+ Mosaico
+
+
+
+
+ Abilitato
+
+
+
+
+ Priorità
+
+
+
+
+ Tile
+
+
+
+
+ Geometria
+
+
+
+
+ Posizione
+
+
+
+
+ ,
+
+
+
+
+ Dimensione
+
+
+
+
+
+ 8
+
+
+
+
+ Indirizzo
+
+
+
+
+ 0x07000000
+
+
+
+ OverrideView
+
+
+
+ Valori specifici per gioco
+
+
+
+
+ Game Boy Advance
+
+
+
+
+
+
+
+ Rilevamento automatico
+
+
+
+
+ RealTime clock
+
+
+
+
+ Giroscopio
+
+
+
+
+ Inclinazione
+
+
+
+
+ Sensore di luce
+
+
+
+
+ Vibrazione
+
+
+
+
+ Tipo di salvataggio
+
+
+
+
+
+ Nessuno
+
+
+
+
+ SRAM
+
+
+
+
+ Flash 512kb
+
+
+
+
+ Flash 1Mb
+
+
+
+
+ EEPROM
+
+
+
+
+ Idle loop
+
+
+
+
+ Caratteristiche Game Boy Player
+
+
+
+
+ Game Boy
+
+
+
+
+ Modello del Game Boy
+
+
+
+
+ Game Boy (DMG)
+
+
+
+
+ Game Boy Color (CGB)
+
+
+
+
+ Game Boy Advance (AGB)
+
+
+
+
+ Controller del banco di memoria
+
+
+
+
+ MBC1
+
+
+
+
+ MBC2
+
+
+
+
+ MBC3
+
+
+
+
+ MBC3 + Reloj
+
+
+
+
+ MBC5
+
+
+
+
+ MBC5 + Vibrazione
+
+
+
+
+ MBC7
+
+
+
+
+ HuC-3
+
+
+
+ PaletteView
+
+
+
+ Palette
+
+
+
+
+ SFondo (BG)
+
+
+
+
+ Oggetti (OBJ)
+
+
+
+
+ Selezione
+
+
+
+
+ Rosso
+
+
+
+
+ Verde
+
+
+
+
+ Blue
+
+
+
+
+
+
+ 0x00 (00)
+
+
+
+
+ Valore in 16 bits
+
+
+
+
+ Codice esadecimale
+
+
+
+
+ Indice palette
+
+
+
+
+ 0x0000
+
+
+
+
+ #000000
+
+
+
+
+ 000
+
+
+
+
+ Esporta BG
+
+
+
+
+ Esporta OBJ
+
+
+
+ QGBA::AssetTile
+
+
+
+ %0%1%2
+
+
+
+
+
+
+ 0x%0 (%1)
+
+
+
+ QGBA::CheatsModel
+
+
+
+ (senza titolo)
+
+
+
+
+ Impossibile aprire il file cheats: %1
+
+
+
+ QGBA::CheatsView
+
+
+
+
+ Aggiungi GameShark
+
+
+
+
+ Aggiungi Pro Action Replay
+
+
+
+
+ Aggiungi CodeBreaker
+
+
+
+
+ Aggiungi GameGenie
+
+
+
+
+
+ Seleziona il file cheats
+
+
+
+ QGBA::GBAKeyEditor
+
+
+
+ Pulisci bottoni
+
+
+
+
+ Pulisci analogici
+
+
+
+
+ Aggiornare
+
+
+
+
+ Impostare tutti
+
+
+
+ QGBA::GDBWindow
+
+
+
+ Impostazioni Server
+
+
+
+
+ Porta locale
+
+
+
+
+ Indirizzo Bind
+
+
+
+
+ Break
+
+
+
+
+ Stop
+
+
+
+
+ Avvia
+
+
+
+
+ Errore
+
+
+
+
+ Impossibile avviare il server GDB
+
+
+
+ QGBA::GIFView
+
+
+
+ Impossibile aprire il file GIF di output: %1
+
+
+
+
+ Seleziona file di output
+
+
+
+
+ Formato di interconnessione grafica (*.gif)
+
+
+
+ QGBA::GameController
+
+
+
+
+ Impossibile aprire il file di gioco: %1
+
+
+
+
+ Impossibile aprire il file di salvataggio: %1
+
+
+
+
+ Impossibile aprire il file snapshot per la lettura: %1
+
+
+
+
+ Impossibile aprire il file snapshot per la scrittura: %1
+
+
+
+
+ Impossibile avviare il processore audio
+
+
+
+ QGBA::IOViewer
+
+
+
+ Modalità Background (BG)
+
+
+
+
+ Modalità 0: 4 tiles layers
+
+
+
+
+ Modalità 1: 2 tile layers + 1 ruotato/scalato tile layer
+
+
+
+
+ Modalità 2: 2 ruotato/scalato tile layers
+
+
+
+
+ Modalità 3: completo 15 bitmap
+
+
+
+
+ Modalità 4: completo 8 bits
+
+
+
+
+ Modalità 5: basso 15-bit bitmap
+
+
+
+
+ Modalità CGB
+
+
+
+
+ Seleziona Frame
+
+
+
+
+ HBlank sbloccato
+
+
+
+
+ Mappatura lineare tile OBJ
+
+
+
+
+ Forza schermo bianco
+
+
+
+
+ Abilitare sfondo 0
+
+
+
+
+ Abilitare sfondo 1
+
+
+
+
+ Abilitare sfondo 2
+
+
+
+
+ Abilitare sfondo 3
+
+
+
+
+ Abilitare OBJ
+
+
+
+
+ Abilitare Window 0
+
+
+
+
+ Abilitare Window 1
+
+
+
+
+ Abilitare Window OBJ
+
+
+
+
+ Attualmente in VBlank
+
+
+
+
+ Attualmente in HBlank
+
+
+
+
+ Attualmente in VCounter
+
+
+
+
+ Abilita VBlank
+
+
+
+
+ Abilita HBlank generazione IRQ
+
+
+
+
+ Abilita generazione IRQ VCounter
+
+
+
+
+ VCounter scanline
+
+
+
+
+ Scanline corrente
+
+
+
+
+
+
+
+ Priorità
+
+
+
+
+
+
+
+ Tile data base (* 16kB)
+
+
+
+
+
+
+
+ Abilita Mosaico
+
+
+
+
+
+
+
+ Abilita 256 colori
+
+
+
+
+
+
+
+ Tile map base (* 2kB)
+
+
+
+
+
+
+
+ Dimensioni dello sfondo
+
+
+
+
+
+ Overflow wraps
+
+
+
+
+
+
+
+ Compensazione orizzontale
+
+
+
+
+
+
+
+ Compensazione verticale
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parte frazionale
+
+
+
+
+
+
+
+
+
+
+
+ Parte Integer
+
+
+
+
+
+
+
+ Parte Integer (inferiore)
+
+
+
+
+
+
+
+ Parte Integer (superiore)
+
+
+
+
+
+ Fine x
+
+
+
+
+
+ Inizio x
+
+
+
+
+
+ Fine y
+
+
+
+
+
+ Inizio y
+
+
+
+
+ Window 0 BG 0
+
+
+
+
+ Window 0 BG 1
+
+
+
+
+ Window 0 BG 2
+
+
+
+
+ Window 0 BG 3
+
+
+
+
+ Window 0 OBJ
+
+
+
+
+ Abilita Window 0 miscelato
+
+
+
+
+ Abilita Window 1 BG 0
+
+
+
+
+ Abilita Window 1 BG 1
+
+
+
+
+ Abilita Window 1 BG 2
+
+
+
+
+ AbilitaWindow 1 BG 3
+
+
+
+
+ Abilita Window 1 OBJ
+
+
+
+
+ Abilita Window 1 miscelato
+
+
+
+
+ Abilita Outside window BG 0
+
+
+
+
+ Abilita Outside window BG 1
+
+
+
+
+ Abilita Outside window BG 2
+
+
+
+
+ Abilita Outside window BG 3
+
+
+
+
+ Abilita Outside window OBJ
+
+
+
+
+ Outside window mezcla
+
+
+
+
+ Abilita OBJ window BG 0
+
+
+
+
+ Abilita OBJ window BG 1
+
+
+
+
+ Abilita OBJ window BG 2
+
+
+
+
+ Abilita OBJ window BG 3
+
+
+
+
+ Abilita OBJ window OBJ
+
+
+
+
+ Abilita OBJ window blend
+
+
+
+
+ Sfondo mosaico verticale
+
+
+
+
+ Sfondo mosaico orizzontale
+
+
+
+
+ Sfondo mosaico oggetto verticale
+
+
+
+
+ Sfondo mosaico oggetto orizzontale
+
+
+
+
+ BG 0 target 1
+
+
+
+
+ BG 1 target 1
+
+
+
+
+ BG 2 target 1
+
+
+
+
+ BG 3 target 1
+
+
+
+
+ OBJ target 1
+
+
+
+
+ Backdrop target 1
+
+
+
+
+ Modalità miscelato
+
+
+
+
+ Disabilitato
+
+
+
+
+ Miscelazione dell'additivo
+
+
+
+
+ Schiarire
+
+
+
+
+ Oscurire
+
+
+
+
+ BG 0 target 2
+
+
+
+
+ BG 1 target 2
+
+
+
+
+ BG 2 target 2
+
+
+
+
+ BG 3 target 2
+
+
+
+
+ OBJ target 2
+
+
+
+
+ Backdrop target 2
+
+
+
+
+ Miscelato A (target 1)
+
+
+
+
+ Miscelato B (target 2)
+
+
+
+
+ Miscelato Y
+
+
+
+
+ Sweep shifts
+
+
+
+
+ Sposta subastratto
+
+
+
+
+ Tempo di sweep (in 1/128seg)
+
+
+
+
+
+
+
+ Lunghezza del suono
+
+
+
+
+
+ Ciclo di lavoro
+
+
+
+
+
+
+ Envelope step time
+
+
+
+
+
+
+ Aumento envelope
+
+
+
+
+
+
+ Volume iniziale
+
+
+
+
+
+
+ Frequenza del suono
+
+
+
+
+
+
+
+ Temporizzato
+
+
+
+
+
+
+
+ Reset
+
+
+
+
+ Tavola delle onde a doppia dimensione
+
+
+
+
+ Tavola delle onde attive
+
+
+
+
+ Canale 3 attivo
+
+
+
+
+ Volume
+
+
+
+
+ 0%
+
+
+
+
+
+ 100%
+
+
+
+
+
+ 50%
+
+
+
+
+
+ 25%
+
+
+
+
+
+
+
+ 75%
+
+
+
+
+ Divisore dell'orologio
+
+
+
+
+ Stadi di registrazione
+
+
+
+
+ 15
+
+
+
+
+ 7
+
+
+
+
+ Cambio di frequenza
+
+
+
+
+ PSG volume destro
+
+
+
+
+ PSG volume sinistro
+
+
+
+
+ Abilita Canale 1 destro
+
+
+
+
+ Abilita Canale 2 destro
+
+
+
+
+ Abilita Canale 3 destro
+
+
+
+
+ Abilita Canale 4 destro
+
+
+
+
+ Abilita Canale 1 sinistro
+
+
+
+
+ Abilita Canale 2 sinistro
+
+
+
+
+ Abilita Canale 3 sinistro
+
+
+
+
+ Abilita Canale 4 sinistro
+
+
+
+
+ PSG volume master
+
+
+
+
+ Canale A forte
+
+
+
+
+ Canale B forte
+
+
+
+
+ Abilita Canale A destro
+
+
+
+
+ Abilta Canale A sinistro
+
+
+
+
+ Canale A temporizzato
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ Resetta canale A
+
+
+
+
+ Abilita Canale B destro
+
+
+
+
+ Abilita Canale B sinistro
+
+
+
+
+ Canale B temporizzato
+
+
+
+
+ Resetta canale B
+
+
+
+
+ Canale 1 attivo
+
+
+
+
+ Canale 2 attivo
+
+
+
+
+ Canale 3 attivo
+
+
+
+
+ Canale 4 attivo
+
+
+
+
+ Abilitare audio
+
+
+
+
+ Polarizzazione
+
+
+
+
+ Risoluzione
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mostra
+
+
+
+
+
+
+
+
+
+
+
+ Indirizzo (inferiore)
+
+
+
+
+
+
+
+
+
+
+
+ Indirizzo (superiore)
+
+
+
+
+
+
+
+ Contatore di parole
+
+
+
+
+
+
+
+ Compensazione offset
+
+
+
+
+
+
+
+
+
+
+
+ Incremento
+
+
+
+
+
+
+
+
+
+
+
+ Decremento
+
+
+
+
+
+
+
+
+
+
+
+ Fissato
+
+
+
+
+
+
+
+ Incremento e ricarica
+
+
+
+
+
+
+
+ Compensazione di origine
+
+
+
+
+
+
+
+ Ripeti
+
+
+
+
+
+
+
+ 32 bit
+
+
+
+
+
+
+
+ Avvia temporizzatore
+
+
+
+
+
+
+
+ Immediato
+
+
+
+
+
+
+
+
+
+ VBlank
+
+
+
+
+
+
+
+
+
+ HBlank
+
+
+
+
+
+
+
+
+
+
+
+
+ IRQ
+
+
+
+
+
+
+
+
+
+
+
+ Abilitare
+
+
+
+
+
+
+ Audio FIFO
+
+
+
+
+ Cattura video
+
+
+
+
+ DRQ
+
+
+
+
+
+
+
+ Valore
+
+
+
+
+
+
+
+ Scala
+
+
+
+
+
+
+
+ 1/64
+
+
+
+
+
+
+
+ 1/256
+
+
+
+
+
+
+
+ 1/1024
+
+
+
+
+
+
+ Cascata
+
+
+
+
+
+ A
+
+
+
+
+
+ B
+
+
+
+
+
+ Seleziona
+
+
+
+
+
+ Avvia
+
+
+
+
+
+ Destro
+
+
+
+
+
+ Sinistro
+
+
+
+
+
+ Sù
+
+
+
+
+
+ Giù
+
+
+
+
+
+ R
+
+
+
+
+
+ L
+
+
+
+
+ Condizione
+
+
+
+
+ SC
+
+
+
+
+ SD
+
+
+
+
+ SI
+
+
+
+
+ SO
+
+
+
+
+
+ VCounter
+
+
+
+
+
+ Timer 0
+
+
+
+
+
+ Timer 1
+
+
+
+
+
+ Timer 2
+
+
+
+
+
+ Timer 3
+
+
+
+
+
+ SIO
+
+
+
+
+
+ DMA 0
+
+
+
+
+
+ DMA 1
+
+
+
+
+
+ DMA 2
+
+
+
+
+
+ DMA 3
+
+
+
+
+
+ Tastiera
+
+
+
+
+
+ Gamepak
+
+
+
+
+ Attesa SRAM
+
+
+
+
+
+
+
+
+ 4
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+ 8
+
+
+
+
+ Cart 0 non sequenziale
+
+
+
+
+ Cart 0 sequenziale
+
+
+
+
+ Cart 1 non sequenziale
+
+
+
+
+ Cart 1 sequenziale
+
+
+
+
+ Cart 2 non sequenziale
+
+
+
+
+ Cart 2 sequenziale
+
+
+
+
+ Terminale PHI
+
+
+
+
+ Disabilita
+
+
+
+
+ 4.19MHz
+
+
+
+
+ 8.38MHz
+
+
+
+
+ 16.78MHz
+
+
+
+
+ Gamepak prefetch
+
+
+
+
+ Abilitare IRQs
+
+
+
+ QGBA::KeyEditor
+
+
+
+
+ ---
+
+
+
+ QGBA::LibraryModel
+
+
+
+ Nome
+
+
+
+
+ Nome del file
+
+
+
+
+ Dimensione
+
+
+
+
+ Piattaforma
+
+
+
+
+ GBA
+
+
+
+
+ GB
+
+
+
+
+ ?
+
+
+
+
+ Posizione
+
+
+
+
+ CRC32
+
+
+
+ QGBA::LoadSaveState
+
+
+
+ Carica stato
+
+
+
+
+ Salva stato
+
+
+
+
+ Vuoto
+
+
+
+
+ Corrotto
+
+
+
+
+ Slot %1
+
+
+
+ QGBA::LogController
+
+
+
+ DEBUG
+
+
+
+
+ STUB
+
+
+
+
+ INFORMAZIONI
+
+
+
+
+ AVVERTENZA
+
+
+
+
+ ERRORE
+
+
+
+
+ FATALE
+
+
+
+
+ ERRORE NEL GIOCO
+
+
+
+ QGBA::MemoryModel
+
+
+
+ Copia selezionato
+
+
+
+
+ Salva selezionato
+
+
+
+
+ Incolla
+
+
+
+
+ Carica
+
+
+
+
+
+ Tutto
+
+
+
+
+ Carica TBL
+
+
+
+
+ Salva la memoria selezionate
+
+
+
+
+ Impossibile aprire il file di output: %1
+
+
+
+
+ Carica memoria
+
+
+
+
+ Impossibile aprire il file di input: %1
+
+
+
+
+ TBL
+
+
+
+
+ ISO-8859-1
+
+
+
+ QGBA::ObjView
+
+
+
+
+ 0x%0
+
+
+
+
+ No
+
+
+
+
+ Normale
+
+
+
+
+ Trans
+
+
+
+
+ OBJWIN
+
+
+
+
+ Invalido
+
+
+
+
+
+ n/d
+
+
+
+ QGBA::PaletteView
+
+
+
+ #%0
+
+
+
+
+ 0x%0
+
+
+
+
+ %0
+
+
+
+
+
+
+ 0x%0 (%1)
+
+
+
+
+ Esporta palette
+
+
+
+
+ WIndows PAL (*.pal);;Tabella dei colori Adobe (*.act)
+
+
+
+
+ Errore nell'aprire il file palette di output : %1
+
+
+
+ QGBA::ROMInfo
+
+
+
+
+
+
+
+ (sconosciuto)
+
+
+
+
+
+ bytes
+
+
+
+
+ (nessun database presente)
+
+
+
+ QGBA::SettingsView
+
+
+
+ Qt Multimedia
+
+
+
+
+ SDL
+
+
+
+
+ Software (Qt)
+
+
+
+
+ OpenGL
+
+
+
+
+ OpenGL (forza la versione 1.x)
+
+
+
+
+ Tastiera
+
+
+
+
+ Controllers
+
+
+
+
+ Tasti di scelta rapida
+
+
+
+
+ Seleziona BIOS
+
+
+
+ QGBA::ShaderSelector
+
+
+
+ Nessun shader attivo
+
+
+
+
+ Carica shader
+
+
+
+
+ %1 Shader (%.shader)
+
+
+
+
+ Nessun shader caricato
+
+
+
+
+ por %1
+
+
+
+
+ Preprocesso
+
+
+
+
+ Pass %1
+
+
+
+ QGBA::ShortcutController
+
+
+
+ Azione
+
+
+
+
+ Tastiera
+
+
+
+
+ Gamepad
+
+
+
+ QGBA::VideoView
+
+
+
+ Errore durante l'archiviazione del video: %1
+
+
+
+
+ Nativo (%0x%1)
+
+
+
+
+ Seleziona file di output
+
+
+
+ QGBA::Window
+
+
+
+ ROM di Game Boy Advance (%1)
+
+
+
+
+ ROMs del Game Boy (%1)
+
+
+
+
+ Tutte le ROM (%1)
+
+
+
+
+ Archivio (%1)
+
+
+
+
+
+
+ Seleziona ROM
+
+
+
+
+ Game Boy Advance file di salvataggio (%1)
+
+
+
+
+
+
+ Seleziona salvataggio
+
+
+
+
+ Seleziona patch
+
+
+
+
+ Patches (*.ips *.ups *.bps)
+
+
+
+
+
+ Salvataggi GameShark (*.sps *.xps)
+
+
+
+
+ Errore fatale
+
+
+
+
+ Il gioco è andato in crash con il seguente errore::
+
+%1
+
+
+
+
+ Non è possibile caricare
+
+
+
+
+ Impossibile caricare il gioco. Sei sicuro che sia nel formato corretto?
+
+
+
+
+ BIOS non implementato
+
+
+
+
+ Questo gioco utilizza una chiamata BIOS non implementata. Utilizza il BIOS ufficiale per una migliore esperienza
+
+
+
+
+ Davvero rendere portatile?
+
+
+
+
+ In questo modo l'emulatore carica la propria configurazione dalla stessa directory dell'eseguibile. Vuoi continuare?
+
+
+
+
+ È necessario riavviare
+
+
+
+
+ Alcune modifiche non avranno effetto finché l'emulatore non viene riavviato.
+
+
+
+
+ - Giocatore %1 di %2
+
+
+
+
+ %1 - %2
+
+
+
+
+ %1 - %2 - %3
+
+
+
+
+ %1 - %2 (%3 fps) - %4
+
+
+
+
+ &File
+
+
+
+
+ Carica &ROM...
+
+
+
+
+ Carica la ROM in archivio...
+
+
+
+
+ Carica il salvataggio temporaneo..
+
+
+
+
+ Carica &patch...
+
+
+
+
+ Boot BIOS
+
+
+
+
+ Sostituire la ROM...
+
+
+
+
+ ROM &info...
+
+
+
+
+ Recente
+
+
+
+
+ Rendi portatile
+
+
+
+
+ &Carica stato
+
+
+
+
+ F10
+
+
+
+
+ &Salva stato
+
+
+
+
+ DO NOT TRANSLATE
+ Shift+F10
+
+
+
+
+ Caricamento rapido
+
+
+
+
+ Salvataggio rapido
+
+
+
+
+ Carica recente
+
+
+
+
+ Salva recente
+
+
+
+
+ Annulla il caricamento dello stato
+
+
+
+
+ DO NOT TRANSLATE
+ F11
+
+
+
+
+ Annulla salva stato
+
+
+
+
+ DO NOT TRANSLATE
+ Shift+F11
+
+
+
+
+
+ Stato &%1
+
+
+
+
+ F%1
+
+
+
+
+ DO NOT TRANSLATE
+ Shift+F%1
+
+
+
+
+ Importa il salvataggio del GameShark
+
+
+
+
+ Esporta salvataggio dal GameShark
+
+
+
+
+ Nuova finestra multigiocatore
+
+
+
+
+ About
+
+
+
+
+ Uscire (&X)
+
+
+
+
+ &Emulazione
+
+
+
+
+ &Reset
+
+
+
+
+ Ctrl+R
+
+
+
+
+ Spegni (&U)
+
+
+
+
+ Yank game pak
+
+
+
+
+ &Pausa
+
+
+
+
+ Ctrl+P
+
+
+
+
+ Salta il prossimo frame (&N)
+
+
+
+
+ Ctrl+N
+
+
+
+
+ Avanzamento rapido (sostenuto)
+
+
+
+
+ Avanzamento rapido (&F)
+
+
+
+
+ Shift+Tab
+
+
+
+
+ Velocità di avanzamento rapido
+
+
+
+
+ Illimitato
+
+
+
+
+ %0x
+
+
+
+
+ Riavvolgi (sostenuto)
+
+
+
+
+ Riavvolgi (&W)
+
+
+
+
+ ~
+
+
+
+
+ Torna indietro
+
+
+
+
+ Ctrl+B
+
+
+
+
+ Sincronizzare su &video
+
+
+
+
+ Sincronizzare su &audio
+
+
+
+
+ Sensore solare
+
+
+
+
+ Aumenta il livello solare
+
+
+
+
+ Riduce il livello solare
+
+
+
+
+ Livello solare brillante
+
+
+
+
+ Livello solare più scuro
+
+
+
+
+ Luminosità %1
+
+
+
+
+ Audio/&Video
+
+
+
+
+ Dimensione Frame
+
+
+
+
+ %1x
+
+
+
+
+ Abilita schermo intero
+
+
+
+
+ Blocca aspect ratio
+
+
+
+
+ Rimostra video
+
+
+
+
+ Frame&skip
+
+
+
+
+ Opzioni shader...
+
+
+
+
+ Muto
+
+
+
+
+ FPS mirato
+
+
+
+
+ 15
+
+
+
+
+ 30
+
+
+
+
+ 45
+
+
+
+
+ Nativo (59.7)
+
+
+
+
+ 60
+
+
+
+
+ 90
+
+
+
+
+ 120
+
+
+
+
+ 240
+
+
+
+
+ Effettua &screenshot
+
+
+
+
+ F12
+
+
+
+
+ Registra salida...
+
+
+
+
+ Registra GIF...
+
+
+
+
+ Layers video
+
+
+
+
+ Sfondo %0
+
+
+
+
+ OBJ (sprites)
+
+
+
+
+ Canali audio
+
+
+
+
+ Canale %0
+
+
+
+
+ Canale A
+
+
+
+
+ Canale B
+
+
+
+
+ &Strumenti
+
+
+
+
+ Visualizza registri... (&L)
+
+
+
+
+ Val&specifico per il gioco...
+
+
+
+
+ Sensori di gioco &Pak...
+
+
+
+
+ &Trucchi...
+
+
+
+
+ Apri debugger console...
+
+
+
+
+ Avvia server &GDB...
+
+
+
+
+ Impostazioni...
+
+
+
+
+ Seleziona cartella
+
+
+
+
+ Aggiungi cartella alla libreria...
+
+
+
+
+ Ver &palette...
+
+
+
+
+ Ver &sprites...
+
+
+
+
+ Ver &tiles...
+
+
+
+
+ Ver memoria...
+
+
+
+
+ Ver reg®istri I/O...
+
+
+
+
+ Esci da schermo intero
+
+
+
+
+ Pulsanti Auto fuoco
+
+
+
+
+ Auto fuoco A
+
+
+
+
+ Auto fuoco B
+
+
+
+
+ Auto fuoco L
+
+
+
+
+ Auto fuoco R
+
+
+
+
+ Avvia Auto fuoco
+
+
+
+
+ Seleziona Auto fuoco
+
+
+
+
+ Auto fuoco sù
+
+
+
+
+ Auto fuoco destro
+
+
+
+
+ Auto fuoco giù
+
+
+
+
+ Auto fuoco sinistro
+
+
+
+ ROMInfo
+
+
+
+ Informazioni della ROM
+
+
+
+
+ Nome del gioco:
+
+
+
+
+ {NAME}
+
+
+
+
+ Nome interno:
+
+
+
+
+ {TITLE}
+
+
+
+
+ ID del gioco:
+
+
+
+
+ {ID}
+
+
+
+
+ Dimensione del file:
+
+
+
+
+ {SIZE}
+
+
+
+
+ CRC32:
+
+
+
+
+ {CRC}
+
+
+
+ SensorView
+
+
+
+ Sensori
+
+
+
+
+ Realtime clock
+
+
+
+
+ Ora fissa
+
+
+
+
+ Ora del sistema
+
+
+
+
+ Avvia tempo a
+
+
+
+
+ Adesso
+
+
+
+
+ dd/MM/yy HH:mm:ss
+
+
+
+
+ Sensore di luce
+
+
+
+
+ Luminosità
+
+
+
+
+ Sensore di inclinazione
+
+
+
+
+
+ Config. Y
+
+
+
+
+
+ Config. X
+
+
+
+
+ Giroscopio
+
+
+
+
+ Sensibilità
+
+
+
+ SettingsView
+
+
+
+ Impostazioni
+
+
+
+
+ Audio/Video
+
+
+
+
+ Interfaccia
+
+
+
+
+ Emulazione
+
+
+
+
+ Paths
+
+
+
+
+ Audio driver:
+
+
+
+
+ Buffer audio:
+
+
+
+
+
+ 1536
+
+
+
+
+ 512
+
+
+
+
+ 768
+
+
+
+
+ 1024
+
+
+
+
+ 2048
+
+
+
+
+ 3072
+
+
+
+
+ 4096
+
+
+
+
+ samples
+
+
+
+
+ Sample rate:
+
+
+
+
+
+ 44100
+
+
+
+
+ 22050
+
+
+
+
+ 32000
+
+
+
+
+ 48000
+
+
+
+
+ Hz
+
+
+
+
+ Volume:
+
+
+
+
+ Muto
+
+
+
+
+ Visualizza driver:
+
+
+
+
+ Frameskip:
+
+
+
+
+ Salta ognuno
+
+
+
+
+
+ frames
+
+
+
+
+ FPS mirato:
+
+
+
+
+ frame per secondo
+
+
+
+
+ Sincronizzare:
+
+
+
+
+ Video
+
+
+
+
+ Audio
+
+
+
+
+ Blocca aspect ratio
+
+
+
+
+ Rimostrare video
+
+
+
+
+ Biblioteca:
+
+
+
+
+ Mostra quando nessun gioco è aperto
+
+
+
+
+ Cancella cache
+
+
+
+
+ Velocità di avanzamento rapido:
+
+
+
+
+
+
+
+
+
+
+ Sfoglia
+
+
+
+
+ Utilizzare il file del BIOS se è stato trovato
+
+
+
+
+ Salta BIOS intro
+
+
+
+
+ ×
+
+
+
+
+ Illimitato
+
+
+
+
+ Sospendi screensaver
+
+
+
+
+ BIOS
+
+
+
+
+ Pausa se inattivo
+
+
+
+
+ Avviare tutto
+
+
+
+
+ Rimuovi conosciuto
+
+
+
+
+ Rileva e rimuovi
+
+
+
+
+ Consenti direzioni opposte
+
+
+
+
+
+ Screenshot
+
+
+
+
+
+ Salva dati
+
+
+
+
+
+ Trucchi
+
+
+
+
+ Abilita riavvolgi
+
+
+
+
+ Riavvolgi storia:
+
+
+
+
+ Idle loops:
+
+
+
+
+ Salva dati extra:
+
+
+
+
+ Carica dati extra:
+
+
+
+
+ File GB BIOS:
+
+
+
+
+ File GBA BIOS:
+
+
+
+
+ File GBC BIOS:
+
+
+
+
+ Salva il gioco
+
+
+
+
+
+
+
+ Stessa directory della ROM
+
+
+
+
+ Salva Stato
+
+
+
+
+ Screenshots
+
+
+
+
+ Patches
+
+
+
+ ShaderSelector
+
+
+
+ Shaders
+
+
+
+
+ Attiva Shader:
+
+
+
+
+ Nome
+
+
+
+
+ Autore
+
+
+
+
+ Descrizione
+
+
+
+
+ Non caricare shader
+
+
+
+
+ Carica Nuovo shader
+
+
+
+ ShortcutView
+
+
+
+ Edita Shortcuts
+
+
+
+
+ Tastiera
+
+
+
+
+ Gamepad
+
+
+
+
+ Cancella
+
+
+
+ TileView
+
+
+
+ Tiles
+
+
+
+
+ 256 colori
+
+
+
+
+ ×
+
+
+
+
+ Magnification
+
+
+
+ VideoView
+
+
+
+ Registra video
+
+
+
+
+ Avvia
+
+
+
+
+ Stop
+
+
+
+
+ Seleziona File
+
+
+
+
+ Presets
+
+
+
+
+ Alta Qualità
+
+
+
+
+ YouTube
+
+
+
+
+
+ WebM
+
+
+
+
+ Senza perdite
+
+
+
+
+ 1080p
+
+
+
+
+ 720p
+
+
+
+
+ 480p
+
+
+
+
+ Nativo
+
+
+
+
+ Formato
+
+
+
+
+ MKV
+
+
+
+
+ AVI
+
+
+
+
+ MP4
+
+
+
+
+ PNG
+
+
+
+
+ h.264
+
+
+
+
+
+
+
+
+
+ Xvid
+
+
+
+
+ FFV1
+
+
+
+
+ FLAC
+
+
+
+
+ Opus
+
+
+
+
+ Vorbis
+
+
+
+
+ MP3
+
+
+
+
+ AAC
+
+
+
+
+ Senza compressione
+
+
+
+
+ Bitrate (kbps)
+
+
+
+
+ VBR
+
+
+
+
+ ABR
+
+
+
+
+ Dimensioni
+
+
+
+
+ :
+
+
+
+
+ ×
+
+
+
+
+ Blocca aspect ratio
+
+
+
+
+ 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;