mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into port/psp2
This commit is contained in:
commit
68e70b61f1
9
CHANGES
9
CHANGES
|
@ -24,6 +24,11 @@ Features:
|
||||||
- Preliminary support for yanking out the game pak while a game is running
|
- Preliminary support for yanking out the game pak while a game is running
|
||||||
- Thumb-drive mode by putting a file called portable.ini in the same folder
|
- Thumb-drive mode by putting a file called portable.ini in the same folder
|
||||||
- Configurable display driver, between software and OpenGL
|
- Configurable display driver, between software and OpenGL
|
||||||
|
- Undo-able savestate loading and saving
|
||||||
|
- Controller profiles now store shortcut settings
|
||||||
|
- Default controller profiles for several common controllers
|
||||||
|
- Libretro now supports BIOS and rumble
|
||||||
|
- Implement BIOS call Stop, for sleep mode
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- ARM7: Fix SWI and IRQ timings
|
- ARM7: Fix SWI and IRQ timings
|
||||||
- GBA Audio: Force audio FIFOs to 32-bit
|
- GBA Audio: Force audio FIFOs to 32-bit
|
||||||
|
@ -58,6 +63,7 @@ Bugfixes:
|
||||||
- Qt: Fix window being too tall after exiting fullscreen
|
- Qt: Fix window being too tall after exiting fullscreen
|
||||||
- Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor
|
- Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor
|
||||||
- GBA Cheats: Fix Pro Action Replay and GameShark issues when used together
|
- GBA Cheats: Fix Pro Action Replay and GameShark issues when used together
|
||||||
|
- Qt: Fix analog buttons not getting unmapped
|
||||||
Misc:
|
Misc:
|
||||||
- Qt: Handle saving input settings better
|
- Qt: Handle saving input settings better
|
||||||
- Debugger: Free watchpoints in addition to breakpoints
|
- Debugger: Free watchpoints in addition to breakpoints
|
||||||
|
@ -100,6 +106,9 @@ Misc:
|
||||||
- GBA Video: Slightly optimize mode 0 mosaic rendering
|
- GBA Video: Slightly optimize mode 0 mosaic rendering
|
||||||
- VFS: Add sync method to force syncing with backing
|
- VFS: Add sync method to force syncing with backing
|
||||||
- GBA: Savedata is now synced shortly after data finishes being written
|
- GBA: Savedata is now synced shortly after data finishes being written
|
||||||
|
- GBA Input: Allow axes and buttons to be mapped to the same key
|
||||||
|
- GBA BIOS: Stub out SoundBias
|
||||||
|
- Qt: Gamepads can now have both buttons and analog axes mapped to the same key
|
||||||
|
|
||||||
0.2.1: (2015-05-13)
|
0.2.1: (2015-05-13)
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
|
@ -43,7 +43,11 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
if (NOT DEFINED LIBDIR)
|
||||||
|
set(LIBDIR "lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
@ -76,51 +80,16 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Version information
|
# Version information
|
||||||
set(LIB_VERSION_MAJOR 0)
|
|
||||||
set(LIB_VERSION_MINOR 3)
|
|
||||||
set(LIB_VERSION_PATCH 0)
|
|
||||||
set(LIB_VERSION_ABI 0.3)
|
|
||||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
|
||||||
|
|
||||||
execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
||||||
|
|
||||||
if(GIT_REV STREQUAL "")
|
|
||||||
set(GIT_REV -1)
|
|
||||||
endif()
|
|
||||||
if(NOT GIT_TAG STREQUAL "")
|
|
||||||
set(VERSION_STRING ${GIT_TAG})
|
|
||||||
elseif(GIT_BRANCH STREQUAL "")
|
|
||||||
set(VERSION_STRING ${LIB_VERSION_STRING})
|
|
||||||
else()
|
|
||||||
if(GIT_BRANCH STREQUAL "master")
|
|
||||||
set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT})
|
|
||||||
else()
|
|
||||||
set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH)
|
|
||||||
set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
-DGIT_COMMIT=${GIT_COMMIT}
|
|
||||||
-DGIT_COMMIT_SHORT=${GIT_COMMIT_SHORT}
|
|
||||||
-DGIT_BRANCH=${GIT_BRANCH}
|
|
||||||
-DGIT_REV=${GIT_REV}
|
|
||||||
-DBINARY_NAME=${BINARY_NAME}
|
-DBINARY_NAME=${BINARY_NAME}
|
||||||
-DPROJECT_NAME=${PROJECT_NAME}
|
-DCONFIG_FILE=${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||||
-DVERSION_STRING=${VERSION_STRING}
|
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.c
|
||||||
-D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR}
|
|
||||||
-P ${CMAKE_SOURCE_DIR}/version.cmake
|
-P ${CMAKE_SOURCE_DIR}/version.cmake
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/version.cmake)
|
include(${CMAKE_SOURCE_DIR}/version.cmake)
|
||||||
|
configure_file(${CMAKE_SOURCE_DIR}/src/util/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||||
list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c)
|
list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c)
|
||||||
source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c)
|
source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c)
|
||||||
|
|
||||||
|
@ -404,7 +373,7 @@ if(BUILD_SHARED)
|
||||||
if(BUILD_STATIC)
|
if(BUILD_STATIC)
|
||||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||||
add_dependencies(${BINARY_NAME}-static version-info)
|
add_dependencies(${BINARY_NAME}-static version-info)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
|
@ -414,7 +383,7 @@ endif()
|
||||||
add_dependencies(${BINARY_NAME} version-info)
|
add_dependencies(${BINARY_NAME} version-info)
|
||||||
|
|
||||||
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
||||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
install(TARGETS ${BINARY_NAME} DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||||
|
|
13
PORTING.md
13
PORTING.md
|
@ -12,7 +12,7 @@ Port-specific TODO
|
||||||
|
|
||||||
The ports are vaguely usable, but by no means should be considered stable.
|
The ports are vaguely usable, but by no means should be considered stable.
|
||||||
|
|
||||||
### 3DS
|
### 3DS (port/3ds)
|
||||||
* Add menu
|
* Add menu
|
||||||
* Add audio
|
* Add audio
|
||||||
* Thread support testing
|
* Thread support testing
|
||||||
|
@ -20,7 +20,7 @@ The ports are vaguely usable, but by no means should be considered stable.
|
||||||
* ARMv6 dynarec
|
* ARMv6 dynarec
|
||||||
* Hardware acceleration
|
* Hardware acceleration
|
||||||
|
|
||||||
### PSP
|
### PSP (port/psp)
|
||||||
* Add menu
|
* Add menu
|
||||||
* Add audio
|
* Add audio
|
||||||
* Thread support
|
* Thread support
|
||||||
|
@ -28,7 +28,14 @@ The ports are vaguely usable, but by no means should be considered stable.
|
||||||
* MIPS dynarec
|
* MIPS dynarec
|
||||||
* Hardware acceleration
|
* Hardware acceleration
|
||||||
|
|
||||||
### Wii
|
### PS Vita (port/psp2)
|
||||||
|
* Add menu
|
||||||
|
* Add audio
|
||||||
|
* Make it faster
|
||||||
|
* Threaded renderer shim
|
||||||
|
* Hardware acceleration
|
||||||
|
|
||||||
|
### Wii (port/wii)
|
||||||
* Add menu
|
* Add menu
|
||||||
* Add audio
|
* Add audio
|
||||||
* Thread support
|
* Thread support
|
||||||
|
|
|
@ -153,7 +153,7 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
|
||||||
|
|
||||||
#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \
|
#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \
|
||||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||||
info->op1.reg = (opcode >> 6) & 0x0007; \
|
info->op1.reg = (opcode >> 8) & 0x0007; \
|
||||||
info->memory.baseReg = REG; \
|
info->memory.baseReg = REG; \
|
||||||
info->memory.offset.immediate = (opcode & 0x00FF) << 2; \
|
info->memory.offset.immediate = (opcode & 0x00FF) << 2; \
|
||||||
info->memory.width = ARM_ACCESS_WORD; \
|
info->memory.width = ARM_ACCESS_WORD; \
|
||||||
|
|
|
@ -191,6 +191,9 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
case 0x2:
|
case 0x2:
|
||||||
GBAHalt(gba);
|
GBAHalt(gba);
|
||||||
break;
|
break;
|
||||||
|
case 0x3:
|
||||||
|
GBAStop(gba);
|
||||||
|
break;
|
||||||
case 0x05:
|
case 0x05:
|
||||||
// VBlankIntrWait
|
// VBlankIntrWait
|
||||||
// Fall through:
|
// Fall through:
|
||||||
|
@ -297,6 +300,10 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x19:
|
||||||
|
// SoundBias is mostly meaningless here
|
||||||
|
GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: SoundBias (19)");
|
||||||
|
break;
|
||||||
case 0x1F:
|
case 0x1F:
|
||||||
_MidiKey2Freq(gba);
|
_MidiKey2Freq(gba);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -79,8 +79,10 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||||
gba->biosVf = 0;
|
gba->biosVf = 0;
|
||||||
|
|
||||||
gba->logHandler = 0;
|
gba->logHandler = 0;
|
||||||
gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
gba->logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||||
gba->stream = 0;
|
gba->stream = 0;
|
||||||
|
gba->keyCallback = 0;
|
||||||
|
gba->stopCallback = 0;
|
||||||
|
|
||||||
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||||
|
|
||||||
|
@ -552,6 +554,14 @@ void GBAHalt(struct GBA* gba) {
|
||||||
gba->cpu->halted = 1;
|
gba->cpu->halted = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBAStop(struct GBA* gba) {
|
||||||
|
if (!gba->stopCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gba->cpu->nextEvent = 0;
|
||||||
|
gba->stopCallback->stop(gba->stopCallback);
|
||||||
|
}
|
||||||
|
|
||||||
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
||||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||||
enum GBALogLevel logLevel = GBA_LOG_ALL;
|
enum GBALogLevel logLevel = GBA_LOG_ALL;
|
||||||
|
@ -777,6 +787,10 @@ void GBAFrameEnded(struct GBA* gba) {
|
||||||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gba->memory.hw.devices & (HW_GB_PLAYER | HW_GB_PLAYER_DETECTION)) {
|
||||||
|
GBAHardwarePlayerUpdate(gba);
|
||||||
|
}
|
||||||
|
|
||||||
struct GBAThread* thread = GBAThreadGetContext();
|
struct GBAThread* thread = GBAThreadGetContext();
|
||||||
if (!thread) {
|
if (!thread) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "arm.h"
|
#include "arm.h"
|
||||||
#include "debugger/debugger.h"
|
#include "debugger/debugger.h"
|
||||||
|
|
||||||
|
#include "gba/interface.h"
|
||||||
#include "gba/memory.h"
|
#include "gba/memory.h"
|
||||||
#include "gba/video.h"
|
#include "gba/video.h"
|
||||||
#include "gba/audio.h"
|
#include "gba/audio.h"
|
||||||
|
@ -35,43 +36,6 @@ enum GBAIRQ {
|
||||||
IRQ_GAMEPAK = 0xD
|
IRQ_GAMEPAK = 0xD
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GBALogLevel {
|
|
||||||
GBA_LOG_FATAL = 0x01,
|
|
||||||
GBA_LOG_ERROR = 0x02,
|
|
||||||
GBA_LOG_WARN = 0x04,
|
|
||||||
GBA_LOG_INFO = 0x08,
|
|
||||||
GBA_LOG_DEBUG = 0x10,
|
|
||||||
GBA_LOG_STUB = 0x20,
|
|
||||||
|
|
||||||
GBA_LOG_GAME_ERROR = 0x100,
|
|
||||||
GBA_LOG_SWI = 0x200,
|
|
||||||
GBA_LOG_STATUS = 0x400,
|
|
||||||
GBA_LOG_SIO = 0x800,
|
|
||||||
|
|
||||||
GBA_LOG_ALL = 0xF3F,
|
|
||||||
|
|
||||||
#ifdef NDEBUG
|
|
||||||
GBA_LOG_DANGER = GBA_LOG_ERROR
|
|
||||||
#else
|
|
||||||
GBA_LOG_DANGER = GBA_LOG_FATAL
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
enum GBAKey {
|
|
||||||
GBA_KEY_A = 0,
|
|
||||||
GBA_KEY_B = 1,
|
|
||||||
GBA_KEY_SELECT = 2,
|
|
||||||
GBA_KEY_START = 3,
|
|
||||||
GBA_KEY_RIGHT = 4,
|
|
||||||
GBA_KEY_LEFT = 5,
|
|
||||||
GBA_KEY_UP = 6,
|
|
||||||
GBA_KEY_DOWN = 7,
|
|
||||||
GBA_KEY_R = 8,
|
|
||||||
GBA_KEY_L = 9,
|
|
||||||
GBA_KEY_MAX,
|
|
||||||
GBA_KEY_NONE = -1
|
|
||||||
};
|
|
||||||
|
|
||||||
enum GBAComponent {
|
enum GBAComponent {
|
||||||
GBA_COMPONENT_DEBUGGER,
|
GBA_COMPONENT_DEBUGGER,
|
||||||
GBA_COMPONENT_CHEAT_DEVICE,
|
GBA_COMPONENT_CHEAT_DEVICE,
|
||||||
|
@ -91,19 +55,10 @@ enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBA;
|
struct GBA;
|
||||||
struct GBARotationSource;
|
|
||||||
struct GBAThread;
|
struct GBAThread;
|
||||||
struct Patch;
|
struct Patch;
|
||||||
struct VFile;
|
struct VFile;
|
||||||
|
|
||||||
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
|
||||||
|
|
||||||
struct GBAAVStream {
|
|
||||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
|
||||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
|
||||||
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBATimer {
|
struct GBATimer {
|
||||||
uint16_t reload;
|
uint16_t reload;
|
||||||
uint16_t oldReload;
|
uint16_t oldReload;
|
||||||
|
@ -156,6 +111,8 @@ struct GBA {
|
||||||
GBALogHandler logHandler;
|
GBALogHandler logHandler;
|
||||||
enum GBALogLevel logLevel;
|
enum GBALogLevel logLevel;
|
||||||
struct GBAAVStream* stream;
|
struct GBAAVStream* stream;
|
||||||
|
struct GBAKeyCallback* keyCallback;
|
||||||
|
struct GBAStopCallback* stopCallback;
|
||||||
|
|
||||||
enum GBAIdleLoopOptimization idleOptimization;
|
enum GBAIdleLoopOptimization idleOptimization;
|
||||||
uint32_t idleLoop;
|
uint32_t idleLoop;
|
||||||
|
@ -199,6 +156,7 @@ void GBAWriteIME(struct GBA* gba, uint16_t value);
|
||||||
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq);
|
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq);
|
||||||
void GBATestIRQ(struct ARMCore* cpu);
|
void GBATestIRQ(struct ARMCore* cpu);
|
||||||
void GBAHalt(struct GBA* gba);
|
void GBAHalt(struct GBA* gba);
|
||||||
|
void GBAStop(struct GBA* gba);
|
||||||
|
|
||||||
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger);
|
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger);
|
||||||
void GBADetachDebugger(struct GBA* gba);
|
void GBADetachDebugger(struct GBA* gba);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "hardware.h"
|
#include "hardware.h"
|
||||||
|
|
||||||
|
#include "gba/io.h"
|
||||||
#include "gba/serialize.h"
|
#include "gba/serialize.h"
|
||||||
#include "util/hash.h"
|
#include "util/hash.h"
|
||||||
|
|
||||||
|
@ -25,6 +26,10 @@ static void _rumbleReadPins(struct GBACartridgeHardware* hw);
|
||||||
|
|
||||||
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
||||||
|
|
||||||
|
static uint16_t _gbpRead(struct GBAKeyCallback*);
|
||||||
|
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||||
|
static int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||||
|
|
||||||
static const int RTC_BYTES[8] = {
|
static const int RTC_BYTES[8] = {
|
||||||
0, // Force reset
|
0, // Force reset
|
||||||
0, // Empty
|
0, // Empty
|
||||||
|
@ -39,6 +44,16 @@ static const int RTC_BYTES[8] = {
|
||||||
void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
||||||
hw->gpioBase = base;
|
hw->gpioBase = base;
|
||||||
GBAHardwareClear(hw);
|
GBAHardwareClear(hw);
|
||||||
|
|
||||||
|
hw->gbpCallback.d.readKeys = _gbpRead;
|
||||||
|
hw->gbpCallback.p = hw;
|
||||||
|
hw->gbpDriver.d.init = 0;
|
||||||
|
hw->gbpDriver.d.deinit = 0;
|
||||||
|
hw->gbpDriver.d.load = 0;
|
||||||
|
hw->gbpDriver.d.unload = 0;
|
||||||
|
hw->gbpDriver.d.writeRegister = _gbpSioWriteRegister;
|
||||||
|
hw->gbpDriver.d.processEvents = _gbpSioProcessEvents;
|
||||||
|
hw->gbpDriver.p = hw;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||||
|
@ -461,6 +476,30 @@ static const uint16_t _logoPalette[] = {
|
||||||
|
|
||||||
static const uint32_t _logoHash = 0xEEDA6963;
|
static const uint32_t _logoHash = 0xEEDA6963;
|
||||||
|
|
||||||
|
static const uint32_t _gbpTxData[] = {
|
||||||
|
0x0000494E, 0x0000494E,
|
||||||
|
0xB6B1494E, 0xB6B1544E,
|
||||||
|
0xABB1544E, 0xABB14E45,
|
||||||
|
0xB1BA4E45, 0xB1BA4F44,
|
||||||
|
0xB0BB4F44, 0xB0BB8002,
|
||||||
|
0x10000010, 0x20000013,
|
||||||
|
0x30000003, 0x30000003,
|
||||||
|
0x30000003, 0x30000003,
|
||||||
|
0x30000003, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t _gbpRxData[] = {
|
||||||
|
0x00000000, 0x494EB6B1,
|
||||||
|
0x494EB6B1, 0x544EB6B1,
|
||||||
|
0x544EABB1, 0x4E45ABB1,
|
||||||
|
0x4E45B1BA, 0x4F44B1BA,
|
||||||
|
0x4F44B0BB, 0x8000B0BB,
|
||||||
|
0x10000010, 0x20000013,
|
||||||
|
0x40000004, 0x40000004,
|
||||||
|
0x40000004, 0x40000004,
|
||||||
|
0x40000004, 0x40000004
|
||||||
|
};
|
||||||
|
|
||||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
||||||
if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) {
|
if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -469,6 +508,83 @@ bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
||||||
return hash == _logoHash;
|
return hash == _logoHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBAHardwarePlayerUpdate(struct GBA* gba) {
|
||||||
|
if (gba->memory.hw.devices & HW_GB_PLAYER) {
|
||||||
|
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
||||||
|
++gba->memory.hw.gbpInputsPosted;
|
||||||
|
gba->memory.hw.gbpInputsPosted %= 3;
|
||||||
|
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
||||||
|
} else {
|
||||||
|
// TODO: Save and restore
|
||||||
|
gba->keyCallback = 0;
|
||||||
|
}
|
||||||
|
gba->memory.hw.gbpTxPosition = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gba->keyCallback || gba->sio.drivers.normal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
||||||
|
gba->memory.hw.devices |= HW_GB_PLAYER;
|
||||||
|
gba->memory.hw.gbpInputsPosted = 0;
|
||||||
|
gba->memory.hw.gbpNextEvent = INT_MAX;
|
||||||
|
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
||||||
|
GBASIOSetDriver(&gba->sio, &gba->memory.hw.gbpDriver.d, SIO_NORMAL_32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _gbpRead(struct GBAKeyCallback* callback) {
|
||||||
|
struct GBAGBPKeyCallback* gbpCallback = (struct GBAGBPKeyCallback*) callback;
|
||||||
|
if (gbpCallback->p->gbpInputsPosted == 2) {
|
||||||
|
return 0x30F;
|
||||||
|
}
|
||||||
|
return 0x3FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||||
|
struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver;
|
||||||
|
if (address == REG_SIOCNT) {
|
||||||
|
if (value & 0x0080) {
|
||||||
|
if (gbp->p->gbpTxPosition <= 16 && gbp->p->gbpTxPosition > 0) {
|
||||||
|
uint32_t rx = gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] << 16);
|
||||||
|
uint32_t expected = _gbpRxData[gbp->p->gbpTxPosition];
|
||||||
|
// TODO: Check expected
|
||||||
|
uint32_t mask = 0;
|
||||||
|
if (gbp->p->gbpTxPosition == 15) {
|
||||||
|
mask = 0x22;
|
||||||
|
if (gbp->p->p->rumble) {
|
||||||
|
gbp->p->p->rumble->setRumble(gbp->p->p->rumble, (rx & mask) == mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gbp->p->gbpNextEvent = 2048;
|
||||||
|
}
|
||||||
|
value &= 0x78FB;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||||
|
struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver;
|
||||||
|
gbp->p->gbpNextEvent -= cycles;
|
||||||
|
if (gbp->p->gbpNextEvent <= 0) {
|
||||||
|
uint32_t tx = 0;
|
||||||
|
if (gbp->p->gbpTxPosition <= 16) {
|
||||||
|
tx = _gbpTxData[gbp->p->gbpTxPosition];
|
||||||
|
++gbp->p->gbpTxPosition;
|
||||||
|
}
|
||||||
|
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||||
|
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
||||||
|
if (gbp->d.p->normalControl.irq) {
|
||||||
|
GBARaiseIRQ(gbp->p->p, IRQ_SIO);
|
||||||
|
}
|
||||||
|
gbp->d.p->normalControl.start = 0;
|
||||||
|
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt;
|
||||||
|
gbp->p->gbpNextEvent = INT_MAX;
|
||||||
|
}
|
||||||
|
return gbp->p->gbpNextEvent;
|
||||||
|
}
|
||||||
|
|
||||||
// == Serialization
|
// == Serialization
|
||||||
|
|
||||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||||
|
@ -485,6 +601,9 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
|
||||||
state->hw.lightCounter = hw->lightCounter;
|
state->hw.lightCounter = hw->lightCounter;
|
||||||
state->hw.lightSample = hw->lightSample;
|
state->hw.lightSample = hw->lightSample;
|
||||||
state->hw.lightEdge = hw->lightEdge;
|
state->hw.lightEdge = hw->lightEdge;
|
||||||
|
state->hw.gbpInputsPosted = hw->gbpInputsPosted;
|
||||||
|
state->hw.gbpTxPosition = hw->gbpTxPosition;
|
||||||
|
state->hw.gbpNextEvent = hw->gbpNextEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||||
|
@ -500,4 +619,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
||||||
hw->lightCounter = state->hw.lightCounter;
|
hw->lightCounter = state->hw.lightCounter;
|
||||||
hw->lightSample = state->hw.lightSample;
|
hw->lightSample = state->hw.lightSample;
|
||||||
hw->lightEdge = state->hw.lightEdge;
|
hw->lightEdge = state->hw.lightEdge;
|
||||||
|
hw->gbpInputsPosted = state->hw.gbpInputsPosted;
|
||||||
|
hw->gbpTxPosition = state->hw.gbpTxPosition;
|
||||||
|
hw->gbpNextEvent = state->hw.gbpNextEvent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#define GBA_HARDWARE_H
|
#define GBA_HARDWARE_H
|
||||||
|
|
||||||
#include "util/common.h"
|
#include "util/common.h"
|
||||||
|
#include "gba/interface.h"
|
||||||
|
|
||||||
#include "macros.h"
|
#include "macros.h"
|
||||||
|
|
||||||
|
@ -14,27 +15,6 @@
|
||||||
|
|
||||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||||
|
|
||||||
struct GBARotationSource {
|
|
||||||
void (*sample)(struct GBARotationSource*);
|
|
||||||
|
|
||||||
int32_t (*readTiltX)(struct GBARotationSource*);
|
|
||||||
int32_t (*readTiltY)(struct GBARotationSource*);
|
|
||||||
|
|
||||||
int32_t (*readGyroZ)(struct GBARotationSource*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBALuminanceSource {
|
|
||||||
void (*sample)(struct GBALuminanceSource*);
|
|
||||||
|
|
||||||
uint8_t (*readLuminance)(struct GBALuminanceSource*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBARTCSource {
|
|
||||||
void (*sample)(struct GBARTCSource*);
|
|
||||||
|
|
||||||
time_t (*unixTime)(struct GBARTCSource*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBARTCGenericSource {
|
struct GBARTCGenericSource {
|
||||||
struct GBARTCSource d;
|
struct GBARTCSource d;
|
||||||
struct GBA* p;
|
struct GBA* p;
|
||||||
|
@ -53,7 +33,9 @@ enum GBAHardwareDevice {
|
||||||
HW_RUMBLE = 2,
|
HW_RUMBLE = 2,
|
||||||
HW_LIGHT_SENSOR = 4,
|
HW_LIGHT_SENSOR = 4,
|
||||||
HW_GYRO = 8,
|
HW_GYRO = 8,
|
||||||
HW_TILT = 16
|
HW_TILT = 16,
|
||||||
|
HW_GB_PLAYER = 32,
|
||||||
|
HW_GB_PLAYER_DETECTION = 64
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GPIORegister {
|
enum GPIORegister {
|
||||||
|
@ -102,6 +84,16 @@ struct GBARumble {
|
||||||
void (*setRumble)(struct GBARumble*, int enable);
|
void (*setRumble)(struct GBARumble*, int enable);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GBAGBPKeyCallback {
|
||||||
|
struct GBAKeyCallback d;
|
||||||
|
struct GBACartridgeHardware* p;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBAGBPSIODriver {
|
||||||
|
struct GBASIODriver d;
|
||||||
|
struct GBACartridgeHardware* p;
|
||||||
|
};
|
||||||
|
|
||||||
DECL_BITFIELD(GPIOPin, uint16_t);
|
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||||
|
|
||||||
struct GBACartridgeHardware {
|
struct GBACartridgeHardware {
|
||||||
|
@ -125,6 +117,12 @@ struct GBACartridgeHardware {
|
||||||
uint16_t tiltX;
|
uint16_t tiltX;
|
||||||
uint16_t tiltY;
|
uint16_t tiltY;
|
||||||
int tiltState;
|
int tiltState;
|
||||||
|
|
||||||
|
unsigned gbpInputsPosted;
|
||||||
|
int gbpTxPosition;
|
||||||
|
int32_t gbpNextEvent;
|
||||||
|
struct GBAGBPKeyCallback gbpCallback;
|
||||||
|
struct GBAGBPSIODriver gbpDriver;
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);
|
void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);
|
||||||
|
@ -141,6 +139,7 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
|
||||||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||||
|
|
||||||
struct GBAVideo;
|
struct GBAVideo;
|
||||||
|
void GBAHardwarePlayerUpdate(struct GBA* gba);
|
||||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video);
|
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video);
|
||||||
|
|
||||||
void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba);
|
void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba);
|
||||||
|
|
|
@ -257,7 +257,10 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
static bool _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||||
|
if (!ConfigurationHasSection(config, sectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
_loadKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
_loadKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||||
_loadKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
_loadKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||||
_loadKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
_loadKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||||
|
@ -279,6 +282,7 @@ static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* section
|
||||||
_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||||
_loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
_loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||||
_loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
_loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
||||||
|
@ -376,7 +380,6 @@ void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->map[input] = GBA_NO_MAPPING;
|
impl->map[input] = GBA_NO_MAPPING;
|
||||||
}
|
}
|
||||||
TableEnumerate(&impl->axes, _unbindAxis, &input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||||
|
@ -430,9 +433,9 @@ int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, in
|
||||||
|
|
||||||
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
|
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
|
||||||
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
||||||
|
TableEnumerate(&impl->axes, _unbindAxis, &description->highDirection);
|
||||||
|
TableEnumerate(&impl->axes, _unbindAxis, &description->lowDirection);
|
||||||
struct GBAAxis* dup = malloc(sizeof(struct GBAAxis));
|
struct GBAAxis* dup = malloc(sizeof(struct GBAAxis));
|
||||||
GBAInputUnbindKey(map, type, description->lowDirection);
|
|
||||||
GBAInputUnbindKey(map, type, description->highDirection);
|
|
||||||
*dup = *description;
|
*dup = *description;
|
||||||
TableInsert(&impl->axes, axis, dup);
|
TableInsert(&impl->axes, axis, dup);
|
||||||
}
|
}
|
||||||
|
@ -483,11 +486,11 @@ void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Config
|
||||||
_saveAll(map, type, sectionName, config);
|
_saveAll(map, type, sectionName, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
bool GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||||
char sectionName[SECTION_NAME_MAX];
|
char sectionName[SECTION_NAME_MAX];
|
||||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||||
_loadAll(map, type, sectionName, config);
|
return _loadAll(map, type, sectionName, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
|
||||||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||||
|
|
||||||
void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
bool GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||||
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||||
|
|
||||||
const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId);
|
const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId);
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/* 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 INTERFACE_H
|
||||||
|
#define INTERFACE_H
|
||||||
|
|
||||||
|
#include "util/common.h"
|
||||||
|
|
||||||
|
enum GBALogLevel {
|
||||||
|
GBA_LOG_FATAL = 0x01,
|
||||||
|
GBA_LOG_ERROR = 0x02,
|
||||||
|
GBA_LOG_WARN = 0x04,
|
||||||
|
GBA_LOG_INFO = 0x08,
|
||||||
|
GBA_LOG_DEBUG = 0x10,
|
||||||
|
GBA_LOG_STUB = 0x20,
|
||||||
|
|
||||||
|
GBA_LOG_GAME_ERROR = 0x100,
|
||||||
|
GBA_LOG_SWI = 0x200,
|
||||||
|
GBA_LOG_STATUS = 0x400,
|
||||||
|
GBA_LOG_SIO = 0x800,
|
||||||
|
|
||||||
|
GBA_LOG_ALL = 0xF3F,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GBAKey {
|
||||||
|
GBA_KEY_A = 0,
|
||||||
|
GBA_KEY_B = 1,
|
||||||
|
GBA_KEY_SELECT = 2,
|
||||||
|
GBA_KEY_START = 3,
|
||||||
|
GBA_KEY_RIGHT = 4,
|
||||||
|
GBA_KEY_LEFT = 5,
|
||||||
|
GBA_KEY_UP = 6,
|
||||||
|
GBA_KEY_DOWN = 7,
|
||||||
|
GBA_KEY_R = 8,
|
||||||
|
GBA_KEY_L = 9,
|
||||||
|
GBA_KEY_MAX,
|
||||||
|
GBA_KEY_NONE = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GBASIOMode {
|
||||||
|
SIO_NORMAL_8 = 0,
|
||||||
|
SIO_NORMAL_32 = 1,
|
||||||
|
SIO_MULTI = 2,
|
||||||
|
SIO_UART = 3,
|
||||||
|
SIO_GPIO = 8,
|
||||||
|
SIO_JOYBUS = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBA;
|
||||||
|
struct GBAAudio;
|
||||||
|
struct GBASIO;
|
||||||
|
struct GBAThread;
|
||||||
|
struct GBAVideoRenderer;
|
||||||
|
|
||||||
|
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||||
|
|
||||||
|
struct GBAAVStream {
|
||||||
|
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
|
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||||
|
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBAKeyCallback {
|
||||||
|
uint16_t (*readKeys)(struct GBAKeyCallback*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBAStopCallback {
|
||||||
|
void (*stop)(struct GBAStopCallback*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBARotationSource {
|
||||||
|
void (*sample)(struct GBARotationSource*);
|
||||||
|
|
||||||
|
int32_t (*readTiltX)(struct GBARotationSource*);
|
||||||
|
int32_t (*readTiltY)(struct GBARotationSource*);
|
||||||
|
|
||||||
|
int32_t (*readGyroZ)(struct GBARotationSource*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBALuminanceSource {
|
||||||
|
void (*sample)(struct GBALuminanceSource*);
|
||||||
|
|
||||||
|
uint8_t (*readLuminance)(struct GBALuminanceSource*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBARTCSource {
|
||||||
|
void (*sample)(struct GBARTCSource*);
|
||||||
|
|
||||||
|
time_t (*unixTime)(struct GBARTCSource*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBASIODriver {
|
||||||
|
struct GBASIO* p;
|
||||||
|
|
||||||
|
bool (*init)(struct GBASIODriver* driver);
|
||||||
|
void (*deinit)(struct GBASIODriver* driver);
|
||||||
|
bool (*load)(struct GBASIODriver* driver);
|
||||||
|
bool (*unload)(struct GBASIODriver* driver);
|
||||||
|
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||||
|
int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
10
src/gba/io.c
10
src/gba/io.c
|
@ -505,7 +505,7 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
GBAHalt(gba);
|
GBAHalt(gba);
|
||||||
} else {
|
} else {
|
||||||
GBALog(gba, GBA_LOG_STUB, "Stop unimplemented");
|
GBAStop(gba);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -584,14 +584,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||||
case REG_KEYINPUT:
|
case REG_KEYINPUT:
|
||||||
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||||
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||||
|
} else {
|
||||||
|
uint16_t input = 0x3FF;
|
||||||
|
if (gba->keyCallback) {
|
||||||
|
input = gba->keyCallback->readKeys(gba->keyCallback);
|
||||||
} else if (gba->keySource) {
|
} else if (gba->keySource) {
|
||||||
uint16_t input = *gba->keySource;
|
input = *gba->keySource;
|
||||||
|
}
|
||||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||||
gba->rr->logInput(gba->rr, input);
|
gba->rr->logInput(gba->rr, input);
|
||||||
}
|
}
|
||||||
return 0x3FF ^ input;
|
return 0x3FF ^ input;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case REG_SIOCNT:
|
case REG_SIOCNT:
|
||||||
return gba->sio.siocnt;
|
return gba->sio.siocnt;
|
||||||
|
|
|
@ -406,7 +406,7 @@
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
if (UNLIKELY(end < outX)) { \
|
if (UNLIKELY(end < outX)) { \
|
||||||
GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \
|
GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw!"); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
|
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
|
||||||
|
|
|
@ -403,12 +403,10 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
||||||
if (win->h.end >= oldWindow.endX) {
|
if (win->h.end >= oldWindow.endX) {
|
||||||
// Trim off extra windows we've overwritten
|
// Trim off extra windows we've overwritten
|
||||||
for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
|
for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
|
||||||
#ifdef DEBUG
|
if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) {
|
||||||
if (activeWindow >= MAX_WINDOW) {
|
GBALog(0, GBA_LOG_FATAL, "Out of bounds window write will occur");
|
||||||
GBALog(0, GBA_LOG_DANGER, "Out of bounds window write will occur");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
|
softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
|
||||||
--softwareRenderer->nWindows;
|
--softwareRenderer->nWindows;
|
||||||
}
|
}
|
||||||
|
@ -428,7 +426,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
||||||
}
|
}
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
if (softwareRenderer->nWindows > MAX_WINDOW) {
|
if (softwareRenderer->nWindows > MAX_WINDOW) {
|
||||||
GBALog(0, GBA_LOG_ABORT, "Out of bounds window write occurred!");
|
GBALog(0, GBA_LOG_FATAL, "Out of bounds window write occurred!");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -532,7 +530,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COLOR_16_BIT
|
#ifdef COLOR_16_BIT
|
||||||
#ifdef __ARM_NEON
|
#if defined(__ARM_NEON) && !defined(__APPLE__)
|
||||||
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
||||||
#else
|
#else
|
||||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||||
|
|
|
@ -407,6 +407,9 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
|
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
|
||||||
|
if (!savedata->vf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (savedata->dirty & SAVEDATA_DIRT_NEW) {
|
if (savedata->dirty & SAVEDATA_DIRT_NEW) {
|
||||||
savedata->dirty &= ~SAVEDATA_DIRT_NEW;
|
savedata->dirty &= ~SAVEDATA_DIRT_NEW;
|
||||||
if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) {
|
if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) {
|
||||||
|
|
|
@ -136,7 +136,8 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||||
* | bit 2: Has light sensor value
|
* | bit 2: Has light sensor value
|
||||||
* | bit 3: Has gyroscope value
|
* | bit 3: Has gyroscope value
|
||||||
* | bit 4: Has tilt values
|
* | bit 4: Has tilt values
|
||||||
* | bits 5 - 7: Reserved
|
* | bit 5: Has Game Boy Player attached
|
||||||
|
* | bits 6 - 7: Reserved
|
||||||
* | 0x002B8 - 0x002B9: Gyroscope sample
|
* | 0x002B8 - 0x002B9: Gyroscope sample
|
||||||
* | 0x002BA - 0x002BB: Tilt x sample
|
* | 0x002BA - 0x002BB: Tilt x sample
|
||||||
* | 0x002BC - 0x002BD: Tilt y sample
|
* | 0x002BC - 0x002BD: Tilt y sample
|
||||||
|
@ -149,8 +150,11 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||||
* | 0x002C0 - 0x002C0: Light sample
|
* | 0x002C0 - 0x002C0: Light sample
|
||||||
* | 0x002C1 - 0x002C3: Flags
|
* | 0x002C1 - 0x002C3: Flags
|
||||||
* | bits 0 - 1: Tilt state machine
|
* | bits 0 - 1: Tilt state machine
|
||||||
* | bits 2 - 31: Reserved
|
* | bits 2 - 3: GB Player inputs posted
|
||||||
* 0x002C4 - 0x002DF: Reserved (leave zero)
|
* | bits 4 - 8: GB Player transmit position
|
||||||
|
* | bits 9 - 23: Reserved
|
||||||
|
* 0x002C4 - 0x002C7: Game Boy Player next event
|
||||||
|
* 0x002C8 - 0x002DF: Reserved (leave zero)
|
||||||
* 0x002E0 - 0x002EF: Savedata state
|
* 0x002E0 - 0x002EF: Savedata state
|
||||||
* | 0x002E0 - 0x002E0: Savedata type
|
* | 0x002E0 - 0x002E0: Savedata type
|
||||||
* | 0x002E1 - 0x002E1: Savedata command (see savedata.h)
|
* | 0x002E1 - 0x002E1: Savedata command (see savedata.h)
|
||||||
|
@ -282,10 +286,13 @@ struct GBASerializedState {
|
||||||
unsigned lightCounter : 12;
|
unsigned lightCounter : 12;
|
||||||
unsigned lightSample : 8;
|
unsigned lightSample : 8;
|
||||||
unsigned tiltState : 2;
|
unsigned tiltState : 2;
|
||||||
unsigned : 22;
|
unsigned gbpInputsPosted : 2;
|
||||||
|
unsigned gbpTxPosition : 5;
|
||||||
|
unsigned : 15;
|
||||||
|
uint32_t gbpNextEvent : 32;
|
||||||
} hw;
|
} hw;
|
||||||
|
|
||||||
uint32_t reservedHardware[7];
|
uint32_t reservedHardware[6];
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
unsigned type : 8;
|
unsigned type : 8;
|
||||||
|
|
|
@ -8,36 +8,16 @@
|
||||||
|
|
||||||
#include "util/common.h"
|
#include "util/common.h"
|
||||||
|
|
||||||
|
#include "gba/interface.h"
|
||||||
|
|
||||||
#define MAX_GBAS 4
|
#define MAX_GBAS 4
|
||||||
|
|
||||||
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
||||||
|
|
||||||
enum GBASIOMode {
|
|
||||||
SIO_NORMAL_8 = 0,
|
|
||||||
SIO_NORMAL_32 = 1,
|
|
||||||
SIO_MULTI = 2,
|
|
||||||
SIO_UART = 3,
|
|
||||||
SIO_GPIO = 8,
|
|
||||||
SIO_JOYBUS = 12
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
RCNT_INITIAL = 0x8000
|
RCNT_INITIAL = 0x8000
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBASIO;
|
|
||||||
|
|
||||||
struct GBASIODriver {
|
|
||||||
struct GBASIO* p;
|
|
||||||
|
|
||||||
bool (*init)(struct GBASIODriver* driver);
|
|
||||||
void (*deinit)(struct GBASIODriver* driver);
|
|
||||||
bool (*load)(struct GBASIODriver* driver);
|
|
||||||
bool (*unload)(struct GBASIODriver* driver);
|
|
||||||
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
|
||||||
int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBASIODriverSet {
|
struct GBASIODriverSet {
|
||||||
struct GBASIODriver* normal;
|
struct GBASIODriver* normal;
|
||||||
struct GBASIODriver* multiplayer;
|
struct GBASIODriver* multiplayer;
|
||||||
|
|
|
@ -116,13 +116,21 @@ bool GBAConfigLoad(struct GBAConfig* config) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
GBAConfigDirectory(path, PATH_MAX);
|
GBAConfigDirectory(path, PATH_MAX);
|
||||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||||
return ConfigurationRead(&config->configTable, path);
|
return GBAConfigLoadPath(config, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GBAConfigSave(const struct GBAConfig* config) {
|
bool GBAConfigSave(const struct GBAConfig* config) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
GBAConfigDirectory(path, PATH_MAX);
|
GBAConfigDirectory(path, PATH_MAX);
|
||||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||||
|
return GBAConfigSavePath(config, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAConfigLoadPath(struct GBAConfig* config, const char* path) {
|
||||||
|
return ConfigurationRead(&config->configTable, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAConfigSavePath(const struct GBAConfig* config, const char* path) {
|
||||||
return ConfigurationWrite(&config->configTable, path);
|
return ConfigurationWrite(&config->configTable, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,8 @@ void GBAConfigDeinit(struct GBAConfig*);
|
||||||
|
|
||||||
bool GBAConfigLoad(struct GBAConfig*);
|
bool GBAConfigLoad(struct GBAConfig*);
|
||||||
bool GBAConfigSave(const struct GBAConfig*);
|
bool GBAConfigSave(const struct GBAConfig*);
|
||||||
|
bool GBAConfigLoadPath(struct GBAConfig*, const char* path);
|
||||||
|
bool GBAConfigSavePath(const struct GBAConfig*, const char* path);
|
||||||
|
|
||||||
void GBAConfigMakePortable(const struct GBAConfig*);
|
void GBAConfigMakePortable(const struct GBAConfig*);
|
||||||
void GBAConfigDirectory(char* out, size_t outLength);
|
void GBAConfigDirectory(char* out, size_t outLength);
|
||||||
|
|
|
@ -285,6 +285,12 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
|
||||||
if (override->hardware & HW_TILT) {
|
if (override->hardware & HW_TILT) {
|
||||||
GBAHardwareInitTilt(&gba->memory.hw);
|
GBAHardwareInitTilt(&gba->memory.hw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (override->hardware & HW_GB_PLAYER_DETECTION) {
|
||||||
|
gba->memory.hw.devices |= HW_GB_PLAYER_DETECTION;
|
||||||
|
} else {
|
||||||
|
gba->memory.hw.devices &= ~HW_GB_PLAYER_DETECTION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (override->idleLoop != IDLE_LOOP_NONE) {
|
if (override->idleLoop != IDLE_LOOP_NONE) {
|
||||||
|
|
|
@ -96,6 +96,18 @@ static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GBAThreadStop {
|
||||||
|
struct GBAStopCallback d;
|
||||||
|
struct GBAThread* p;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void _stopCallback(struct GBAStopCallback* stop) {
|
||||||
|
struct GBAThreadStop* callback = (struct GBAThreadStop*) stop;
|
||||||
|
if (callback->p->stopCallback(callback->p)) {
|
||||||
|
_changeState(callback->p, THREAD_EXITING, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static THREAD_ENTRY _GBAThreadRun(void* context) {
|
static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
#ifdef USE_PTHREADS
|
#ifdef USE_PTHREADS
|
||||||
pthread_once(&_contextOnce, _createTLS);
|
pthread_once(&_contextOnce, _createTLS);
|
||||||
|
@ -129,6 +141,14 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
gba.logLevel = threadContext->logLevel;
|
gba.logLevel = threadContext->logLevel;
|
||||||
gba.logHandler = threadContext->logHandler;
|
gba.logHandler = threadContext->logHandler;
|
||||||
gba.stream = threadContext->stream;
|
gba.stream = threadContext->stream;
|
||||||
|
|
||||||
|
struct GBAThreadStop stop;
|
||||||
|
if (threadContext->stopCallback) {
|
||||||
|
stop.d.stop = _stopCallback;
|
||||||
|
stop.p = threadContext;
|
||||||
|
gba.stopCallback = &stop.d;
|
||||||
|
}
|
||||||
|
|
||||||
gba.idleOptimization = threadContext->idleOptimization;
|
gba.idleOptimization = threadContext->idleOptimization;
|
||||||
#ifdef USE_PTHREADS
|
#ifdef USE_PTHREADS
|
||||||
pthread_setspecific(_contextKey, threadContext);
|
pthread_setspecific(_contextKey, threadContext);
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct GBACheatSet;
|
||||||
struct GBAOptions;
|
struct GBAOptions;
|
||||||
|
|
||||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||||
|
typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext);
|
||||||
|
|
||||||
enum ThreadState {
|
enum ThreadState {
|
||||||
THREAD_INITIALIZED = -1,
|
THREAD_INITIALIZED = -1,
|
||||||
|
@ -86,6 +87,7 @@ struct GBAThread {
|
||||||
ThreadCallback startCallback;
|
ThreadCallback startCallback;
|
||||||
ThreadCallback cleanCallback;
|
ThreadCallback cleanCallback;
|
||||||
ThreadCallback frameCallback;
|
ThreadCallback frameCallback;
|
||||||
|
ThreadStopCallback stopCallback;
|
||||||
void* userData;
|
void* userData;
|
||||||
void (*run)(struct GBAThread*);
|
void (*run)(struct GBAThread*);
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
#include "gba/serialize.h"
|
#include "gba/serialize.h"
|
||||||
#include "gba/supervisor/overrides.h"
|
#include "gba/supervisor/overrides.h"
|
||||||
#include "gba/video.h"
|
#include "gba/video.h"
|
||||||
|
#include "util/circle-buffer.h"
|
||||||
#include "util/vfs.h"
|
#include "util/vfs.h"
|
||||||
|
|
||||||
#define SAMPLES 1024
|
#define SAMPLES 1024
|
||||||
|
#define RUMBLE_PWM 35
|
||||||
|
|
||||||
static retro_environment_t environCallback;
|
static retro_environment_t environCallback;
|
||||||
static retro_video_refresh_t videoCallback;
|
static retro_video_refresh_t videoCallback;
|
||||||
|
@ -22,11 +24,13 @@ static retro_audio_sample_batch_t audioCallback;
|
||||||
static retro_input_poll_t inputPollCallback;
|
static retro_input_poll_t inputPollCallback;
|
||||||
static retro_input_state_t inputCallback;
|
static retro_input_state_t inputCallback;
|
||||||
static retro_log_printf_t logCallback;
|
static retro_log_printf_t logCallback;
|
||||||
|
static retro_set_rumble_state_t rumbleCallback;
|
||||||
|
|
||||||
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
||||||
|
|
||||||
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
||||||
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
|
static void _setRumble(struct GBARumble* rumble, int enable);
|
||||||
|
|
||||||
static struct GBA gba;
|
static struct GBA gba;
|
||||||
static struct ARMCore cpu;
|
static struct ARMCore cpu;
|
||||||
|
@ -35,7 +39,11 @@ static struct VFile* rom;
|
||||||
static void* data;
|
static void* data;
|
||||||
static struct VFile* save;
|
static struct VFile* save;
|
||||||
static void* savedata;
|
static void* savedata;
|
||||||
|
static struct VFile* bios;
|
||||||
static struct GBAAVStream stream;
|
static struct GBAAVStream stream;
|
||||||
|
static int rumbleLevel;
|
||||||
|
static struct CircleBuffer rumbleHistory;
|
||||||
|
static struct GBARumble rumble;
|
||||||
|
|
||||||
unsigned retro_api_version(void) {
|
unsigned retro_api_version(void) {
|
||||||
return RETRO_API_VERSION;
|
return RETRO_API_VERSION;
|
||||||
|
@ -112,7 +120,15 @@ void retro_init(void) {
|
||||||
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
|
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
|
||||||
|
|
||||||
// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
|
// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
|
||||||
// TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE
|
|
||||||
|
struct retro_rumble_interface rumbleInterface;
|
||||||
|
if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
|
||||||
|
rumbleCallback = rumbleInterface.set_rumble_state;
|
||||||
|
CircleBufferInit(&rumbleHistory, RUMBLE_PWM);
|
||||||
|
rumble.setRumble = _setRumble;
|
||||||
|
} else {
|
||||||
|
rumbleCallback = 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct retro_log_callback log;
|
struct retro_log_callback log;
|
||||||
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
||||||
|
@ -132,8 +148,21 @@ void retro_init(void) {
|
||||||
gba.logHandler = GBARetroLog;
|
gba.logHandler = GBARetroLog;
|
||||||
gba.stream = &stream;
|
gba.stream = &stream;
|
||||||
gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
|
gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
|
||||||
|
if (rumbleCallback) {
|
||||||
|
gba.rumble = &rumble;
|
||||||
|
}
|
||||||
rom = 0;
|
rom = 0;
|
||||||
|
|
||||||
|
const char* sysDir = 0;
|
||||||
|
if (environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir)) {
|
||||||
|
char biosPath[PATH_MAX];
|
||||||
|
snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, "gba_bios.bin");
|
||||||
|
bios = VFileOpen(biosPath, O_RDONLY);
|
||||||
|
if (bios) {
|
||||||
|
GBALoadBIOS(&gba, bios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GBAVideoSoftwareRendererCreate(&renderer);
|
GBAVideoSoftwareRendererCreate(&renderer);
|
||||||
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||||
renderer.outputBufferStride = 256;
|
renderer.outputBufferStride = 256;
|
||||||
|
@ -148,6 +177,10 @@ void retro_init(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void retro_deinit(void) {
|
void retro_deinit(void) {
|
||||||
|
if (bios) {
|
||||||
|
bios->close(bios);
|
||||||
|
bios = 0;
|
||||||
|
}
|
||||||
GBADestroy(&gba);
|
GBADestroy(&gba);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +209,10 @@ void retro_run(void) {
|
||||||
|
|
||||||
void retro_reset(void) {
|
void retro_reset(void) {
|
||||||
ARMReset(&cpu);
|
ARMReset(&cpu);
|
||||||
|
|
||||||
|
if (rumbleCallback) {
|
||||||
|
CircleBufferClear(&rumbleHistory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool retro_load_game(const struct retro_game_info* game) {
|
bool retro_load_game(const struct retro_game_info* game) {
|
||||||
|
@ -213,6 +250,7 @@ void retro_unload_game(void) {
|
||||||
save = 0;
|
save = 0;
|
||||||
free(savedata);
|
free(savedata);
|
||||||
savedata = 0;
|
savedata = 0;
|
||||||
|
CircleBufferDeinit(&rumbleHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t retro_serialize_size(void) {
|
size_t retro_serialize_size(void) {
|
||||||
|
@ -311,10 +349,12 @@ void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* f
|
||||||
case GBA_LOG_INFO:
|
case GBA_LOG_INFO:
|
||||||
case GBA_LOG_GAME_ERROR:
|
case GBA_LOG_GAME_ERROR:
|
||||||
case GBA_LOG_SWI:
|
case GBA_LOG_SWI:
|
||||||
|
case GBA_LOG_STATUS:
|
||||||
retroLevel = RETRO_LOG_INFO;
|
retroLevel = RETRO_LOG_INFO;
|
||||||
break;
|
break;
|
||||||
case GBA_LOG_DEBUG:
|
case GBA_LOG_DEBUG:
|
||||||
case GBA_LOG_STUB:
|
case GBA_LOG_STUB:
|
||||||
|
case GBA_LOG_SIO:
|
||||||
retroLevel = RETRO_LOG_DEBUG;
|
retroLevel = RETRO_LOG_DEBUG;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -346,3 +386,18 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
||||||
renderer->getPixels(renderer, &stride, &pixels);
|
renderer->getPixels(renderer, &stride, &pixels);
|
||||||
videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride);
|
videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _setRumble(struct GBARumble* rumble, int enable) {
|
||||||
|
UNUSED(rumble);
|
||||||
|
if (!rumbleCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rumbleLevel += enable;
|
||||||
|
if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
|
||||||
|
int8_t oldLevel;
|
||||||
|
CircleBufferRead8(&rumbleHistory, &oldLevel);
|
||||||
|
rumbleLevel -= oldLevel;
|
||||||
|
}
|
||||||
|
CircleBufferWrite8(&rumbleHistory, enable);
|
||||||
|
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ public slots:
|
||||||
virtual void setBufferSamples(int samples) = 0;
|
virtual void setBufferSamples(int samples) = 0;
|
||||||
virtual void inputParametersChanged() = 0;
|
virtual void inputParametersChanged() = 0;
|
||||||
|
|
||||||
|
virtual unsigned sampleRate() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GBAThread* input() { return m_context; }
|
GBAThread* input() { return m_context; }
|
||||||
|
|
||||||
|
|
|
@ -83,3 +83,10 @@ void AudioProcessorQt::inputParametersChanged() {
|
||||||
m_device->setFormat(m_audioOutput->format());
|
m_device->setFormat(m_audioOutput->format());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned AudioProcessorQt::sampleRate() const {
|
||||||
|
if (!m_audioOutput) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_audioOutput->format().sampleRate();
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ public slots:
|
||||||
virtual void setBufferSamples(int samples);
|
virtual void setBufferSamples(int samples);
|
||||||
virtual void inputParametersChanged();
|
virtual void inputParametersChanged();
|
||||||
|
|
||||||
|
virtual unsigned sampleRate() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QAudioOutput* m_audioOutput;
|
QAudioOutput* m_audioOutput;
|
||||||
AudioDevice* m_device;
|
AudioDevice* m_device;
|
||||||
|
|
|
@ -54,3 +54,7 @@ void AudioProcessorSDL::setBufferSamples(int samples) {
|
||||||
|
|
||||||
void AudioProcessorSDL::inputParametersChanged() {
|
void AudioProcessorSDL::inputParametersChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned AudioProcessorSDL::sampleRate() const {
|
||||||
|
return m_audio.obtainedSpec.freq;
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ public slots:
|
||||||
virtual void setBufferSamples(int samples);
|
virtual void setBufferSamples(int samples);
|
||||||
virtual void inputParametersChanged();
|
virtual void inputParametersChanged();
|
||||||
|
|
||||||
|
virtual unsigned sampleRate() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GBASDLAudio m_audio;
|
GBASDLAudio m_audio;
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,6 +61,7 @@ set(SOURCE_FILES
|
||||||
GamepadAxisEvent.cpp
|
GamepadAxisEvent.cpp
|
||||||
GamepadButtonEvent.cpp
|
GamepadButtonEvent.cpp
|
||||||
InputController.cpp
|
InputController.cpp
|
||||||
|
InputProfile.cpp
|
||||||
KeyEditor.cpp
|
KeyEditor.cpp
|
||||||
LoadSaveState.cpp
|
LoadSaveState.cpp
|
||||||
LogController.cpp
|
LogController.cpp
|
||||||
|
|
|
@ -51,6 +51,10 @@ Display::Display(QWidget* parent)
|
||||||
{
|
{
|
||||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||||
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||||
|
connect(&m_mouseTimer, SIGNAL(timeout()), this, SIGNAL(hideCursor()));
|
||||||
|
m_mouseTimer.setSingleShot(true);
|
||||||
|
m_mouseTimer.setInterval(MOUSE_DISAPPEAR_TIMER);
|
||||||
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::resizeEvent(QResizeEvent*) {
|
void Display::resizeEvent(QResizeEvent*) {
|
||||||
|
@ -69,3 +73,9 @@ void Display::filter(bool filter) {
|
||||||
void Display::showMessage(const QString& message) {
|
void Display::showMessage(const QString& message) {
|
||||||
m_messagePainter.showMessage(message);
|
m_messagePainter.showMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Display::mouseMoveEvent(QMouseEvent*) {
|
||||||
|
emit showCursor();
|
||||||
|
m_mouseTimer.stop();
|
||||||
|
m_mouseTimer.start();
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,10 @@ public:
|
||||||
bool isAspectRatioLocked() const { return m_lockAspectRatio; }
|
bool isAspectRatioLocked() const { return m_lockAspectRatio; }
|
||||||
bool isFiltered() const { return m_filter; }
|
bool isFiltered() const { return m_filter; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void showCursor();
|
||||||
|
void hideCursor();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void startDrawing(GBAThread* context) = 0;
|
virtual void startDrawing(GBAThread* context) = 0;
|
||||||
virtual void stopDrawing() = 0;
|
virtual void stopDrawing() = 0;
|
||||||
|
@ -47,15 +51,19 @@ public slots:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent*);
|
void resizeEvent(QResizeEvent*);
|
||||||
|
virtual void mouseMoveEvent(QMouseEvent*) override;
|
||||||
|
|
||||||
MessagePainter* messagePainter() { return &m_messagePainter; }
|
MessagePainter* messagePainter() { return &m_messagePainter; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Driver s_driver;
|
static Driver s_driver;
|
||||||
|
static const int MOUSE_DISAPPEAR_TIMER = 1000;
|
||||||
|
|
||||||
MessagePainter m_messagePainter;
|
MessagePainter m_messagePainter;
|
||||||
bool m_lockAspectRatio;
|
bool m_lockAspectRatio;
|
||||||
bool m_filter;
|
bool m_filter;
|
||||||
|
QTimer m_mouseTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
|
||||||
, m_drawThread(nullptr)
|
, m_drawThread(nullptr)
|
||||||
, m_context(nullptr)
|
, m_context(nullptr)
|
||||||
{
|
{
|
||||||
|
m_gl->setMouseTracking(true);
|
||||||
|
m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayGL::~DisplayGL() {
|
DisplayGL::~DisplayGL() {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
|
|
||||||
#include <QGLWidget>
|
#include <QGLWidget>
|
||||||
|
#include <QMouseEvent>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent*) override {}
|
void paintEvent(QPaintEvent*) override {}
|
||||||
void resizeEvent(QResizeEvent*) override {}
|
void resizeEvent(QResizeEvent*) override {}
|
||||||
|
void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class PainterGL;
|
class PainterGL;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "GBAKeyEditor.h"
|
#include "GBAKeyEditor.h"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
@ -24,6 +25,7 @@ const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
|
||||||
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
|
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_profileSelect(nullptr)
|
, m_profileSelect(nullptr)
|
||||||
|
, m_clear(nullptr)
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
, m_profile(profile)
|
, m_profile(profile)
|
||||||
, m_controller(controller)
|
, m_controller(controller)
|
||||||
|
@ -32,6 +34,7 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
||||||
setMinimumSize(300, 300);
|
setMinimumSize(300, 300);
|
||||||
|
|
||||||
const GBAInputMap* map = controller->map();
|
const GBAInputMap* map = controller->map();
|
||||||
|
controller->stealFocus(this);
|
||||||
|
|
||||||
m_keyDU = new KeyEditor(this);
|
m_keyDU = new KeyEditor(this);
|
||||||
m_keyDD = new KeyEditor(this);
|
m_keyDD = new KeyEditor(this);
|
||||||
|
@ -64,31 +67,36 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
||||||
m_controller->loadProfile(m_type, m_profile);
|
m_controller->loadProfile(m_type, m_profile);
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_clear = new QWidget(this);
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout;
|
||||||
|
m_clear->setLayout(layout);
|
||||||
|
layout->setSpacing(6);
|
||||||
|
|
||||||
|
QPushButton* clearButton = new QPushButton(tr("Clear Button"));
|
||||||
|
layout->addWidget(clearButton);
|
||||||
|
connect(clearButton, &QAbstractButton::pressed, [this]() {
|
||||||
|
if (!findFocus()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
|
||||||
|
(*m_currentKey)->clearButton();
|
||||||
|
(*m_currentKey)->blockSignals(signalsBlocked);
|
||||||
|
});
|
||||||
|
|
||||||
|
QPushButton* clearAxis = new QPushButton(tr("Clear Analog"));
|
||||||
|
layout->addWidget(clearAxis);
|
||||||
|
connect(clearAxis, &QAbstractButton::pressed, [this]() {
|
||||||
|
if (!findFocus()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
|
||||||
|
(*m_currentKey)->clearAxis();
|
||||||
|
(*m_currentKey)->blockSignals(signalsBlocked);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDD, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDL, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDR, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keySelect, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyStart, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyA, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyB, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyL, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyR, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
|
||||||
|
|
||||||
connect(m_keyDU, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDD, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyDR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keySelect, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyStart, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyA, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyB, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
connect(m_keyR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
|
||||||
|
|
||||||
m_buttons = new QWidget(this);
|
m_buttons = new QWidget(this);
|
||||||
QVBoxLayout* layout = new QVBoxLayout;
|
QVBoxLayout* layout = new QVBoxLayout;
|
||||||
m_buttons->setLayout(layout);
|
m_buttons->setLayout(layout);
|
||||||
|
@ -115,6 +123,11 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
||||||
m_keyR
|
m_keyR
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (auto& key : m_keyOrder) {
|
||||||
|
connect(key, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||||
|
connect(key, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||||
|
}
|
||||||
|
|
||||||
m_currentKey = m_keyOrder.end();
|
m_currentKey = m_keyOrder.end();
|
||||||
|
|
||||||
m_background.load(":/res/keymap.qpic");
|
m_background.load(":/res/keymap.qpic");
|
||||||
|
@ -141,7 +154,11 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
|
||||||
setLocation(m_keyR, 0.9, 0.1);
|
setLocation(m_keyR, 0.9, 0.1);
|
||||||
|
|
||||||
if (m_profileSelect) {
|
if (m_profileSelect) {
|
||||||
setLocation(m_profileSelect, 0.5, 0.7);
|
setLocation(m_profileSelect, 0.5, 0.67);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_clear) {
|
||||||
|
setLocation(m_clear, 0.5, 0.77);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +168,19 @@ void GBAKeyEditor::paintEvent(QPaintEvent* event) {
|
||||||
painter.drawPicture(0, 0, m_background);
|
painter.drawPicture(0, 0, m_background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBAKeyEditor::closeEvent(QCloseEvent*) {
|
||||||
|
m_controller->releaseFocus(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAKeyEditor::event(QEvent* event) {
|
||||||
|
if (event->type() == QEvent::WindowActivate) {
|
||||||
|
m_controller->stealFocus(this);
|
||||||
|
} else if (event->type() == QEvent::WindowDeactivate) {
|
||||||
|
m_controller->releaseFocus(this);
|
||||||
|
}
|
||||||
|
return QWidget::event(event);
|
||||||
|
}
|
||||||
|
|
||||||
void GBAKeyEditor::setNext() {
|
void GBAKeyEditor::setNext() {
|
||||||
findFocus();
|
findFocus();
|
||||||
|
|
||||||
|
@ -167,6 +197,10 @@ void GBAKeyEditor::setNext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAKeyEditor::save() {
|
void GBAKeyEditor::save() {
|
||||||
|
#ifdef BUILD_SDL
|
||||||
|
m_controller->unbindAllAxes(m_type);
|
||||||
|
#endif
|
||||||
|
|
||||||
bindKey(m_keyDU, GBA_KEY_UP);
|
bindKey(m_keyDU, GBA_KEY_UP);
|
||||||
bindKey(m_keyDD, GBA_KEY_DOWN);
|
bindKey(m_keyDD, GBA_KEY_DOWN);
|
||||||
bindKey(m_keyDL, GBA_KEY_LEFT);
|
bindKey(m_keyDL, GBA_KEY_LEFT);
|
||||||
|
@ -239,14 +273,11 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) {
|
||||||
|
|
||||||
void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
|
void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
|
||||||
#ifdef BUILD_SDL
|
#ifdef BUILD_SDL
|
||||||
if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) {
|
if (m_type == SDL_BINDING_BUTTON) {
|
||||||
m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key);
|
m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key);
|
||||||
} else {
|
|
||||||
#endif
|
|
||||||
m_controller->bindKey(m_type, keyEditor->value(), key);
|
|
||||||
#ifdef BUILD_SDL
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
m_controller->bindKey(m_type, keyEditor->value(), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GBAKeyEditor::findFocus() {
|
bool GBAKeyEditor::findFocus() {
|
||||||
|
|
|
@ -35,6 +35,8 @@ public slots:
|
||||||
protected:
|
protected:
|
||||||
virtual void resizeEvent(QResizeEvent*) override;
|
virtual void resizeEvent(QResizeEvent*) override;
|
||||||
virtual void paintEvent(QPaintEvent*) override;
|
virtual void paintEvent(QPaintEvent*) override;
|
||||||
|
virtual bool event(QEvent*) override;
|
||||||
|
virtual void closeEvent(QCloseEvent*) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setNext();
|
void setNext();
|
||||||
|
@ -64,6 +66,7 @@ private:
|
||||||
KeyEditor* keyById(GBAKey);
|
KeyEditor* keyById(GBAKey);
|
||||||
|
|
||||||
QComboBox* m_profileSelect;
|
QComboBox* m_profileSelect;
|
||||||
|
QWidget* m_clear;
|
||||||
QWidget* m_buttons;
|
QWidget* m_buttons;
|
||||||
KeyEditor* m_keyDU;
|
KeyEditor* m_keyDU;
|
||||||
KeyEditor* m_keyDD;
|
KeyEditor* m_keyDD;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "AudioProcessor.h"
|
#include "AudioProcessor.h"
|
||||||
#include "InputController.h"
|
#include "InputController.h"
|
||||||
|
#include "LogController.h"
|
||||||
#include "MultiplayerController.h"
|
#include "MultiplayerController.h"
|
||||||
#include "VFileDevice.h"
|
#include "VFileDevice.h"
|
||||||
|
|
||||||
|
@ -51,6 +52,8 @@ GameController::GameController(QObject* parent)
|
||||||
, m_inputController(nullptr)
|
, m_inputController(nullptr)
|
||||||
, m_multiplayer(nullptr)
|
, m_multiplayer(nullptr)
|
||||||
, m_stateSlot(1)
|
, m_stateSlot(1)
|
||||||
|
, m_backupLoadState(nullptr)
|
||||||
|
, m_backupSaveState(nullptr)
|
||||||
{
|
{
|
||||||
m_renderer = new GBAVideoSoftwareRenderer;
|
m_renderer = new GBAVideoSoftwareRenderer;
|
||||||
GBAVideoSoftwareRendererCreate(m_renderer);
|
GBAVideoSoftwareRendererCreate(m_renderer);
|
||||||
|
@ -90,6 +93,13 @@ GameController::GameController(QObject* parent)
|
||||||
context->gba->rumble = controller->m_inputController->rumble();
|
context->gba->rumble = controller->m_inputController->rumble();
|
||||||
context->gba->rotationSource = controller->m_inputController->rotationSource();
|
context->gba->rotationSource = controller->m_inputController->rotationSource();
|
||||||
controller->m_fpsTarget = context->fpsTarget;
|
controller->m_fpsTarget = context->fpsTarget;
|
||||||
|
|
||||||
|
if (GBALoadState(context, context->stateDir, 0)) {
|
||||||
|
VFile* vf = GBAGetState(context->gba, context->stateDir, 0, true);
|
||||||
|
if (vf) {
|
||||||
|
vf->truncate(vf, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
controller->gameStarted(context);
|
controller->gameStarted(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,8 +121,22 @@ GameController::GameController(QObject* parent)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
m_threadContext.stopCallback = [](GBAThread* context) {
|
||||||
|
if (!context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
if (!GBASaveState(context, context->stateDir, 0, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(controller, "closeGame");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
m_threadContext.logHandler = [](GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
|
m_threadContext.logHandler = [](GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
|
||||||
static const char* stubMessage = "Stub software interrupt";
|
static const char* stubMessage = "Stub software interrupt: %02X";
|
||||||
|
static const char* savestateMessage = "State %i loaded";
|
||||||
|
static const char* savestateFailedMessage = "State %i failed to load";
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -123,6 +147,25 @@ GameController::GameController(QObject* parent)
|
||||||
int immediate = va_arg(argc, int);
|
int immediate = va_arg(argc, int);
|
||||||
va_end(argc);
|
va_end(argc);
|
||||||
controller->unimplementedBiosCall(immediate);
|
controller->unimplementedBiosCall(immediate);
|
||||||
|
} else if (level == GBA_LOG_STATUS) {
|
||||||
|
// Slot 0 is reserved for suspend points
|
||||||
|
if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
|
||||||
|
va_list argc;
|
||||||
|
va_copy(argc, args);
|
||||||
|
int slot = va_arg(argc, int);
|
||||||
|
va_end(argc);
|
||||||
|
if (slot == 0) {
|
||||||
|
format = "Loaded suspend state";
|
||||||
|
}
|
||||||
|
} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) {
|
||||||
|
va_list argc;
|
||||||
|
va_copy(argc, args);
|
||||||
|
int slot = va_arg(argc, int);
|
||||||
|
va_end(argc);
|
||||||
|
if (slot == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (level == GBA_LOG_FATAL) {
|
if (level == GBA_LOG_FATAL) {
|
||||||
QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
|
QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
|
||||||
|
@ -162,6 +205,7 @@ GameController::~GameController() {
|
||||||
GBACheatDeviceDestroy(&m_cheatDevice);
|
GBACheatDeviceDestroy(&m_cheatDevice);
|
||||||
delete m_renderer;
|
delete m_renderer;
|
||||||
delete[] m_drawContext;
|
delete[] m_drawContext;
|
||||||
|
delete m_backupLoadState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::setMultiplayerController(MultiplayerController* controller) {
|
void GameController::setMultiplayerController(MultiplayerController* controller) {
|
||||||
|
@ -475,7 +519,9 @@ void GameController::startRewinding() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_wasPaused = isPaused();
|
m_wasPaused = isPaused();
|
||||||
|
bool signalsBlocked = blockSignals(true);
|
||||||
setPaused(true);
|
setPaused(true);
|
||||||
|
blockSignals(signalsBlocked);
|
||||||
m_rewindTimer.start();
|
m_rewindTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +530,9 @@ void GameController::stopRewinding() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_rewindTimer.stop();
|
m_rewindTimer.stop();
|
||||||
|
bool signalsBlocked = blockSignals(true);
|
||||||
setPaused(m_wasPaused);
|
setPaused(m_wasPaused);
|
||||||
|
blockSignals(signalsBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::keyPressed(int key) {
|
void GameController::keyPressed(int key) {
|
||||||
|
@ -556,11 +604,16 @@ void GameController::setUseBIOS(bool use) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::loadState(int slot) {
|
void GameController::loadState(int slot) {
|
||||||
if (slot > 0) {
|
if (slot > 0 && slot != m_stateSlot) {
|
||||||
m_stateSlot = slot;
|
m_stateSlot = slot;
|
||||||
|
m_backupSaveState.clear();
|
||||||
}
|
}
|
||||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||||
GameController* controller = static_cast<GameController*>(context->userData);
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
if (!controller->m_backupLoadState) {
|
||||||
|
controller->m_backupLoadState = new GBASerializedState;
|
||||||
|
}
|
||||||
|
GBASerialize(context->gba, controller->m_backupLoadState);
|
||||||
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
|
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
|
||||||
controller->frameAvailable(controller->m_drawContext);
|
controller->frameAvailable(controller->m_drawContext);
|
||||||
controller->stateLoaded(context);
|
controller->stateLoaded(context);
|
||||||
|
@ -574,10 +627,50 @@ void GameController::saveState(int slot) {
|
||||||
}
|
}
|
||||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||||
GameController* controller = static_cast<GameController*>(context->userData);
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, false);
|
||||||
|
if (vf) {
|
||||||
|
controller->m_backupSaveState.resize(vf->size(vf));
|
||||||
|
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
||||||
|
vf->close(vf);
|
||||||
|
}
|
||||||
GBASaveState(context, context->stateDir, controller->m_stateSlot, true);
|
GBASaveState(context, context->stateDir, controller->m_stateSlot, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameController::loadBackupState() {
|
||||||
|
if (!m_backupLoadState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||||
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
if (GBADeserialize(context->gba, controller->m_backupLoadState)) {
|
||||||
|
GBALog(context->gba, GBA_LOG_STATUS, "Undid state load");
|
||||||
|
controller->frameAvailable(controller->m_drawContext);
|
||||||
|
controller->stateLoaded(context);
|
||||||
|
}
|
||||||
|
delete controller->m_backupLoadState;
|
||||||
|
controller->m_backupLoadState = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameController::saveBackupState() {
|
||||||
|
if (m_backupSaveState.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||||
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
|
VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, true);
|
||||||
|
if (vf) {
|
||||||
|
vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
|
||||||
|
vf->close(vf);
|
||||||
|
GBALog(context->gba, GBA_LOG_STATUS, "Undid state save");
|
||||||
|
}
|
||||||
|
controller->m_backupSaveState.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void GameController::setVideoSync(bool set) {
|
void GameController::setVideoSync(bool set) {
|
||||||
m_videoSync = set;
|
m_videoSync = set;
|
||||||
if (!m_turbo) {
|
if (!m_turbo) {
|
||||||
|
@ -746,7 +839,7 @@ void GameController::redoSamples(int samples) {
|
||||||
if (m_threadContext.gba) {
|
if (m_threadContext.gba) {
|
||||||
sampleRate = m_threadContext.gba->audio.sampleRate;
|
sampleRate = m_threadContext.gba->audio.sampleRate;
|
||||||
}
|
}
|
||||||
ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100);
|
ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, m_audioProcess->sampleRate());
|
||||||
m_threadContext.audioBuffers = ceil(samples / ratio);
|
m_threadContext.audioBuffers = ceil(samples / ratio);
|
||||||
#else
|
#else
|
||||||
m_threadContext.audioBuffers = samples;
|
m_threadContext.audioBuffers = samples;
|
||||||
|
|
|
@ -55,7 +55,7 @@ public:
|
||||||
void threadContinue();
|
void threadContinue();
|
||||||
|
|
||||||
bool isPaused();
|
bool isPaused();
|
||||||
bool isLoaded() { return m_gameOpen; }
|
bool isLoaded() { return m_gameOpen && GBAThreadIsActive(&m_threadContext); }
|
||||||
|
|
||||||
bool audioSync() const { return m_audioSync; }
|
bool audioSync() const { return m_audioSync; }
|
||||||
bool videoSync() const { return m_videoSync; }
|
bool videoSync() const { return m_videoSync; }
|
||||||
|
@ -120,6 +120,8 @@ public slots:
|
||||||
void setFPSTarget(float fps);
|
void setFPSTarget(float fps);
|
||||||
void loadState(int slot = 0);
|
void loadState(int slot = 0);
|
||||||
void saveState(int slot = 0);
|
void saveState(int slot = 0);
|
||||||
|
void loadBackupState();
|
||||||
|
void saveBackupState();
|
||||||
void setVideoSync(bool);
|
void setVideoSync(bool);
|
||||||
void setAudioSync(bool);
|
void setAudioSync(bool);
|
||||||
void setFrameskip(int);
|
void setFrameskip(int);
|
||||||
|
@ -192,6 +194,8 @@ private:
|
||||||
bool m_wasPaused;
|
bool m_wasPaused;
|
||||||
|
|
||||||
int m_stateSlot;
|
int m_stateSlot;
|
||||||
|
GBASerializedState* m_backupLoadState;
|
||||||
|
QByteArray m_backupSaveState;
|
||||||
|
|
||||||
InputController* m_inputController;
|
InputController* m_inputController;
|
||||||
MultiplayerController* m_multiplayer;
|
MultiplayerController* m_multiplayer;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "ConfigController.h"
|
#include "ConfigController.h"
|
||||||
#include "GamepadAxisEvent.h"
|
#include "GamepadAxisEvent.h"
|
||||||
#include "GamepadButtonEvent.h"
|
#include "GamepadButtonEvent.h"
|
||||||
|
#include "InputProfile.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
@ -24,7 +25,7 @@ int InputController::s_sdlInited = 0;
|
||||||
GBASDLEvents InputController::s_sdlEvents;
|
GBASDLEvents InputController::s_sdlEvents;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
InputController::InputController(int playerId, QObject* parent)
|
InputController::InputController(int playerId, QWidget* topLevel, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_playerId(playerId)
|
, m_playerId(playerId)
|
||||||
, m_config(nullptr)
|
, m_config(nullptr)
|
||||||
|
@ -33,6 +34,8 @@ InputController::InputController(int playerId, QObject* parent)
|
||||||
, m_playerAttached(false)
|
, m_playerAttached(false)
|
||||||
#endif
|
#endif
|
||||||
, m_allowOpposing(false)
|
, m_allowOpposing(false)
|
||||||
|
, m_topLevel(topLevel)
|
||||||
|
, m_focusParent(topLevel)
|
||||||
{
|
{
|
||||||
GBAInputMapInit(&m_inputMap);
|
GBAInputMapInit(&m_inputMap);
|
||||||
|
|
||||||
|
@ -106,8 +109,15 @@ void InputController::loadConfiguration(uint32_t type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
||||||
GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
bool loaded = GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
||||||
recalibrateAxes();
|
recalibrateAxes();
|
||||||
|
if (!loaded) {
|
||||||
|
const InputProfile* ip = InputProfile::findProfile(profile);
|
||||||
|
if (ip) {
|
||||||
|
ip->apply(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit profileLoaded(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputController::saveConfiguration() {
|
void InputController::saveConfiguration() {
|
||||||
|
@ -403,6 +413,10 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct
|
||||||
GBAInputBindAxis(&m_inputMap, type, axis, &description);
|
GBAInputBindAxis(&m_inputMap, type, axis, &description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputController::unbindAllAxes(uint32_t type) {
|
||||||
|
GBAInputUnbindAllAxes(&m_inputMap, type);
|
||||||
|
}
|
||||||
|
|
||||||
void InputController::testGamepad(int type) {
|
void InputController::testGamepad(int type) {
|
||||||
auto activeAxes = activeGamepadAxes(type);
|
auto activeAxes = activeGamepadAxes(type);
|
||||||
auto oldAxes = m_activeAxes;
|
auto oldAxes = m_activeAxes;
|
||||||
|
@ -424,7 +438,7 @@ void InputController::testGamepad(int type) {
|
||||||
if (newlyAboveThreshold) {
|
if (newlyAboveThreshold) {
|
||||||
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
|
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
|
||||||
postPendingEvent(event->gbaKey());
|
postPendingEvent(event->gbaKey());
|
||||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
sendGamepadEvent(event);
|
||||||
if (!event->isAccepted()) {
|
if (!event->isAccepted()) {
|
||||||
clearPendingEvent(event->gbaKey());
|
clearPendingEvent(event->gbaKey());
|
||||||
}
|
}
|
||||||
|
@ -433,7 +447,7 @@ void InputController::testGamepad(int type) {
|
||||||
for (auto axis : oldAxes) {
|
for (auto axis : oldAxes) {
|
||||||
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
|
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
|
||||||
clearPendingEvent(event->gbaKey());
|
clearPendingEvent(event->gbaKey());
|
||||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
sendGamepadEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!QApplication::focusWidget()) {
|
if (!QApplication::focusWidget()) {
|
||||||
|
@ -446,7 +460,7 @@ void InputController::testGamepad(int type) {
|
||||||
for (int button : activeButtons) {
|
for (int button : activeButtons) {
|
||||||
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
|
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
|
||||||
postPendingEvent(event->gbaKey());
|
postPendingEvent(event->gbaKey());
|
||||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
sendGamepadEvent(event);
|
||||||
if (!event->isAccepted()) {
|
if (!event->isAccepted()) {
|
||||||
clearPendingEvent(event->gbaKey());
|
clearPendingEvent(event->gbaKey());
|
||||||
}
|
}
|
||||||
|
@ -454,10 +468,23 @@ void InputController::testGamepad(int type) {
|
||||||
for (int button : oldButtons) {
|
for (int button : oldButtons) {
|
||||||
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
|
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
|
||||||
clearPendingEvent(event->gbaKey());
|
clearPendingEvent(event->gbaKey());
|
||||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
sendGamepadEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputController::sendGamepadEvent(QEvent* event) {
|
||||||
|
QWidget* focusWidget = nullptr;
|
||||||
|
if (m_focusParent) {
|
||||||
|
focusWidget = m_focusParent->focusWidget();
|
||||||
|
if (!focusWidget) {
|
||||||
|
focusWidget = m_focusParent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
focusWidget = QApplication::focusWidget();
|
||||||
|
}
|
||||||
|
QApplication::sendEvent(focusWidget, event);
|
||||||
|
}
|
||||||
|
|
||||||
void InputController::postPendingEvent(GBAKey key) {
|
void InputController::postPendingEvent(GBAKey key) {
|
||||||
m_pendingEvents.insert(key);
|
m_pendingEvents.insert(key);
|
||||||
}
|
}
|
||||||
|
@ -483,3 +510,13 @@ void InputController::setScreensaverSuspendable(bool suspendable) {
|
||||||
GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
|
GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void InputController::stealFocus(QWidget* focus) {
|
||||||
|
m_focusParent = focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputController::releaseFocus(QWidget* focus) {
|
||||||
|
if (focus == m_focusParent) {
|
||||||
|
m_focusParent = m_topLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static const uint32_t KEYBOARD = 0x51545F4B;
|
static const uint32_t KEYBOARD = 0x51545F4B;
|
||||||
|
|
||||||
InputController(int playerId = 0, QObject* parent = nullptr);
|
InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
|
||||||
~InputController();
|
~InputController();
|
||||||
|
|
||||||
void setConfiguration(ConfigController* config);
|
void setConfiguration(ConfigController* config);
|
||||||
|
@ -60,6 +60,7 @@ public:
|
||||||
void recalibrateAxes();
|
void recalibrateAxes();
|
||||||
|
|
||||||
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
|
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
|
||||||
|
void unbindAllAxes(uint32_t type);
|
||||||
|
|
||||||
QStringList connectedGamepads(uint32_t type) const;
|
QStringList connectedGamepads(uint32_t type) const;
|
||||||
int gamepad(uint32_t type) const;
|
int gamepad(uint32_t type) const;
|
||||||
|
@ -74,9 +75,15 @@ public:
|
||||||
float gyroSensitivity() const;
|
float gyroSensitivity() const;
|
||||||
void setGyroSensitivity(float sensitivity);
|
void setGyroSensitivity(float sensitivity);
|
||||||
|
|
||||||
|
void stealFocus(QWidget* focus);
|
||||||
|
void releaseFocus(QWidget* focus);
|
||||||
|
|
||||||
GBARumble* rumble();
|
GBARumble* rumble();
|
||||||
GBARotationSource* rotationSource();
|
GBARotationSource* rotationSource();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void profileLoaded(const QString& profile);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void testGamepad(int type);
|
void testGamepad(int type);
|
||||||
|
|
||||||
|
@ -91,11 +98,14 @@ private:
|
||||||
void postPendingEvent(GBAKey);
|
void postPendingEvent(GBAKey);
|
||||||
void clearPendingEvent(GBAKey);
|
void clearPendingEvent(GBAKey);
|
||||||
bool hasPendingEvent(GBAKey) const;
|
bool hasPendingEvent(GBAKey) const;
|
||||||
|
void sendGamepadEvent(QEvent*);
|
||||||
|
|
||||||
GBAInputMap m_inputMap;
|
GBAInputMap m_inputMap;
|
||||||
ConfigController* m_config;
|
ConfigController* m_config;
|
||||||
int m_playerId;
|
int m_playerId;
|
||||||
bool m_allowOpposing;
|
bool m_allowOpposing;
|
||||||
|
QWidget* m_topLevel;
|
||||||
|
QWidget* m_focusParent;
|
||||||
|
|
||||||
#ifdef BUILD_SDL
|
#ifdef BUILD_SDL
|
||||||
static int s_sdlInited;
|
static int s_sdlInited;
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
/* 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)
|
||||||
|
(int[GBA_KEY_MAX]) {
|
||||||
|
/*keyA */ 11,
|
||||||
|
/*keyB */ 10,
|
||||||
|
/*keySelect */ 5,
|
||||||
|
/*keyStart */ 4,
|
||||||
|
/*keyRight */ 3,
|
||||||
|
/*keyLeft */ 2,
|
||||||
|
/*keyUp */ 0,
|
||||||
|
/*keyDown */ 1,
|
||||||
|
/*keyR */ 9,
|
||||||
|
/*keyL */ 8
|
||||||
|
},
|
||||||
|
(ShortcutButton[]) {
|
||||||
|
{"loadState", 12},
|
||||||
|
{"saveState", 13},
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
(ShortcutAxis[]) {
|
||||||
|
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||||
|
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 4},
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"(Microsoft X-Box 360 pad|Xbox Gamepad \\(userspace driver\\))", // Linux
|
||||||
|
(int[GBA_KEY_MAX]) {
|
||||||
|
/*keyA */ 1,
|
||||||
|
/*keyB */ 0,
|
||||||
|
/*keySelect */ 6,
|
||||||
|
/*keyStart */ 7,
|
||||||
|
/*keyRight */ -1,
|
||||||
|
/*keyLeft */ -1,
|
||||||
|
/*keyUp */ -1,
|
||||||
|
/*keyDown */ -1,
|
||||||
|
/*keyR */ 5,
|
||||||
|
/*keyL */ 4
|
||||||
|
},
|
||||||
|
(ShortcutButton[]) {
|
||||||
|
{"loadState", 2},
|
||||||
|
{"saveState", 3},
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
(ShortcutAxis[]) {
|
||||||
|
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||||
|
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Controller", // The Xbox 360 controller drivers on OS X are vague...
|
||||||
|
(int[GBA_KEY_MAX]) {
|
||||||
|
/*keyA */ 1,
|
||||||
|
/*keyB */ 0,
|
||||||
|
/*keySelect */ 9,
|
||||||
|
/*keyStart */ 8,
|
||||||
|
/*keyRight */ 14,
|
||||||
|
/*keyLeft */ 13,
|
||||||
|
/*keyUp */ 11,
|
||||||
|
/*keyDown */ 12,
|
||||||
|
/*keyR */ 5,
|
||||||
|
/*keyL */ 4
|
||||||
|
},
|
||||||
|
(ShortcutButton[]) {
|
||||||
|
{"loadState", 2},
|
||||||
|
{"saveState", 3},
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
(ShortcutAxis[]) {
|
||||||
|
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||||
|
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PLAYSTATION\\(R\\)3 Controller", // DualShock 3 (OS X)
|
||||||
|
(int[GBA_KEY_MAX]) {
|
||||||
|
/*keyA */ 13,
|
||||||
|
/*keyB */ 14,
|
||||||
|
/*keySelect */ 0,
|
||||||
|
/*keyStart */ 3,
|
||||||
|
/*keyRight */ 5,
|
||||||
|
/*keyLeft */ 7,
|
||||||
|
/*keyUp */ 4,
|
||||||
|
/*keyDown */ 6,
|
||||||
|
/*keyR */ 11,
|
||||||
|
/*keyL */ 10
|
||||||
|
},
|
||||||
|
(ShortcutButton[]) {
|
||||||
|
{"loadState", 15},
|
||||||
|
{"saveState", 12},
|
||||||
|
{"holdFastForward", 9},
|
||||||
|
{"holdRewind", 8},
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Wiimote \\(..-..-..-..-..-..\\)", // WJoy (OS X)
|
||||||
|
(int[GBA_KEY_MAX]) {
|
||||||
|
/*keyA */ 15,
|
||||||
|
/*keyB */ 16,
|
||||||
|
/*keySelect */ 7,
|
||||||
|
/*keyStart */ 6,
|
||||||
|
/*keyRight */ 14,
|
||||||
|
/*keyLeft */ 13,
|
||||||
|
/*keyUp */ 11,
|
||||||
|
/*keyDown */ 12,
|
||||||
|
/*keyR */ 20,
|
||||||
|
/*keyL */ 19
|
||||||
|
},
|
||||||
|
(ShortcutButton[]) {
|
||||||
|
{"loadState", 18},
|
||||||
|
{"saveState", 17},
|
||||||
|
{"holdFastForward", 22},
|
||||||
|
{"holdRewind", 21},
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr InputProfile::InputProfile(const char* name,
|
||||||
|
int keys[GBA_KEY_MAX],
|
||||||
|
const ShortcutButton* shortcutButtons,
|
||||||
|
const ShortcutAxis* shortcutAxes,
|
||||||
|
AxisValue axes[GBA_KEY_MAX],
|
||||||
|
const struct Coord& tiltAxis,
|
||||||
|
const struct Coord& gyroAxis,
|
||||||
|
float gyroSensitivity)
|
||||||
|
: m_profileName(name)
|
||||||
|
, m_keys {
|
||||||
|
keys[GBA_KEY_A],
|
||||||
|
keys[GBA_KEY_B],
|
||||||
|
keys[GBA_KEY_SELECT],
|
||||||
|
keys[GBA_KEY_START],
|
||||||
|
keys[GBA_KEY_RIGHT],
|
||||||
|
keys[GBA_KEY_LEFT],
|
||||||
|
keys[GBA_KEY_UP],
|
||||||
|
keys[GBA_KEY_DOWN],
|
||||||
|
keys[GBA_KEY_R],
|
||||||
|
keys[GBA_KEY_L]
|
||||||
|
}
|
||||||
|
, m_shortcutButtons(shortcutButtons)
|
||||||
|
, m_shortcutAxes(shortcutAxes)
|
||||||
|
, m_axes {
|
||||||
|
axes[GBA_KEY_A],
|
||||||
|
axes[GBA_KEY_B],
|
||||||
|
axes[GBA_KEY_SELECT],
|
||||||
|
axes[GBA_KEY_START],
|
||||||
|
axes[GBA_KEY_RIGHT],
|
||||||
|
axes[GBA_KEY_LEFT],
|
||||||
|
axes[GBA_KEY_UP],
|
||||||
|
axes[GBA_KEY_DOWN],
|
||||||
|
axes[GBA_KEY_R],
|
||||||
|
axes[GBA_KEY_L]
|
||||||
|
}
|
||||||
|
, m_tiltAxis(tiltAxis)
|
||||||
|
, m_gyroAxis(gyroAxis)
|
||||||
|
, m_gyroSensitivity(gyroSensitivity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputProfile* InputProfile::findProfile(const QString& name) {
|
||||||
|
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(InputController* controller) const {
|
||||||
|
for (size_t i = 0; i < GBA_KEY_MAX; ++i) {
|
||||||
|
#ifdef BUILD_SDL
|
||||||
|
controller->bindKey(SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i));
|
||||||
|
controller->bindAxis(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 {
|
||||||
|
for (size_t i = 0; m_shortcutButtons[i].shortcut; ++i) {
|
||||||
|
const ShortcutButton& shortcut = m_shortcutButtons[i];
|
||||||
|
if (QLatin1String(shortcut.shortcut) == shortcutName) {
|
||||||
|
*button = shortcut.button;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputProfile::lookupShortcutAxis(const QString& shortcutName, int* axis, GamepadAxisEvent::Direction* direction) const {
|
||||||
|
for (size_t i = 0; m_shortcutAxes[i].shortcut; ++i) {
|
||||||
|
const ShortcutAxis& shortcut = m_shortcutAxes[i];
|
||||||
|
if (QLatin1String(shortcut.shortcut) == shortcutName) {
|
||||||
|
*axis = shortcut.axis;
|
||||||
|
*direction = shortcut.direction;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* 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"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "gba/interface.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QGBA {
|
||||||
|
|
||||||
|
class InputController;
|
||||||
|
|
||||||
|
class InputProfile {
|
||||||
|
public:
|
||||||
|
static const InputProfile* findProfile(const QString& name);
|
||||||
|
|
||||||
|
void apply(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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShortcutButton {
|
||||||
|
const char* shortcut;
|
||||||
|
int button;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShortcutAxis {
|
||||||
|
const char* shortcut;
|
||||||
|
GamepadAxisEvent::Direction direction;
|
||||||
|
int axis;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr InputProfile(const char* name,
|
||||||
|
int keys[GBA_KEY_MAX],
|
||||||
|
const ShortcutButton* shortcutButtons = (ShortcutButton[]) {{}},
|
||||||
|
const ShortcutAxis* shortcutAxes = (ShortcutAxis[]) {{}},
|
||||||
|
AxisValue axes[GBA_KEY_MAX] = (AxisValue[GBA_KEY_MAX]) {
|
||||||
|
{}, {}, {}, {},
|
||||||
|
{ GamepadAxisEvent::Direction::POSITIVE, 0 },
|
||||||
|
{ GamepadAxisEvent::Direction::NEGATIVE, 0 },
|
||||||
|
{ GamepadAxisEvent::Direction::NEGATIVE, 1 },
|
||||||
|
{ GamepadAxisEvent::Direction::POSITIVE, 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 ShortcutButton* m_shortcutButtons;
|
||||||
|
const ShortcutAxis* m_shortcutAxes;
|
||||||
|
Coord m_tiltAxis;
|
||||||
|
Coord m_gyroAxis;
|
||||||
|
float m_gyroSensitivity;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -15,22 +15,20 @@ using namespace QGBA;
|
||||||
KeyEditor::KeyEditor(QWidget* parent)
|
KeyEditor::KeyEditor(QWidget* parent)
|
||||||
: QLineEdit(parent)
|
: QLineEdit(parent)
|
||||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||||
|
, m_key(-1)
|
||||||
|
, m_axis(-1)
|
||||||
, m_button(false)
|
, m_button(false)
|
||||||
{
|
{
|
||||||
setAlignment(Qt::AlignCenter);
|
setAlignment(Qt::AlignCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyEditor::setValue(int key) {
|
void KeyEditor::setValue(int key) {
|
||||||
|
m_key = key;
|
||||||
if (m_button) {
|
if (m_button) {
|
||||||
if (key < 0) {
|
updateButtonText();
|
||||||
clear();
|
|
||||||
} else {
|
|
||||||
setText(QString::number(key));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setText(QKeySequence(key).toString(QKeySequence::NativeText));
|
setText(QKeySequence(key).toString(QKeySequence::NativeText));
|
||||||
}
|
}
|
||||||
m_key = key;
|
|
||||||
emit valueChanged(key);
|
emit valueChanged(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,18 +39,30 @@ void KeyEditor::setValueKey(int key) {
|
||||||
|
|
||||||
void KeyEditor::setValueButton(int button) {
|
void KeyEditor::setValueButton(int button) {
|
||||||
m_button = true;
|
m_button = true;
|
||||||
m_direction = GamepadAxisEvent::NEUTRAL;
|
|
||||||
setValue(button);
|
setValue(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyEditor::setValueAxis(int axis, int32_t value) {
|
void KeyEditor::setValueAxis(int axis, int32_t value) {
|
||||||
m_button = true;
|
m_button = true;
|
||||||
m_key = axis;
|
m_axis = axis;
|
||||||
m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE;
|
m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE;
|
||||||
setText((value < 0 ? "-" : "+") + QString::number(axis));
|
updateButtonText();
|
||||||
emit axisChanged(axis, m_direction);
|
emit axisChanged(axis, m_direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeyEditor::clearButton() {
|
||||||
|
m_button = true;
|
||||||
|
setValue(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyEditor::clearAxis() {
|
||||||
|
m_button = true;
|
||||||
|
m_axis = -1;
|
||||||
|
m_direction = GamepadAxisEvent::NEUTRAL;
|
||||||
|
updateButtonText();
|
||||||
|
emit axisChanged(m_axis, m_direction);
|
||||||
|
}
|
||||||
|
|
||||||
QSize KeyEditor::sizeHint() const {
|
QSize KeyEditor::sizeHint() const {
|
||||||
QSize hint = QLineEdit::sizeHint();
|
QSize hint = QLineEdit::sizeHint();
|
||||||
hint.setWidth(40);
|
hint.setWidth(40);
|
||||||
|
@ -85,3 +95,18 @@ bool KeyEditor::event(QEvent* event) {
|
||||||
}
|
}
|
||||||
return QWidget::event(event);
|
return QWidget::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeyEditor::updateButtonText() {
|
||||||
|
QStringList text;
|
||||||
|
if (m_key >= 0) {
|
||||||
|
text.append(QString::number(m_key));
|
||||||
|
}
|
||||||
|
if (m_direction != GamepadAxisEvent::NEUTRAL) {
|
||||||
|
text.append((m_direction == GamepadAxisEvent::NEGATIVE ? "-" : "+") + QString::number(m_axis));
|
||||||
|
}
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
setText(tr("---"));
|
||||||
|
} else {
|
||||||
|
setText(text.join("/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ public:
|
||||||
int value() const { return m_key; }
|
int value() const { return m_key; }
|
||||||
|
|
||||||
GamepadAxisEvent::Direction direction() const { return m_direction; }
|
GamepadAxisEvent::Direction direction() const { return m_direction; }
|
||||||
|
int axis() const { return m_axis; }
|
||||||
|
|
||||||
virtual QSize sizeHint() const override;
|
virtual QSize sizeHint() const override;
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ public slots:
|
||||||
void setValueKey(int key);
|
void setValueKey(int key);
|
||||||
void setValueButton(int button);
|
void setValueButton(int button);
|
||||||
void setValueAxis(int axis, int32_t value);
|
void setValueAxis(int axis, int32_t value);
|
||||||
|
void clearButton();
|
||||||
|
void clearAxis();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valueChanged(int key);
|
void valueChanged(int key);
|
||||||
|
@ -38,7 +41,10 @@ protected:
|
||||||
virtual bool event(QEvent* event) override;
|
virtual bool event(QEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateButtonText();
|
||||||
|
|
||||||
int m_key;
|
int m_key;
|
||||||
|
int m_axis;
|
||||||
bool m_button;
|
bool m_button;
|
||||||
GamepadAxisEvent::Direction m_direction;
|
GamepadAxisEvent::Direction m_direction;
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,6 +39,7 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
|
||||||
connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||||
connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||||
connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||||
|
connect(m_ui.hwGBPlayer, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||||
|
|
||||||
connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride()));
|
connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride()));
|
||||||
|
|
||||||
|
@ -80,6 +81,9 @@ void OverrideView::updateOverrides() {
|
||||||
m_override.hardware |= HW_RUMBLE;
|
m_override.hardware |= HW_RUMBLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (m_ui.hwGBPlayer->isChecked()) {
|
||||||
|
m_override.hardware |= HW_GB_PLAYER_DETECTION;
|
||||||
|
}
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
uint32_t parsedIdleLoop = m_ui.idleLoop->text().toInt(&ok, 16);
|
uint32_t parsedIdleLoop = m_ui.idleLoop->text().toInt(&ok, 16);
|
||||||
|
@ -115,6 +119,7 @@ void OverrideView::gameStarted(GBAThread* thread) {
|
||||||
m_ui.hwLight->setChecked(thread->gba->memory.hw.devices & HW_LIGHT_SENSOR);
|
m_ui.hwLight->setChecked(thread->gba->memory.hw.devices & HW_LIGHT_SENSOR);
|
||||||
m_ui.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT);
|
m_ui.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT);
|
||||||
m_ui.hwRumble->setChecked(thread->gba->memory.hw.devices & HW_RUMBLE);
|
m_ui.hwRumble->setChecked(thread->gba->memory.hw.devices & HW_RUMBLE);
|
||||||
|
m_ui.hwGBPlayer->setChecked(thread->gba->memory.hw.devices & HW_GB_PLAYER_DETECTION);
|
||||||
|
|
||||||
if (thread->gba->idleLoop != IDLE_LOOP_NONE) {
|
if (thread->gba->idleLoop != IDLE_LOOP_NONE) {
|
||||||
m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16));
|
m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16));
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>409</width>
|
<width>401</width>
|
||||||
<height>228</height>
|
<height>203</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -23,13 +23,19 @@
|
||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetFixedSize</enum>
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
</property>
|
</property>
|
||||||
<item row="2" column="1">
|
<item row="4" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer_2">
|
<spacer name="horizontalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -44,7 +50,135 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" rowspan="3">
|
<item row="3" column="1">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save type</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="savetype">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Autodetect</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>None</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SRAM</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Flash 512kb</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Flash 1Mb</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>EEPROM</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Idle loop</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="idleLoop"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="hwGBPlayer">
|
||||||
|
<property name="text">
|
||||||
|
<string>Game Boy Player features</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" rowspan="3">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string/>
|
<string/>
|
||||||
|
@ -113,83 +247,6 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
|
||||||
<property name="title">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<layout class="QFormLayout" name="formLayout_5">
|
|
||||||
<property name="fieldGrowthPolicy">
|
|
||||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Save type</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="savetype">
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Autodetect</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>None</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>SRAM</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Flash 512kb</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Flash 1Mb</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>EEPROM</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Idle loop</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QLineEdit" name="idleLoop"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="Line" name="line">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|
|
@ -36,6 +36,19 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
|
||||||
loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||||
loadSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
loadSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
||||||
|
|
||||||
|
double fastForwardRatio = loadSetting("fastForwardRatio").toDouble();
|
||||||
|
if (fastForwardRatio <= 0) {
|
||||||
|
m_ui.fastForwardUnbounded->setChecked(true);
|
||||||
|
m_ui.fastForwardRatio->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
m_ui.fastForwardUnbounded->setChecked(false);
|
||||||
|
m_ui.fastForwardRatio->setEnabled(true);
|
||||||
|
m_ui.fastForwardRatio->setValue(fastForwardRatio);
|
||||||
|
}
|
||||||
|
connect(m_ui.fastForwardUnbounded, &QAbstractButton::toggled, [this](bool checked) {
|
||||||
|
m_ui.fastForwardRatio->setEnabled(!checked);
|
||||||
|
});
|
||||||
|
|
||||||
QString idleOptimization = loadSetting("idleOptimization");
|
QString idleOptimization = loadSetting("idleOptimization");
|
||||||
if (idleOptimization == "ignore") {
|
if (idleOptimization == "ignore") {
|
||||||
m_ui.idleOptimization->setCurrentIndex(0);
|
m_ui.idleOptimization->setCurrentIndex(0);
|
||||||
|
@ -103,6 +116,12 @@ void SettingsView::updateConfig() {
|
||||||
saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||||
saveSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
saveSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
||||||
|
|
||||||
|
if (m_ui.fastForwardUnbounded->isChecked()) {
|
||||||
|
saveSetting("fastForwardRatio", "-1");
|
||||||
|
} else {
|
||||||
|
saveSetting("fastForwardRatio", m_ui.fastForwardRatio);
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) {
|
switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) {
|
||||||
case IDLE_LOOP_IGNORE:
|
case IDLE_LOOP_IGNORE:
|
||||||
saveSetting("idleOptimization", "ignore");
|
saveSetting("idleOptimization", "ignore");
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>698</width>
|
<width>707</width>
|
||||||
<height>366</height>
|
<height>420</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -380,21 +380,21 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="0" colspan="2">
|
<item row="10" column="0" colspan="2">
|
||||||
<widget class="Line" name="line_2">
|
<widget class="Line" name="line_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="11" column="1">
|
||||||
<widget class="QCheckBox" name="allowOpposingDirections">
|
<widget class="QCheckBox" name="allowOpposingDirections">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Allow opposing input directions</string>
|
<string>Allow opposing input directions</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="12" column="1">
|
||||||
<widget class="QCheckBox" name="suspendScreensaver">
|
<widget class="QCheckBox" name="suspendScreensaver">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Suspend screensaver</string>
|
<string>Suspend screensaver</string>
|
||||||
|
@ -404,14 +404,14 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="10" column="0">
|
<item row="13" column="0">
|
||||||
<widget class="QLabel" name="label_15">
|
<widget class="QLabel" name="label_15">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Idle loops</string>
|
<string>Idle loops</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="10" column="1">
|
<item row="13" column="1">
|
||||||
<widget class="QComboBox" name="idleOptimization">
|
<widget class="QComboBox" name="idleOptimization">
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -430,6 +430,52 @@
|
||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="fastForwardRatio">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string>×</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>20.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.500000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>5.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_18">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fast forward speed</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<widget class="QCheckBox" name="fastForwardUnbounded">
|
||||||
|
<property name="text">
|
||||||
|
<string>Unbounded</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0" colspan="2">
|
||||||
|
<widget class="Line" name="line_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "ConfigController.h"
|
#include "ConfigController.h"
|
||||||
#include "GamepadButtonEvent.h"
|
#include "GamepadButtonEvent.h"
|
||||||
|
#include "InputProfile.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
@ -18,6 +19,7 @@ ShortcutController::ShortcutController(QObject* parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
, m_rootMenu(nullptr)
|
, m_rootMenu(nullptr)
|
||||||
, m_config(nullptr)
|
, m_config(nullptr)
|
||||||
|
, m_profile(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,6 +241,9 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) {
|
||||||
}
|
}
|
||||||
if (m_config) {
|
if (m_config) {
|
||||||
m_config->setQtOption(item->name(), button, BUTTON_SECTION);
|
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()),
|
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||||
createIndex(index.row(), 2, index.internalPointer()));
|
createIndex(index.row(), 2, index.internalPointer()));
|
||||||
|
@ -272,6 +277,9 @@ void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadA
|
||||||
d = '-';
|
d = '-';
|
||||||
}
|
}
|
||||||
m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
|
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()),
|
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||||
createIndex(index.row(), 2, index.internalPointer()));
|
createIndex(index.row(), 2, index.internalPointer()));
|
||||||
|
@ -365,6 +373,9 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
||||||
|
if (item->name().isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
|
QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
|
||||||
if (!shortcut.isNull()) {
|
if (!shortcut.isNull()) {
|
||||||
QKeySequence keySequence(shortcut.toString());
|
QKeySequence keySequence(shortcut.toString());
|
||||||
|
@ -377,19 +388,45 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
||||||
}
|
}
|
||||||
item->setShortcut(keySequence);
|
item->setShortcut(keySequence);
|
||||||
}
|
}
|
||||||
QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION);
|
loadGamepadShortcuts(item);
|
||||||
if (!button.isNull()) {
|
}
|
||||||
|
|
||||||
|
void ShortcutController::loadGamepadShortcuts(ShortcutItem* 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();
|
int oldButton = item->button();
|
||||||
item->setButton(button.toInt());
|
|
||||||
if (oldButton >= 0) {
|
if (oldButton >= 0) {
|
||||||
m_buttons.take(oldButton);
|
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;
|
m_buttons[button.toInt()] = item;
|
||||||
}
|
}
|
||||||
QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION);
|
|
||||||
if (!axis.isNull()) {
|
QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
|
||||||
int oldAxis = item->axis();
|
int oldAxis = item->axis();
|
||||||
GamepadAxisEvent::Direction oldDirection = item->direction();
|
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();
|
QString axisDesc = axis.toString();
|
||||||
if (axisDesc.size() >= 2) {
|
if (axisDesc.size() >= 2) {
|
||||||
GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
|
GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
|
||||||
|
@ -403,9 +440,6 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
||||||
int axis = axisDesc.mid(1).toInt(&ok);
|
int axis = axisDesc.mid(1).toInt(&ok);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
item->setAxis(axis, direction);
|
item->setAxis(axis, direction);
|
||||||
if (oldAxis >= 0) {
|
|
||||||
m_axes.take(qMakePair(oldAxis, oldDirection));
|
|
||||||
}
|
|
||||||
m_axes[qMakePair(axis, direction)] = item;
|
m_axes[qMakePair(axis, direction)] = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,6 +466,21 @@ QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
|
||||||
return QKeySequence(modifier + key);
|
return QKeySequence(modifier + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShortcutController::loadProfile(const QString& profile) {
|
||||||
|
m_profileName = profile;
|
||||||
|
m_profile = InputProfile::findProfile(profile);
|
||||||
|
onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
|
||||||
|
loadGamepadShortcuts(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
|
||||||
|
for (ShortcutItem& subitem : item->items()) {
|
||||||
|
func(&subitem);
|
||||||
|
onSubitems(&subitem, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
|
ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
|
||||||
: m_action(action)
|
: m_action(action)
|
||||||
, m_shortcut(action->shortcut())
|
, m_shortcut(action->shortcut())
|
||||||
|
|
|
@ -21,6 +21,7 @@ class QString;
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class ConfigController;
|
class ConfigController;
|
||||||
|
class InputProfile;
|
||||||
|
|
||||||
class ShortcutController : public QAbstractItemModel {
|
class ShortcutController : public QAbstractItemModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -29,6 +30,8 @@ private:
|
||||||
constexpr static const char* const KEY_SECTION = "shortcutKey";
|
constexpr static const char* const KEY_SECTION = "shortcutKey";
|
||||||
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
|
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
|
||||||
constexpr static const char* const AXIS_SECTION = "shortcutAxis";
|
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.";
|
||||||
|
|
||||||
class ShortcutItem {
|
class ShortcutItem {
|
||||||
public:
|
public:
|
||||||
|
@ -84,6 +87,7 @@ public:
|
||||||
ShortcutController(QObject* parent = nullptr);
|
ShortcutController(QObject* parent = nullptr);
|
||||||
|
|
||||||
void setConfigController(ConfigController* controller);
|
void setConfigController(ConfigController* controller);
|
||||||
|
void setProfile(const QString& profile);
|
||||||
|
|
||||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
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 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
@ -111,6 +115,9 @@ public:
|
||||||
|
|
||||||
static QKeySequence keyEventToSequence(const QKeyEvent*);
|
static QKeySequence keyEventToSequence(const QKeyEvent*);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void loadProfile(const QString& profile);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject*, QEvent*) override;
|
bool eventFilter(QObject*, QEvent*) override;
|
||||||
|
|
||||||
|
@ -118,6 +125,8 @@ private:
|
||||||
ShortcutItem* itemAt(const QModelIndex& index);
|
ShortcutItem* itemAt(const QModelIndex& index);
|
||||||
const ShortcutItem* itemAt(const QModelIndex& index) const;
|
const ShortcutItem* itemAt(const QModelIndex& index) const;
|
||||||
void loadShortcuts(ShortcutItem*);
|
void loadShortcuts(ShortcutItem*);
|
||||||
|
void loadGamepadShortcuts(ShortcutItem*);
|
||||||
|
void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func);
|
||||||
|
|
||||||
ShortcutItem m_rootMenu;
|
ShortcutItem m_rootMenu;
|
||||||
QMap<QMenu*, ShortcutItem*> m_menuMap;
|
QMap<QMenu*, ShortcutItem*> m_menuMap;
|
||||||
|
@ -125,6 +134,8 @@ private:
|
||||||
QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes;
|
QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes;
|
||||||
QMap<QKeySequence, ShortcutItem*> m_heldKeys;
|
QMap<QKeySequence, ShortcutItem*> m_heldKeys;
|
||||||
ConfigController* m_config;
|
ConfigController* m_config;
|
||||||
|
QString m_profileName;
|
||||||
|
const InputProfile* m_profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "ShortcutView.h"
|
#include "ShortcutView.h"
|
||||||
|
|
||||||
#include "GamepadButtonEvent.h"
|
#include "GamepadButtonEvent.h"
|
||||||
|
#include "InputController.h"
|
||||||
#include "ShortcutController.h"
|
#include "ShortcutController.h"
|
||||||
|
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
@ -15,6 +16,7 @@ using namespace QGBA;
|
||||||
ShortcutView::ShortcutView(QWidget* parent)
|
ShortcutView::ShortcutView(QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_controller(nullptr)
|
, m_controller(nullptr)
|
||||||
|
, m_input(nullptr)
|
||||||
{
|
{
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
m_ui.keyEdit->setValueButton(-1);
|
m_ui.keyEdit->setValueButton(-1);
|
||||||
|
@ -32,6 +34,14 @@ void ShortcutView::setController(ShortcutController* controller) {
|
||||||
m_ui.shortcutTable->setModel(controller);
|
m_ui.shortcutTable->setModel(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShortcutView::setInputController(InputController* controller) {
|
||||||
|
if (m_input) {
|
||||||
|
m_input->releaseFocus(this);
|
||||||
|
}
|
||||||
|
m_input = controller;
|
||||||
|
m_input->stealFocus(this);
|
||||||
|
}
|
||||||
|
|
||||||
bool ShortcutView::eventFilter(QObject*, QEvent* event) {
|
bool ShortcutView::eventFilter(QObject*, QEvent* event) {
|
||||||
if (event->type() == QEvent::KeyPress) {
|
if (event->type() == QEvent::KeyPress) {
|
||||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
|
@ -111,3 +121,20 @@ void ShortcutView::updateAxis(int axis, int direction) {
|
||||||
m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis,
|
m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis,
|
||||||
static_cast<GamepadAxisEvent::Direction>(direction));
|
static_cast<GamepadAxisEvent::Direction>(direction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShortcutView::closeEvent(QCloseEvent*) {
|
||||||
|
if (m_input) {
|
||||||
|
m_input->releaseFocus(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShortcutView::event(QEvent* event) {
|
||||||
|
if (m_input) {
|
||||||
|
if (event->type() == QEvent::WindowActivate) {
|
||||||
|
m_input->stealFocus(this);
|
||||||
|
} else if (event->type() == QEvent::WindowDeactivate) {
|
||||||
|
m_input->releaseFocus(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QWidget::event(event);
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
|
class InputController;
|
||||||
class ShortcutController;
|
class ShortcutController;
|
||||||
|
|
||||||
class ShortcutView : public QWidget {
|
class ShortcutView : public QWidget {
|
||||||
|
@ -23,9 +24,12 @@ public:
|
||||||
ShortcutView(QWidget* parent = nullptr);
|
ShortcutView(QWidget* parent = nullptr);
|
||||||
|
|
||||||
void setController(ShortcutController* controller);
|
void setController(ShortcutController* controller);
|
||||||
|
void setInputController(InputController* input);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool eventFilter(QObject* obj, QEvent* event) override;
|
virtual bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
virtual bool event(QEvent*) override;
|
||||||
|
virtual void closeEvent(QCloseEvent*) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void load(const QModelIndex&);
|
void load(const QModelIndex&);
|
||||||
|
@ -38,6 +42,7 @@ private:
|
||||||
Ui::ShortcutView m_ui;
|
Ui::ShortcutView m_ui;
|
||||||
|
|
||||||
ShortcutController* m_controller;
|
ShortcutController* m_controller;
|
||||||
|
InputController* m_input;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
||||||
, m_screenWidget(new WindowBackground())
|
, m_screenWidget(new WindowBackground())
|
||||||
, m_logo(":/res/mgba-1024.png")
|
, m_logo(":/res/mgba-1024.png")
|
||||||
, m_config(config)
|
, m_config(config)
|
||||||
, m_inputController(playerId)
|
, m_inputController(playerId, this)
|
||||||
#ifdef USE_FFMPEG
|
#ifdef USE_FFMPEG
|
||||||
, m_videoView(nullptr)
|
, m_videoView(nullptr)
|
||||||
#endif
|
#endif
|
||||||
|
@ -128,6 +128,15 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
||||||
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
|
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
|
||||||
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
|
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
|
||||||
connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS()));
|
connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS()));
|
||||||
|
connect(m_display, &Display::hideCursor, [this]() {
|
||||||
|
if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display) {
|
||||||
|
setCursor(Qt::BlankCursor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_display, &Display::showCursor, [this]() {
|
||||||
|
unsetCursor();
|
||||||
|
});
|
||||||
|
connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&)));
|
||||||
|
|
||||||
m_log.setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS);
|
m_log.setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS);
|
||||||
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
|
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
|
||||||
|
@ -296,6 +305,7 @@ void Window::openSettingsWindow() {
|
||||||
SettingsView* settingsWindow = new SettingsView(m_config);
|
SettingsView* settingsWindow = new SettingsView(m_config);
|
||||||
connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&)));
|
connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&)));
|
||||||
connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver()));
|
connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver()));
|
||||||
|
connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart()));
|
||||||
openView(settingsWindow);
|
openView(settingsWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +315,7 @@ void Window::openShortcutWindow() {
|
||||||
#endif
|
#endif
|
||||||
ShortcutView* shortcutView = new ShortcutView();
|
ShortcutView* shortcutView = new ShortcutView();
|
||||||
shortcutView->setController(m_shortcutController);
|
shortcutView->setController(m_shortcutController);
|
||||||
|
shortcutView->setInputController(&m_inputController);
|
||||||
openView(shortcutView);
|
openView(shortcutView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,6 +572,23 @@ void Window::unimplementedBiosCall(int call) {
|
||||||
fail->show();
|
fail->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::tryMakePortable() {
|
||||||
|
QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"),
|
||||||
|
tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::Cancel, this, Qt::Sheet);
|
||||||
|
confirm->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
connect(confirm->button(QMessageBox::Yes), SIGNAL(clicked()), m_config, SLOT(makePortable()));
|
||||||
|
confirm->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::mustRestart() {
|
||||||
|
QMessageBox* dialog = new QMessageBox(QMessageBox::Warning, tr("Restart needed"),
|
||||||
|
tr("Some changes will not take effect until the emulator is restarted."),
|
||||||
|
QMessageBox::Ok, this, Qt::Sheet);
|
||||||
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
void Window::recordFrame() {
|
void Window::recordFrame() {
|
||||||
m_frameList.append(QDateTime::currentDateTime());
|
m_frameList.append(QDateTime::currentDateTime());
|
||||||
while (m_frameList.count() > FRAME_LIST_SIZE) {
|
while (m_frameList.count() > FRAME_LIST_SIZE) {
|
||||||
|
@ -642,7 +670,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
|
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
|
|
||||||
addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), m_config, SLOT(makePortable())), "makePortable");
|
addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable");
|
||||||
|
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
|
|
||||||
|
@ -676,6 +704,21 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
quickLoadMenu->addSeparator();
|
quickLoadMenu->addSeparator();
|
||||||
quickSaveMenu->addSeparator();
|
quickSaveMenu->addSeparator();
|
||||||
|
|
||||||
|
QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu);
|
||||||
|
undoLoadState->setShortcut(tr("F11"));
|
||||||
|
connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState()));
|
||||||
|
m_gameActions.append(undoLoadState);
|
||||||
|
addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState");
|
||||||
|
|
||||||
|
QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu);
|
||||||
|
undoSaveState->setShortcut(tr("Shift+F11"));
|
||||||
|
connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState()));
|
||||||
|
m_gameActions.append(undoSaveState);
|
||||||
|
addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState");
|
||||||
|
|
||||||
|
quickLoadMenu->addSeparator();
|
||||||
|
quickSaveMenu->addSeparator();
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = 1; i < 10; ++i) {
|
for (i = 1; i < 10; ++i) {
|
||||||
quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
|
quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
|
||||||
|
@ -937,14 +980,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
|
|
||||||
#ifdef USE_FFMPEG
|
#ifdef USE_FFMPEG
|
||||||
QAction* recordOutput = new QAction(tr("Record output..."), avMenu);
|
QAction* recordOutput = new QAction(tr("Record output..."), avMenu);
|
||||||
recordOutput->setShortcut(tr("F11"));
|
|
||||||
connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow()));
|
connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow()));
|
||||||
addControlledAction(avMenu, recordOutput, "recordOutput");
|
addControlledAction(avMenu, recordOutput, "recordOutput");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_MAGICK
|
#ifdef USE_MAGICK
|
||||||
QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu);
|
QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu);
|
||||||
recordGIF->setShortcut(tr("Shift+F11"));
|
|
||||||
connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow()));
|
connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow()));
|
||||||
addControlledAction(avMenu, recordGIF, "recordGIF");
|
addControlledAction(avMenu, recordGIF, "recordGIF");
|
||||||
#endif
|
#endif
|
||||||
|
@ -1092,6 +1133,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
|
|
||||||
void Window::attachWidget(QWidget* widget) {
|
void Window::attachWidget(QWidget* widget) {
|
||||||
m_screenWidget->layout()->addWidget(widget);
|
m_screenWidget->layout()->addWidget(widget);
|
||||||
|
unsetCursor();
|
||||||
static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
|
static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,9 @@ private slots:
|
||||||
void gameFailed();
|
void gameFailed();
|
||||||
void unimplementedBiosCall(int);
|
void unimplementedBiosCall(int);
|
||||||
|
|
||||||
|
void tryMakePortable();
|
||||||
|
void mustRestart();
|
||||||
|
|
||||||
void recordFrame();
|
void recordFrame();
|
||||||
void showFPS();
|
void showFPS();
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,10 @@ void ConfigurationClearValue(struct Configuration* configuration, const char* se
|
||||||
HashTableRemove(currentSection, key);
|
HashTableRemove(currentSection, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConfigurationHasSection(const struct Configuration* configuration, const char* section) {
|
||||||
|
return HashTableLookup(&configuration->sections, section);
|
||||||
|
}
|
||||||
|
|
||||||
const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
|
const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
|
||||||
const struct Table* currentSection = &configuration->root;
|
const struct Table* currentSection = &configuration->root;
|
||||||
if (section) {
|
if (section) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ void ConfigurationSetIntValue(struct Configuration*, const char* section, const
|
||||||
void ConfigurationSetUIntValue(struct Configuration*, const char* section, const char* key, unsigned value);
|
void ConfigurationSetUIntValue(struct Configuration*, const char* section, const char* key, unsigned value);
|
||||||
void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
|
void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
|
||||||
|
|
||||||
|
bool ConfigurationHasSection(const struct Configuration*, const char* section);
|
||||||
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key);
|
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key);
|
||||||
|
|
||||||
void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);
|
void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);
|
||||||
|
|
|
@ -173,5 +173,9 @@ static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
UNUSED(buffer);
|
UNUSED(buffer);
|
||||||
UNUSED(size);
|
UNUSED(size);
|
||||||
struct VFileFD* vfd = (struct VFileFD*) vf;
|
struct VFileFD* vfd = (struct VFileFD*) vf;
|
||||||
|
#ifndef _WIN32
|
||||||
return fsync(vfd->fd) == 0;
|
return fsync(vfd->fd) == 0;
|
||||||
|
#else
|
||||||
|
return FlushFileBuffers((HANDLE) _get_osfhandle(vfd->fd));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) {
|
||||||
SzArEx_Init(&vd->db);
|
SzArEx_Init(&vd->db);
|
||||||
SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp);
|
SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp);
|
||||||
if (res != SZ_OK) {
|
if (res != SZ_OK) {
|
||||||
|
File_Close(&vd->archiveStream.file);
|
||||||
free(vd);
|
free(vd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +116,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) {
|
||||||
bool _vf7zClose(struct VFile* vf) {
|
bool _vf7zClose(struct VFile* vf) {
|
||||||
struct VFile7z* vf7z = (struct VFile7z*) vf;
|
struct VFile7z* vf7z = (struct VFile7z*) vf;
|
||||||
IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer);
|
IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer);
|
||||||
|
File_Close(&vf7z->vd->archiveStream.file);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ static void* _vfmMap(struct VFile* vf, size_t size, int flags);
|
||||||
static void _vfmUnmap(struct VFile* vf, void* memory, size_t size);
|
static void _vfmUnmap(struct VFile* vf, void* memory, size_t size);
|
||||||
static void _vfmTruncate(struct VFile* vf, size_t size);
|
static void _vfmTruncate(struct VFile* vf, size_t size);
|
||||||
static ssize_t _vfmSize(struct VFile* vf);
|
static ssize_t _vfmSize(struct VFile* vf);
|
||||||
|
static bool _vfmSync(struct VFile* vf, const void* buffer, size_t size);
|
||||||
|
|
||||||
struct VFile* VFileFromMemory(void* mem, size_t size) {
|
struct VFile* VFileFromMemory(void* mem, size_t size) {
|
||||||
if (!mem || !size) {
|
if (!mem || !size) {
|
||||||
|
@ -43,6 +44,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size) {
|
||||||
vfm->d.unmap = _vfmUnmap;
|
vfm->d.unmap = _vfmUnmap;
|
||||||
vfm->d.truncate = _vfmTruncate;
|
vfm->d.truncate = _vfmTruncate;
|
||||||
vfm->d.size = _vfmSize;
|
vfm->d.size = _vfmSize;
|
||||||
|
vfm->d.sync = _vfmSync;
|
||||||
|
|
||||||
return &vfm->d;
|
return &vfm->d;
|
||||||
}
|
}
|
||||||
|
@ -137,3 +139,10 @@ ssize_t _vfmSize(struct VFile* vf) {
|
||||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
return vfm->size;
|
return vfm->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _vfmSync(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
|
UNUSED(vf);
|
||||||
|
UNUSED(buffer);
|
||||||
|
UNUSED(size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
if(NOT ${BINARY_NAME}_SOURCE_DIR)
|
if(NOT PROJECT_NAME)
|
||||||
set(${BINARY_NAME}_SOURCE_DIR ${CMAKE_SOURCE_DIR})
|
set(PROJECT_NAME "mGBA")
|
||||||
|
endif()
|
||||||
|
set(LIB_VERSION_MAJOR 0)
|
||||||
|
set(LIB_VERSION_MINOR 3)
|
||||||
|
set(LIB_VERSION_PATCH 0)
|
||||||
|
set(LIB_VERSION_ABI 0.3)
|
||||||
|
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||||
|
|
||||||
|
execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
|
||||||
|
if(GIT_REV STREQUAL "")
|
||||||
|
set(GIT_REV -1)
|
||||||
|
endif()
|
||||||
|
if(NOT GIT_TAG STREQUAL "")
|
||||||
|
set(VERSION_STRING ${GIT_TAG})
|
||||||
|
elseif(GIT_BRANCH STREQUAL "")
|
||||||
|
set(VERSION_STRING ${LIB_VERSION_STRING})
|
||||||
|
else()
|
||||||
|
if(GIT_BRANCH STREQUAL "master")
|
||||||
|
set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||||
|
else()
|
||||||
|
set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
configure_file("${${BINARY_NAME}_SOURCE_DIR}/src/util/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c")
|
if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH)
|
||||||
|
set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_FILE AND OUT_FILE)
|
||||||
|
configure_file("${CONFIG_FILE}" "${OUT_FILE}")
|
||||||
|
endif()
|
||||||
|
|
Loading…
Reference in New Issue