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