diff --git a/CHANGES b/CHANGES index bdc51e5e6..b3a18ad7e 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,12 @@ Features: - 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 - 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, rumble and solar sensor + - Implement BIOS call Stop, for sleep mode + - Automatically load patches, if found Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit @@ -57,6 +63,10 @@ Bugfixes: - ARM7: ARMHotplugDetach should call deinit - 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 + - GBA Cheats: Fix Pro Action Replay and GameShark issues when used together + - Qt: Fix analog buttons not getting unmapped + - GBA Video: Prevent tiles < 512 from being used in modes 3 - 5 + - Qt: Fix passing command line options Misc: - Qt: Handle saving input settings better - Debugger: Free watchpoints in addition to breakpoints @@ -96,6 +106,15 @@ Misc: - GBA Audio: Implement audio reset for channels A/B - GBA Hardware: Backport generic RTC source into core - All: Proper handling of Unicode file paths + - GBA Video: Slightly optimize mode 0 mosaic rendering + - VFS: Add sync method to force syncing with backing + - 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 + - Qt: Increase usability of key mapper + - Qt: Show checkmark for window sizes + - Qt: Set window path to loaded ROM 0.2.1: (2015-05-13) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 52f31e41a..2e08466a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") set(BUILD_GL ON CACHE STRING "Build with OpenGL") +set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c) @@ -43,7 +44,11 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) 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) @@ -76,51 +81,16 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES) endfunction() # 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 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} - -DPROJECT_NAME=${PROJECT_NAME} - -DVERSION_STRING=${VERSION_STRING} - -D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DCONFIG_FILE=${CMAKE_SOURCE_DIR}/src/util/version.c.in + -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.c -P ${CMAKE_SOURCE_DIR}/version.cmake WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) 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) source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c) @@ -156,7 +126,14 @@ endif() if(BUILD_GL) find_package(OpenGL QUIET) if(NOT OPENGL_FOUND) - set(BUILD_GL OFF) + set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE) + endif() +endif() +if(BUILD_GLES2 AND NOT BUILD_RASPI) + find_path(OPENGLES2_INCLUDE_DIR NAMES GLES2/gl2.h) + find_library(OPENGLES2_LIBRARY NAMES GLESv2 GLESv2_CM) + if(NOT OPENGLES2_INCLUDE_DIR OR NOT OPENGLES2_LIBRARY) + set(BUILD_GLES2 OFF CACHE BOOL "OpenGL|ES 2 not found" FORCE) endif() endif() find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") @@ -219,11 +196,16 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA) endif() if(WII) + add_definitions(-U__STRICT_ANSI__) add_executable(${BINARY_NAME}.elf ${CMAKE_SOURCE_DIR}/src/platform/wii/main.c) target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol) endif() +if(BUILD_RASPI) + set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE) +endif() + if(BUILD_PANDORA) add_definitions(-DBUILD_PANDORA) endif() @@ -245,6 +227,7 @@ endif() check_function_exists(newlocale HAVE_NEWLOCALE) check_function_exists(freelocale HAVE_FREELOCALE) check_function_exists(uselocale HAVE_USELOCALE) +check_function_exists(setlocale HAVE_SETLOCALE) if(HAVE_STRDUP) add_definitions(-DHAVE_STRDUP) @@ -264,6 +247,9 @@ if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) endif() endif() +if(HAVE_SETLOCALE) + add_definitions(-DHAVE_SETLOCALE) +endif() # Features set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c) @@ -410,10 +396,11 @@ endif() if(BUILD_SHARED) add_library(${BINARY_NAME} SHARED ${SRC}) + set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) 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) endif() else() @@ -423,7 +410,7 @@ endif() add_dependencies(${BINARY_NAME} version-info) 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} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) 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-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) @@ -441,6 +428,10 @@ if(BUILD_GL) add_definitions(-DBUILD_GL) endif() +if(BUILD_GLES2) + add_definitions(-DBUILD_GLES2) +endif() + if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) diff --git a/PORTING.md b/PORTING.md index 6e4fdae16..baa6f3712 100644 --- a/PORTING.md +++ b/PORTING.md @@ -12,7 +12,7 @@ Port-specific TODO The ports are vaguely usable, but by no means should be considered stable. -### 3DS +### 3DS (port/3ds) * Add menu * Add audio * Thread support testing @@ -20,7 +20,7 @@ The ports are vaguely usable, but by no means should be considered stable. * ARMv6 dynarec * Hardware acceleration -### PSP +### PSP (port/psp) * Add menu * Add audio * Thread support @@ -28,7 +28,14 @@ The ports are vaguely usable, but by no means should be considered stable. * MIPS dynarec * 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 audio * Thread support diff --git a/README.md b/README.md index be79140fc..eb96a9f70 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,6 @@ Footnotes - OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) - Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) - BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141)) -- Game Pak prefetch ([Bug #195](http://mgba.io/b/195)) -- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199)) [2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. diff --git a/res/keymap.qpic b/res/keymap.qpic index ffb7f8b7d..782d0c7f2 100644 Binary files a/res/keymap.qpic and b/res/keymap.qpic differ diff --git a/res/keymap.svg b/res/keymap.svg index 5ec9d5099..9b37a3aa4 100644 --- a/res/keymap.svg +++ b/res/keymap.svg @@ -7,130 +7,266 @@ - - - + + + + + + + + - - - - - - + - + - - - + + + - + - + - + - - + + - + - + - - + + - - + + - + - - + + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/mgba-1024.png b/res/mgba-1024.png index 4ef829578..2afd2aa8b 100644 Binary files a/res/mgba-1024.png and b/res/mgba-1024.png differ diff --git a/res/mgba-qt.desktop b/res/mgba-qt.desktop index 0e717229f..8f87bb40d 100644 --- a/res/mgba-qt.desktop +++ b/res/mgba-qt.desktop @@ -6,6 +6,7 @@ Terminal=false Type=Application Name=mGBA GenericName=Game Boy Advance Emulator +Comment=Nintendo Game Boy Advance Emulator Categories=Game;Emulator; MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; - +Keywords=emulator;Nintendo;advance;gba;Game Boy Advance; diff --git a/src/arm/decoder-thumb.c b/src/arm/decoder-thumb.c index d1eac9c7d..626004f2c 100644 --- a/src/arm/decoder-thumb.c +++ b/src/arm/decoder-thumb.c @@ -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_THUMB_DECODER(NAME, MNEMONIC, \ - info->op1.reg = (opcode >> 6) & 0x0007; \ + info->op1.reg = (opcode >> 8) & 0x0007; \ info->memory.baseReg = REG; \ info->memory.offset.immediate = (opcode & 0x00FF) << 2; \ info->memory.width = ARM_ACCESS_WORD; \ diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 90d657af1..f7d1014d5 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -56,6 +56,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, { "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, { "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, + { "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, + { "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, { "c", _continue, 0, "Continue execution" }, { "continue", _continue, 0, "Continue execution" }, { "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, diff --git a/src/gba/bios.c b/src/gba/bios.c index 3af31b9fd..a20c5ff59 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -191,6 +191,9 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { case 0x2: GBAHalt(gba); break; + case 0x3: + GBAStop(gba); + break; case 0x05: // VBlankIntrWait // Fall through: @@ -297,6 +300,10 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { break; } break; + case 0x19: + // SoundBias is mostly meaningless here + GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: SoundBias (19)"); + break; case 0x1F: _MidiKey2Freq(gba); break; diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c index 0dd6f68b2..996de638d 100644 --- a/src/gba/cheats/gameshark.c +++ b/src/gba/cheats/gameshark.c @@ -197,6 +197,7 @@ bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { switch (set->gsaVersion) { case 0: + case 3: GBACheatSetGameSharkVersion(set, 1); // Fall through case 1: diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 8ec3b67e4..c5e01b66d 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -297,9 +297,10 @@ bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t switch (set->gsaVersion) { case 0: + case 1: GBACheatSetGameSharkVersion(set, 3); // Fall through - case 1: + case 3: GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); return GBACheatAddProActionReplayRaw(set, o1, o2); } diff --git a/src/gba/gba.c b/src/gba/gba.c index 1be44c506..c221f587f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -79,8 +79,10 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) { gba->biosVf = 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->keyCallback = 0; + gba->stopCallback = 0; gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS); @@ -92,6 +94,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) { gba->idleDetectionFailures = 0; gba->realisticTiming = true; + gba->hardCrash = true; gba->performingDMA = false; } @@ -552,6 +555,14 @@ void GBAHalt(struct GBA* gba) { 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) { struct GBAThread* threadContext = GBAThreadGetContext(); enum GBALogLevel logLevel = GBA_LOG_ALL; @@ -756,6 +767,8 @@ void GBAFrameStarted(struct GBA* gba) { } void GBAFrameEnded(struct GBA* gba) { + GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter); + if (gba->rr) { gba->rr->nextFrame(gba->rr); } @@ -775,6 +788,10 @@ void GBAFrameEnded(struct GBA* gba) { 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(); if (!thread) { return; diff --git a/src/gba/gba.h b/src/gba/gba.h index aff2120d7..e472a14ac 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -11,6 +11,7 @@ #include "arm.h" #include "debugger/debugger.h" +#include "gba/interface.h" #include "gba/memory.h" #include "gba/video.h" #include "gba/audio.h" @@ -35,43 +36,6 @@ enum GBAIRQ { 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 { GBA_COMPONENT_DEBUGGER, GBA_COMPONENT_CHEAT_DEVICE, @@ -91,19 +55,10 @@ enum { }; struct GBA; -struct GBARotationSource; struct GBAThread; struct Patch; 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 { uint16_t reload; uint16_t oldReload; @@ -156,6 +111,8 @@ struct GBA { GBALogHandler logHandler; enum GBALogLevel logLevel; struct GBAAVStream* stream; + struct GBAKeyCallback* keyCallback; + struct GBAStopCallback* stopCallback; enum GBAIdleLoopOptimization idleOptimization; uint32_t idleLoop; @@ -167,6 +124,7 @@ struct GBA { bool taintedRegisters[16]; bool realisticTiming; + bool hardCrash; }; struct GBACartridge { @@ -199,6 +157,7 @@ void GBAWriteIME(struct GBA* gba, uint16_t value); void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq); void GBATestIRQ(struct ARMCore* cpu); void GBAHalt(struct GBA* gba); +void GBAStop(struct GBA* gba); void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger); void GBADetachDebugger(struct GBA* gba); diff --git a/src/gba/hardware.c b/src/gba/hardware.c index d764325fd..1ce2e18eb 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "hardware.h" +#include "gba/io.h" #include "gba/serialize.h" #include "util/hash.h" @@ -12,6 +13,8 @@ #include #endif +const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; + static void _readPins(struct GBACartridgeHardware* hw); static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins); @@ -29,6 +32,10 @@ static void _rumbleReadPins(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] = { 0, // Force reset 0, // Empty @@ -43,6 +50,16 @@ static const int RTC_BYTES[8] = { void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { hw->gpioBase = base; 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) { @@ -469,6 +486,30 @@ static const uint16_t _logoPalette[] = { 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) { if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) { return false; @@ -477,6 +518,83 @@ bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) { 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 void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) { @@ -493,6 +611,9 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria state->hw.lightCounter = hw->lightCounter; state->hw.lightSample = hw->lightSample; 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) { @@ -508,4 +629,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer hw->lightCounter = state->hw.lightCounter; hw->lightSample = state->hw.lightSample; hw->lightEdge = state->hw.lightEdge; + hw->gbpInputsPosted = state->hw.gbpInputsPosted; + hw->gbpTxPosition = state->hw.gbpTxPosition; + hw->gbpNextEvent = state->hw.gbpNextEvent; } diff --git a/src/gba/hardware.h b/src/gba/hardware.h index 5ba17c221..7923d0669 100644 --- a/src/gba/hardware.h +++ b/src/gba/hardware.h @@ -7,6 +7,7 @@ #define GBA_HARDWARE_H #include "util/common.h" +#include "gba/interface.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) -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 GBARTCSource d; struct GBA* p; @@ -53,7 +33,9 @@ enum GBAHardwareDevice { HW_RUMBLE = 2, HW_LIGHT_SENSOR = 4, HW_GYRO = 8, - HW_TILT = 16 + HW_TILT = 16, + HW_GB_PLAYER = 32, + HW_GB_PLAYER_DETECTION = 64 }; enum GPIORegister { @@ -102,6 +84,16 @@ struct GBARumble { 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); struct GBACartridgeHardware { @@ -125,6 +117,12 @@ struct GBACartridgeHardware { uint16_t tiltX; uint16_t tiltY; int tiltState; + + unsigned gbpInputsPosted; + int gbpTxPosition; + int32_t gbpNextEvent; + struct GBAGBPKeyCallback gbpCallback; + struct GBAGBPSIODriver gbpDriver; }; 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); struct GBAVideo; +void GBAHardwarePlayerUpdate(struct GBA* gba); bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video); void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba); diff --git a/src/gba/input.c b/src/gba/input.c index 65cf7a650..6ae95844c 100644 --- a/src/gba/input.c +++ b/src/gba/input.c @@ -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_B, "B"); _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_LEFT, "Left"); _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) { @@ -348,6 +352,20 @@ enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key return GBA_KEY_NONE; } +int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset) { + int keys = 0; + for (; bits; bits >>= 1, ++offset) { + if (bits & 1) { + enum GBAKey key = GBAInputMapKey(map, type, offset); + if (key == GBA_KEY_NONE) { + continue; + } + keys |= 1 << key; + } + } + return keys; +} + void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) { struct GBAInputMapImpl* impl = _guaranteeMap(map, type); GBAInputUnbindKey(map, type, input); @@ -362,7 +380,6 @@ void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input if (impl) { impl->map[input] = GBA_NO_MAPPING; } - TableEnumerate(&impl->axes, _unbindAxis, &input); } int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) { @@ -416,9 +433,10 @@ 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) { struct GBAInputMapImpl* impl = _guaranteeMap(map, type); + struct GBAAxis d2 = *description; + TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection); + TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection); struct GBAAxis* dup = malloc(sizeof(struct GBAAxis)); - GBAInputUnbindKey(map, type, description->lowDirection); - GBAInputUnbindKey(map, type, description->highDirection); *dup = *description; TableInsert(&impl->axes, axis, dup); } @@ -469,11 +487,11 @@ void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct 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]; snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); 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) { diff --git a/src/gba/input.h b/src/gba/input.h index 8610b6f24..2332b7e75 100644 --- a/src/gba/input.h +++ b/src/gba/input.h @@ -30,6 +30,7 @@ void GBAInputMapInit(struct GBAInputMap*); void GBAInputMapDeinit(struct GBAInputMap*); enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key); +int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset); void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input); void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, enum GBAKey input); int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input); @@ -45,7 +46,7 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const 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); const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId); diff --git a/src/gba/interface.h b/src/gba/interface.h new file mode 100644 index 000000000..396f4b549 --- /dev/null +++ b/src/gba/interface.h @@ -0,0 +1,107 @@ +/* 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*); +}; + +extern const int GBA_LUX_LEVELS[10]; + +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 diff --git a/src/gba/io.c b/src/gba/io.c index a93227669..304a6253a 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -505,7 +505,7 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) { if (!value) { GBAHalt(gba); } else { - GBALog(gba, GBA_LOG_STUB, "Stop unimplemented"); + GBAStop(gba); } return; } @@ -584,14 +584,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_KEYINPUT: if (gba->rr && gba->rr->isPlaying(gba->rr)) { return 0x3FF ^ gba->rr->queryInput(gba->rr); - } else if (gba->keySource) { - uint16_t input = *gba->keySource; + } else { + uint16_t input = 0x3FF; + if (gba->keyCallback) { + input = gba->keyCallback->readKeys(gba->keyCallback); + } else if (gba->keySource) { + input = *gba->keySource; + } if (gba->rr && gba->rr->isRecording(gba->rr)) { gba->rr->logInput(gba->rr, input); } return 0x3FF ^ input; } - break; case REG_SIOCNT: return gba->sio.siocnt; diff --git a/src/gba/memory.c b/src/gba/memory.c index 32fa3f917..a1195eeb8 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -278,9 +278,11 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { memory->activeRegion = -1; cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; - if (!gba->yankedRomSize) { - GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); + enum GBALogLevel errorLevel = GBA_LOG_FATAL; + if (gba->yankedRomSize || !gba->hardCrash) { + errorLevel = GBA_LOG_GAME_ERROR; } + GBALog(gba, errorLevel, "Jumped to invalid address: %08X", address); return; } cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion]; @@ -634,8 +636,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { #define STORE_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ } else { \ STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \ } \ wait += waitstatesRegion[REGION_VRAM]; @@ -728,8 +734,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); } else { STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); } break; case REGION_OAM: @@ -794,8 +802,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address); break; } - ((int8_t*) gba->video.renderer->vram)[address & 0x1FFFE] = value; - ((int8_t*) gba->video.renderer->vram)[(address & 0x1FFFE) | 1] = value; + gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); break; case REGION_OAM: GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OAM: 0x%08X", address); @@ -818,6 +826,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == SAVEDATA_SRAM) { memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; + memory->savedata.dirty |= SAVEDATA_DIRT_NEW; } else if (memory->hw.devices & HW_TILT) { GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value); } else { diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index 0f5101109..6ff17e55b 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -98,11 +98,7 @@ tileData &= 0xF; \ tileData |= tileData << 4; \ tileData |= tileData << 8; \ - tileData |= tileData << 12; \ tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ carryData = tileData; \ } \ } \ @@ -126,11 +122,7 @@ tileData &= 0xF; \ tileData |= tileData << 4; \ tileData |= tileData << 8; \ - tileData |= tileData << 12; \ tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ carryData = tileData; \ } \ mosaicWait = mosaicH; \ @@ -414,7 +406,7 @@ return; \ } \ 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; \ } \ DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 2d13b7340..eb793db23 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -116,6 +116,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re x >>= 23; uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; + if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { + return 0; + } int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { int target2 = renderer->target2Bd << 4; diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 035ad0c11..7dae88af6 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -13,6 +13,7 @@ static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer); +static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); @@ -46,6 +47,7 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) { renderer->d.reset = GBAVideoSoftwareRendererReset; renderer->d.deinit = GBAVideoSoftwareRendererDeinit; renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister; + renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM; renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM; renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette; renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline; @@ -327,6 +329,11 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender return value; } +static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { + UNUSED(renderer); + UNUSED(address); +} + static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer; softwareRenderer->oamDirty = 1; @@ -403,12 +410,10 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, if (win->h.end >= oldWindow.endX) { // Trim off extra windows we've overwritten for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) { -#ifdef DEBUG - if (activeWindow >= MAX_WINDOW) { - GBALog(0, GBA_LOG_DANGER, "Out of bounds window write will occur"); + if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) { + GBALog(0, GBA_LOG_FATAL, "Out of bounds window write will occur"); return; } -#endif softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1]; --softwareRenderer->nWindows; } @@ -428,7 +433,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, } #ifdef DEBUG 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 } @@ -532,7 +537,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render } #ifdef COLOR_16_BIT -#ifdef __ARM_NEON +#if defined(__ARM_NEON) && !defined(__APPLE__) _to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS); #else for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 0321b36bd..daa8aca68 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -20,6 +20,7 @@ // Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip). // An average estimation is as follows. #define FLASH_SETTLE_CYCLES 18000 +#define CLEANUP_THRESHOLD 15 static void _flashSwitchBank(struct GBASavedata* savedata, int bank); static void _flashErase(struct GBASavedata* savedata); @@ -33,6 +34,8 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) { savedata->vf = vf; savedata->realVf = vf; savedata->mapMode = MAP_WRITE; + savedata->dirty = 0; + savedata->dirtAge = 0; } void GBASavedataDeinit(struct GBASavedata* savedata) { @@ -252,6 +255,7 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8 case FLASH_STATE_RAW: switch (savedata->command) { case FLASH_COMMAND_PROGRAM: + savedata->dirty |= SAVEDATA_DIRT_NEW; savedata->currentBank[address] = value; savedata->command = FLASH_COMMAND_NONE; break; @@ -359,6 +363,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32 uint8_t current = savedata->data[savedata->writeAddress >> 3]; current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7))); current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7)); + savedata->dirty |= SAVEDATA_DIRT_NEW; savedata->data[savedata->writeAddress >> 3] = current; ++savedata->writeAddress; } else { @@ -401,6 +406,41 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) { return 0; } +void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) { + if (!savedata->vf) { + return; + } + if (savedata->dirty & SAVEDATA_DIRT_NEW) { + savedata->dirty &= ~SAVEDATA_DIRT_NEW; + if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) { + savedata->dirtAge = frameCount; + savedata->dirty |= SAVEDATA_DIRT_SEEN; + } + } else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) { + size_t size; + switch (savedata->type) { + case SAVEDATA_EEPROM: + size = SIZE_CART_EEPROM; + break; + case SAVEDATA_SRAM: + size = SIZE_CART_SRAM; + break; + case SAVEDATA_FLASH512: + size = SIZE_CART_FLASH512; + break; + case SAVEDATA_FLASH1M: + size = SIZE_CART_FLASH1M; + break; + default: + size = 0; + break; + } + savedata->vf->sync(savedata->vf, savedata->data, size); + savedata->dirty = 0; + GBALog(0, GBA_LOG_INFO, "Savedata synced"); + } +} + void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) { state->savedata.type = savedata->type; state->savedata.command = savedata->command; @@ -451,6 +491,7 @@ void _flashSwitchBank(struct GBASavedata* savedata, int bank) { void _flashErase(struct GBASavedata* savedata) { GBALog(0, GBA_LOG_DEBUG, "Performing flash chip erase"); + savedata->dirty |= SAVEDATA_DIRT_NEW; size_t size = SIZE_CART_FLASH512; if (savedata->type == SAVEDATA_FLASH1M) { size = SIZE_CART_FLASH1M; @@ -460,6 +501,7 @@ void _flashErase(struct GBASavedata* savedata) { void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) { GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart); + savedata->dirty |= SAVEDATA_DIRT_NEW; size_t size = 0x1000; if (savedata->type == SAVEDATA_FLASH1M) { GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart); diff --git a/src/gba/savedata.h b/src/gba/savedata.h index a3030eb03..a260fefe3 100644 --- a/src/gba/savedata.h +++ b/src/gba/savedata.h @@ -51,6 +51,11 @@ enum FlashManufacturer { FLASH_MFG_SANYO = 0x1362 }; +enum SavedataDirty { + SAVEDATA_DIRT_NEW = 1, + SAVEDATA_DIRT_SEEN = 2 +}; + enum { SAVEDATA_FLASH_BASE = 0x0E005555, @@ -77,6 +82,9 @@ struct GBASavedata { unsigned settling; int dust; + enum SavedataDirty dirty; + uint32_t dirtAge; + enum FlashStateMachine flashState; }; @@ -98,6 +106,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8 uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata); void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize); +void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount); + struct GBASerializedState; void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData); void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData); diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 1ccfd123c..4c23f4bb7 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -136,7 +136,8 @@ extern const uint32_t GBA_SAVESTATE_MAGIC; * | bit 2: Has light sensor value * | bit 3: Has gyroscope value * | bit 4: Has tilt values - * | bits 5 - 7: Reserved + * | bit 5: Has Game Boy Player attached + * | bits 6 - 7: Reserved * | 0x002B8 - 0x002B9: Gyroscope sample * | 0x002BA - 0x002BB: Tilt x sample * | 0x002BC - 0x002BD: Tilt y sample @@ -149,8 +150,11 @@ extern const uint32_t GBA_SAVESTATE_MAGIC; * | 0x002C0 - 0x002C0: Light sample * | 0x002C1 - 0x002C3: Flags * | bits 0 - 1: Tilt state machine - * | bits 2 - 31: Reserved - * 0x002C4 - 0x002DF: Reserved (leave zero) + * | bits 2 - 3: GB Player inputs posted + * | 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 - 0x002E0: Savedata type * | 0x002E1 - 0x002E1: Savedata command (see savedata.h) @@ -282,10 +286,13 @@ struct GBASerializedState { unsigned lightCounter : 12; unsigned lightSample : 8; unsigned tiltState : 2; - unsigned : 22; + unsigned gbpInputsPosted : 2; + unsigned gbpTxPosition : 5; + unsigned : 15; + uint32_t gbpNextEvent : 32; } hw; - uint32_t reservedHardware[7]; + uint32_t reservedHardware[6]; struct { unsigned type : 8; diff --git a/src/gba/sio.h b/src/gba/sio.h index 61e979385..5963698d9 100644 --- a/src/gba/sio.h +++ b/src/gba/sio.h @@ -8,36 +8,16 @@ #include "util/common.h" +#include "gba/interface.h" + #define MAX_GBAS 4 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 { 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 GBASIODriver* normal; struct GBASIODriver* multiplayer; diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index c32d72e42..19e5c2bd6 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -116,16 +116,49 @@ bool GBAConfigLoad(struct GBAConfig* config) { char path[PATH_MAX]; GBAConfigDirectory(path, PATH_MAX); 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) { char path[PATH_MAX]; GBAConfigDirectory(path, PATH_MAX); 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); } +void GBAConfigMakePortable(const struct GBAConfig* config) { + struct VFile* portable; +#ifndef _WIN32 + char out[PATH_MAX]; + getcwd(out, PATH_MAX); + strncat(out, PATH_SEP "portable.ini", PATH_MAX - strlen(out)); + portable = VFileOpen(out, O_WRONLY | O_CREAT); +#else + char out[MAX_PATH]; + wchar_t wpath[MAX_PATH]; + wchar_t wprojectName[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH); + HMODULE hModule = GetModuleHandleW(NULL); + GetModuleFileNameW(hModule, wpath, MAX_PATH); + PathRemoveFileSpecW(wpath); + WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0); + StringCchCatA(out, MAX_PATH, "\\portable.ini"); + portable = VFileOpen(out, O_WRONLY | O_CREAT); +#endif + if (portable) { + portable->close(portable); + GBAConfigSave(config); + } +} + void GBAConfigDirectory(char* out, size_t outLength) { struct VFile* portable; #ifndef _WIN32 diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h index 86c5a93cc..07c824cd2 100644 --- a/src/gba/supervisor/config.h +++ b/src/gba/supervisor/config.h @@ -51,7 +51,10 @@ void GBAConfigDeinit(struct GBAConfig*); bool GBAConfigLoad(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 GBAConfigDirectory(char* out, size_t outLength); const char* GBAConfigGetValue(const struct GBAConfig*, const char* key); diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index 5ed596644..85e0f8462 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -285,6 +285,12 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri if (override->hardware & HW_TILT) { 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) { @@ -294,3 +300,12 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri } } } + +void GBAOverrideApplyDefaults(struct GBA* gba) { + struct GBACartridgeOverride override; + const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; + memcpy(override.id, &cart->id, sizeof(override.id)); + if (GBAOverrideFind(0, &override)) { + GBAOverrideApply(gba, &override); + } +} diff --git a/src/gba/supervisor/overrides.h b/src/gba/supervisor/overrides.h index cd374572e..9dc7fb67a 100644 --- a/src/gba/supervisor/overrides.h +++ b/src/gba/supervisor/overrides.h @@ -25,5 +25,6 @@ void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* o struct GBA; void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*); +void GBAOverrideApplyDefaults(struct GBA*); #endif diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 587c6c9a3..94eff4a61 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -96,10 +96,22 @@ 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) { #ifdef USE_PTHREADS pthread_once(&_contextOnce, _createTLS); -#else +#elif _WIN32 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); #endif @@ -129,10 +141,18 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { gba.logLevel = threadContext->logLevel; gba.logHandler = threadContext->logHandler; 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; #ifdef USE_PTHREADS pthread_setspecific(_contextKey, threadContext); -#else +#elif _WIN32 TlsSetValue(_contextKey, threadContext); #endif @@ -399,6 +419,16 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR); + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ups", O_RDONLY); + } + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ips", O_RDONLY); + } + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".bps", O_RDONLY); + } + MutexInit(&threadContext->stateMutex); ConditionInit(&threadContext->stateCond); @@ -410,7 +440,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->interruptDepth = 0; -#ifndef _WIN32 +#ifdef USE_PTHREADS sigset_t signals; sigemptyset(&signals); sigaddset(&signals, SIGINT); @@ -722,6 +752,10 @@ struct GBAThread* GBAThreadGetContext(void) { InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); return TlsGetValue(_contextKey); } +#else +struct GBAThread* GBAThreadGetContext(void) { + return 0; +} #endif #ifdef USE_PNG diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index aa86940f2..f9dab3248 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -21,6 +21,7 @@ struct GBACheatSet; struct GBAOptions; typedef void (*ThreadCallback)(struct GBAThread* threadContext); +typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext); enum ThreadState { THREAD_INITIALIZED = -1, @@ -86,6 +87,7 @@ struct GBAThread { ThreadCallback startCallback; ThreadCallback cleanCallback; ThreadCallback frameCallback; + ThreadStopCallback stopCallback; void* userData; void (*run)(struct GBAThread*); diff --git a/src/gba/video.c b/src/gba/video.c index 66ebedd34..39856c2a9 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -17,6 +17,7 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer); static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); +static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y); @@ -47,6 +48,7 @@ static struct GBAVideoRenderer dummyRenderer = { .reset = GBAVideoDummyRendererReset, .deinit = GBAVideoDummyRendererDeinit, .writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister, + .writeVRAM = GBAVideoDummyRendererWriteVRAM, .writePalette = GBAVideoDummyRendererWritePalette, .writeOAM = GBAVideoDummyRendererWriteOAM, .drawScanline = GBAVideoDummyRendererDrawScanline, @@ -222,6 +224,12 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* return value; } +static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { + UNUSED(renderer); + UNUSED(address); + // Nothing to do +} + static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { UNUSED(renderer); UNUSED(address); diff --git a/src/gba/video.h b/src/gba/video.h index a94235238..6713a8f0b 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -161,6 +161,7 @@ struct GBAVideoRenderer { void (*deinit)(struct GBAVideoRenderer* renderer); uint16_t (*writeVideoRegister)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); + void (*writeVRAM)(struct GBAVideoRenderer* renderer, uint32_t address); void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam); void (*drawScanline)(struct GBAVideoRenderer* renderer, int y); diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 967627e43..9a986dd0e 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -8,13 +8,18 @@ #include "util/common.h" #include "gba/gba.h" +#include "gba/interface.h" #include "gba/renderers/video-software.h" #include "gba/serialize.h" #include "gba/supervisor/overrides.h" #include "gba/video.h" +#include "util/circle-buffer.h" #include "util/vfs.h" #define SAMPLES 1024 +#define RUMBLE_PWM 35 + +#define SOLAR_SENSOR_LEVEL "mgba_solar_sensor_level" static retro_environment_t environCallback; static retro_video_refresh_t videoCallback; @@ -22,11 +27,15 @@ static retro_audio_sample_batch_t audioCallback; static retro_input_poll_t inputPollCallback; static retro_input_state_t inputCallback; 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 _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio); static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); +static void _setRumble(struct GBARumble* rumble, int enable); +static uint8_t _readLux(struct GBALuminanceSource* lux); +static void _updateLux(struct GBALuminanceSource* lux); static struct GBA gba; static struct ARMCore cpu; @@ -35,7 +44,13 @@ static struct VFile* rom; static void* data; static struct VFile* save; static void* savedata; +static struct VFile* bios; static struct GBAAVStream stream; +static int rumbleLevel; +static struct CircleBuffer rumbleHistory; +static struct GBARumble rumble; +static struct GBALuminanceSource lux; +static int luxLevel; unsigned retro_api_version(void) { return RETRO_API_VERSION; @@ -43,6 +58,13 @@ unsigned retro_api_version(void) { void retro_set_environment(retro_environment_t env) { environCallback = env; + + struct retro_variable vars[] = { + { SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" }, + { 0, 0 } + }; + + environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars); } void retro_set_video_refresh(retro_video_refresh_t video) { @@ -107,12 +129,27 @@ void retro_init(void) { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" } + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" } }; environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors); // 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; + } + + luxLevel = 0; + lux.readLuminance = _readLux; + lux.sample = _updateLux; + _updateLux(&lux); struct retro_log_callback log; if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) { @@ -132,8 +169,22 @@ void retro_init(void) { gba.logHandler = GBARetroLog; gba.stream = &stream; gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings + if (rumbleCallback) { + gba.rumble = &rumble; + } + gba.luminanceSource = &lux; 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); renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); renderer.outputBufferStride = 256; @@ -148,6 +199,10 @@ void retro_init(void) { } void retro_deinit(void) { + if (bios) { + bios->close(bios); + bios = 0; + } GBADestroy(&gba); } @@ -168,6 +223,26 @@ void retro_run(void) { keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8; keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9; + static bool wasAdjustingLux = false; + if (wasAdjustingLux) { + wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) || + inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3); + } else { + if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) { + ++luxLevel; + if (luxLevel > 10) { + luxLevel = 10; + } + wasAdjustingLux = true; + } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) { + --luxLevel; + if (luxLevel < 0) { + luxLevel = 0; + } + wasAdjustingLux = true; + } + } + int frameCount = gba.video.frameCounter; while (gba.video.frameCounter == frameCount) { ARMRunLoop(&cpu); @@ -176,6 +251,10 @@ void retro_run(void) { void retro_reset(void) { ARMReset(&cpu); + + if (rumbleCallback) { + CircleBufferClear(&rumbleHistory); + } } bool retro_load_game(const struct retro_game_info* game) { @@ -198,13 +277,7 @@ bool retro_load_game(const struct retro_game_info* game) { save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); GBALoadROM(&gba, rom, save, game->path); - - struct GBACartridgeOverride override; - const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom; - memcpy(override.id, &cart->id, sizeof(override.id)); - if (GBAOverrideFind(0, &override)) { - GBAOverrideApply(&gba, &override); - } + GBAOverrideApplyDefaults(&gba); ARMReset(&cpu); return true; @@ -219,6 +292,7 @@ void retro_unload_game(void) { save = 0; free(savedata); savedata = 0; + CircleBufferDeinit(&rumbleHistory); } size_t retro_serialize_size(void) { @@ -317,10 +391,12 @@ void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* f case GBA_LOG_INFO: case GBA_LOG_GAME_ERROR: case GBA_LOG_SWI: + case GBA_LOG_STATUS: retroLevel = RETRO_LOG_INFO; break; case GBA_LOG_DEBUG: case GBA_LOG_STUB: + case GBA_LOG_SIO: retroLevel = RETRO_LOG_DEBUG; break; } @@ -352,3 +428,55 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer->getPixels(renderer, &stride, &pixels); 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); +} + +static void _updateLux(struct GBALuminanceSource* lux) { + UNUSED(lux); + struct retro_variable var = { + .key = SOLAR_SENSOR_LEVEL, + .value = 0 + }; + + bool updated = false; + if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) { + return; + } + if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) { + return; + } + + char* end; + int newLuxLevel = strtol(var.value, &end, 10); + if (!*end) { + if (newLuxLevel > 10) { + luxLevel = 10; + } else if (newLuxLevel < 0) { + luxLevel = 0; + } else { + luxLevel = newLuxLevel; + } + } +} + +static uint8_t _readLux(struct GBALuminanceSource* lux) { + UNUSED(lux); + int value = 0x16; + if (luxLevel > 0) { + value += GBA_LUX_LEVELS[luxLevel - 1]; + } + return 0xFF - value; +} diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c new file mode 100644 index 000000000..a667cf8e8 --- /dev/null +++ b/src/platform/opengl/gles2.c @@ -0,0 +1,136 @@ +/* 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 "gles2.h" + +#include "gba/video.h" + +static const char* const _vertexShader = + "attribute vec4 position;\n" + "varying vec2 texCoord;\n" + + "void main() {\n" + " gl_Position = position;\n" + " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n" + "}"; + +static const char* const _fragmentShader = + "varying vec2 texCoord;\n" + "uniform sampler2D tex;\n" + + "void main() {\n" + " vec4 color = texture2D(tex, texCoord);\n" + " color.a = 1.;\n" + " gl_FragColor = color;" + "}"; + +static const GLfloat _vertices[] = { + -1.f, -1.f, + -1.f, 1.f, + 1.f, 1.f, + 1.f, -1.f, +}; + +static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) { + UNUSED(handle); + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glGenTextures(1, &context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif + + glShaderSource(context->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0); + glShaderSource(context->vertexShader, 1, (const GLchar**) &_vertexShader, 0); + glAttachShader(context->program, context->vertexShader); + glAttachShader(context->program, context->fragmentShader); + char log[1024]; + glCompileShader(context->fragmentShader); + glCompileShader(context->vertexShader); + glGetShaderInfoLog(context->fragmentShader, 1024, 0, log); + glGetShaderInfoLog(context->vertexShader, 1024, 0, log); + glLinkProgram(context->program); + glGetProgramInfoLog(context->program, 1024, 0, log); + printf("%s\n", log); + context->texLocation = glGetUniformLocation(context->program, "tex"); + context->positionLocation = glGetAttribLocation(context->program, "position"); + glClearColor(0.f, 0.f, 0.f, 1.f); +} + +static void GBAGLES2ContextDeinit(struct VideoBackend* v) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glDeleteTextures(1, &context->tex); +} + +static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) { + int drawW = w; + int drawH = h; + if (v->lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + glViewport(0, 0, 240, 160); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); +} + +static void GBAGLES2ContextClear(struct VideoBackend* v) { + UNUSED(v); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); +} + +void GBAGLES2ContextDrawFrame(struct VideoBackend* v) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glUseProgram(context->program); + glUniform1i(context->texLocation, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, context->tex); + glVertexAttribPointer(context->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); + glEnableVertexAttribArray(context->positionLocation); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glUseProgram(0); +} + +void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glBindTexture(GL_TEXTURE_2D, context->tex); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); +#endif +} + +void GBAGLES2ContextCreate(struct GBAGLES2Context* context) { + context->d.init = GBAGLES2ContextInit; + context->d.deinit = GBAGLES2ContextDeinit; + context->d.resized = GBAGLES2ContextResized; + context->d.swap = 0; + context->d.clear = GBAGLES2ContextClear; + context->d.postFrame = GBAGLES2ContextPostFrame; + context->d.drawFrame = GBAGLES2ContextDrawFrame; + context->d.setMessage = 0; + context->d.clearMessage = 0; +} diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h new file mode 100644 index 000000000..c1f97bc27 --- /dev/null +++ b/src/platform/opengl/gles2.h @@ -0,0 +1,27 @@ +/* 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 GLES2_H +#define GLES2_H + +#include + +#include "platform/video-backend.h" + +struct GBAGLES2Context { + struct VideoBackend d; + + GLuint tex; + GLuint fragmentShader; + GLuint vertexShader; + GLuint program; + GLuint bufferObject; + GLuint texLocation; + GLuint positionLocation; +}; + +void GBAGLES2ContextCreate(struct GBAGLES2Context*); + +#endif diff --git a/src/platform/perf-main.c b/src/platform/perf-main.c index 564765314..942dd57d9 100644 --- a/src/platform/perf-main.c +++ b/src/platform/perf-main.c @@ -186,7 +186,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) } } GBASyncWaitFrameEnd(&context->sync); - if (*frames == duration) { + if (duration > 0 && *frames == duration) { _GBAPerfShutdown(0); } if (_dispatchExiting) { diff --git a/src/platform/posix/threading.h b/src/platform/posix/threading.h index 7ca05ce15..71015526a 100644 --- a/src/platform/posix/threading.h +++ b/src/platform/posix/threading.h @@ -10,7 +10,7 @@ #include #include -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__OpenBSD__) #include #endif @@ -79,7 +79,7 @@ static inline int ThreadJoin(Thread thread) { static inline int ThreadSetName(const char* name) { #ifdef __APPLE__ return pthread_setname_np(name); -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0; #else diff --git a/src/platform/qt/AboutScreen.cpp b/src/platform/qt/AboutScreen.cpp new file mode 100644 index 000000000..ca53d334a --- /dev/null +++ b/src/platform/qt/AboutScreen.cpp @@ -0,0 +1,36 @@ +/* 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 "AboutScreen.h" + +#include "util/version.h" + +#include + +using namespace QGBA; + +AboutScreen::AboutScreen(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + QPixmap logo(":/res/mgba-1024.png"); + logo = logo.scaled(m_ui.logo->minimumSize() * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + logo.setDevicePixelRatio(devicePixelRatio()); + m_ui.logo->setPixmap(logo); + + m_ui.projectName->setText(QLatin1String(projectName)); + m_ui.projectVersion->setText(QLatin1String(projectVersion)); + QString gitInfo = m_ui.gitInfo->text(); + gitInfo.replace("{gitBranch}", QLatin1String(gitBranch)); + gitInfo.replace("{gitCommit}", QLatin1String(gitCommit)); + m_ui.gitInfo->setText(gitInfo); + QString description = m_ui.description->text(); + description.replace("{projectName}", QLatin1String(projectName)); + m_ui.description->setText(description); + QString extraLinks = m_ui.extraLinks->text(); + extraLinks.replace("{gitBranch}", QLatin1String(gitBranch)); + m_ui.extraLinks->setText(extraLinks); +} diff --git a/src/platform/qt/AboutScreen.h b/src/platform/qt/AboutScreen.h new file mode 100644 index 000000000..aebf1bdc1 --- /dev/null +++ b/src/platform/qt/AboutScreen.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2013-2014 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_ABOUT_SCREEN +#define QGBA_ABOUT_SCREEN + +#include + +#include "ui_AboutScreen.h" + +namespace QGBA { + +class AboutScreen : public QDialog { +Q_OBJECT + +public: + AboutScreen(QWidget* parent = nullptr); + +private: + Ui::AboutScreen m_ui; +}; + +} + +#endif diff --git a/src/platform/qt/AboutScreen.ui b/src/platform/qt/AboutScreen.ui new file mode 100644 index 000000000..8a4865936 --- /dev/null +++ b/src/platform/qt/AboutScreen.ui @@ -0,0 +1,177 @@ + + + AboutScreen + + + + 0 + 0 + 565 + 268 + + + + About + + + + QLayout::SetFixedSize + + + + + + 36 + 75 + true + + + + {projectName} + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 10 + + + + © 2013 – 2015 Jeffrey Pfau — Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + + + Qt::AlignCenter + + + true + + + + + + + + 75 + true + + + + {projectVersion} + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + 8 + + + 16 + + + 12 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 256 + 192 + + + + {logo} + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + {projectName} is an open-source Game Boy Advance emulator + + + Qt::AlignCenter + + + true + + + + + + + <a href="http://mgba.io/">Website</a> • <a href="https://forumsmgba.io/">Forums / Support</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> • <a href="https://github.com/mgba-emu/mgba/blob/{gitBranch}/LICENSE">License</a> + + + Qt::AlignCenter + + + true + + + + + + + + 10 + + + + Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt> + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp index 6c58e2c39..df4e269de 100644 --- a/src/platform/qt/AudioProcessor.cpp +++ b/src/platform/qt/AudioProcessor.cpp @@ -38,10 +38,10 @@ AudioProcessor* AudioProcessor::create() { #endif default: -#ifdef BUILD_QT_MULTIMEDIA - return new AudioProcessorQt(); -#else +#ifdef BUILD_SDL return new AudioProcessorSDL(); +#else + return new AudioProcessorQt(); #endif } } diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index a29763c23..abbca9395 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/platform/qt/AudioProcessor.h @@ -39,6 +39,8 @@ public slots: virtual void setBufferSamples(int samples) = 0; virtual void inputParametersChanged() = 0; + virtual unsigned sampleRate() const = 0; + protected: GBAThread* input() { return m_context; } diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index 23a3f8e7f..281ca6bba 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/platform/qt/AudioProcessorQt.cpp @@ -83,3 +83,10 @@ void AudioProcessorQt::inputParametersChanged() { m_device->setFormat(m_audioOutput->format()); } } + +unsigned AudioProcessorQt::sampleRate() const { + if (!m_audioOutput) { + return 0; + } + return m_audioOutput->format().sampleRate(); +} diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index 38edf28d7..ee9dde24d 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -28,6 +28,8 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); + virtual unsigned sampleRate() const override; + private: QAudioOutput* m_audioOutput; AudioDevice* m_device; diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index ce6e66bd4..0caa9765c 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -54,3 +54,7 @@ void AudioProcessorSDL::setBufferSamples(int samples) { void AudioProcessorSDL::inputParametersChanged() { } + +unsigned AudioProcessorSDL::sampleRate() const { + return m_audio.obtainedSpec.freq; +} diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index 9a918f6b6..002dcd839 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -29,6 +29,8 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); + virtual unsigned sampleRate() const override; + private: GBASDLAudio m_audio; }; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index a6ff2aea2..9aac6faca 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -28,17 +28,31 @@ endif() set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -find_package(Qt5Multimedia) +if(NOT WIN32 OR NOT BUILD_SDL) + find_package(Qt5Multimedia) +endif() find_package(Qt5OpenGL) find_package(Qt5Widgets) -if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND) +if(NOT BUILD_GL AND NOT BUILD_GLES2) + message(WARNING "OpenGL is required to build the Qt port") + set(BUILD_QT OFF PARENT_SCOPE) + return() +endif() + +if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND) message(WARNING "Cannot find Qt modules") set(BUILD_QT OFF PARENT_SCOPE) return() endif() +if(BUILD_GL) list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) +endif() + +if(BUILD_GLES2) +list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c) +endif() get_target_property(QT_TYPE Qt5::Core TYPE) if(QT_TYPE STREQUAL STATIC_LIBRARY) @@ -47,6 +61,7 @@ if(QT_TYPE STREQUAL STATIC_LIBRARY) endif() set(SOURCE_FILES + AboutScreen.cpp AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp @@ -61,6 +76,7 @@ set(SOURCE_FILES GamepadAxisEvent.cpp GamepadButtonEvent.cpp InputController.cpp + InputProfile.cpp KeyEditor.cpp LoadSaveState.cpp LogController.cpp @@ -82,6 +98,7 @@ set(SOURCE_FILES VideoView.cpp) qt5_wrap_ui(UI_FILES + AboutScreen.ui CheatsView.ui GIFView.ui LoadSaveState.ui @@ -141,7 +158,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}") list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) -target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) +target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE) install(TARGETS ${BINARY_NAME}-qt diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 1996160a9..65ee51528 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -115,8 +115,8 @@ ConfigController::ConfigController(QObject* parent) m_opts.rewindBufferCapacity = 0; m_opts.useBios = true; m_opts.suspendScreensaver = true; - GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigLoad(&m_config); + GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigMap(&m_config, &m_opts); } @@ -126,7 +126,11 @@ ConfigController::~ConfigController() { } bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[]) { - return ::parseArguments(args, &m_config, argc, argv, 0); + if (::parseArguments(args, &m_config, argc, argv, 0)) { + GBAConfigMap(&m_config, &m_opts); + return true; + } + return false; } ConfigOption* ConfigController::addOption(const char* key) { @@ -258,3 +262,19 @@ void ConfigController::write() { GBAConfigSave(&m_config); m_settings->sync(); } + +void ConfigController::makePortable() { + GBAConfigMakePortable(&m_config); + + char path[PATH_MAX]; + GBAConfigDirectory(path, sizeof(path)); + QString fileName(path); + fileName.append(QDir::separator()); + fileName.append("qt.ini"); + QSettings* settings2 = new QSettings(fileName, QSettings::IniFormat, this); + for (const auto& key : m_settings->allKeys()) { + settings2->setValue(key, m_settings->value(key)); + } + delete m_settings; + m_settings = settings2; +} diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index eb26fa896..3b0de11b2 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -89,6 +89,7 @@ public slots: void setOption(const char* key, const QVariant& value); void setQtOption(const QString& key, const QVariant& value, const QString& group = QString()); + void makePortable(); void write(); private: diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 7f0a37f50..014162562 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -51,6 +51,10 @@ Display::Display(QWidget* parent) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 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*) { @@ -68,4 +72,13 @@ void Display::filter(bool filter) { void Display::showMessage(const QString& message) { m_messagePainter.showMessage(message); + if (!isDrawing()) { + forceDraw(); + } +} + +void Display::mouseMoveEvent(QMouseEvent*) { + emit showCursor(); + m_mouseTimer.stop(); + m_mouseTimer.start(); } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 6ae6a35c2..3e6d738f2 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -33,6 +33,12 @@ public: bool isAspectRatioLocked() const { return m_lockAspectRatio; } bool isFiltered() const { return m_filter; } + virtual bool isDrawing() const = 0; + +signals: + void showCursor(); + void hideCursor(); + public slots: virtual void startDrawing(GBAThread* context) = 0; virtual void stopDrawing() = 0; @@ -47,15 +53,19 @@ public slots: protected: void resizeEvent(QResizeEvent*); + virtual void mouseMoveEvent(QMouseEvent*) override; MessagePainter* messagePainter() { return &m_messagePainter; } + private: static Driver s_driver; + static const int MOUSE_DISAPPEAR_TIMER = 1000; MessagePainter m_messagePainter; bool m_lockAspectRatio; bool m_filter; + QTimer m_mouseTimer; }; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 456840751..9446da083 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -16,11 +16,14 @@ using namespace QGBA; DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) : Display(parent) + , m_isDrawing(false) , m_gl(new EmptyGLWidget(format, this)) , m_painter(new PainterGL(m_gl)) , m_drawThread(nullptr) , m_context(nullptr) { + m_gl->setMouseTracking(true); + m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work? } DisplayGL::~DisplayGL() { @@ -31,6 +34,7 @@ void DisplayGL::startDrawing(GBAThread* thread) { if (m_drawThread) { return; } + m_isDrawing = true; m_painter->setContext(thread); m_painter->setMessagePainter(messagePainter()); m_context = thread; @@ -53,6 +57,7 @@ void DisplayGL::startDrawing(GBAThread* thread) { void DisplayGL::stopDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -67,6 +72,7 @@ void DisplayGL::stopDrawing() { void DisplayGL::pauseDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -79,6 +85,7 @@ void DisplayGL::pauseDrawing() { void DisplayGL::unpauseDrawing() { if (m_drawThread) { + m_isDrawing = true; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -133,7 +140,11 @@ PainterGL::PainterGL(QGLWidget* parent) , m_context(nullptr) , m_messagePainter(nullptr) { +#ifdef BUILD_GL GBAGLContextCreate(&m_backend); +#elif defined(BUILD_GLES2) + GBAGLES2ContextCreate(&m_backend); +#endif m_backend.d.swap = [](VideoBackend* v) { PainterGL* painter = static_cast(v->user); painter->m_gl->swapBuffers(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index c49bab16c..ebca3d635 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -9,11 +9,16 @@ #include "Display.h" #include +#include #include #include extern "C" { +#ifdef BUILD_GL #include "platform/opengl/gl.h" +#elif defined(BUILD_GLES2) +#include "platform/opengl/gles2.h" +#endif } struct GBAThread; @@ -27,6 +32,7 @@ public: protected: void paintEvent(QPaintEvent*) override {} void resizeEvent(QResizeEvent*) override {} + void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); } }; class PainterGL; @@ -37,6 +43,8 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + bool isDrawing() const override { return m_isDrawing; } + public slots: void startDrawing(GBAThread* context) override; void stopDrawing() override; @@ -54,6 +62,7 @@ protected: private: void resizePainter(); + bool m_isDrawing; QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread; @@ -88,7 +97,11 @@ private: QGLWidget* m_gl; bool m_active; GBAThread* m_context; +#ifdef BUILD_GL GBAGLContext m_backend; +#elif defined(BUILD_GLES2) + GBAGLES2Context m_backend; +#endif QSize m_size; MessagePainter* m_messagePainter; }; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 182ecd2bc..71e684055 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -15,11 +15,13 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) + , m_isDrawing(false) , m_backing(nullptr) { } void DisplayQt::startDrawing(GBAThread*) { + m_isDrawing = true; } void DisplayQt::lockAspectRatio(bool lock) { diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index f7fefb0ee..1d5acc5b4 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -21,11 +21,13 @@ Q_OBJECT public: DisplayQt(QWidget* parent = nullptr); + bool isDrawing() const override { return m_isDrawing; } + public slots: void startDrawing(GBAThread* context) override; - void stopDrawing() override {} - void pauseDrawing() override {} - void unpauseDrawing() override {} + void stopDrawing() override { m_isDrawing = false; } + void pauseDrawing() override { m_isDrawing = false; } + void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void filter(bool filter) override; @@ -35,6 +37,7 @@ protected: virtual void paintEvent(QPaintEvent*) override; private: + bool m_isDrawing; QImage m_backing; }; diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c40eb42ba..3200f45e7 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -45,31 +45,32 @@ GBAApp::GBAApp(int& argc, char* argv[]) Display::setDriver(static_cast(m_configController.getQtOption("displayDriver").toInt())); } + GBAArguments args; + bool loaded = m_configController.parseArguments(&args, argc, argv); + if (loaded && args.showHelp) { + usage(argv[0], 0); + ::exit(0); + return; + } + Window* w = new Window(&m_configController); connect(w, &Window::destroyed, [this]() { m_windows[0] = nullptr; }); m_windows[0] = w; -#ifndef Q_OS_MAC - w->show(); -#endif - - GBAArguments args; - if (m_configController.parseArguments(&args, argc, argv)) { + if (loaded) { w->argumentsPassed(&args); } else { w->loadConfig(); } freeArguments(&args); + w->show(); AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); w->controller()->reloadAudioDriver(); w->controller()->setMultiplayerController(&m_multiplayer); -#ifdef Q_OS_MAC - w->show(); -#endif } bool GBAApp::event(QEvent* event) { @@ -91,14 +92,9 @@ Window* GBAApp::newWindow() { }); m_windows[windowId] = w; w->setAttribute(Qt::WA_DeleteOnClose); -#ifndef Q_OS_MAC - w->show(); -#endif w->loadConfig(); - w->controller()->setMultiplayerController(&m_multiplayer); -#ifdef Q_OS_MAC w->show(); -#endif + w->controller()->setMultiplayerController(&m_multiplayer); return w; } diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index 4a8577979..73e0f6ac6 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -6,6 +6,7 @@ #include "GBAKeyEditor.h" #include +#include #include #include #include @@ -17,13 +18,14 @@ using namespace QGBA; const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; -const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431; -const qreal GBAKeyEditor::DPAD_WIDTH = 0.1; -const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1; +const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432; +const qreal GBAKeyEditor::DPAD_WIDTH = 0.12; +const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12; GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent) : QWidget(parent) , m_profileSelect(nullptr) + , m_clear(nullptr) , m_type(type) , m_profile(profile) , m_controller(controller) @@ -32,6 +34,7 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& setMinimumSize(300, 300); const GBAInputMap* map = controller->map(); + controller->stealFocus(this); m_keyDU = 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); 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 - 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); QVBoxLayout* layout = new QVBoxLayout; m_buttons->setLayout(layout); @@ -115,6 +123,11 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& 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_background.load(":/res/keymap.qpic"); @@ -135,13 +148,17 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) { setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y); setLocation(m_keySelect, 0.415, 0.93); setLocation(m_keyStart, 0.585, 0.93); - setLocation(m_keyA, 0.826, 0.451); - setLocation(m_keyB, 0.667, 0.490); + setLocation(m_keyA, 0.826, 0.475); + setLocation(m_keyB, 0.667, 0.514); setLocation(m_keyL, 0.1, 0.1); setLocation(m_keyR, 0.9, 0.1); 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); } +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() { findFocus(); @@ -167,6 +197,10 @@ void GBAKeyEditor::setNext() { } void GBAKeyEditor::save() { +#ifdef BUILD_SDL + m_controller->unbindAllAxes(m_type); +#endif + bindKey(m_keyDU, GBA_KEY_UP); bindKey(m_keyDD, GBA_KEY_DOWN); bindKey(m_keyDL, GBA_KEY_LEFT); @@ -239,14 +273,11 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) { void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { #ifdef BUILD_SDL - if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) { - m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key); - } else { -#endif - m_controller->bindKey(m_type, keyEditor->value(), key); -#ifdef BUILD_SDL + if (m_type == SDL_BINDING_BUTTON) { + m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key); } #endif + m_controller->bindKey(m_type, keyEditor->value(), key); } bool GBAKeyEditor::findFocus() { diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h index 9496ffffe..c1ba3229f 100644 --- a/src/platform/qt/GBAKeyEditor.h +++ b/src/platform/qt/GBAKeyEditor.h @@ -35,6 +35,8 @@ public slots: protected: virtual void resizeEvent(QResizeEvent*) override; virtual void paintEvent(QPaintEvent*) override; + virtual bool event(QEvent*) override; + virtual void closeEvent(QCloseEvent*) override; private slots: void setNext(); @@ -64,6 +66,7 @@ private: KeyEditor* keyById(GBAKey); QComboBox* m_profileSelect; + QWidget* m_clear; QWidget* m_buttons; KeyEditor* m_keyDU; KeyEditor* m_keyDD; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 8440cd284..91b22a921 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -7,6 +7,7 @@ #include "AudioProcessor.h" #include "InputController.h" +#include "LogController.h" #include "MultiplayerController.h" #include "VFileDevice.h" @@ -28,8 +29,6 @@ extern "C" { using namespace QGBA; using namespace std; -const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; - GameController::GameController(QObject* parent) : QObject(parent) , m_drawContext(new uint32_t[256 * 256]) @@ -48,9 +47,13 @@ GameController::GameController(QObject* parent) , m_turboForced(false) , m_turboSpeed(-1) , m_wasPaused(false) + , m_audioChannels{ true, true, true, true, true, true } + , m_videoLayers{ true, true, true, true, true } , m_inputController(nullptr) , m_multiplayer(nullptr) , m_stateSlot(1) + , m_backupLoadState(nullptr) + , m_backupSaveState(nullptr) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer); @@ -89,7 +92,25 @@ GameController::GameController(QObject* parent) context->gba->rtcSource = &controller->m_rtc.d; context->gba->rumble = controller->m_inputController->rumble(); context->gba->rotationSource = controller->m_inputController->rotationSource(); + context->gba->audio.forceDisableCh[0] = !controller->m_audioChannels[0]; + context->gba->audio.forceDisableCh[1] = !controller->m_audioChannels[1]; + context->gba->audio.forceDisableCh[2] = !controller->m_audioChannels[2]; + context->gba->audio.forceDisableCh[3] = !controller->m_audioChannels[3]; + context->gba->audio.forceDisableChA = !controller->m_audioChannels[4]; + context->gba->audio.forceDisableChB = !controller->m_audioChannels[5]; + context->gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0]; + context->gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1]; + context->gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2]; + context->gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3]; + context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4]; 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); }; @@ -100,19 +121,33 @@ GameController::GameController(QObject* parent) m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { - GBAThreadPauseFromThread(context); - controller->gamePaused(&controller->m_threadContext); - } if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { controller->frameAvailable(controller->m_drawContext); } else { controller->frameAvailable(nullptr); } + if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { + GBAThreadPauseFromThread(context); + controller->gamePaused(&controller->m_threadContext); + } + }; + + m_threadContext.stopCallback = [](GBAThread* context) { + if (!context) { + return false; + } + GameController* controller = static_cast(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) { - 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) { return; } @@ -123,6 +158,25 @@ GameController::GameController(QObject* parent) int immediate = va_arg(argc, int); va_end(argc); 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) { QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args))); @@ -162,6 +216,7 @@ GameController::~GameController() { GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; + delete m_backupLoadState; } void GameController::setMultiplayerController(MultiplayerController* controller) { @@ -284,6 +339,7 @@ void GameController::openGame(bool biosOnly) { } m_inputController->recalibrateAxes(); + memset(m_drawContext, 0xF8, 1024 * 256); if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false; @@ -409,8 +465,7 @@ void GameController::setPaused(bool paused) { return; } if (paused) { - GBAThreadPause(&m_threadContext); - emit gamePaused(&m_threadContext); + m_pauseAfterFrame.testAndSetRelaxed(false, true); } else { GBAThreadUnpause(&m_threadContext); emit gameUnpaused(&m_threadContext); @@ -475,7 +530,9 @@ void GameController::startRewinding() { return; } m_wasPaused = isPaused(); + bool signalsBlocked = blockSignals(true); setPaused(true); + blockSignals(signalsBlocked); m_rewindTimer.start(); } @@ -484,7 +541,9 @@ void GameController::stopRewinding() { return; } m_rewindTimer.stop(); + bool signalsBlocked = blockSignals(true); setPaused(m_wasPaused); + blockSignals(signalsBlocked); } void GameController::keyPressed(int key) { @@ -532,6 +591,49 @@ void GameController::setAudioBufferSamples(int samples) { QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); } +void GameController::setAudioChannelEnabled(int channel, bool enable) { + if (channel > 5 || channel < 0) { + return; + } + m_audioChannels[channel] = enable; + if (m_gameOpen) { + switch (channel) { + case 0: + case 1: + case 2: + case 3: + m_threadContext.gba->audio.forceDisableCh[channel] = !enable; + break; + case 4: + m_threadContext.gba->audio.forceDisableChA = !enable; + break; + case 5: + m_threadContext.gba->audio.forceDisableChB = !enable; + break; + } + } +} + +void GameController::setVideoLayerEnabled(int layer, bool enable) { + if (layer > 4 || layer < 0) { + return; + } + m_videoLayers[layer] = enable; + if (m_gameOpen) { + switch (layer) { + case 0: + case 1: + case 2: + case 3: + m_threadContext.gba->video.renderer->disableBG[layer] = !enable; + break; + case 4: + m_threadContext.gba->video.renderer->disableOBJ = !enable; + break; + } + } +} + void GameController::setFPSTarget(float fps) { threadInterrupt(); m_fpsTarget = fps; @@ -556,11 +658,16 @@ void GameController::setUseBIOS(bool use) { } void GameController::loadState(int slot) { - if (slot > 0) { + if (slot > 0 && slot != m_stateSlot) { m_stateSlot = slot; + m_backupSaveState.clear(); } GBARunOnThread(&m_threadContext, [](GBAThread* context) { GameController* controller = static_cast(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)) { controller->frameAvailable(controller->m_drawContext); controller->stateLoaded(context); @@ -574,10 +681,50 @@ void GameController::saveState(int slot) { } GBARunOnThread(&m_threadContext, [](GBAThread* context) { GameController* controller = static_cast(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); }); } +void GameController::loadBackupState() { + if (!m_backupLoadState) { + return; + } + + GBARunOnThread(&m_threadContext, [](GBAThread* context) { + GameController* controller = static_cast(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(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) { m_videoSync = set; if (!m_turbo) { @@ -701,7 +848,7 @@ void GameController::setLuminanceValue(uint8_t value) { value = std::max(value - 0x16, 0); m_luxLevel = 10; for (int i = 0; i < 10; ++i) { - if (value < LUX_LEVELS[i]) { + if (value < GBA_LUX_LEVELS[i]) { m_luxLevel = i; break; } @@ -713,7 +860,7 @@ void GameController::setLuminanceLevel(int level) { int value = 0x16; level = std::max(0, std::min(10, level)); if (level > 0) { - value += LUX_LEVELS[level - 1]; + value += GBA_LUX_LEVELS[level - 1]; } setLuminanceValue(value); } @@ -746,7 +893,7 @@ void GameController::redoSamples(int samples) { if (m_threadContext.gba) { 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); #else m_threadContext.audioBuffers = samples; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 349a39ce6..14fbdf7e4 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -55,7 +55,7 @@ public: void threadContinue(); bool isPaused(); - bool isLoaded() { return m_gameOpen; } + bool isLoaded() { return m_gameOpen && GBAThreadIsActive(&m_threadContext); } bool audioSync() const { return m_audioSync; } bool videoSync() const { return m_videoSync; } @@ -72,6 +72,8 @@ public: void setOptions(const GBAOptions*); + int stateSlot() const { return m_stateSlot; } + #ifdef USE_GDB_STUB ARMDebugger* debugger(); void setDebugger(ARMDebugger*); @@ -117,9 +119,13 @@ public slots: void keyReleased(int key); void clearKeys(); void setAudioBufferSamples(int samples); + void setAudioChannelEnabled(int channel, bool enable = true); + void setVideoLayerEnabled(int layer, bool enable = true); void setFPSTarget(float fps); void loadState(int slot = 0); void saveState(int slot = 0); + void loadBackupState(); + void saveBackupState(); void setVideoSync(bool); void setAudioSync(bool); void setFrameskip(int); @@ -191,7 +197,12 @@ private: QTimer m_rewindTimer; bool m_wasPaused; + bool m_audioChannels[6]; + bool m_videoLayers[5]; + int m_stateSlot; + GBASerializedState* m_backupLoadState; + QByteArray m_backupSaveState; InputController* m_inputController; MultiplayerController* m_multiplayer; @@ -203,8 +214,6 @@ private: uint8_t m_luxValue; int m_luxLevel; - static const int LUX_LEVELS[10]; - GBARTCGenericSource m_rtc; }; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index ec5ef6b37..85bf49b1c 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -8,6 +8,7 @@ #include "ConfigController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" +#include "InputProfile.h" #include #include @@ -24,7 +25,7 @@ int InputController::s_sdlInited = 0; GBASDLEvents InputController::s_sdlEvents; #endif -InputController::InputController(int playerId, QObject* parent) +InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) : QObject(parent) , m_playerId(playerId) , m_config(nullptr) @@ -33,6 +34,8 @@ InputController::InputController(int playerId, QObject* parent) , m_playerAttached(false) #endif , m_allowOpposing(false) + , m_topLevel(topLevel) + , m_focusParent(topLevel) { GBAInputMapInit(&m_inputMap); @@ -106,8 +109,15 @@ void InputController::loadConfiguration(uint32_t type) { } 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(); + if (!loaded) { + const InputProfile* ip = InputProfile::findProfile(profile); + if (ip) { + ip->apply(this); + } + } + emit profileLoaded(profile); } void InputController::saveConfiguration() { @@ -198,9 +208,11 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device) GBARumble* InputController::rumble() { #ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) if (m_playerAttached) { return &m_sdlPlayer.rumble.d; } +#endif #endif return nullptr; } @@ -403,6 +415,10 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct GBAInputBindAxis(&m_inputMap, type, axis, &description); } +void InputController::unbindAllAxes(uint32_t type) { + GBAInputUnbindAllAxes(&m_inputMap, type); +} + void InputController::testGamepad(int type) { auto activeAxes = activeGamepadAxes(type); auto oldAxes = m_activeAxes; @@ -424,7 +440,7 @@ void InputController::testGamepad(int type) { if (newlyAboveThreshold) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this); postPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); } @@ -433,7 +449,7 @@ void InputController::testGamepad(int type) { for (auto axis : oldAxes) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this); clearPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); } if (!QApplication::focusWidget()) { @@ -446,7 +462,7 @@ void InputController::testGamepad(int type) { for (int button : activeButtons) { GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this); postPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); } @@ -454,10 +470,23 @@ void InputController::testGamepad(int type) { for (int button : oldButtons) { GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this); 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) { m_pendingEvents.insert(key); } @@ -470,16 +499,36 @@ bool InputController::hasPendingEvent(GBAKey key) const { return m_pendingEvents.contains(key); } -#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) void InputController::suspendScreensaver() { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) GBASDLSuspendScreensaver(&s_sdlEvents); +#endif +#endif } void InputController::resumeScreensaver() { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) GBASDLResumeScreensaver(&s_sdlEvents); +#endif +#endif } void InputController::setScreensaverSuspendable(bool suspendable) { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) 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; + } +} diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 05493db8e..f73524bad 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -32,7 +32,7 @@ Q_OBJECT public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(int playerId = 0, QObject* parent = nullptr); + InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config); @@ -60,6 +60,7 @@ public: void recalibrateAxes(); void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); + void unbindAllAxes(uint32_t type); QStringList connectedGamepads(uint32_t type) const; int gamepad(uint32_t type) const; @@ -74,28 +75,35 @@ public: float gyroSensitivity() const; void setGyroSensitivity(float sensitivity); + void stealFocus(QWidget* focus); + void releaseFocus(QWidget* focus); + GBARumble* rumble(); GBARotationSource* rotationSource(); +signals: + void profileLoaded(const QString& profile); + public slots: void testGamepad(int type); -#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) // TODO: Move these to somewhere that makes sense void suspendScreensaver(); void resumeScreensaver(); void setScreensaverSuspendable(bool); -#endif private: void postPendingEvent(GBAKey); void clearPendingEvent(GBAKey); bool hasPendingEvent(GBAKey) const; + void sendGamepadEvent(QEvent*); GBAInputMap m_inputMap; ConfigController* m_config; int m_playerId; bool m_allowOpposing; + QWidget* m_topLevel; + QWidget* m_focusParent; #ifdef BUILD_SDL static int s_sdlInited; diff --git a/src/platform/qt/InputProfile.cpp b/src/platform/qt/InputProfile.cpp new file mode 100644 index 000000000..c4e542bc7 --- /dev/null +++ b/src/platform/qt/InputProfile.cpp @@ -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 + +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(i)); + controller->bindAxis(SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast(i)); +#endif + } + controller->registerTiltAxisX(m_tiltAxis.x); + controller->registerTiltAxisY(m_tiltAxis.y); + controller->registerGyroAxisX(m_gyroAxis.x); + controller->registerGyroAxisY(m_gyroAxis.y); + controller->setGyroSensitivity(m_gyroSensitivity); +} + +bool InputProfile::lookupShortcutButton(const QString& shortcutName, int* button) const { + 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; +} diff --git a/src/platform/qt/InputProfile.h b/src/platform/qt/InputProfile.h new file mode 100644 index 000000000..8a710db5a --- /dev/null +++ b/src/platform/qt/InputProfile.h @@ -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 diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index fd76b2def..ef06d6e41 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -15,22 +15,20 @@ using namespace QGBA; KeyEditor::KeyEditor(QWidget* parent) : QLineEdit(parent) , m_direction(GamepadAxisEvent::NEUTRAL) + , m_key(-1) + , m_axis(-1) , m_button(false) { setAlignment(Qt::AlignCenter); } void KeyEditor::setValue(int key) { + m_key = key; if (m_button) { - if (key < 0) { - clear(); - } else { - setText(QString::number(key)); - } + updateButtonText(); } else { setText(QKeySequence(key).toString(QKeySequence::NativeText)); } - m_key = key; emit valueChanged(key); } @@ -41,18 +39,30 @@ void KeyEditor::setValueKey(int key) { void KeyEditor::setValueButton(int button) { m_button = true; - m_direction = GamepadAxisEvent::NEUTRAL; setValue(button); } void KeyEditor::setValueAxis(int axis, int32_t value) { m_button = true; - m_key = axis; + m_axis = axis; m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE; - setText((value < 0 ? "-" : "+") + QString::number(axis)); + updateButtonText(); 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 hint = QLineEdit::sizeHint(); hint.setWidth(40); @@ -85,3 +95,18 @@ bool KeyEditor::event(QEvent* 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("/")); + } +} diff --git a/src/platform/qt/KeyEditor.h b/src/platform/qt/KeyEditor.h index 817c00c1c..8fdd04f34 100644 --- a/src/platform/qt/KeyEditor.h +++ b/src/platform/qt/KeyEditor.h @@ -20,6 +20,7 @@ public: int value() const { return m_key; } GamepadAxisEvent::Direction direction() const { return m_direction; } + int axis() const { return m_axis; } virtual QSize sizeHint() const override; @@ -28,6 +29,8 @@ public slots: void setValueKey(int key); void setValueButton(int button); void setValueAxis(int axis, int32_t value); + void clearButton(); + void clearAxis(); signals: void valueChanged(int key); @@ -38,7 +41,10 @@ protected: virtual bool event(QEvent* event) override; private: + void updateButtonText(); + int m_key; + int m_axis; bool m_button; GamepadAxisEvent::Direction m_direction; }; diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 15796b67e..054dd13b4 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -23,7 +23,7 @@ using namespace QGBA; LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) : QWidget(parent) , m_controller(controller) - , m_currentFocus(0) + , m_currentFocus(controller->stateSlot() - 1) , m_mode(LoadSave::LOAD) { m_ui.setupUi(this); @@ -45,6 +45,13 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); }); } + if (m_currentFocus >= 9) { + m_currentFocus = 0; + } + if (m_currentFocus < 0) { + m_currentFocus = 0; + } + QAction* escape = new QAction(this); escape->connect(escape, SIGNAL(triggered()), this, SLOT(close())); escape->setShortcut(QKeySequence("Esc")); diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 76a6ca35a..738760684 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -39,6 +39,7 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, connect(m_ui.hwLight, 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.hwGBPlayer, SIGNAL(clicked()), this, SLOT(updateOverrides())); connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride())); @@ -80,6 +81,9 @@ void OverrideView::updateOverrides() { m_override.hardware |= HW_RUMBLE; } } + if (m_ui.hwGBPlayer->isChecked()) { + m_override.hardware |= HW_GB_PLAYER_DETECTION; + } bool ok; 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.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT); 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) { m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16)); diff --git a/src/platform/qt/OverrideView.ui b/src/platform/qt/OverrideView.ui index 6925ede96..bb99fc40a 100644 --- a/src/platform/qt/OverrideView.ui +++ b/src/platform/qt/OverrideView.ui @@ -6,8 +6,8 @@ 0 0 - 409 - 228 + 401 + 203 @@ -23,13 +23,19 @@ QLayout::SetFixedSize - + Qt::Horizontal + + + 0 + 0 + + @@ -44,7 +50,135 @@ - + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + + + + + Save type + + + + + + + + Autodetect + + + + + None + + + + + SRAM + + + + + Flash 512kb + + + + + Flash 1Mb + + + + + EEPROM + + + + + + + + Idle loop + + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Game Boy Player features + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + @@ -113,83 +247,6 @@ - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Save type - - - - - - - - Autodetect - - - - - None - - - - - SRAM - - - - - Flash 512kb - - - - - Flash 1Mb - - - - - EEPROM - - - - - - - - Idle loop - - - - - - - - - - Qt::Horizontal - - - - - - - - - - Qt::Vertical - - - diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 5f288da37..cc343cd29 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -36,6 +36,19 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); 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"); if (idleOptimization == "ignore") { m_ui.idleOptimization->setCurrentIndex(0); @@ -103,6 +116,12 @@ void SettingsView::updateConfig() { saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); 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) { case IDLE_LOOP_IGNORE: saveSetting("idleOptimization", "ignore"); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 9de1f76e4..bfd7af451 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -6,8 +6,8 @@ 0 0 - 698 - 366 + 707 + 420 @@ -380,21 +380,21 @@ - + Qt::Horizontal - + Allow opposing input directions - + Suspend screensaver @@ -404,14 +404,14 @@ - + Idle loops - + @@ -430,6 +430,52 @@ + + + + false + + + × + + + 0.010000000000000 + + + 20.000000000000000 + + + 0.500000000000000 + + + 5.000000000000000 + + + + + + + Fast forward speed + + + + + + + Unbounded + + + true + + + + + + + Qt::Horizontal + + + diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp index 34993a386..2c0276956 100644 --- a/src/platform/qt/ShortcutController.cpp +++ b/src/platform/qt/ShortcutController.cpp @@ -7,6 +7,7 @@ #include "ConfigController.h" #include "GamepadButtonEvent.h" +#include "InputProfile.h" #include #include @@ -18,6 +19,7 @@ ShortcutController::ShortcutController(QObject* parent) : QAbstractItemModel(parent) , m_rootMenu(nullptr) , m_config(nullptr) + , m_profile(nullptr) { } @@ -239,6 +241,9 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) { } if (m_config) { m_config->setQtOption(item->name(), button, BUTTON_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); + } } emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); @@ -272,6 +277,9 @@ void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadA d = '-'; } m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); + } } emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); @@ -365,6 +373,9 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { } void ShortcutController::loadShortcuts(ShortcutItem* item) { + if (item->name().isNull()) { + return; + } QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); if (!shortcut.isNull()) { QKeySequence keySequence(shortcut.toString()); @@ -377,19 +388,45 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) { } item->setShortcut(keySequence); } - QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION); - if (!button.isNull()) { - int oldButton = item->button(); - item->setButton(button.toInt()); - if (oldButton >= 0) { - m_buttons.take(oldButton); + loadGamepadShortcuts(item); +} + +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(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + item->setButton(-1); + } + if (button.isNull() && m_profile) { + int buttonInt; + if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { + button = buttonInt; } + } + if (!button.isNull()) { + item->setButton(button.toInt()); m_buttons[button.toInt()] = item; } - QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION); + + QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + item->setAxis(-1, GamepadAxisEvent::NEUTRAL); + } + if (axis.isNull() && m_profile) { + int axisInt; + GamepadAxisEvent::Direction direction; + if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { + axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); + } + } if (!axis.isNull()) { - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); QString axisDesc = axis.toString(); if (axisDesc.size() >= 2) { GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; @@ -403,9 +440,6 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) { int axis = axisDesc.mid(1).toInt(&ok); if (ok) { item->setAxis(axis, direction); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - } m_axes[qMakePair(axis, direction)] = item; } } @@ -432,6 +466,21 @@ QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) { 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 func) { + for (ShortcutItem& subitem : item->items()) { + func(&subitem); + onSubitems(&subitem, func); + } +} + ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent) : m_action(action) , m_shortcut(action->shortcut()) diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index f8be878bf..811a34f34 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -21,6 +21,7 @@ class QString; namespace QGBA { class ConfigController; +class InputProfile; class ShortcutController : public QAbstractItemModel { Q_OBJECT @@ -29,6 +30,8 @@ private: constexpr static const char* const KEY_SECTION = "shortcutKey"; constexpr static const char* const BUTTON_SECTION = "shortcutButton"; constexpr static const char* const AXIS_SECTION = "shortcutAxis"; + constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; + constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; class ShortcutItem { public: @@ -84,6 +87,7 @@ public: ShortcutController(QObject* parent = nullptr); void setConfigController(ConfigController* controller); + void setProfile(const QString& profile); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; @@ -99,6 +103,8 @@ public: const QKeySequence& shortcut, const QString& visibleName, const QString& name); void addMenu(QMenu* menu, QMenu* parent = nullptr); + QAction* getAction(const QString& name); + QKeySequence shortcutAt(const QModelIndex& index) const; bool isMenuAt(const QModelIndex& index) const; @@ -111,6 +117,9 @@ public: static QKeySequence keyEventToSequence(const QKeyEvent*); +public slots: + void loadProfile(const QString& profile); + protected: bool eventFilter(QObject*, QEvent*) override; @@ -118,6 +127,8 @@ private: ShortcutItem* itemAt(const QModelIndex& index); const ShortcutItem* itemAt(const QModelIndex& index) const; void loadShortcuts(ShortcutItem*); + void loadGamepadShortcuts(ShortcutItem*); + void onSubitems(ShortcutItem*, std::function func); ShortcutItem m_rootMenu; QMap m_menuMap; @@ -125,6 +136,8 @@ private: QMap, ShortcutItem*> m_axes; QMap m_heldKeys; ConfigController* m_config; + QString m_profileName; + const InputProfile* m_profile; }; } diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index b0ba9045f..036d9cf76 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/platform/qt/ShortcutView.cpp @@ -6,6 +6,7 @@ #include "ShortcutView.h" #include "GamepadButtonEvent.h" +#include "InputController.h" #include "ShortcutController.h" #include @@ -15,6 +16,7 @@ using namespace QGBA; ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) , m_controller(nullptr) + , m_input(nullptr) { m_ui.setupUi(this); m_ui.keyEdit->setValueButton(-1); @@ -32,6 +34,14 @@ void ShortcutView::setController(ShortcutController* 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) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); @@ -111,3 +121,20 @@ void ShortcutView::updateAxis(int axis, int direction) { m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, static_cast(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); +} diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h index af6baadaa..59a997347 100644 --- a/src/platform/qt/ShortcutView.h +++ b/src/platform/qt/ShortcutView.h @@ -14,6 +14,7 @@ namespace QGBA { +class InputController; class ShortcutController; class ShortcutView : public QWidget { @@ -23,9 +24,12 @@ public: ShortcutView(QWidget* parent = nullptr); void setController(ShortcutController* controller); + void setInputController(InputController* input); protected: virtual bool eventFilter(QObject* obj, QEvent* event) override; + virtual bool event(QEvent*) override; + virtual void closeEvent(QCloseEvent*) override; private slots: void load(const QModelIndex&); @@ -38,6 +42,7 @@ private: Ui::ShortcutView m_ui; ShortcutController* m_controller; + InputController* m_input; }; } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 2feb07d6d..60b8861ee 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -13,6 +13,7 @@ #include #include +#include "AboutScreen.h" #include "CheatsView.h" #include "ConfigController.h" #include "Display.h" @@ -40,8 +41,8 @@ extern "C" { using namespace QGBA; -#ifdef __WIN32 -// This is a macro everywhere except MinGW, it seems +#if defined(__WIN32) || defined(__OpenBSD__) +// This is a macro everywhere except MinGW and OpenBSD, it seems using std::isnan; #endif @@ -53,7 +54,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) , m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) - , m_inputController(playerId) + , m_inputController(playerId, this) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif @@ -128,6 +129,15 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int))); connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); + connect(m_display, &Display::hideCursor, [this]() { + if (static_cast(m_screenWidget->layout())->currentWidget() == m_display) { + m_screenWidget->setCursor(Qt::BlankCursor); + } + }); + connect(m_display, &Display::showCursor, [this]() { + m_screenWidget->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_fpsTimer.setInterval(FPS_TIMER_INTERVAL); @@ -162,6 +172,7 @@ void Window::argumentsPassed(GBAArguments* args) { void Window::resizeFrame(int width, int height) { QSize newSize(width, height); + m_screenWidget->setSizeHint(newSize); newSize -= m_screenWidget->size(); newSize += size(); resize(newSize); @@ -296,6 +307,7 @@ void Window::openSettingsWindow() { SettingsView* settingsWindow = new SettingsView(m_config); connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); + connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart())); openView(settingsWindow); } @@ -305,6 +317,7 @@ void Window::openShortcutWindow() { #endif ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); + shortcutView->setInputController(&m_inputController); openView(shortcutView); } @@ -333,6 +346,11 @@ void Window::openMemoryWindow() { openView(memoryWindow); } +void Window::openAboutScreen() { + AboutScreen* about = new AboutScreen(); + openView(about); +} + #ifdef BUILD_SDL void Window::openGamepadWindow() { const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); @@ -409,14 +427,34 @@ void Window::keyReleaseEvent(QKeyEvent* event) { event->accept(); } -void Window::resizeEvent(QResizeEvent*) { +void Window::resizeEvent(QResizeEvent* event) { if (!isFullScreen()) { m_config->setOption("height", m_screenWidget->height()); m_config->setOption("width", m_screenWidget->width()); } + + int factor = 0; + if (event->size().width() % VIDEO_HORIZONTAL_PIXELS == 0 && event->size().height() % VIDEO_VERTICAL_PIXELS == 0 && + event->size().width() / VIDEO_HORIZONTAL_PIXELS == event->size().height() / VIDEO_VERTICAL_PIXELS) { + factor = event->size().width() / VIDEO_HORIZONTAL_PIXELS; + } + for (QMap::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) { + bool enableSignals = iter.value()->blockSignals(true); + if (iter.key() == factor) { + iter.value()->setChecked(true); + } else { + iter.value()->setChecked(false); + } + iter.value()->blockSignals(enableSignals); + } + m_config->setOption("fullscreen", isFullScreen()); } +void Window::showEvent(QShowEvent* event) { + resizeFrame(m_screenWidget->sizeHint().width(), m_screenWidget->sizeHint().height()); +} + void Window::closeEvent(QCloseEvent* event) { emit shutdown(); m_config->setQtOption("windowPos", pos()); @@ -424,6 +462,10 @@ void Window::closeEvent(QCloseEvent* event) { QMainWindow::closeEvent(event); } +void Window::focusInEvent(QFocusEvent*) { + m_display->forceDraw(); +} + void Window::focusOutEvent(QFocusEvent*) { m_controller->setTurbo(false, false); m_controller->stopRewinding(); @@ -464,7 +506,6 @@ void Window::enterFullScreen() { return; } showFullScreen(); - setCursor(Qt::BlankCursor); #ifndef Q_OS_MAC if (m_controller->isLoaded() && !m_controller->isPaused()) { menuBar()->hide(); @@ -476,7 +517,7 @@ void Window::exitFullScreen() { if (!isFullScreen()) { return; } - unsetCursor(); + m_screenWidget->unsetCursor(); menuBar()->show(); showNormal(); } @@ -504,6 +545,7 @@ void Window::gameStarted(GBAThread* context) { action->setDisabled(false); } if (context->fname) { + setWindowFilePath(context->fname); appendMRU(context->fname); } updateTitle(); @@ -523,10 +565,12 @@ void Window::gameStopped() { foreach (QAction* action, m_gameActions) { action->setDisabled(true); } + setWindowFilePath(QString()); updateTitle(); detachWidget(m_display); m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); m_screenWidget->setPixmap(m_logo); + m_screenWidget->unsetCursor(); m_fpsTimer.stop(); } @@ -561,6 +605,23 @@ void Window::unimplementedBiosCall(int call) { 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() { m_frameList.append(QDateTime::currentDateTime()); while (m_frameList.count() > FRAME_LIST_SIZE) { @@ -642,6 +703,10 @@ void Window::setupMenu(QMenuBar* menubar) { fileMenu->addSeparator(); + addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable"); + + fileMenu->addSeparator(); + QAction* loadState = new QAction(tr("&Load state"), fileMenu); loadState->setShortcut(tr("F10")); connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); @@ -672,6 +737,21 @@ void Window::setupMenu(QMenuBar* menubar) { quickLoadMenu->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; for (i = 1; i < 10; ++i) { quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); @@ -705,6 +785,14 @@ void Window::setupMenu(QMenuBar* menubar) { }); addControlledAction(fileMenu, multiWindow, "multiWindow"); +#ifndef Q_OS_MAC + fileMenu->addSeparator(); +#endif + + QAction* about = new QAction(tr("About"), fileMenu); + connect(about, SIGNAL(triggered()), this, SLOT(openAboutScreen())); + fileMenu->addAction(about); + #ifndef Q_OS_MAC addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif @@ -850,10 +938,12 @@ void Window::setupMenu(QMenuBar* menubar) { m_shortcutController->addMenu(frameMenu, avMenu); for (int i = 1; i <= 6; ++i) { QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); + setSize->setCheckable(true); connect(setSize, &QAction::triggered, [this, i]() { showNormal(); resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i); }); + m_frameSizes[i] = setSize; addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i))); } QKeySequence fullscreenKeys; @@ -933,14 +1023,12 @@ void Window::setupMenu(QMenuBar* menubar) { #ifdef USE_FFMPEG QAction* recordOutput = new QAction(tr("Record output..."), avMenu); - recordOutput->setShortcut(tr("F11")); connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow())); addControlledAction(avMenu, recordOutput, "recordOutput"); #endif #ifdef USE_MAGICK QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); - recordGIF->setShortcut(tr("Shift+F11")); connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); addControlledAction(avMenu, recordGIF, "recordGIF"); #endif @@ -952,16 +1040,14 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); enableBg->setCheckable(true); enableBg->setChecked(true); - connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; }); - m_gameActions.append(enableBg); + connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->setVideoLayerEnabled(i, enable); }); addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); } QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); enableObj->setCheckable(true); enableObj->setChecked(true); - connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; }); - m_gameActions.append(enableObj); + connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); }); addControlledAction(videoLayers, enableObj, "enableOBJ"); QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); @@ -970,23 +1056,20 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); enableCh->setCheckable(true); enableCh->setChecked(true); - connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; }); - m_gameActions.append(enableCh); + connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(i, enable); }); addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1)); } QAction* enableChA = new QAction(tr("Channel A"), audioChannels); enableChA->setCheckable(true); enableChA->setChecked(true); - connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; }); - m_gameActions.append(enableChA); + connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(4, enable); }); addControlledAction(audioChannels, enableChA, QString("enableChA")); QAction* enableChB = new QAction(tr("Channel B"), audioChannels); enableChB->setCheckable(true); enableChB->setChecked(true); - connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; }); - m_gameActions.append(enableChB); + connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); }); addControlledAction(audioChannels, enableChB, QString("enableChB")); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); @@ -1088,6 +1171,7 @@ void Window::setupMenu(QMenuBar* menubar) { void Window::attachWidget(QWidget* widget) { m_screenWidget->layout()->addWidget(widget); + unsetCursor(); static_cast(m_screenWidget->layout())->setCurrentWidget(widget); } @@ -1169,10 +1253,10 @@ void WindowBackground::paintEvent(QPaintEvent*) { painter.fillRect(QRect(QPoint(), size()), Qt::black); QSize s = size(); QSize ds = s; - if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) { - ds.setWidth(s.height() * m_aspectWidth / m_aspectHeight); - } else if (s.width() * m_aspectHeight < s.height() * m_aspectWidth) { - ds.setHeight(s.width() * m_aspectHeight / m_aspectWidth); + if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) { + ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight); + } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) { + ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index f0377c185..16295f264 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -82,6 +82,8 @@ public slots: void openPaletteWindow(); void openMemoryWindow(); + void openAboutScreen(); + #ifdef BUILD_SDL void openGamepadWindow(); #endif @@ -102,7 +104,9 @@ protected: virtual void keyPressEvent(QKeyEvent* event) override; virtual void keyReleaseEvent(QKeyEvent* event) override; virtual void resizeEvent(QResizeEvent*) override; + virtual void showEvent(QShowEvent*) override; virtual void closeEvent(QCloseEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; virtual void focusOutEvent(QFocusEvent*) override; virtual void dragEnterEvent(QDragEnterEvent*) override; virtual void dropEvent(QDropEvent*) override; @@ -115,6 +119,9 @@ private slots: void gameFailed(); void unimplementedBiosCall(int); + void tryMakePortable(); + void mustRestart(); + void recordFrame(); void showFPS(); @@ -141,6 +148,7 @@ private: GameController* m_controller; Display* m_display; QList m_gameActions; + QMap m_frameSizes; LogController m_log; LogView* m_logView; LoadSaveState* m_stateWindow; diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 8152cf3a2..3dfca440b 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -10,9 +10,11 @@ #include #ifdef _WIN32 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#ifdef BUILD_QT_MULTIMEDIA Q_IMPORT_PLUGIN(QWindowsAudioPlugin); #endif #endif +#endif int main(int argc, char* argv[]) { QGBA::GBAApp application(argc, argv); diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 88510b37b..6e02b2933 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -51,13 +51,16 @@ set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c) if(BUILD_RASPI) add_definitions(-DBUILD_RASPI) - set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c) - set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host") + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c) + set(OPENGLES2_LIBRARY "-lEGL -lGLESv2 -lbcm_host") + set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") - add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC}) + add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") - target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY}) + target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY}) install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) + unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND endif() if(BUILD_PANDORA) @@ -66,13 +69,18 @@ else() list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) if(BUILD_GL) list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) - list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) include_directories(${OPENGL_INCLUDE_DIR}) endif() + if(BUILD_GLES2) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c) + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) + include_directories(${OPENGLES2_INCLUDE_DIR}) + endif() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY}) +target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl) diff --git a/src/platform/sdl/egl-sdl.c b/src/platform/sdl/egl-sdl.c deleted file mode 100644 index e14b753a2..000000000 --- a/src/platform/sdl/egl-sdl.c +++ /dev/null @@ -1,166 +0,0 @@ -/* Copyright (c) 2013-2014 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 "main.h" - -static const char* _vertexShader = - "attribute vec4 position;\n" - "varying vec2 texCoord;\n" - - "void main() {\n" - " gl_Position = position;\n" - " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n" - "}"; - -static const char* _fragmentShader = - "varying vec2 texCoord;\n" - "uniform sampler2D tex;\n" - - "void main() {\n" - " vec4 color = texture2D(tex, texCoord);\n" - " color.a = 1.;\n" - " gl_FragColor = color;" - "}"; - -static const GLfloat _vertices[] = { - -1.f, -1.f, - -1.f, 1.f, - 1.f, 1.f, - 1.f, -1.f, -}; - -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { - bcm_host_init(); - renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - int major, minor; - if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) { - printf("Failed to initialize EGL"); - return false; - } - - if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) { - printf("Failed to get GLES API"); - return false; - } - - const EGLint requestConfig[] = { - EGL_RED_SIZE, 5, - EGL_GREEN_SIZE, 5, - EGL_BLUE_SIZE, 5, - EGL_ALPHA_SIZE, 1, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE - }; - - EGLConfig config; - EGLint numConfigs; - - if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) { - printf("Failed to choose EGL config\n"); - return false; - } - - const EGLint contextAttributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - int dispWidth = 240, dispHeight = 160, adjWidth; - renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes); - graphics_get_display_size(0, &dispWidth, &dispHeight); - adjWidth = dispHeight / 2 * 3; - - DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0); - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); - - VC_RECT_T destRect = { - .x = (dispWidth - adjWidth) / 2, - .y = 0, - .width = adjWidth, - .height = dispHeight - }; - - VC_RECT_T srcRect = { - .x = 0, - .y = 0, - .width = 240 << 16, - .height = 160 << 16 - }; - - DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0); - vc_dispmanx_update_submit_sync(update); - - renderer->window.element = element; - renderer->window.width = dispWidth; - renderer->window.height = dispHeight; - - renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0); - if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) { - return false; - } - - renderer->d.outputBuffer = memalign(16, 256 * 256 * 4); - renderer->d.outputBufferStride = 256; - glGenTextures(1, &renderer->tex); - glBindTexture(GL_TEXTURE_2D, renderer->tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - renderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - renderer->vertexShader = glCreateShader(GL_VERTEX_SHADER); - renderer->program = glCreateProgram(); - - glShaderSource(renderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0); - glShaderSource(renderer->vertexShader, 1, (const GLchar**) &_vertexShader, 0); - glAttachShader(renderer->program, renderer->vertexShader); - glAttachShader(renderer->program, renderer->fragmentShader); - char log[1024]; - glCompileShader(renderer->fragmentShader); - glCompileShader(renderer->vertexShader); - glGetShaderInfoLog(renderer->fragmentShader, 1024, 0, log); - glGetShaderInfoLog(renderer->vertexShader, 1024, 0, log); - glLinkProgram(renderer->program); - glGetProgramInfoLog(renderer->program, 1024, 0, log); - printf("%s\n", log); - renderer->texLocation = glGetUniformLocation(renderer->program, "tex"); - renderer->positionLocation = glGetAttribLocation(renderer->program, "position"); - glClearColor(1.f, 0.f, 0.f, 1.f); -} - -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { - SDL_Event event; - - while (context->state < THREAD_EXITING) { - while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &renderer->player, &event); - } - - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { - glViewport(0, 0, 240, 160); - glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(renderer->program); - glUniform1i(renderer->texLocation, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, renderer->tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer); - glVertexAttribPointer(renderer->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); - glEnableVertexAttribArray(renderer->positionLocation); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glUseProgram(0); - eglSwapBuffers(renderer->display, renderer->surface); - } - GBASyncWaitFrameEnd(&context->sync); - } -} - -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { - eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(renderer->display, renderer->surface); - eglDestroyContext(renderer->display, renderer->context); - eglTerminate(renderer->display); - bcm_host_deinit(); -} diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c new file mode 100644 index 000000000..30158653d --- /dev/null +++ b/src/platform/sdl/gl-common.c @@ -0,0 +1,47 @@ +/* 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 "main.h" + +void GBASDLGLCommonSwap(struct VideoBackend* context) { + struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_SwapWindow(renderer->window); +#else + UNUSED(renderer); + SDL_GL_SwapBuffers(); +#endif +} + +void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer) { +#ifndef COLOR_16_BIT + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); +#else + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); +#ifdef COLOR_5_6_5 + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); +#else + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); +#endif + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); + renderer->glCtx = SDL_GL_CreateContext(renderer->window); + SDL_GL_SetSwapInterval(1); + SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); + renderer->player.window = renderer->window; +#else + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); +#ifdef COLOR_16_BIT + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL); +#else + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL); +#endif +#endif +} diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h new file mode 100644 index 000000000..c07fd7926 --- /dev/null +++ b/src/platform/sdl/gl-common.h @@ -0,0 +1,13 @@ +/* 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 SDL_GL_COMMON_H +#define SDL_GL_COMMON_H +#include "main.h" + +void GBASDLGLCommonSwap(struct VideoBackend* context); +void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer); + +#endif diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 429d67a65..12073b12a 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -5,18 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include "gl-common.h" + #include "gba/supervisor/thread.h" #include "platform/opengl/gl.h" -static void _sdlSwap(struct VideoBackend* context) { - struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user; -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GL_SwapWindow(renderer->window); -#else - SDL_GL_SwapBuffers(); -#endif -} - static void _doViewport(int w, int h, struct VideoBackend* v) { v->resized(v, w, h); v->clear(v); @@ -35,34 +28,7 @@ void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { } bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { -#ifndef COLOR_16_BIT - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); -#else - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); -#ifdef COLOR_5_6_5 - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); -#else - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); -#endif - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); -#endif - -#if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); - renderer->glCtx = SDL_GL_CreateContext(renderer->window); - SDL_GL_SetSwapInterval(1); - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); - renderer->player.window = renderer->window; -#else - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); -#ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL); -#else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL); -#endif -#endif + GBASDLGLCommonInit(renderer); renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL); renderer->d.outputBufferStride = 256; @@ -71,7 +37,7 @@ bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { renderer->gl.d.user = renderer; renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; renderer->gl.d.filter = renderer->filter; - renderer->gl.d.swap = _sdlSwap; + renderer->gl.d.swap = GBASDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); _doViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c new file mode 100644 index 000000000..a3a617471 --- /dev/null +++ b/src/platform/sdl/gles2-sdl.c @@ -0,0 +1,144 @@ +/* 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 "main.h" + +#include "gl-common.h" + +#include + +static bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer); +static void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLGLES2Init; + renderer->deinit = GBASDLGLES2Deinit; + renderer->runloop = GBASDLGLES2Runloop; +} + +bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer) { +#ifdef BUILD_RASPI + bcm_host_init(); + renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + int major, minor; + if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) { + printf("Failed to initialize EGL"); + return false; + } + + if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) { + printf("Failed to get GLES API"); + return false; + } + + const EGLint requestConfig[] = { + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 5, + EGL_BLUE_SIZE, 5, + EGL_ALPHA_SIZE, 1, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + EGLConfig config; + EGLint numConfigs; + + if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) { + printf("Failed to choose EGL config\n"); + return false; + } + + const EGLint contextAttributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + int dispWidth = 240, dispHeight = 160, adjWidth; + renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes); + graphics_get_display_size(0, &dispWidth, &dispHeight); + adjWidth = dispHeight / 2 * 3; + + DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0); + DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); + + VC_RECT_T destRect = { + .x = (dispWidth - adjWidth) / 2, + .y = 0, + .width = adjWidth, + .height = dispHeight + }; + + VC_RECT_T srcRect = { + .x = 0, + .y = 0, + .width = 240 << 16, + .height = 160 << 16 + }; + + DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0); + vc_dispmanx_update_submit_sync(update); + + renderer->window.element = element; + renderer->window.width = dispWidth; + renderer->window.height = dispHeight; + + renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0); + if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) { + return false; + } +#else + GBASDLGLCommonInit(renderer); +#endif + + renderer->d.outputBuffer = memalign(16, 256 * 256 * 4); + renderer->d.outputBufferStride = 256; + + GBAGLES2ContextCreate(&renderer->gl); + renderer->gl.d.user = renderer; + renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; + renderer->gl.d.filter = renderer->filter; + renderer->gl.d.swap = GBASDLGLCommonSwap; + renderer->gl.d.init(&renderer->gl.d, 0); + return true; +} + +void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { + SDL_Event event; + struct VideoBackend* v = &renderer->gl.d; + + while (context->state < THREAD_EXITING) { + while (SDL_PollEvent(&event)) { + GBASDLHandleEvent(context, &renderer->player, &event); + } + + if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + v->postFrame(v, renderer->d.outputBuffer); + } + v->drawFrame(v); + GBASyncWaitFrameEnd(&context->sync); +#ifdef BUILD_RASPI + eglSwapBuffers(renderer->display, renderer->surface); +#else + v->swap(v); +#endif + } +} + +void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer) { + if (renderer->gl.d.deinit) { + renderer->gl.d.deinit(&renderer->gl.d); + } +#ifdef BUILD_RASPI + eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(renderer->display, renderer->surface); + eglDestroyContext(renderer->display, renderer->context); + eglTerminate(renderer->display); + bcm_host_deinit(); +#elif SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_DeleteContext(renderer->glCtx); +#endif + free(renderer->d.outputBuffer); +} diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 0b3143eeb..058055ac2 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -86,6 +86,8 @@ int main(int argc, char** argv) { #ifdef BUILD_GL GBASDLGLCreate(&renderer); +#elif defined(BUILD_GLES2) + GBASDLGLES2Create(&renderer); #else GBASDLSWCreate(&renderer); #endif diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index 4a428c190..66ae8c75c 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -20,13 +20,16 @@ #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include -#include #include #include #pragma GCC diagnostic pop #endif +#ifdef BUILD_GLES2 +#include "platform/opengl/gles2.h" +#endif + #ifdef USE_PIXMAN #include #endif @@ -57,6 +60,8 @@ struct SDLSoftwareRenderer { #ifdef BUILD_GL struct GBAGLContext gl; +#elif BUILD_GLES2 + struct GBAGLES2Context gl; #endif #ifdef USE_PIXMAN @@ -69,13 +74,6 @@ struct SDLSoftwareRenderer { EGLSurface surface; EGLContext context; EGL_DISPMANX_WINDOW_T window; - GLuint tex; - GLuint fragmentShader; - GLuint vertexShader; - GLuint program; - GLuint bufferObject; - GLuint texLocation; - GLuint positionLocation; #endif #ifdef BUILD_PANDORA @@ -90,4 +88,8 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer); #ifdef BUILD_GL void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); #endif + +#ifdef BUILD_GLES2 +void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer); +#endif #endif diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index e316a7f2f..c63e5dc76 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -22,7 +22,7 @@ #endif #define GYRO_STEPS 100 -#define RUMBLE_PWM 35 +#define RUMBLE_PWM 20 #if SDL_VERSION_ATLEAST(2, 0, 0) static void _GBASDLSetRumble(struct GBARumble* rumble, int enable); diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 88184049e..094ab42d9 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -25,6 +25,7 @@ static void GBAWiiFrame(void); static bool GBAWiiLoadGame(const char* path); static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); +static void _audioDMA(void); static struct GBA gba; static struct ARMCore cpu; @@ -41,9 +42,18 @@ static GXTexObj tex; static void* framebuffer[2]; static int whichFb = 0; +static struct GBAStereoSample audioBuffer[2][SAMPLES] __attribute__ ((__aligned__(32))); +static size_t audioBufferSize = 0; +static int currentAudioBuffer = 0; + int main() { VIDEO_Init(); PAD_Init(); + AUDIO_Init(0); + AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ); + AUDIO_RegisterDMACallback(_audioDMA); + + memset(audioBuffer, 0, sizeof(audioBuffer)); #if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5) #error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 @@ -140,8 +150,8 @@ int main() { GBAAudioResizeBuffer(&gba.audio, SAMPLES); #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF - blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 44100); - blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 44100); + blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 48000); + blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 48000); #endif if (!GBAWiiLoadGame("/rom.gba")) { @@ -149,6 +159,21 @@ int main() { } while (true) { +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + int available = blip_samples_avail(gba.audio.left); + if (available + audioBufferSize > SAMPLES) { + available = SAMPLES - audioBufferSize; + } + if (available > 0) { + blip_read_samples(gba.audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true); + blip_read_samples(gba.audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true); + audioBufferSize += available; + } + if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) { + _audioDMA(); + AUDIO_StartDMA(); + } +#endif PAD_ScanPads(); u16 padkeys = PAD_ButtonsHeld(0); int keys = 0; @@ -284,3 +309,13 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* UNUSED(renderer); GBAWiiFrame(); } + +static void _audioDMA(void) { + if (!audioBufferSize) { + return; + } + currentAudioBuffer = !currentAudioBuffer; + DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample)); + AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample)); + audioBufferSize = 0; +} diff --git a/src/util/configuration.c b/src/util/configuration.c index dd4efbb51..a96616ae0 100644 --- a/src/util/configuration.c +++ b/src/util/configuration.c @@ -100,6 +100,10 @@ void ConfigurationClearValue(struct Configuration* configuration, const char* se 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 struct Table* currentSection = &configuration->root; if (section) { diff --git a/src/util/configuration.h b/src/util/configuration.h index 098b143a9..14a0e7014 100644 --- a/src/util/configuration.h +++ b/src/util/configuration.h @@ -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 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); void ConfigurationClearValue(struct Configuration*, const char* section, const char* key); diff --git a/src/util/formatting.c b/src/util/formatting.c index a2d572781..d14843d3f 100644 --- a/src/util/formatting.c +++ b/src/util/formatting.c @@ -15,11 +15,14 @@ int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) { int res = snprintf(str, size, "%*.g", FLT_DIG, f); uselocale(old); return res; -#else +#elif defined(HAVE_SETLOCALE) char* old = setlocale(LC_NUMERIC, locale); int res = snprintf(str, size, "%*.g", FLT_DIG, f); setlocale(LC_NUMERIC, old); return res; +#else + UNUSED(locale); + return snprintf(str, size, "%*.g", FLT_DIG, f); #endif } @@ -30,11 +33,14 @@ float strtof_l(const char* restrict str, char** restrict end, locale_t locale) { float res = strtof(str, end); uselocale(old); return res; -#else +#elif defined(HAVE_SETLOCALE) char* old = setlocale(LC_NUMERIC, locale); float res = strtof(str, end); setlocale(LC_NUMERIC, old); return res; +#else + UNUSED(locale); + return strtof(str, end); #endif } #endif diff --git a/src/util/vfs.h b/src/util/vfs.h index 3f74d721a..6fca9013c 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -39,6 +39,7 @@ struct VFile { void (*unmap)(struct VFile* vf, void* memory, size_t size); void (*truncate)(struct VFile* vf, size_t size); ssize_t (*size)(struct VFile* vf); + bool (*sync)(struct VFile* vf, const void* buffer, size_t size); }; struct VDirEntry { diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index 383d6bbff..ce57c38f9 100644 --- a/src/util/vfs/vfs-fd.c +++ b/src/util/vfs/vfs-fd.c @@ -30,6 +30,7 @@ static void* _vfdMap(struct VFile* vf, size_t size, int flags); static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); static void _vfdTruncate(struct VFile* vf, size_t size); static ssize_t _vfdSize(struct VFile* vf); +static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size); struct VFile* VFileOpenFD(const char* path, int flags) { if (!path) { @@ -66,6 +67,7 @@ struct VFile* VFileFromFD(int fd) { vfd->d.unmap = _vfdUnmap; vfd->d.truncate = _vfdTruncate; vfd->d.size = _vfdSize; + vfd->d.sync = _vfdSync; return &vfd->d; } @@ -166,3 +168,14 @@ static ssize_t _vfdSize(struct VFile* vf) { } return stat.st_size; } + +static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) { + UNUSED(buffer); + UNUSED(size); + struct VFileFD* vfd = (struct VFileFD*) vf; +#ifndef _WIN32 + return fsync(vfd->fd) == 0; +#else + return FlushFileBuffers((HANDLE) _get_osfhandle(vfd->fd)); +#endif +} diff --git a/src/util/vfs/vfs-file.c b/src/util/vfs/vfs-file.c index 70b676e63..d140b08af 100644 --- a/src/util/vfs/vfs-file.c +++ b/src/util/vfs/vfs-file.c @@ -24,6 +24,7 @@ static void* _vffMap(struct VFile* vf, size_t size, int flags); static void _vffUnmap(struct VFile* vf, void* memory, size_t size); static void _vffTruncate(struct VFile* vf, size_t size); static ssize_t _vffSize(struct VFile* vf); +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size); struct VFile* VFileFOpen(const char* path, const char* mode) { if (!path && !mode) { @@ -57,6 +58,7 @@ struct VFile* VFileFromFILE(FILE* file) { vff->d.unmap = _vffUnmap; vff->d.truncate = _vffTruncate; vff->d.size = _vffSize; + vff->d.sync = _vffSync; return &vff->d; } @@ -140,3 +142,14 @@ static ssize_t _vffSize(struct VFile* vf) { fseek(vff->file, pos, SEEK_SET); return size; } + +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + if (buffer && size) { + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_SET); + fwrite(buffer, size, 1, vff->file); + fseek(vff->file, pos, SEEK_SET); + } + return fflush(vff->file) == 0; +} diff --git a/src/util/vfs/vfs-lzma.c b/src/util/vfs/vfs-lzma.c index 21e580fdb..c090ac921 100644 --- a/src/util/vfs/vfs-lzma.c +++ b/src/util/vfs/vfs-lzma.c @@ -56,6 +56,7 @@ static void* _vf7zMap(struct VFile* vf, size_t size, int flags); static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size); static void _vf7zTruncate(struct VFile* vf, size_t size); static ssize_t _vf7zSize(struct VFile* vf); +static bool _vf7zSync(struct VFile* vf, const void* buffer, size_t size); static bool _vd7zClose(struct VDir* vd); static void _vd7zRewind(struct VDir* vd); @@ -94,6 +95,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) { SzArEx_Init(&vd->db); SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp); if (res != SZ_OK) { + File_Close(&vd->archiveStream.file); free(vd); return 0; } @@ -114,6 +116,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) { bool _vf7zClose(struct VFile* vf) { struct VFile7z* vf7z = (struct VFile7z*) vf; IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer); + File_Close(&vf7z->vd->archiveStream.file); return true; } @@ -291,6 +294,7 @@ struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) { vf->d.unmap = _vf7zUnmap; vf->d.truncate = _vf7zTruncate; vf->d.size = _vf7zSize; + vf->d.sync = _vf7zSync; return &vf->d; } @@ -308,4 +312,11 @@ const char* _vde7zName(struct VDirEntry* vde) { return vde7z->utf8; } +bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); + return false; +} + #endif diff --git a/src/util/vfs/vfs-mem.c b/src/util/vfs/vfs-mem.c index 8d52eb0b1..10b03ef19 100644 --- a/src/util/vfs/vfs-mem.c +++ b/src/util/vfs/vfs-mem.c @@ -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 _vfmTruncate(struct VFile* vf, size_t size); 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) { if (!mem || !size) { @@ -43,6 +44,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size) { vfm->d.unmap = _vfmUnmap; vfm->d.truncate = _vfmTruncate; vfm->d.size = _vfmSize; + vfm->d.sync = _vfmSync; return &vfm->d; } @@ -137,3 +139,10 @@ ssize_t _vfmSize(struct VFile* vf) { struct VFileMem* vfm = (struct VFileMem*) vf; return vfm->size; } + +bool _vfmSync(struct VFile* vf, const void* buffer, size_t size) { + UNUSED(vf); + UNUSED(buffer); + UNUSED(size); + return true; +} diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 5a1de8781..feda1cea5 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -43,6 +43,7 @@ static void* _vfzMap(struct VFile* vf, size_t size, int flags); static void _vfzUnmap(struct VFile* vf, void* memory, size_t size); static void _vfzTruncate(struct VFile* vf, size_t size); static ssize_t _vfzSize(struct VFile* vf); +static bool _vfzSync(struct VFile* vf, const void* buffer, size_t size); static bool _vdzClose(struct VDir* vd); static void _vdzRewind(struct VDir* vd); @@ -289,6 +290,7 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { vfz->d.unmap = _vfzUnmap; vfz->d.truncate = _vfzTruncate; vfz->d.size = _vfzSize; + vfz->d.sync = _vfzSync; return &vfz->d; } @@ -302,4 +304,11 @@ const char* _vdezName(struct VDirEntry* vde) { return s.name; } +bool _vfzSync(struct VFile* vf, const void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); + return false; +} + #endif diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh index e1c523fb8..c63cd5bc3 100755 --- a/tools/sanitize-deb.sh +++ b/tools/sanitize-deb.sh @@ -56,8 +56,8 @@ while [ $# -gt 0 ]; do sed -i~ "s/,$//g" deb-temp/DEBIAN/control sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control rm deb-temp/DEBIAN/control~ - chown -R 0:0 deb-temp - chmod 600 deb-temp/DEBIAN/md5sums + chmod 644 deb-temp/DEBIAN/md5sums + chown -R root:root deb-temp dpkg-deb -b deb-temp $DEB rm -rf deb-temp shift diff --git a/version.cmake b/version.cmake index 32bf65821..5ec40e6bf 100644 --- a/version.cmake +++ b/version.cmake @@ -1,5 +1,37 @@ -if(NOT ${BINARY_NAME}_SOURCE_DIR) - set(${BINARY_NAME}_SOURCE_DIR ${CMAKE_SOURCE_DIR}) +if(NOT PROJECT_NAME) + 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() + + if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH) + set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING}) + endif() endif() -configure_file("${${BINARY_NAME}_SOURCE_DIR}/src/util/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c") +if(CONFIG_FILE AND OUT_FILE) + configure_file("${CONFIG_FILE}" "${OUT_FILE}") +endif()