Merge branch 'feature/input-revamp' into medusa

This commit is contained in:
Vicki Pfau 2017-07-06 16:07:45 -07:00
commit 09cff0779e
111 changed files with 7652 additions and 2425 deletions

42
CHANGES
View File

@ -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:

View File

@ -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}")

View File

@ -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
<a name="flashdetect">[3]</a> 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.
<a name="osxver">[4]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
<a name="osxver">[3]</a> 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/

View File

@ -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 {

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -16,6 +16,8 @@ struct GBCartridgeOverride {
int headerCrc32;
enum GBModel model;
enum GBMemoryBankControllerType mbc;
uint32_t gbColors[4];
};
struct Configuration;

View File

@ -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);

View File

@ -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];

View File

@ -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

View File

@ -3,6 +3,7 @@ Fog
Reilly Grant
Philip Horton
Jordan Jorgensen
mars
Rohit Nirmal
Rhys Powell
rootfather

View File

@ -7,5 +7,5 @@ passes=1
[pass.0]
fragmentShader=agb001.fs
blend=1
width=960
height=640
width=-4
height=-4

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -7,5 +7,5 @@ passes=1
[pass.0]
fragmentShader=wiiu.fs
blend=1
width=960
height=640
width=-4
height=-4

View File

@ -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;
}

View File

@ -26,7 +26,7 @@
#include <mgba/feature/video-logger.h>
#endif
const static struct mCoreFilter {
static const struct mCoreFilter {
bool (*filter)(struct VFile*);
struct mCore* (*open)(void);
enum mPlatform platform;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -8,42 +8,58 @@
#include <mgba/internal/arm/arm.h>
#include <mgba/internal/ds/ds.h>
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);
}

View File

@ -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[] = {

View File

@ -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) {

View File

@ -29,26 +29,26 @@
#include <mgba/internal/gba/input.h>
#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 },

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -5,12 +5,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gb/timer.h>
#include <mgba/internal/lr35902/lr35902.h>
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/io.h>
#include <mgba/internal/gb/serialize.h>
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;
}

View File

@ -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) {

View File

@ -28,7 +28,7 @@
#include <mgba/internal/gba/input.h>
#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 },

View File

@ -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);

View File

@ -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;

View File

@ -8,6 +8,58 @@
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
#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);
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -30,6 +30,7 @@ void free(void*);
#include <mgba/core/core.h>
#include <mgba/core/mem-search.h>
#include <mgba/core/tile-cache.h>
#include <mgba/core/version.h>
#define PYEXPORT extern "Python+C"
#include "platform/python/log.h"

View File

@ -23,6 +23,7 @@ ffi.set_source("mgba._pylib", """
#include <mgba/core/log.h>
#include <mgba/core/mem-search.h>
#include <mgba/core/tile-cache.h>
#include <mgba/core/version.h>
#include <mgba/internal/arm/arm.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/input.h>

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -83,7 +83,7 @@
</font>
</property>
<property name="text">
<string>© 2013 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
<string>© 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.</string>
</property>
<property name="alignment">

View File

@ -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)

View File

@ -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 <QFileInfo>
#include <QFileOpenEvent>
#include <QIcon>
#include <QTranslator>
#include <mgba/core/version.h>
#include <mgba/internal/gba/video.h>
#include <mgba-util/socket.h>
#include <mgba-util/vfs.h>
@ -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*>("const uint32_t*");
qRegisterMetaType<mCoreThread*>("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<Display::Driver>(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<Display::Driver>(m_configController->getQtOption("displayDriver").toInt()));
}
reloadGameDB();
if (!m_configController.getQtOption("audioDriver").isNull()) {
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
if (!m_configController->getQtOption("audioDriver").isNull()) {
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(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<Window*>& paused) {
QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
QList<Window*> 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<Window*> 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<Window*> 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;
}

View File

@ -10,7 +10,6 @@
#include <QFileDialog>
#include <QThread>
#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<Window*>* paused);
void continueAll(const QList<Window*>& paused);
ConfigController m_configController;
ConfigController* m_configController;
QList<Window*> m_windows;
MultiplayerController m_multiplayer;

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GBAKeyEditor.h"
#include <QApplication>
#include <QComboBox>
#include <QHBoxLayout>
#include <QPaintEvent>
@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) {
}
bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
KeyEditor* keyEditor = static_cast<KeyEditor*>(obj);
if (event->type() == QEvent::FocusOut) {
keyEditor->setPalette(QApplication::palette(keyEditor));
}
if (event->type() != QEvent::FocusIn) {
return false;
}
findFocus(static_cast<KeyEditor*>(obj));
QPalette palette = keyEditor->palette();
palette.setBrush(keyEditor->backgroundRole(), palette.highlight());
palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText());
keyEditor->setPalette(palette);
findFocus(keyEditor);
return true;
}

View File

@ -9,6 +9,7 @@
#include "InputController.h"
#include "LogController.h"
#include "MultiplayerController.h"
#include "Override.h"
#include "VFileDevice.h"
#include <QCoreApplication>
@ -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;
}
}

View File

@ -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 <QMenu>
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;
}

View File

@ -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 <functional>
#include <QAction>
#include <mgba/core/core.h>
namespace QGBA {
class InputItem {
public:
typedef QPair<std::function<void ()>, std::function<void ()>> 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<InputItem>& items() { return m_items; }
const QList<InputItem>& 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<InputItem> m_items;
InputItem* m_parent;
};
}
#endif

View File

@ -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 <QAction>
#include <QKeyEvent>
#include <QMenu>
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<const InputItem*>(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<InputItem*>(parent.internalPointer());
}
return createIndex(row, column, const_cast<InputItem*>(&pmenu->items()[row]));
}
QModelIndex InputModel::parent(const QModelIndex& index) const {
if (!index.isValid() || !index.internalPointer()) {
return QModelIndex();
}
InputItem* item = static_cast<InputItem*>(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<const InputItem*>(index.internalPointer());
return item->items().count();
}
InputItem* InputModel::add(QMenu* menu, std::function<void (InputItem*)> 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<void()> press, std::function<void()> 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<void()> press, std::function<void()> 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<InputItem*>(index.internalPointer());
}
if (!index.parent().isValid()) {
return nullptr;
}
InputItem* pmenu = static_cast<InputItem*>(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<InputItem*>(index.internalPointer());
}
if (!index.parent().isValid()) {
return nullptr;
}
InputItem* pmenu = static_cast<InputItem*>(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<void(InputItem*)> 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;
}

View File

@ -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 <mgba/core/core.h>
#include "GamepadAxisEvent.h"
#include "InputItem.h"
#include <QAbstractItemModel>
#include <functional>
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<void (QMenu*, int, bool)> 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<void()> press, std::function<void()> release,
int shortcut, const QString& visibleName, const QString& name);
void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> 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<void (InputItem*)>);
InputItem* itemAt(const QModelIndex& index);
const InputItem* itemAt(const QModelIndex& index) const;
bool loadShortcuts(InputItem*);
void loadGamepadShortcuts(InputItem*);
void onSubitems(InputItem*, std::function<void(InputItem*)> func);
void updateKey(InputItem* item, int keySequence);
QModelIndex index(InputItem* item) const;
InputItem m_rootMenu;
QMap<QMenu*, InputItem*> m_menuMap;
QMap<int, InputItem*> m_buttons;
QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes;
QMap<int, InputItem*> m_heldKeys;
QMap<QPair<mPlatform, int>, InputItem*> m_keys;
ConfigController* m_config;
std::function<void (QMenu*, int key, bool down)> m_keyCallback;
QString m_profileName;
const InputProfile* m_profile;
};
}
#endif

View File

@ -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 <QRegExp>
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<int> keys,
const Shortcuts<int> shortcutButtons,
const Shortcuts<Axis> shortcutAxes,
const KeyList<AxisValue> 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<GBAKey>(i));
controller->bindAxis(platform, SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(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;
}

View File

@ -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 <mgba/core/core.h>
#include <mgba/gba/interface.h>
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 <typename T> struct Shortcuts {
T loadState;
T saveState;
T holdFastForward;
T holdRewind;
};
struct Axis {
GamepadAxisEvent::Direction direction;
int axis;
};
template <typename T> 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<int> keys,
const Shortcuts<int> shortcutButtons = { -1, -1, -1, -1},
const Shortcuts<Axis> shortcutAxes = {
{GamepadAxisEvent::Direction::NEUTRAL, -1},
{GamepadAxisEvent::Direction::NEUTRAL, -1},
{GamepadAxisEvent::Direction::NEUTRAL, -1},
{GamepadAxisEvent::Direction::NEUTRAL, -1}},
const KeyList<AxisValue> 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<int> m_shortcutButtons;
const Shortcuts<Axis> m_shortcutAxes;
Coord m_tiltAxis;
Coord m_gyroAxis;
float m_gyroSensitivity;
};
}
#endif

View File

@ -7,7 +7,7 @@
#include "GamepadAxisEvent.h"
#include "GamepadButtonEvent.h"
#include "InputModel.h"
#include "InputIndex.h"
#include <QFontMetrics>
#include <QKeyEvent>
@ -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);

View File

@ -9,6 +9,7 @@
#include <mgba/core/core.h>
#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();
}

View File

@ -28,6 +28,9 @@ public slots:
void search();
void searchWithin();
private slots:
void openMemory();
private:
bool createParams(mCoreMemorySearchParams*);

View File

@ -17,7 +17,7 @@
</size>
</property>
<property name="windowTitle">
<string>Form</string>
<string>Memory Search</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
@ -28,6 +28,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
@ -194,11 +197,8 @@
</item>
<item>
<widget class="QPushButton" name="viewMem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>View in Memory View</string>
<string>Open in Memory Viewer</string>
</property>
</widget>
</item>
@ -214,10 +214,26 @@
</layout>
</widget>
<resources/>
<connections/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MemorySearch</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>315</x>
<y>357</y>
</hint>
<hint type="destinationlabel">
<x>315</x>
<y>188</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="type"/>
<buttongroup name="width"/>
<buttongroup name="numType"/>
<buttongroup name="type"/>
</buttongroups>
</ui>

View File

@ -22,6 +22,7 @@ public:
public slots:
void update();
void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); }
private slots:
void setIndex(int);

View File

@ -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<const GBA*>(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<const GB*>(m_controller->thread()->core->board);
const GBObj* obj = &gb->video.oam.obj[m_objId];

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "OverrideView.h"
#include <QColorDialog>
#include <QPushButton>
#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<QWidget*>(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();

View File

@ -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<enum GBModel> s_gbModelList;
static QList<enum GBMemoryBankControllerType> s_mbcList;
#endif

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>443</width>
<height>282</height>
<width>444</width>
<height>284</height>
</rect>
</property>
<property name="sizePolicy">
@ -326,6 +326,93 @@
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Colors</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QFrame" name="color0">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="color1">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="color2">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="color3">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>

View File

@ -13,13 +13,15 @@
#include "ShortcutView.h"
#include <mgba/core/serialize.h>
#include <mgba/core/version.h>
#include <mgba/internal/gba/gba.h>
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());
}

View File

@ -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*);

View File

@ -398,17 +398,30 @@
</widget>
<widget class="QWidget" name="interface_2">
<layout class="QFormLayout" name="formLayout_4">
<item row="1" column="1">
<widget class="QCheckBox" name="showLibrary">
<item row="0" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Show when no game open</string>
</property>
<property name="checked">
<bool>true</bool>
<string>Language</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="languages">
<item>
<property name="text">
<string>English</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Library:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="libraryStyle">
<item>
<property name="text">
@ -422,38 +435,38 @@
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<item row="3" column="1">
<widget class="QCheckBox" name="showLibrary">
<property name="text">
<string>Library:</string>
<string>Show when no game open</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="4" column="1">
<widget class="QPushButton" name="clearCache">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Clear cache</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<widget class="QCheckBox" name="suspendScreensaver">
<property name="text">
<string>Suspend screensaver</string>
@ -463,13 +476,20 @@
</property>
</widget>
</item>
<item row="6" column="1">
<item row="8" column="1">
<widget class="QCheckBox" name="pauseOnFocusLost">
<property name="text">
<string>Pause when inactive</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="emulation">

View File

@ -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<void (QComboBox::*)(int)>(&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<GamepadAxisEvent::Direction>(direction));
item->setAxis(axis, static_cast<GamepadAxisEvent::Direction>(direction));
}
void ShortcutView::closeEvent(QCloseEvent*) {

View File

@ -7,6 +7,8 @@
#define QGBA_SHORTCUT_VIEW
#include "GamepadAxisEvent.h"
#include "InputIndex.h"
#include "InputModel.h"
#include <QWidget>
@ -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;
};

View File

@ -72,6 +72,27 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Current Gamepad</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="gamepadName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -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;

View File

@ -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<QDateTime> m_frameList;
QTimer m_fpsTimer;

View File

@ -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 <mgba/internal/ds/input.h>
#endif
#include <initializer_list>
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<QMenu>(new QMenu(tr("Autofire")));
m_inputModel->addMenu(m_autofireMenu.get());
static QList<QPair<QString, int>> 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<QMenu>(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<GBAKey>(mInputMapKey(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i));
GBAKey key = static_cast<GBAKey>(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<GBAKey>(mInputMapAxis(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, value));
enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value));
if (key != GBA_KEY_NONE) {
activeButtons |= 1 << key;
}
@ -486,31 +478,28 @@ QSet<QPair<int, GamepadAxisEvent::Direction>> 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<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {
@ -567,32 +556,30 @@ QSet<QPair<int, GamepadHatEvent::Direction>> 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<QKeyEvent*>(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<GamepadButtonEvent*>(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<GamepadAxisEvent*>(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<Context*>(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<InputController*>(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
}

View File

@ -8,6 +8,7 @@
#include "GamepadAxisEvent.h"
#include "GamepadHatEvent.h"
#include "InputIndex.h"
#include <memory>
@ -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<QPair<int, GamepadHatEvent::Direction>> 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<mPlatform, mInputMap> 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<mPlatform, const mInputPlatformInfo*> m_keyInfo;
const mInputPlatformInfo* m_activeKeyInfo = nullptr;
std::unique_ptr<QMenu> m_bindings;
std::unique_ptr<QMenu> m_autofire;
#ifdef BUILD_SDL
static int s_sdlInited;
@ -139,10 +150,6 @@ private:
QVector<int> m_deadzones;
std::unique_ptr<QMenu> m_inputMenu;
std::unique_ptr<QMenu> m_autofireMenu;
QMap<mPlatform, QModelIndex> m_inputMenuIndices;
QSet<int> m_activeButtons;
QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes;
QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats;

View File

@ -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<const InputIndex*>(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);
}
}
}

View File

@ -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 <QMap>
#include <QString>
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<InputItem*>& items() const { return m_items; }
template<typename... Args> 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<InputItem*> m_items;
QMap<QString, InputItem*> m_names;
QMap<const QMenu*, InputItem*> m_menus;
QMap<int, InputItem*> m_shortcuts;
QMap<int, InputItem*> m_buttons;
QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes;
ConfigController* m_config = nullptr;
};
}
#endif

View File

@ -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 <QMenu>
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();
}
}
}

View File

@ -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 <functional>
#include <QAction>
namespace QGBA {
class InputItem : public QObject {
Q_OBJECT
public:
typedef QPair<std::function<void ()>, std::function<void ()>> 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

View File

@ -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 <QAction>
#include <QKeyEvent>
#include <QMenu>
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<const QMenu*> 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<const InputModelItem*>(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<InputModelItem*>(parent.internalPointer());
return createIndex(row, column, const_cast<InputModelItem*>(&m_tree[p->obj][row]));
}
return createIndex(row, column, const_cast<InputModelItem*>(&m_topLevelMenus[row]));
}
QModelIndex InputModel::parent(const QModelIndex& index) const {
if (!index.isValid() || !index.internalPointer()) {
return QModelIndex();
}
const QObject* obj = static_cast<const InputModelItem*>(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<InputModelItem*>(&m_tree[parent][index]));
}
if (m_topLevelMenus.contains(item)) {
int index = m_topLevelMenus.indexOf(item);
return createIndex(index, column, const_cast<InputModelItem*>(&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<const InputModelItem*>(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<InputModelItem*>(index.internalPointer())->item;
}
const InputItem* InputModel::itemAt(const QModelIndex& index) const {
if (!index.isValid() || !index.internalPointer()) {
return nullptr;
}
return static_cast<const InputModelItem*>(index.internalPointer())->item;
}

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