diff --git a/.travis.yml b/.travis.yml index ee7e322ed..c35d58b3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ services: os: linux env: - DOCKER_TAG=ubuntu:xenial -- DOCKER_TAG=ubuntu:artful - DOCKER_TAG=ubuntu:bionic +- DOCKER_TAG=ubuntu:cosmic - DOCKER_TAG=3ds - DOCKER_TAG=wii - DOCKER_TAG=vita diff --git a/CHANGES b/CHANGES index f7ceefdfb..fe2a88e73 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,10 @@ Misc: - DS Core: Add symbol loading - DS Video: Simplify VRAM mapping +0.8.0: (Future) +Bugfixes: + - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) + 0.7.0: (Future) Features: - ELF support @@ -121,15 +125,40 @@ Misc: Changes from beta 1: Features: - Libretro: Add Game Boy cheat support + - Qt: Separate fast forward volume control (fixes mgba.io/i/846, mgba.io/i/1143) + - Switch: Rumble support + - Switch: Rotation support + - Qt: State file load/save menu options + - Windows installer + - Tile viewer now has adjustable width Bugfixes: - PSP2: Fix audio crackling after fast forward - PSP2: Fix audio crackling when buffer is full - 3DS: Fix unused screens not clearing (fixes mgba.io/i/1184) + - GBA Video: Fix caching with background toggling (fixes mgba.io/i/1118) + - Wii: Fix drawing caching regression (fixes mgba.io/i/1185) + - Switch: Fix incorrect mapping for fast forward cap + - GB, GBA: Fix broken opposing button filter (fixes mgba.io/i/1191) + - Qt: Fix jumbled background when paused + - Qt: Fix FPS counter on Windows + - GB, GBA Savedata: Fix leaks when loading masked save (fixes mgba.io/i/1197) + - Qt: Fix focus issues with load/save state overlay + - GB Video: Fix SGB border hole size Misc: - mGUI: Add SGB border configuration option + - mGUI: Add support for different settings types + - Wii: Define _GNU_SOURCE (fixes mgba.io/i/1106) + - Wii: Expose stretch configuration in settings + - Wii: Stretch now sets pixel-accurate mode size cap + - Qt: Ensure camera image is valid + - GB: Improved SGB2 support + - Libretro: Reduce rumble callbacks + - Debugger: Minor text fixes + - Qt: Debugger console history + - Qt: Detect presence of GL_ARB_framebuffer_object -0.6 beta 1: (2018-09-24) -- Initial beta for 0.6 +0.7 beta 1: (2018-09-24) +- Initial beta for 0.7 0.6.3: (2018-04-14) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index f1341b95c..8ace81430 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.1) if(POLICY CMP0025) cmake_policy(SET CMP0025 NEW) endif() +if(POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) +endif() project(medusa) set(BINARY_NAME medusa-emu CACHE INTERNAL "Name of output binaries") if(NOT MSVC) @@ -326,7 +329,7 @@ if(WII) add_definitions(-U__STRICT_ANSI__) endif() -if(DEFINED 3DS) +if(3DS OR WII) add_definitions(-D_GNU_SOURCE) endif() @@ -550,7 +553,11 @@ if(USE_FFMPEG) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}") if(USE_LIBSWRESAMPLE) string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) - math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") + if(${libswresample_VERSION} EQUAL "3.1.100") + set(LIBSWRESAMPLE_VERSION_DEBIAN 3) + else() + math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") + endif() set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}") else() string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) @@ -577,6 +584,8 @@ if(USE_MAGICK) string(REGEX MATCH "^[0-9]+" MAGICKWAND_VERSION_MAJOR ${MagickWand_VERSION}) if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7") set(MAGICKWAND_DEB_VERSION "5") + elseif(${MagickWand_VERSION} EQUAL "6.9.10") + set(MAGICKWAND_DEB_VERSION "-6.q16-6") elseif(${MagickWand_VERSION} EQUAL "6.9.7") set(MAGICKWAND_DEB_VERSION "-6.q16-3") else() @@ -944,7 +953,11 @@ if(BUILD_LIBRETRO) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) - install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP) + if(MSVC) + install(TARGETS ${BINARY_NAME}_libretro RUNTIME DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro) + else() + install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP) + endif() endif() if(BUILD_OPENEMU) @@ -1030,6 +1043,26 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mgba COMPONENT lib${BINARY_NAME}) +if(WIN32) + set(BIN_DIR ".\\") + string(REGEX REPLACE "[^-A-Za-z0-9_.]" "-" CLEAN_VERSION_STRING "${VERSION_STRING}") + file(RELATIVE_PATH SETUP_DIR_SLASH "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/setup") + file(RELATIVE_PATH RES_DIR_SLASH "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/res") + string(REPLACE "/" "\\" SETUP_DIR "${SETUP_DIR_SLASH}") + string(REPLACE "/" "\\" RES_DIR "${RES_DIR_SLASH}") + if(CMAKE_SYSTEM_PROCESSOR MATCHES ".*64$") + set(WIN_BITS 64) + else() + set(WIN_BITS 32) + endif() + if(GIT_TAG) + set(IS_RELEASE 1) + else() + set(IS_RELEASE 0) + endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/setup/setup.iss.in" ${CMAKE_CURRENT_BINARY_DIR}/setup.iss) +endif() + # Packaging install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/res/licenses DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME}) if(EXTRA_LICENSES) @@ -1038,20 +1071,45 @@ endif() file(GLOB READMES ${CMAKE_CURRENT_SOURCE_DIR}/README*.md) -find_program(DOS2UNIX NAMES dos2unix) +find_program(UNIX2DOS NAMES unix2dos) find_program(MARKDOWN NAMES markdown kramdown pandoc) -if(UNIX OR NOT DOS2UNIX) +if(UNIX OR NOT UNIX2DOS) if(UNIX OR NOT MARKDOWN) install(FILES ${READMES} DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME}) endif() install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME}) else() - add_custom_command(OUTPUT CHANGES.txt COMMAND ${DOS2UNIX} -n "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES") - add_custom_command(OUTPUT LICENSE.txt COMMAND ${DOS2UNIX} -n "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + add_custom_command(OUTPUT CHANGES.txt COMMAND ${UNIX2DOS} -n "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES") + add_custom_command(OUTPUT LICENSE.txt COMMAND ${UNIX2DOS} -n "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") add_custom_target(CHANGES ALL DEPENDS CHANGES.txt) add_custom_target(LICENSE ALL DEPENDS LICENSE.txt) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME}) + if(DISTBUILD AND WIN32) + if(INSTALLER_NAME) + set(INSTALLER_TARGET "${INSTALLER_NAME}.exe") + set(ISCC_FLAGS "/F${INSTALLER_NAME}") + else() + set(INSTALLER_TARGET "${PROJECT_NAME}-setup-${CLEAN_VERSION_STRING}-win${WIN_BITS}.exe") + endif() + if(CMAKE_CROSSCOMPILING) + find_program(WINE NAMES wine wine-stable wine-development) + find_file(ISCC ISCC.exe HINTS "$ENV{HOME}/.wine/drive_c/Program Files/" PATH_SUFFIXES "Inno Setup 5") + message(STATUS "${WINE}" "${ISCC}" setup.iss /Q ${ISCC_FLAGS}) + add_custom_command(OUTPUT ${INSTALLER_TARGET} + COMMAND "${WINE}" "${ISCC}" setup.iss /Q ${ISCC_FLAGS} + DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl CHANGES LICENSE) + else() + find_program(ISCC NAMES ISCC ISCC.exe PATH_SUFFIXES "Inno Setup 5") + add_custom_command(OUTPUT ${INSTALLER_TARGET} + COMMAND "${ISCC}" setup.iss /Q ${ISCC_FLAGS} + DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl CHANGES LICENSE) + endif() + if(ISCC) + add_custom_target(installer ALL DEPENDS ${INSTALLER_TARGET}) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${INSTALLER_TARGET}" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT installer) + endif() + endif() endif() if(MARKDOWN) @@ -1076,9 +1134,11 @@ set(CPACK_PACKAGE_CONTACT "Jeffrey Pfau ") set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_DEBIAN_PACKAGE_SECTION "games") -SET(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_DEB_COMPONENT_INSTALL ON) -set(CPACK_STRIP_FILES ON) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CPACK_STRIP_FILES ON) +endif() if(DISTBUILD) set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) @@ -1089,19 +1149,19 @@ if(DISTBUILD) install(FILES "$.dSYM" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg) endif() endif() - if(WIN32 OR APPLE) + if(APPLE) set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-qt-dbg ${BINARY_NAME}-sdl-dbg ${BINARY_NAME}-perf) - if(APPLE) - set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) - endif() + set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + elseif(WIN32) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-qt-dbg ${BINARY_NAME}-sdl-dbg ${BINARY_NAME}-perf installer) elseif(3DS) - set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-3ds ${BINARY_NAME}-perf) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-3ds ${BINARY_NAME}-perf) elseif(WII) - set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-wii) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-wii) elseif(PSP2) - set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-psp2) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-psp2) elseif(SWITCH) - set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-switch) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-switch) endif() endif() @@ -1140,17 +1200,23 @@ if(SDL_FOUND) cpack_add_component(${BINARY_NAME}-sdl GROUP sdl) endif() -if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") +if(DISTBUILD) cpack_add_component_group(debug PARENT_GROUP dev) if(BUILD_SHARED AND NOT IS_EMBEDDED) cpack_add_component(lib${BINARY_NAME}-dbg GROUP debug) endif() + if(IS_EMBEDDED) + cpack_add_component(${BINARY_NAME}-dbg GROUP debug) + endif() if(BUILD_QT) cpack_add_component(${BINARY_NAME}-qt-dbg GROUP debug) endif() if(SDL_FOUND) cpack_add_component(${BINARY_NAME}-sdl-dbg GROUP debug) endif() + if(WIN32) + cpack_add_component_group(installer PARENT_GROUP base) + endif() endif() cpack_add_component_group(test PARENT_GROUP dev) diff --git a/cinema/gba/obj/unaligned-256-linear/baseline_0001.png b/cinema/gba/obj/unaligned-256-linear/baseline_0001.png index f658bc32f..4be2744f7 100644 Binary files a/cinema/gba/obj/unaligned-256-linear/baseline_0001.png and b/cinema/gba/obj/unaligned-256-linear/baseline_0001.png differ diff --git a/cinema/gba/obj/unaligned-256-linear/baseline_0003.png b/cinema/gba/obj/unaligned-256-linear/baseline_0003.png index 04451274a..5f20a6405 100644 Binary files a/cinema/gba/obj/unaligned-256-linear/baseline_0003.png and b/cinema/gba/obj/unaligned-256-linear/baseline_0003.png differ diff --git a/cinema/gba/obj/unaligned-256-linear/baseline_0004.png b/cinema/gba/obj/unaligned-256-linear/baseline_0004.png index 04451274a..5f20a6405 100644 Binary files a/cinema/gba/obj/unaligned-256-linear/baseline_0004.png and b/cinema/gba/obj/unaligned-256-linear/baseline_0004.png differ diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 70bcec2e1..cd928813b 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -178,8 +178,12 @@ typedef intptr_t ssize_t; #define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#ifdef _MSC_VER +#define LOAD_32BE(DEST, ADDR, ARR) DEST = _byteswap_ulong(((uint32_t*) ARR)[(ADDR) >> 2]) +#else #define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #endif +#endif #define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START)) #define CHECK_BITS(SRC, START, END) ((SRC) & MAKE_MASK(START, END)) diff --git a/include/mgba-util/gui/menu.h b/include/mgba-util/gui/menu.h index 72a099a15..dca1d60b3 100644 --- a/include/mgba-util/gui/menu.h +++ b/include/mgba-util/gui/menu.h @@ -12,12 +12,37 @@ CXX_GUARD_START #include +#define GUI_V_V (struct GUIVariant) { .type = GUI_VARIANT_VOID } +#define GUI_V_U(U) (struct GUIVariant) { .type = GUI_VARIANT_UNSIGNED, .v.u = (U) } +#define GUI_V_I(I) (struct GUIVariant) { .type = GUI_VARIANT_INT, .v.i = (I) } +#define GUI_V_F(F) (struct GUIVariant) { .type = GUI_VARIANT_FLOAT, .v.f = (F) } +#define GUI_V_S(S) (struct GUIVariant) { .type = GUI_VARIANT_STRING, .v.s = (S) } + +enum GUIVariantType { + GUI_VARIANT_VOID = 0, + GUI_VARIANT_UNSIGNED, + GUI_VARIANT_INT, + GUI_VARIANT_FLOAT, + GUI_VARIANT_STRING +}; + +struct GUIVariant { + enum GUIVariantType type; + union { + unsigned u; + int i; + float f; + const char* s; + } v; +}; + struct GUIMenu; struct GUIMenuItem { const char* title; void* data; unsigned state; const char* const* validStates; + const struct GUIVariant* stateMappings; unsigned nStates; struct GUIMenu* submenu; }; diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index 47c3a03df..ac9d6230d 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -117,6 +117,8 @@ struct GB { bool earlyExit; struct mTimingEvent eiPending; unsigned doubleSpeed; + + bool allowOpposingDirections; }; struct GBCartridge { diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 8796f113e..4bd899fe6 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -82,6 +82,8 @@ struct GBA { struct GBATimer timers[4]; int springIRQ; + struct mTimingEvent irqEvent; + uint32_t biosChecksum; int* keySource; struct mRotationSource* rotationSource; @@ -141,8 +143,6 @@ void GBADestroy(struct GBA* gba); void GBAReset(struct ARMCore* cpu); void GBASkipBIOS(struct GBA* gba); -void GBAWriteIE(struct GBA* gba, uint16_t value); -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); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 0e2656fa3..b95a07a1f 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -98,28 +98,28 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x00202 - 0x00203: Old reload value * | 0x00204 - 0x00207: Last event * | 0x00208 - 0x0020B: Next event - * | 0x0020C - 0x0020F: Next IRQ + * | 0x0020C - 0x0020F: Reserved * | 0x00210 - 0x00213: Miscellaneous flags * 0x00214 - 0x00227: Timer 1 * | 0x00214 - 0x00215: Reload value * | 0x00216 - 0x00217: Old reload value * | 0x00218 - 0x0021B: Last event * | 0x0021C - 0x0021F: Next event - * | 0x00220 - 0x00223: Next IRQ + * | 0x00220 - 0x00223: Reserved * | 0x00224 - 0x00227: Miscellaneous flags * 0x00228 - 0x0023B: Timer 2 * | 0x00228 - 0x00229: Reload value * | 0x0022A - 0x0022B: Old reload value * | 0x0022C - 0x0022F: Last event * | 0x00230 - 0x00233: Next event - * | 0x00234 - 0x00237: Next IRQ + * | 0x00234 - 0x00237: Reserved * | 0x00238 - 0x0023B: Miscellaneous flags * 0x0023C - 0x00250: Timer 3 * | 0x0023C - 0x0023D: Reload value * | 0x0023E - 0x0023F: Old reload value * | 0x00240 - 0x00243: Last event * | 0x00244 - 0x00247: Next event - * | 0x00248 - 0x0024B: Next IRQ + * | 0x00248 - 0x0024B: Reserved * | 0x0024C - 0x0024F: Miscellaneous flags * 0x00250 - 0x0025F: DMA 0 * | 0x00250 - 0x00253: DMA next source @@ -196,7 +196,9 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 0x0031C - 0x0031F: Miscellaneous flags * | bit 0: Is CPU halted? * | bit 1: POSTFLG - * 0x00320 - 0x003FF: Reserved (leave zero) + * | bit 2: Is IRQ pending? + * 0x00320 - 0x00323: Next IRQ event + * 0x00324 - 0x003FF: Reserved (leave zero) * 0x00400 - 0x007FF: I/O memory * 0x00800 - 0x00BFF: Palette * 0x00C00 - 0x00FFF: OAM @@ -227,6 +229,7 @@ DECL_BIT(GBASerializedSavedataFlags, DustSettling, 5); DECL_BITFIELD(GBASerializedMiscFlags, uint32_t); DECL_BIT(GBASerializedMiscFlags, Halted, 0); DECL_BIT(GBASerializedMiscFlags, POSTFLG, 1); +DECL_BIT(GBASerializedMiscFlags, IrqPending, 2); struct GBASerializedState { uint32_t versionMagic; @@ -267,10 +270,10 @@ struct GBASerializedState { struct { uint16_t reload; - uint16_t reserved; + uint16_t reserved0; uint32_t lastEvent; uint32_t nextEvent; - uint32_t nextIrq; + uint32_t reserved1; GBATimerFlags flags; } timers[4]; @@ -320,8 +323,9 @@ struct GBASerializedState { uint32_t lastPrefetchedPc; GBASerializedMiscFlags miscFlags; + uint32_t nextIrq; - uint32_t reserved[56]; + uint32_t reserved[55]; uint16_t io[SIZE_IO >> 1]; uint16_t pram[SIZE_PALETTE_RAM >> 1]; diff --git a/include/mgba/internal/gba/timer.h b/include/mgba/internal/gba/timer.h index 9253c409b..2175e120d 100644 --- a/include/mgba/internal/gba/timer.h +++ b/include/mgba/internal/gba/timer.h @@ -17,13 +17,11 @@ DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4); DECL_BIT(GBATimerFlags, CountUp, 4); DECL_BIT(GBATimerFlags, DoIrq, 5); DECL_BIT(GBATimerFlags, Enable, 6); -DECL_BIT(GBATimerFlags, IrqPending, 7); struct GBATimer { uint16_t reload; int32_t lastEvent; struct mTimingEvent event; - struct mTimingEvent irq; GBATimerFlags flags; int forcedPrescale; }; @@ -33,7 +31,7 @@ struct GBA; void GBATimerInit(struct GBA* gba); void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate); -void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate); +bool GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate); void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate); void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew); void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload); diff --git a/src/arm/arm.c b/src/arm/arm.c index 4771ead69..c70dcecd3 100644 --- a/src/arm/arm.c +++ b/src/arm/arm.c @@ -175,6 +175,7 @@ void ARMRaiseIRQ(struct ARMCore* cpu) { cpu->spsr = cpsr; cpu->cpsr.i = 1; cpu->cycles += currentCycles; + cpu->halted = 0; } void ARMRaiseSWI(struct ARMCore* cpu) { diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index d0ed6b672..e4ff8ab5b 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -66,8 +66,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "break", _setBreakpoint, "Is", "Set a breakpoint" }, { "c", _continue, "", "Continue execution" }, { "continue", _continue, "", "Continue execution" }, - { "d", _clearBreakpoint, "I", "Delete a breakpoint" }, - { "delete", _clearBreakpoint, "I", "Delete a breakpoint" }, + { "d", _clearBreakpoint, "I", "Delete a breakpoint or watchpoint" }, + { "delete", _clearBreakpoint, "I", "Delete a breakpoint or watchpoint" }, { "dis", _disassemble, "Ii", "Disassemble instructions" }, { "disasm", _disassemble, "Ii", "Disassemble instructions" }, { "disassemble", _disassemble, "Ii", "Disassemble instructions" }, @@ -841,9 +841,9 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r case DEBUGGER_ENTER_WATCHPOINT: if (info) { if (info->type.wp.accessType & WATCHPOINT_WRITE) { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue); + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08X, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue); } else { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->type.wp.oldValue); + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08X)\n", info->address, info->type.wp.oldValue); } } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint\n"); diff --git a/src/ds/renderers/software.c b/src/ds/renderers/software.c index 1595d9ca1..0e5198e2f 100644 --- a/src/ds/renderers/software.c +++ b/src/ds/renderers/software.c @@ -489,6 +489,19 @@ static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* rend } } + if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) { + ++softwareRenderer->bg[0].enabled; + } + if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) { + ++softwareRenderer->bg[1].enabled; + } + if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) { + ++softwareRenderer->bg[2].enabled; + } + if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) { + ++softwareRenderer->bg[3].enabled; + } + GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer); } diff --git a/src/ds/timer.c b/src/ds/timer.c index 6e2797b64..7f0978595 100644 --- a/src/ds/timer.c +++ b/src/ds/timer.c @@ -10,57 +10,42 @@ static void DSTimerIrq(struct DSCommon* dscore, int timerId) { struct GBATimer* timer = &dscore->timers[timerId]; - if (GBATimerFlagsIsIrqPending(timer->flags)) { - timer->flags = GBATimerFlagsClearIrqPending(timer->flags); + if (GBATimerFlagsIsDoIrq(timer->flags)) { DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0 + timerId); } } -static void DSTimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - DSTimerIrq(context, 0); -} - -static void DSTimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - DSTimerIrq(context, 1); -} - -static void DSTimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - DSTimerIrq(context, 2); -} - -static void DSTimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - DSTimerIrq(context, 3); -} - static void DSTimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; GBATimerUpdate(timing, &dscore->timers[0], &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate); + DSTimerIrq(dscore, 0); + if (GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate)) { + DSTimerIrq(dscore, 1); + } } static void DSTimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; GBATimerUpdate(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate); + DSTimerIrq(dscore, 1); + if (GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate)) { + DSTimerIrq(dscore, 2); + } } static void DSTimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; GBATimerUpdate(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate); + DSTimerIrq(dscore, 2); + if (GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate)) { + DSTimerIrq(dscore, 3); + } } static void DSTimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct DSCommon* dscore = context; GBATimerUpdate(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate); + DSTimerIrq(dscore, 3); } void DSTimerInit(struct DS* ds) { @@ -81,22 +66,6 @@ void DSTimerInit(struct DS* ds) { ds->ds7.timers[3].event.callback = DSTimerUpdate3; ds->ds7.timers[3].event.context = &ds->ds7; ds->ds7.timers[3].event.priority = 0x23; - ds->ds7.timers[0].irq.name = "DS7 Timer 0 IRQ"; - ds->ds7.timers[0].irq.callback = DSTimerIrq0; - ds->ds7.timers[0].irq.context = &ds->ds7; - ds->ds7.timers[0].irq.priority = 0x28; - ds->ds7.timers[1].irq.name = "DS7 Timer 1 IRQ"; - ds->ds7.timers[1].irq.callback = DSTimerIrq1; - ds->ds7.timers[1].irq.context = &ds->ds7; - ds->ds7.timers[1].irq.priority = 0x29; - ds->ds7.timers[2].irq.name = "DS7 Timer 2 IRQ"; - ds->ds7.timers[2].irq.callback = DSTimerIrq2; - ds->ds7.timers[2].irq.context = &ds->ds7; - ds->ds7.timers[2].irq.priority = 0x2A; - ds->ds7.timers[3].irq.name = "DS7 Timer 3 IRQ"; - ds->ds7.timers[3].irq.callback = DSTimerIrq3; - ds->ds7.timers[3].irq.context = &ds->ds7; - ds->ds7.timers[3].irq.priority = 0x2B; memset(ds->ds9.timers, 0, sizeof(ds->ds9.timers)); ds->ds9.timers[0].event.name = "DS9 Timer 0"; @@ -119,22 +88,6 @@ void DSTimerInit(struct DS* ds) { ds->ds9.timers[3].event.context = &ds->ds9; ds->ds9.timers[3].event.priority = 0x23; ds->ds9.timers[3].forcedPrescale = 1; - ds->ds9.timers[0].irq.name = "DS9 Timer 0 IRQ"; - ds->ds9.timers[0].irq.callback = DSTimerIrq0; - ds->ds9.timers[0].irq.context = &ds->ds9; - ds->ds9.timers[0].irq.priority = 0x28; - ds->ds9.timers[1].irq.name = "DS9 Timer 1 IRQ"; - ds->ds9.timers[1].irq.callback = DSTimerIrq1; - ds->ds9.timers[1].irq.context = &ds->ds9; - ds->ds9.timers[1].irq.priority = 0x29; - ds->ds9.timers[2].irq.name = "DS9 Timer 2 IRQ"; - ds->ds9.timers[2].irq.callback = DSTimerIrq2; - ds->ds9.timers[2].irq.context = &ds->ds9; - ds->ds9.timers[2].irq.priority = 0x2A; - ds->ds9.timers[3].irq.name = "DS9 Timer 3 IRQ"; - ds->ds9.timers[3].irq.callback = DSTimerIrq3; - ds->ds9.timers[3].irq.context = &ds->ds9; - ds->ds9.timers[3].irq.priority = 0x2B; } void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t value) { diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index 3a938fade..7aa397833 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -162,7 +162,50 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t if (!item->validStates || !item->data) { continue; } - mCoreConfigGetUIntValue(&runner->config, item->data, &item->state); + if (item->stateMappings) { + item->state = 0; + + size_t j; + for (j = 0; j < item->nStates; ++j) { + const struct GUIVariant* v = &item->stateMappings[j]; + struct GUIVariant test; + switch (v->type) { + case GUI_VARIANT_VOID: + if (!mCoreConfigGetValue(&runner->config, item->data)) { + item->state = j; + break; + } + break; + case GUI_VARIANT_UNSIGNED: + if (mCoreConfigGetUIntValue(&runner->config, item->data, &test.v.u) && test.v.u == v->v.u) { + item->state = j; + break; + } + break; + case GUI_VARIANT_INT: + if (mCoreConfigGetIntValue(&runner->config, item->data, &test.v.i) && test.v.i == v->v.i) { + item->state = j; + break; + } + break; + case GUI_VARIANT_FLOAT: + if (mCoreConfigGetFloatValue(&runner->config, item->data, &test.v.f) && fabsf(test.v.f - v->v.f) <= 1e-3f) { + item->state = j; + break; + } + break; + case GUI_VARIANT_STRING: + test.v.s = mCoreConfigGetValue(&runner->config, item->data); + if (test.v.s && strcmp(test.v.s, v->v.s) == 0) { + item->state = j; + break; + } + break; + } + } + } else { + mCoreConfigGetUIntValue(&runner->config, item->data, &item->state); + } } while (true) { @@ -185,10 +228,31 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t } for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { item = GUIMenuItemListGetPointer(&menu.items, i); - if (!item->validStates || !item->data) { + if (!item->validStates || !item->data || ((const char*) item->data)[0] == '*') { continue; } - mCoreConfigSetUIntValue(&runner->config, item->data, item->state); + if (item->stateMappings) { + const struct GUIVariant* v = &item->stateMappings[item->state]; + switch (v->type) { + case GUI_VARIANT_VOID: + mCoreConfigSetValue(&runner->config, item->data, NULL); + break; + case GUI_VARIANT_UNSIGNED: + mCoreConfigSetUIntValue(&runner->config, item->data, v->v.u); + break; + case GUI_VARIANT_INT: + mCoreConfigSetUIntValue(&runner->config, item->data, v->v.i); + break; + case GUI_VARIANT_FLOAT: + mCoreConfigSetFloatValue(&runner->config, item->data, v->v.f); + break; + case GUI_VARIANT_STRING: + mCoreConfigSetValue(&runner->config, item->data, v->v.s); + break; + } + } else { + mCoreConfigSetUIntValue(&runner->config, item->data, item->state); + } } if (runner->keySources) { size_t i; diff --git a/src/gb/audio.c b/src/gb/audio.c index 994eaeedb..ee5ec9ed8 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -150,7 +150,7 @@ void GBAudioReset(struct GBAudio* audio) { audio->playingCh2 = false; audio->playingCh3 = false; audio->playingCh4 = false; - if (audio->p && audio->p->model != GB_MODEL_SGB) { + if (audio->p && !(audio->p->model & GB_MODEL_SGB)) { audio->playingCh1 = true; audio->enable = true; *audio->nr52 |= 0x01; diff --git a/src/gb/core.c b/src/gb/core.c index c74b748f0..bebe04abb 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -219,8 +219,12 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf mCoreConfigCopyValue(&core->config, config, "gb.model"); mCoreConfigCopyValue(&core->config, config, "sgb.model"); mCoreConfigCopyValue(&core->config, config, "cgb.model"); + mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections"); + + int fakeBool = 0; + mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool); + gb->allowOpposingDirections = fakeBool; - int fakeBool; if (mCoreConfigGetIntValue(config, "sgb.borders", &fakeBool)) { gb->video.sgbBorders = fakeBool; gb->video.renderer->enableSGBBorder(gb->video.renderer, fakeBool); @@ -234,7 +238,7 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) { struct GB* gb = core->board; - if (gb && (gb->model != GB_MODEL_SGB || !gb->video.sgbBorders)) { + if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { *width = GB_VIDEO_HORIZONTAL_PIXELS; *height = GB_VIDEO_VERTICAL_PIXELS; } else { @@ -381,9 +385,9 @@ static void _GBCoreReset(struct mCore* core) { GBDetectModel(gb); if (gb->model == GB_MODEL_DMG && modelGB) { gb->model = GBNameToModel(modelGB); - } else if (gb->model == GB_MODEL_CGB && modelCGB) { + } else if ((gb->model & GB_MODEL_CGB) && modelCGB) { gb->model = GBNameToModel(modelCGB); - } else if (gb->model == GB_MODEL_SGB && modelSGB) { + } else if ((gb->model & GB_MODEL_SGB) && modelSGB) { gb->model = GBNameToModel(modelSGB); } } @@ -1069,6 +1073,7 @@ static bool _GBVLPLoadState(struct mCore* core, const void* buffer) { gb->cpu->pc = GB_BASE_HRAM; gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc); + GBVideoReset(&gb->video); GBVideoDeserialize(&gb->video, state); GBIODeserialize(gb, state); GBAudioReset(&gb->audio); diff --git a/src/gb/gb.c b/src/gb/gb.c index 29b12df2d..508483c1b 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -30,6 +30,7 @@ static const uint8_t _knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66}; #define DMG_2_BIOS_CHECKSUM 0x59C8598E #define MGB_BIOS_CHECKSUM 0xE6920754 #define SGB_BIOS_CHECKSUM 0xEC8A83B9 +#define SGB2_BIOS_CHECKSUM 0X53D0DD63 #define CGB_BIOS_CHECKSUM 0x41884E46 mLOG_DEFINE_CATEGORY(GB, "GB", "gb"); @@ -249,7 +250,11 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) { } void GBSavedataMask(struct GB* gb, struct VFile* vf, bool writeback) { + struct VFile* oldVf = gb->sramVf; GBSramDeinit(gb); + if (oldVf && oldVf != gb->sramRealVf) { + oldVf->close(oldVf); + } gb->sramVf = vf; gb->sramMaskWriteback = writeback; gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ); @@ -396,6 +401,7 @@ bool GBIsBIOS(struct VFile* vf) { case DMG_2_BIOS_CHECKSUM: case MGB_BIOS_CHECKSUM: case SGB_BIOS_CHECKSUM: + case SGB2_BIOS_CHECKSUM: case CGB_BIOS_CHECKSUM: return true; default: @@ -584,6 +590,9 @@ void GBDetectModel(struct GB* gb) { case SGB_BIOS_CHECKSUM: gb->model = GB_MODEL_SGB; break; + case SGB2_BIOS_CHECKSUM: + gb->model = GB_MODEL_SGB2; + break; case CGB_BIOS_CHECKSUM: gb->model = GB_MODEL_CGB; break; diff --git a/src/gb/io.c b/src/gb/io.c index d24576333..84b3a35bc 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -105,6 +105,7 @@ static const uint8_t _registerMask[] = { }; static uint8_t _readKeys(struct GB* gb); +static uint8_t _readKeysFiltered(struct GB* gb); static void _writeSGBBits(struct GB* gb, int bits) { if (!bits) { @@ -198,7 +199,7 @@ void GBIOReset(struct GB* gb) { } GBIOWrite(gb, REG_WY, 0x00); GBIOWrite(gb, REG_WX, 0x00); - if (gb->model >= GB_MODEL_CGB) { + if (gb->model & GB_MODEL_CGB) { GBIOWrite(gb, REG_UNK4C, 0); GBIOWrite(gb, REG_JOYP, 0xFF); GBIOWrite(gb, REG_VBK, 0); @@ -210,7 +211,7 @@ void GBIOReset(struct GB* gb) { GBIOWrite(gb, REG_HDMA3, 0xFF); GBIOWrite(gb, REG_HDMA4, 0xFF); gb->memory.io[REG_HDMA5] = 0xFF; - } else if (gb->model == GB_MODEL_SGB) { + } else if (gb->model & GB_MODEL_SGB) { GBIOWrite(gb, REG_JOYP, 0xFF); } GBIOWrite(gb, REG_IE, 0x00); @@ -403,7 +404,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case REG_JOYP: gb->memory.io[REG_JOYP] = value | 0x0F; _readKeys(gb); - if (gb->model == GB_MODEL_SGB) { + if (gb->model & GB_MODEL_SGB) { _writeSGBBits(gb, (value >> 4) & 3); } return; @@ -557,10 +558,25 @@ static uint8_t _readKeys(struct GB* gb) { return gb->memory.io[REG_JOYP]; } +static uint8_t _readKeysFiltered(struct GB* gb) { + uint8_t keys = _readKeys(gb); + if (!gb->allowOpposingDirections && (keys & 0x30) == 0x20) { + unsigned rl = keys & 0x03; + unsigned ud = keys & 0x0C; + if (!rl) { + keys |= 0x03; + } + if (!ud) { + keys |= 0x0C; + } + } + return keys; +} + uint8_t GBIORead(struct GB* gb, unsigned address) { switch (address) { case REG_JOYP: - return _readKeys(gb); + return _readKeysFiltered(gb); case REG_IE: return gb->memory.ie; case REG_WAVE_0: @@ -703,7 +719,7 @@ void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WY, state->io[REG_WY]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WX, state->io[REG_WX]); - if (gb->model == GB_MODEL_SGB) { + if (gb->model & GB_MODEL_SGB) { gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_BGP, state->io[REG_BGP]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_OBP0, state->io[REG_OBP0]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_OBP1, state->io[REG_OBP1]); diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index d2e000422..c5e0d3c7a 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -30,7 +30,7 @@ static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* rende static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) { size_t sgbOffset = 0; - if (renderer->model == GB_MODEL_SGB) { + if (renderer->model & GB_MODEL_SGB) { return; } int y; @@ -56,7 +56,7 @@ static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) { int x, y; for (y = 0; y < 224; ++y) { for (x = 0; x < 256; x += 8) { - if (x >= 48 && x < 208 && y >= 40 && y < 104) { + if (x >= 48 && x < 208 && y >= 40 && y < 184) { continue; } uint16_t mapData; @@ -79,16 +79,13 @@ static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) { int paletteBase = SGBBgAttributesGetPalette(mapData) * 0x10; int colorSelector; + int flip = 0; if (SGBBgAttributesIsXFlip(mapData)) { - for (i = 0; i < 8; ++i) { - colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3; - renderer->outputBuffer[base + i] = renderer->palette[paletteBase | colorSelector]; - } - } else { - for (i = 7; i >= 0; --i) { - colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3; - renderer->outputBuffer[base + 7 - i] = renderer->palette[paletteBase | colorSelector]; - } + flip = 7; + } + for (i = 7; i >= 0; --i) { + colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3; + renderer->outputBuffer[(base + 7 - i) ^ flip] = renderer->palette[paletteBase | colorSelector]; } } } @@ -432,7 +429,7 @@ static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* render static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; color_t color = mColorFrom555(value); - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { if (index < 0x10 && index && !(index & 3)) { color = softwareRenderer->palette[0]; } else if (index >= 0x40 && !(index & 0xF)) { @@ -463,7 +460,7 @@ static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer } softwareRenderer->palette[index] = color; - if (softwareRenderer->model == GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { + if (softwareRenderer->model & GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { renderer->writePalette(renderer, 0x04, value); renderer->writePalette(renderer, 0x08, value); renderer->writePalette(renderer, 0x0C, value); @@ -528,7 +525,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i } size_t sgbOffset = 0; - if (softwareRenderer->model == GB_MODEL_SGB && softwareRenderer->sgbBorders) { + if (softwareRenderer->model & GB_MODEL_SGB && softwareRenderer->sgbBorders) { sgbOffset = softwareRenderer->outputBufferStride * 40 + 48; } color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset]; @@ -536,7 +533,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i int p = 0; switch (softwareRenderer->d.sgbRenderMode) { case 0: - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { p = softwareRenderer->d.sgbAttributes[(startX >> 5) + 5 * (y >> 3)]; p >>= 6 - ((x / 4) & 0x6); p &= 3; @@ -546,7 +543,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]]; } for (; x + 7 < (endX & ~7); x += 8) { - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)]; p >>= 6 - ((x / 4) & 0x6); p &= 3; @@ -561,7 +558,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i row[x + 6] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 6] & 0x7F]]; row[x + 7] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 7] & 0x7F]]; } - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)]; p >>= 6 - ((x / 4) & 0x6); p &= 3; @@ -678,7 +675,7 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { _clearScreen(softwareRenderer); } - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { switch (softwareRenderer->sgbCommandHeader >> 3) { case SGB_PAL_SET: case SGB_ATTR_SET: @@ -711,7 +708,10 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) static void GBVideoSoftwareRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - if (softwareRenderer->model == GB_MODEL_SGB) { + if (softwareRenderer->model & GB_MODEL_SGB) { + if (enable == softwareRenderer->sgbBorders) { + return; + } softwareRenderer->sgbBorders = enable; if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) { _regenerateSGBBorder(softwareRenderer); diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 67046a888..4eef6ae67 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -64,7 +64,7 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) { GBTimerSerialize(&gb->timer, state); GBAudioSerialize(&gb->audio, state); - if (gb->model == GB_MODEL_SGB) { + if (gb->model & GB_MODEL_SGB) { GBSGBSerialize(gb, state); } } @@ -187,7 +187,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); - if (gb->model == GB_MODEL_SGB && canSgb) { + if (gb->model & GB_MODEL_SGB && canSgb) { GBSGBDeserialize(gb, state); } diff --git a/src/gb/video.c b/src/gb/video.c index f90536ae0..dcb49523c 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -107,7 +107,7 @@ void GBVideoReset(struct GBVideo* video) { video->renderer->oam = &video->oam; memset(&video->palette, 0, sizeof(video->palette)); - if (video->p->model == GB_MODEL_SGB) { + if (video->p->model & GB_MODEL_SGB) { video->renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM); video->renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM); video->renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM); @@ -491,7 +491,7 @@ void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) video->renderer->writePalette(video->renderer, 9 * 4 + 3, video->palette[9 * 4 + 3]); break; } - } else if (video->p->model == GB_MODEL_SGB) { + } else if (video->p->model & GB_MODEL_SGB) { video->renderer->writeVideoRegister(video->renderer, address, value); } else { switch (address) { diff --git a/src/gba/core.c b/src/gba/core.c index 605161c35..4a34fcb7a 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -247,6 +247,11 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con } } + int fakeBool = 0; + mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool); + gba->allowOpposingDirections = fakeBool; + + mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections"); mCoreConfigCopyValue(&core->config, config, "gba.bios"); #ifndef DISABLE_THREADING diff --git a/src/gba/gba.c b/src/gba/gba.c index df05ea401..2dea59962 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -25,6 +25,8 @@ #include #endif +#define GBA_IRQ_DELAY 7 + mLOG_DEFINE_CATEGORY(GBA, "GBA", "gba"); mLOG_DEFINE_CATEGORY(GBA_DEBUG, "GBA Debug", "gba.debug"); @@ -45,6 +47,8 @@ static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode); static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode); static void GBABreakpoint(struct ARMCore* cpu, int immediate); +static void _triggerIRQ(struct mTiming*, void* user, uint32_t cyclesLate); + #ifdef USE_DEBUGGERS static bool _setSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); static bool _clearSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode); @@ -86,7 +90,6 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { GBAHardwareInit(&gba->memory.hw, NULL); - gba->springIRQ = 0; gba->keySource = 0; gba->rotationSource = 0; gba->luminanceSource = 0; @@ -116,6 +119,11 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { gba->yankedRomSize = 0; mTimingInit(&gba->timing, &gba->cpu->cycles, &gba->cpu->nextEvent); + + gba->irqEvent.name = "GBA IRQ Event"; + gba->irqEvent.callback = _triggerIRQ; + gba->irqEvent.context = gba; + gba->irqEvent.priority = 0; } void GBAUnloadROM(struct GBA* gba) { @@ -138,6 +146,8 @@ void GBAUnloadROM(struct GBA* gba) { gba->memory.rom = NULL; gba->isPristine = false; + gba->memory.savedata.maskWriteback = false; + GBASavedataUnmask(&gba->memory.savedata); GBASavedataDeinit(&gba->memory.savedata); if (gba->memory.savedata.realVf) { gba->memory.savedata.realVf->close(gba->memory.savedata.realVf); @@ -245,11 +255,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) { gba->bus |= cpu->prefetch[1] << 16; } - if (gba->springIRQ && !cpu->cpsr.i) { - ARMRaiseIRQ(cpu); - gba->springIRQ = 0; - } - int32_t nextEvent = cpu->nextEvent; while (cpu->cycles >= nextEvent) { cpu->nextEvent = INT_MAX; @@ -472,34 +477,17 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) { gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); } -void GBAWriteIE(struct GBA* gba, uint16_t value) { - if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) { - ARMRaiseIRQ(gba->cpu); - } -} - -void GBAWriteIME(struct GBA* gba, uint16_t value) { - if (value && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { - ARMRaiseIRQ(gba->cpu); - } -} - void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq) { gba->memory.io[REG_IF >> 1] |= 1 << irq; - - if (gba->memory.io[REG_IE >> 1] & 1 << irq) { - gba->cpu->halted = 0; - if (gba->memory.io[REG_IME >> 1]) { - ARMRaiseIRQ(gba->cpu); - } - } + GBATestIRQ(gba->cpu); } void GBATestIRQ(struct ARMCore* cpu) { struct GBA* gba = (struct GBA*) cpu->master; - if (gba->memory.io[REG_IME >> 1] && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { - gba->springIRQ = gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]; - gba->cpu->nextEvent = gba->cpu->cycles; + if (gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { + if (!mTimingIsScheduled(&gba->timing, &gba->irqEvent)) { + mTimingSchedule(&gba->timing, &gba->irqEvent, GBA_IRQ_DELAY); + } } } @@ -854,6 +842,20 @@ void GBATestKeypadIRQ(struct GBA* gba) { } } +static void _triggerIRQ(struct mTiming* timing, void* user, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + struct GBA* gba = user; + gba->cpu->halted = 0; + if (!(gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1])) { + return; + } + + if (gba->memory.io[REG_IME >> 1] && !gba->cpu->cpsr.i) { + ARMRaiseIRQ(gba->cpu); + } +} + void GBASetBreakpoint(struct GBA* gba, struct mCPUComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { size_t immediate; for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) { diff --git a/src/gba/io.c b/src/gba/io.c index 4a23054d9..d458c2e52 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -547,15 +547,18 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { GBAAdjustWaitstates(gba, value); break; case REG_IE: - GBAWriteIE(gba, value); - break; + gba->memory.io[REG_IE >> 1] = value; + GBATestIRQ(gba->cpu); + return; case REG_IF: - gba->springIRQ &= ~value; value = gba->memory.io[REG_IF >> 1] & ~value; - break; + gba->memory.io[REG_IF >> 1] = value; + GBATestIRQ(gba->cpu); + return; case REG_IME: - GBAWriteIME(gba, value); - break; + gba->memory.io[REG_IME >> 1] = value; + GBATestIRQ(gba->cpu); + return; case REG_MAX: // Some bad interrupt libraries will write to this break; @@ -931,7 +934,6 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) { STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload); STORE_32(gba->timers[i].lastEvent - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].lastEvent); STORE_32(gba->timers[i].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent); - STORE_32(gba->timers[i].irq.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextIrq); STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags); STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); @@ -971,10 +973,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { if (GBATimerFlagsIsEnable(gba->timers[i].flags)) { mTimingSchedule(&gba->timing, &gba->timers[i].event, when); } - LOAD_32(when, 0, &state->timers[i].nextIrq); - if (GBATimerFlagsIsIrqPending(gba->timers[i].flags)) { - mTimingSchedule(&gba->timing, &gba->timers[i].irq, when); - } LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io); LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource); diff --git a/src/gba/memory.c b/src/gba/memory.c index 4e60ca85c..8766ffd97 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -99,13 +99,6 @@ void GBAMemoryDeinit(struct GBA* gba) { if (gba->memory.rom) { mappedMemoryFree(gba->memory.rom, gba->memory.romSize); } - gba->memory.savedata.maskWriteback = false; - GBASavedataUnmask(&gba->memory.savedata); - GBASavedataDeinit(&gba->memory.savedata); - if (gba->memory.savedata.realVf) { - gba->memory.savedata.realVf->close(gba->memory.savedata.realVf); - } - if (gba->memory.agbPrintBuffer) { mappedMemoryFree(gba->memory.agbPrintBuffer, SIZE_AGB_PRINT); } diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index a23eaf653..8211e11ec 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -582,6 +582,8 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render return; } + CLEAN_SCANLINE(softwareRenderer, y); + color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) { int x; @@ -694,15 +696,19 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) { ++softwareRenderer->bg[0].enabled; + DIRTY_SCANLINE(softwareRenderer, y); } if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) { ++softwareRenderer->bg[1].enabled; + DIRTY_SCANLINE(softwareRenderer, y); } if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) { ++softwareRenderer->bg[2].enabled; + DIRTY_SCANLINE(softwareRenderer, y); } if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) { ++softwareRenderer->bg[3].enabled; + DIRTY_SCANLINE(softwareRenderer, y); } GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer); @@ -718,7 +724,6 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render #else memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row)); #endif - CLEAN_SCANLINE(softwareRenderer, y); } static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) { @@ -765,9 +770,10 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, } static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) { + int wasActive = renderer->bg[bg].enabled; if (!active) { renderer->bg[bg].enabled = 0; - } else if (!renderer->bg[bg].enabled && active) { + } else if (!wasActive && active) { if (renderer->nextY == 0) { renderer->bg[bg].enabled = 4; } else { diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 1c34100dd..ba702e36b 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -88,7 +88,11 @@ void GBASavedataDeinit(struct GBASavedata* savedata) { void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf, bool writeback) { enum SavedataType type = savedata->type; + struct VFile* oldVf = savedata->vf; GBASavedataDeinit(savedata); + if (oldVf && oldVf != savedata->realVf) { + oldVf->close(oldVf); + } savedata->vf = vf; savedata->mapMode = MAP_READ; savedata->maskWriteback = writeback; diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 72cdd453f..458ba3d3a 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000; -const uint32_t GBA_SAVESTATE_VERSION = 0x00000002; +const uint32_t GBA_SAVESTATE_VERSION = 0x00000003; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); @@ -62,6 +62,10 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { GBASerializedMiscFlags miscFlags = 0; miscFlags = GBASerializedMiscFlagsSetHalted(miscFlags, gba->cpu->halted); miscFlags = GBASerializedMiscFlagsSetPOSTFLG(miscFlags, gba->memory.io[REG_POSTFLG >> 1] & 1); + if (mTimingIsScheduled(&gba->timing, &gba->irqEvent)) { + miscFlags = GBASerializedMiscFlagsFillIrqPending(miscFlags); + STORE_32(gba->irqEvent.when - mTimingCurrentTime(&gba->timing), 0, &state->nextIrq); + } STORE_32(miscFlags, 0, &state->miscFlags); GBAMemorySerialize(&gba->memory, state); @@ -179,6 +183,11 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { LOAD_32(miscFlags, 0, &state->miscFlags); gba->cpu->halted = GBASerializedMiscFlagsGetHalted(miscFlags); gba->memory.io[REG_POSTFLG >> 1] = GBASerializedMiscFlagsGetPOSTFLG(miscFlags); + if (GBASerializedMiscFlagsIsIrqPending(miscFlags)) { + int32_t when; + LOAD_32(when, 0, &state->nextIrq); + mTimingSchedule(&gba->timing, &gba->irqEvent, when); + } GBAVideoDeserialize(&gba->video, state); GBAMemoryDeserialize(&gba->memory, state); diff --git a/src/gba/timer.c b/src/gba/timer.c index b3387f574..b7fe943d9 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -8,7 +8,6 @@ #include #include -#define TIMER_IRQ_DELAY 7 #define TIMER_RELOAD_DELAY 0 #define TIMER_STARTUP_DELAY 2 @@ -16,49 +15,17 @@ static void GBATimerIrq(struct GBA* gba, int timerId) { struct GBATimer* timer = &gba->timers[timerId]; - if (GBATimerFlagsIsIrqPending(timer->flags)) { - timer->flags = GBATimerFlagsClearIrqPending(timer->flags); + if (GBATimerFlagsIsDoIrq(timer->flags)) { GBARaiseIRQ(gba, IRQ_TIMER0 + timerId); } } -static void GBATimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - GBATimerIrq(context, 0); -} - -static void GBATimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - GBATimerIrq(context, 1); -} - -static void GBATimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - GBATimerIrq(context, 2); -} - -static void GBATimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - GBATimerIrq(context, 3); -} - void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) { if (GBATimerFlagsIsCountUp(timer->flags)) { *io = timer->reload; } else { GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate); } - - if (GBATimerFlagsIsDoIrq(timer->flags)) { - timer->flags = GBATimerFlagsFillIrqPending(timer->flags); - if (!mTimingIsScheduled(timing, &timer->irq)) { - mTimingSchedule(timing, &timer->irq, TIMER_IRQ_DELAY - cyclesLate); - } - } } static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLate) { @@ -74,38 +41,50 @@ static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLat } } -void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) { +bool GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) { if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? ++*io; if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) { GBATimerUpdate(timing, nextTimer, io, cyclesLate); + return true; } } + return false; } static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; GBATimerUpdateAudio(gba, 0, cyclesLate); GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate); + GBATimerIrq(gba, 0); + if (GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate)) { + GBATimerIrq(gba, 1); + } } static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; GBATimerUpdateAudio(gba, 1, cyclesLate); GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate); + GBATimerIrq(gba, 1); + if (GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate)) { + GBATimerIrq(gba, 2); + } } static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate); - GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate); + GBATimerIrq(gba, 2); + if (GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate)) { + GBATimerIrq(gba, 3); + } } static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate); + GBATimerIrq(gba, 3); } void GBATimerInit(struct GBA* gba) { @@ -126,22 +105,6 @@ void GBATimerInit(struct GBA* gba) { gba->timers[3].event.callback = GBATimerUpdate3; gba->timers[3].event.context = gba; gba->timers[3].event.priority = 0x23; - gba->timers[0].irq.name = "GBA Timer 0 IRQ"; - gba->timers[0].irq.callback = GBATimerIrq0; - gba->timers[0].irq.context = gba; - gba->timers[0].irq.priority = 0x28; - gba->timers[1].irq.name = "GBA Timer 1 IRQ"; - gba->timers[1].irq.callback = GBATimerIrq1; - gba->timers[1].irq.context = gba; - gba->timers[1].irq.priority = 0x29; - gba->timers[2].irq.name = "GBA Timer 2 IRQ"; - gba->timers[2].irq.callback = GBATimerIrq2; - gba->timers[2].irq.context = gba; - gba->timers[2].irq.priority = 0x2A; - gba->timers[3].irq.name = "GBA Timer 3 IRQ"; - gba->timers[3].irq.callback = GBATimerIrq3; - gba->timers[3].irq.context = gba; - gba->timers[3].irq.priority = 0x2B; } void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) { diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index e4558e6bb..f61ab45fd 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -113,6 +113,8 @@ if(BUILD_PERF) endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf) + +install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index fb6703ceb..aa9a24faf 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -23,7 +23,6 @@ #include #include #endif -#include #include #include @@ -55,8 +54,8 @@ static void* data; static size_t dataSize; static void* savedata; static struct mAVStream stream; -static int rumbleLevel; -static struct CircleBuffer rumbleHistory; +static int rumbleUp; +static int rumbleDown; static struct mRumble rumble; static struct GBALuminanceSource lux; static int luxLevel; @@ -254,7 +253,6 @@ void retro_init(void) { 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; @@ -348,6 +346,18 @@ void retro_run(void) { unsigned width, height; core->desiredVideoDimensions(core, &width, &height); videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256); + + if (rumbleCallback) { + if (rumbleUp) { + rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown)); + rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown)); + } else { + rumbleCallback(0, RETRO_RUMBLE_STRONG, 0); + rumbleCallback(0, RETRO_RUMBLE_WEAK, 0); + } + rumbleUp = 0; + rumbleDown = 0; + } } static void _setupMaps(struct mCore* core) { @@ -438,9 +448,8 @@ void retro_reset(void) { core->reset(core); _setupMaps(core); - if (rumbleCallback) { - CircleBufferClear(&rumbleHistory); - } + rumbleUp = 0; + rumbleDown = 0; } bool retro_load_game(const struct retro_game_info* game) { @@ -558,7 +567,6 @@ void retro_unload_game(void) { data = 0; mappedMemoryFree(savedata, SIZE_CART_FLASH1M); savedata = 0; - CircleBufferDeinit(&rumbleHistory); } size_t retro_serialize_size(void) { @@ -755,15 +763,11 @@ static void _setRumble(struct mRumble* rumble, int enable) { if (!rumbleCallback) { return; } - rumbleLevel += enable; - if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) { - int8_t oldLevel; - CircleBufferRead8(&rumbleHistory, &oldLevel); - rumbleLevel -= oldLevel; + if (enable) { + ++rumbleUp; + } else { + ++rumbleDown; } - CircleBufferWrite8(&rumbleHistory, enable); - rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM); - rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM); } static void _updateLux(struct GBALuminanceSource* lux) { diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 34ea754e7..1fb0c9928 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -61,4 +61,5 @@ vita_create_vpk(${BINARY_NAME}.vpk MGBA00001 ${BINARY_NAME}.self FILE ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents/startup.png FILE ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents/template.xml) +install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 4cee5acc4..f10700423 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -349,6 +349,10 @@ elseif(WIN32) find_program(BASH bash) install(CODE "execute_process(COMMAND \"${BASH}\" \"${CMAKE_SOURCE_DIR}/tools/deploy-win.sh\" \"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe\" \"\${CMAKE_INSTALL_PREFIX}\" \"\$ENV{PWD}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")" COMPONENT ${BINARY_NAME}-qt) endif() + if(DISTBUILD) + file(WRITE "${CMAKE_BINARY_DIR}/portable.ini" "") + install(FILES "${CMAKE_BINARY_DIR}/portable.ini" DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT ${BINARY_NAME}-qt) + endif() endif() if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 46f777c5a..d26222f20 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -249,6 +249,10 @@ void CoreController::loadConfig(ConfigController* config) { m_autosave = config->getOption("autosave", false).toInt(); m_autoload = config->getOption("autoload", true).toInt(); m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt(); + m_fastForwardVolume = config->getOption("fastForwardVolume", -1).toInt(); + m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt(); + mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume"); + mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute"); mCoreLoadForeignConfig(m_threadContext.core, config->config()); if (hasStarted()) { updateFastForward(); @@ -782,15 +786,29 @@ void CoreController::finishFrame() { void CoreController::updateFastForward() { if (m_fastForward || m_fastForwardForced) { + if (m_fastForwardVolume >= 0) { + m_threadContext.core->opts.volume = m_fastForwardVolume; + } + if (m_fastForwardMute >= 0) { + m_threadContext.core->opts.mute = m_fastForwardMute; + } if (m_fastForwardRatio > 0) { m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio; } else { setSync(false); } } else { + if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) { + m_threadContext.core->opts.volume = 0x100; + } + int fakeBool = 0; + mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool); + m_threadContext.core->opts.mute = fakeBool; m_threadContext.impl->sync.fpsTarget = m_fpsTarget; setSync(true); } + // XXX: Have a way of just updating opts + m_threadContext.core->loadConfig(m_threadContext.core, &m_threadContext.core->config); } CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread) diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 107f4b2eb..a5e98a289 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -200,6 +200,8 @@ private: int m_fastForward = false; int m_fastForwardForced = false; + int m_fastForwardVolume = -1; + int m_fastForwardMute = -1; float m_fastForwardRatio = -1.f; float m_fpsTarget; diff --git a/src/platform/qt/DebuggerConsole.cpp b/src/platform/qt/DebuggerConsole.cpp index a9c505da8..b7ac06323 100644 --- a/src/platform/qt/DebuggerConsole.cpp +++ b/src/platform/qt/DebuggerConsole.cpp @@ -17,6 +17,8 @@ DebuggerConsole::DebuggerConsole(DebuggerConsoleController* controller, QWidget* { m_ui.setupUi(this); + m_ui.prompt->installEventFilter(this); + connect(m_ui.prompt, &QLineEdit::returnPressed, this, &DebuggerConsole::postLine); connect(controller, &DebuggerConsoleController::log, this, &DebuggerConsole::log); connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::attach); @@ -36,7 +38,47 @@ void DebuggerConsole::postLine() { if (line.isEmpty()) { m_consoleController->enterLine(QString("\n")); } else { + m_history.append(line); + m_historyOffset = 0; log(QString("> %1\n").arg(line)); m_consoleController->enterLine(line); } } + +bool DebuggerConsole::eventFilter(QObject*, QEvent* event) { + if (event->type() != QEvent::KeyPress) { + return false; + } + if (m_history.isEmpty()) { + return false; + } + QKeyEvent* keyEvent = static_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Down: + if (m_historyOffset <= 0) { + return false; + } + --m_historyOffset; + break; + case Qt::Key_Up: + if (m_historyOffset >= m_history.size()) { + return false; + } + ++m_historyOffset; + break; + case Qt::Key_End: + m_historyOffset = 0; + break; + case Qt::Key_Home: + m_historyOffset = m_history.size(); + break; + default: + return false; + } + if (m_historyOffset == 0) { + m_ui.prompt->clear(); + } else { + m_ui.prompt->setText(m_history[m_history.size() - m_historyOffset]); + } + return true; +} \ No newline at end of file diff --git a/src/platform/qt/DebuggerConsole.h b/src/platform/qt/DebuggerConsole.h index f4bd4cadb..5fe523c24 100644 --- a/src/platform/qt/DebuggerConsole.h +++ b/src/platform/qt/DebuggerConsole.h @@ -21,8 +21,13 @@ private slots: void log(const QString&); void postLine(); +protected: + bool eventFilter(QObject*, QEvent*) override; + private: Ui::DebuggerConsole m_ui; + QStringList m_history; + int m_historyOffset; DebuggerConsoleController* m_consoleController; }; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index ec77aea82..6d8bae787 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -40,6 +40,7 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) } DisplayGL::~DisplayGL() { + stopDrawing(); delete m_painter; } @@ -196,8 +197,15 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent) mGLES2Context* gl2Backend; #endif + m_gl->makeCurrent(); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + + QStringList extensions = QString(reinterpret_cast(glGetString(GL_EXTENSIONS))).split(' '); + #if !defined(_WIN32) || defined(USE_EPOXY) - if (majorVersion >= 2) { + if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) { gl2Backend = static_cast(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; @@ -218,10 +226,6 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent) painter->m_gl->swapBuffers(); }; - m_gl->makeCurrent(); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif m_backend->init(m_backend, reinterpret_cast(m_gl->winId())); #if !defined(_WIN32) || defined(USE_EPOXY) if (m_supportsShaders) { diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index d9db6a7d3..93cbfee66 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -27,8 +27,9 @@ LoadSaveState::LoadSaveState(std::shared_ptr controller, QWidget , m_mode(LoadSave::LOAD) , m_currentFocus(controller->stateSlot() - 1) { - setAttribute(Qt::WA_TranslucentBackground); m_ui.setupUi(this); + m_ui.lsLabel->setFocusProxy(this); + setFocusPolicy(Qt::ClickFocus); m_slots[0] = m_ui.state1; m_slots[1] = m_ui.state2; @@ -56,6 +57,7 @@ LoadSaveState::LoadSaveState(std::shared_ptr controller, QWidget if (m_currentFocus < 0) { m_currentFocus = 0; } + m_slots[m_currentFocus]->setFocus(); QAction* escape = new QAction(this); connect(escape, &QAction::triggered, this, &QWidget::close); @@ -242,6 +244,10 @@ void LoadSaveState::showEvent(QShowEvent* event) { QWidget::showEvent(event); } +void LoadSaveState::focusInEvent(QFocusEvent*) { + m_slots[m_currentFocus]->setFocus(); +} + void LoadSaveState::paintEvent(QPaintEvent*) { QPainter painter(this); QRect full(QPoint(), size()); diff --git a/src/platform/qt/LoadSaveState.h b/src/platform/qt/LoadSaveState.h index b762a00e1..5596c1b10 100644 --- a/src/platform/qt/LoadSaveState.h +++ b/src/platform/qt/LoadSaveState.h @@ -41,6 +41,7 @@ protected: virtual void closeEvent(QCloseEvent*) override; virtual void showEvent(QShowEvent*) override; virtual void paintEvent(QPaintEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; private: void loadState(int slot); diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index 9deb4f5e0..54c49cc2c 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -32,7 +32,7 @@ SensorView::SensorView(InputController* input, QWidget* parent) m_ui.time->setDateTime(QDateTime::currentDateTime()); }); - m_timer.setInterval(2); + m_timer.setInterval(15); connect(&m_timer, &QTimer::timeout, this, &SensorView::updateSensors); if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) { m_ui.tilt->hide(); diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index a8ecff918..5678d67a8 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -43,6 +43,18 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC reloadConfig(); + connect(m_ui.volume, static_cast(&QSlider::valueChanged), [this](int v) { + if (v < m_ui.volumeFf->value()) { + m_ui.volumeFf->setValue(v); + } + }); + + connect(m_ui.mute, &QAbstractButton::toggled, [this](bool e) { + if (e) { + m_ui.muteFf->setChecked(e); + } + }); + if (m_ui.savegamePath->text().isEmpty()) { m_ui.savegameSameDir->setChecked(true); } @@ -344,6 +356,8 @@ void SettingsView::updateConfig() { saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); + saveSetting("fastForwardVolume", m_ui.volumeFf); + saveSetting("fastForwardMute", m_ui.muteFf); saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); saveSetting("resampleVideo", m_ui.resampleVideo); @@ -472,8 +486,10 @@ void SettingsView::reloadConfig() { loadSetting("autofireThreshold", m_ui.autofireThreshold); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling); - loadSetting("volume", m_ui.volume); - loadSetting("mute", m_ui.mute); + loadSetting("volume", m_ui.volume, 0x100); + loadSetting("mute", m_ui.mute, false); + loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value()); + loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked()); loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); loadSetting("resampleVideo", m_ui.resampleVideo); @@ -604,9 +620,9 @@ void SettingsView::loadSetting(const char* key, QLineEdit* field) { field->setText(option); } -void SettingsView::loadSetting(const char* key, QSlider* field) { +void SettingsView::loadSetting(const char* key, QSlider* field, int defaultVal) { QString option = loadSetting(key); - field->setValue(option.toInt()); + field->setValue(option.isNull() ? defaultVal : option.toInt()); } void SettingsView::loadSetting(const char* key, QSpinBox* field) { diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 928b71653..6835f8f84 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -75,7 +75,7 @@ private: void loadSetting(const char* key, QComboBox*); void loadSetting(const char* key, QDoubleSpinBox*); void loadSetting(const char* key, QLineEdit*); - void loadSetting(const char* key, QSlider*); + void loadSetting(const char* key, QSlider*, int defaultVal = 0); void loadSetting(const char* key, QSpinBox*); QString loadSetting(const char* key); }; diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 1b1b23663..7d5f69e32 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -263,21 +263,61 @@ - + + + + Fast forward volume: + + + + + + + + + + 128 + 0 + + + + 256 + + + 16 + + + 256 + + + Qt::Horizontal + + + + + + + Mute + + + + + + Qt::Horizontal - + Display driver: - + @@ -287,14 +327,14 @@ - + Frameskip: - + @@ -315,14 +355,14 @@ - + FPS target: - + @@ -349,21 +389,21 @@ - + Qt::Horizontal - + Sync: - + @@ -381,7 +421,7 @@ - + Lock aspect ratio @@ -389,19 +429,19 @@ - - - Bilinear filtering - - - - Force integer scaling + + + + Bilinear filtering + + + diff --git a/src/platform/qt/TilePainter.cpp b/src/platform/qt/TilePainter.cpp index 454530a7c..bbf975ca6 100644 --- a/src/platform/qt/TilePainter.cpp +++ b/src/platform/qt/TilePainter.cpp @@ -59,8 +59,12 @@ void TilePainter::setTileCount(int tiles) { int w = width() / m_size; int h = (tiles + w - 1) * m_size / w; setMinimumSize(m_size, h - (h % m_size)); - resizeEvent(nullptr); + } else { + int w = minimumSize().width() / m_size; + int h = (tiles + w - 1) * m_size / w; + setMinimumSize(minimumSize().width(), h - (h % m_size)); } + resizeEvent(nullptr); } void TilePainter::setTileMagnification(int mag) { diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 38c2610f7..063caf8d3 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -65,6 +65,21 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) connect(m_ui.magnification, static_cast(&QSpinBox::valueChanged), [this]() { updateTiles(true); }); + + connect(m_ui.tilesPerRow, static_cast(&QSpinBox::valueChanged), [this](int count) { + m_ui.tiles->setMinimumSize(m_ui.magnification->value() * 8 * count, m_ui.tiles->minimumSize().height()); + updateTiles(true); + }); + + connect(m_ui.tileFit, &QAbstractButton::toggled, [this](bool selected) { + if (!selected) { + m_ui.tiles->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_ui.tiles->setMinimumSize(m_ui.magnification->value() * 8 * m_ui.tilesPerRow->value(), m_ui.tiles->minimumSize().height()); + } else { + m_ui.tiles->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + } + updateTiles(true); + }); } #ifdef M_CORE_GBA diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui index 608e1d2d0..02c4c533f 100644 --- a/src/platform/qt/TileView.ui +++ b/src/platform/qt/TileView.ui @@ -6,48 +6,15 @@ 0 0 - 501 - 335 + 693 + 467 Tiles - - - - - - - - - - - 0 - 0 - - - - × - - - 1 - - - 4 - - - - - - - Magnification - - - - - - + + @@ -66,7 +33,7 @@ 0 0 - 256 + 405 768 @@ -112,7 +79,7 @@ - + Qt::Vertical @@ -125,22 +92,84 @@ + + + - - + + 15 - + 256 colors + + + + + 0 + 0 + + + + × + + + 1 + + + 4 + + + + + + + Magnification + + + + + + + false + + + 1 + + + 64 + + + 32 + + + + + + + Tiles per row + + + + + + + Fit to window + + + true + + + @@ -164,6 +193,22 @@ + + tileFit + toggled(bool) + tilesPerRow + setDisabled(bool) + + + 162 + 180 + + + 39 + 133 + + + magnification valueChanged(int) @@ -171,12 +216,12 @@ setTileMagnification(int) - 36 - 83 + 39 + 81 - 339 - 396 + 462 + 391 @@ -187,11 +232,11 @@ setDisabled(bool) - 158 - 29 + 148 + 24 - 44 + 39 29 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 4b49e8b92..006f8d25d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -103,7 +103,6 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi setAcceptDrops(true); setAttribute(Qt::WA_DeleteOnClose); updateTitle(); - reloadDisplayDriver(); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap @@ -160,12 +159,10 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi connect(this, &Window::shutdown, m_logView, &QWidget::hide); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); - connect(&m_frameTimer, &QTimer::timeout, this, &Window::delimitFrames); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); - m_frameTimer.setInterval(FRAME_LIST_INTERVAL); m_focusCheck.setInterval(200); setupMenu(menuBar()); @@ -270,8 +267,10 @@ void Window::reloadConfig() { } m_display->resizeContext(); } - m_display->lockAspectRatio(opts->lockAspectRatio); - m_display->filter(opts->resampleVideo); + if (m_display) { + m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->filter(opts->resampleVideo); + } m_inputController.setScreensaverSuspendable(opts->suspendScreensaver); } @@ -410,6 +409,22 @@ void Window::selectSave(bool temporary) { } } +void Window::selectState(bool load) { + QStringList formats{"*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9"}; + QString filter = tr("mGBA savestate files (%1)").arg(formats.join(QChar(' '))); + if (load) { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select savestate"), filter); + if (!filename.isEmpty()) { + m_controller->loadState(filename); + } + } else { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select savestate"), filter); + if (!filename.isEmpty()) { + m_controller->saveState(filename); + } + } +} + void Window::multiplayerChanged() { if (!m_controller) { return; @@ -600,9 +615,7 @@ void Window::showEvent(QShowEvent* event) { enterFullScreen(); m_fullscreenOnStart = false; } - if (m_display) { - reloadDisplayDriver(); - } + reloadDisplayDriver(); } void Window::closeEvent(QCloseEvent* event) { @@ -614,6 +627,7 @@ void Window::closeEvent(QCloseEvent* event) { m_config->setOption("width", VIDEO_HORIZONTAL_PIXELS * m_savedScale); } saveConfig(); + m_display.reset(); QMainWindow::closeEvent(event); } @@ -745,6 +759,9 @@ void Window::gameStarted() { if (m_savedScale > 0) { resizeFrame(size * m_savedScale); } + if (!m_display) { + reloadDisplayDriver(); + } attachWidget(m_display.get()); setMouseTracking(true); m_display->setMinimumSize(size); @@ -830,7 +847,6 @@ void Window::gameStopped() { m_audioChannels->clear(); m_fpsTimer.stop(); - m_frameTimer.stop(); m_focusCheck.stop(); emit paused(false); @@ -960,19 +976,8 @@ void Window::mustRestart() { } void Window::recordFrame() { - if (m_frameList.isEmpty()) { - m_frameList.append(1); - } else { - ++m_frameList.back(); - } -} - -void Window::delimitFrames() { - if (m_frameList.size() >= FRAME_LIST_SIZE) { - m_frameCounter -= m_frameList.takeAt(0); - } - m_frameCounter += m_frameList.back(); - m_frameList.append(0); + m_frameList.append(m_frameTimer.nsecsElapsed()); + m_frameTimer.restart(); } void Window::showFPS() { @@ -980,7 +985,12 @@ void Window::showFPS() { updateTitle(); return; } - float fps = m_frameCounter * 10000.f / (FRAME_LIST_INTERVAL * (m_frameList.size() - 1)); + qint64 total = 0; + for (qint64 t : m_frameList) { + total += t; + } + double fps = (m_frameList.size() * 1e10) / total; + m_frameList.clear(); fps = round(fps) / 10.f; updateTitle(fps); } @@ -1113,6 +1123,12 @@ void Window::setupMenu(QMenuBar* menubar) { m_platformActions.append(qMakePair(loadState, SUPPORT_GB | SUPPORT_GBA)); addControlledAction(fileMenu, loadState, "loadState"); + QAction* loadStateFile = new QAction(tr("Load state file..."), fileMenu); + connect(loadStateFile, &QAction::triggered, [this]() { this->selectState(true); }); + m_gameActions.append(loadStateFile); + m_nonMpActions.append(loadStateFile); + addControlledAction(fileMenu, loadStateFile, "loadStateFile"); + QAction* saveState = new QAction(tr("&Save state"), fileMenu); saveState->setShortcut(tr("Shift+F10")); connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); @@ -1121,6 +1137,12 @@ void Window::setupMenu(QMenuBar* menubar) { m_platformActions.append(qMakePair(saveState, SUPPORT_GB | SUPPORT_GBA)); addControlledAction(fileMenu, saveState, "saveState"); + QAction* saveStateFile = new QAction(tr("Save state file..."), fileMenu); + connect(saveStateFile, &QAction::triggered, [this]() { this->selectState(false); }); + m_gameActions.append(saveStateFile); + m_nonMpActions.append(saveStateFile); + addControlledAction(fileMenu, saveStateFile, "saveStateFile"); + QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); @@ -1424,7 +1446,9 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); lockAspectRatio->connect([this](const QVariant& value) { - m_display->lockAspectRatio(value.toBool()); + if (m_display) { + m_display->lockAspectRatio(value.toBool()); + } if (m_controller) { m_screenWidget->setLockAspectRatio(value.toBool()); } @@ -1434,7 +1458,9 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling"); lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); lockIntegerScaling->connect([this](const QVariant& value) { - m_display->lockIntegerScaling(value.toBool()); + if (m_display) { + m_display->lockIntegerScaling(value.toBool()); + } if (m_controller) { m_screenWidget->setLockIntegerScaling(value.toBool()); } @@ -1444,7 +1470,9 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu); resampleVideo->connect([this](const QVariant& value) { - m_display->filter(value.toBool()); + if (m_display) { + m_display->filter(value.toBool()); + } }, this); m_config->updateOption("resampleVideo"); @@ -1671,6 +1699,16 @@ void Window::setupMenu(QMenuBar* menubar) { reloadConfig(); }, this); + ConfigOption* volumeFf = m_config->addOption("fastForwardVolume"); + volumeFf->connect([this](const QVariant& value) { + reloadConfig(); + }, this); + + ConfigOption* muteFf = m_config->addOption("fastForwardMute"); + muteFf->connect([this](const QVariant& value) { + reloadConfig(); + }, this); + ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); rewindEnable->connect([this](const QVariant& value) { reloadConfig(); @@ -1706,7 +1744,6 @@ void Window::setupMenu(QMenuBar* menubar) { showFps->connect([this](const QVariant& value) { if (!value.toInt()) { m_fpsTimer.stop(); - m_frameTimer.stop(); updateTitle(); } else if (m_controller) { m_fpsTimer.start(); @@ -1810,7 +1847,7 @@ void Window::focusCheck() { void Window::updateFrame() { QSize size = m_controller->screenDimensions(); QImage currentImage(reinterpret_cast(m_controller->drawContext()), size.width(), size.height(), - 256 * BYTES_PER_PIXEL, QImage::Format_RGBX8888); + size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888); QPixmap pixmap; pixmap.convertFromImage(currentImage); m_screenWidget->setPixmap(pixmap); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 3b49b2bc3..ab42f5d29 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,7 @@ public slots: void addDirToLibrary(); #endif void selectSave(bool temporary); + void selectState(bool load); void selectPatch(); void enterFullScreen(); void exitFullScreen(); @@ -131,7 +133,6 @@ private slots: void mustRestart(); void recordFrame(); - void delimitFrames(); void showFPS(); void focusCheck(); @@ -139,8 +140,6 @@ private slots: private: static const int FPS_TIMER_INTERVAL = 2000; - static const int FRAME_LIST_INTERVAL = 100; - static const int FRAME_LIST_SIZE = 40; void setupMenu(QMenuBar*); void openStateWindow(LoadSave); @@ -186,10 +185,9 @@ private: QPixmap m_logo{":/res/medusa-bg.jpg"}; ConfigController* m_config; InputController m_inputController; - QList m_frameList; - int m_frameCounter = 0; + QList m_frameList; + QElapsedTimer m_frameTimer; QTimer m_fpsTimer; - QTimer m_frameTimer; QList m_mruFiles; QMenu* m_mruMenu = nullptr; QMenu* m_videoLayers; diff --git a/src/platform/qt/input/InputController.cpp b/src/platform/qt/input/InputController.cpp index c46ad44f0..e342abe94 100644 --- a/src/platform/qt/input/InputController.cpp +++ b/src/platform/qt/input/InputController.cpp @@ -947,13 +947,13 @@ void InputController::rebindKey(const QString& key) { } void InputController::loadCamImage(const QString& path) { - QMutexLocker locker(&m_image.mutex); - m_image.image.load(path); - m_image.resizedImage = QImage(); - m_image.outOfDate = true; + setCamImage(QImage(path)); } void InputController::setCamImage(const QImage& image) { + if (image.isNull()) { + return; + } QMutexLocker locker(&m_image.mutex); m_image.image = image; m_image.resizedImage = QImage(); diff --git a/src/platform/qt/ts/medusa-emu-de.ts b/src/platform/qt/ts/medusa-emu-de.ts index 75b6d452e..610dcc565 100644 --- a/src/platform/qt/ts/medusa-emu-de.ts +++ b/src/platform/qt/ts/medusa-emu-de.ts @@ -1165,40 +1165,6 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.0x%0 (%1) - - QGBA::AudioDevice - - - Can't set format of context-less audio device - - - - - Audio device is missing its core - - - - - Writing data to read-only audio device - - - - - QGBA::AudioProcessorQt - - - Can't start an audio processor without input - - - - - QGBA::AudioProcessorSDL - - - Can't start an audio processor without input - - - QGBA::CheatsModel @@ -1245,22 +1211,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen @@ -2768,27 +2734,27 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::LoadSaveState - + Load State Savestate laden - + Save State Savestate speichern - + Empty Leer - + Corrupted Defekt - + Slot %1 Speicherplatz %1 @@ -2903,7 +2869,6 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - All Alle @@ -2913,32 +2878,32 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.TBL laden - + Save selected memory Ausgewählten Speicher abspeichern - + Failed to open output file: %1 Fehler beim Öffnen der Ausgabedatei: %1 - + Load memory Lade Speicher - + Failed to open input file: %1 Fehler beim Laden der Eingabedatei: %1 - + TBL TBL - + ISO-8859-1 ISO-8859-1 @@ -3100,59 +3065,59 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::SettingsView - - + + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (erzwinge Version 1.x) - + None (Still Image) Keiner (Standbild) - + Keyboard Tastatur - + Controllers Gamepads - + Shortcuts Tastenkürzel - - + + Shaders Shader - + Select BIOS BIOS auswählen @@ -3193,12 +3158,12 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::VideoView - + Failed to open output video file: %1 Fehler beim Öffnen der Ausgabe-Videodatei: %1 - + Native (%0x%1) Nativ (%0x%1) @@ -3254,34 +3219,45 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - - + + Select save Speicherdatei wählen - + + mGBA savestate files (%1) + mGBA Savestate-Dateien (%1) + + + + + Select savestate + Savestate auswählen + + + Select patch Patch wählen - + Patches (*.ips *.ups *.bps) Patches (*.ips *.ups *.bps) - + Select image Bild auswählen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Bild-Datei (*.png *.gif *.jpg *.jpeg);;Alle Dateien (*) - - + + GameShark saves (*.sps *.xps) GameShark-Speicherdaten (*.sps *.xps) @@ -3301,17 +3277,17 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Video-Log auswählen - + Video logs (*.mvl) Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -3320,538 +3296,508 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load alternate save... Alternative Speicherdatei laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate (aktueller Zustand) &laden - + F10 F10 - + + Load state file... + Ssavestate-Datei laden... + + + &Save state Savestate (aktueller Zustand) &speichern - + Shift+F10 Umschalt+F10 - + + Save state file... + Savestate-Datei speichern... + + + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Zustand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Load camera image... Lade Kamerabild... - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown Schli&eßen - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + Frame&skip Frame&skip - + Mute Stummschalten - + FPS target Bildwiederholrate - - 15 - 15 - - - - 30 - 30 - - - - 45 - 45 - - - - Native (59.7) - Nativ (59.7) - - - - 60 - 60 - - - - 90 - 90 - - - - 120 - 120 - - - - 240 - 240 - - - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + Record video log... Video-Log aufzeichnen... - + Stop video log Video-Log beenden - + Game Boy Printer... Game Boy Printer... - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + Adjust layer placement... Lage der Bildebenen anpassen... - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole öffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... @@ -3876,37 +3822,42 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + + Native (59.7275) + Nativ (59.7275) + + + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View &map... &Map betrachten... - + View memory... Speicher betrachten... @@ -3921,72 +3872,72 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Speicher durchsuchen... - + View &I/O registers... &I/O-Register betrachten... - + Exit fullscreen Vollbildmodus beenden - + GameShark Button (held) GameShark-Taste (gehalten) - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links @@ -4263,233 +4214,233 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. + Mute Stummschalten - + + Fast forward volume: + Vorspul-Lautstärke: + + + Display driver: Anzeige-Treiber: - + Frameskip: Frameskip: - + Skip every Überspringe - - + + frames Bild(er) - + FPS target: Bildwiederholrate: - + frames per second Bilder pro Sekunde - + Sync: Synchronisierung: - + Video Video - + Audio Audio - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Erzwinge pixelgenaue Skalierung (Integer scaling) - + Language Sprache - + English Englisch - + List view Listenansicht - + Tree view Baumansicht - + Show FPS in title bar Bildwiederholrate in der Titelleiste anzeigen - + Automatically save cheats Cheats automatisch speichern - + Automatically load cheats Cheats automatisch laden - + Automatically save state Zustand (Savestate) automatisch speichern - + Automatically load state Zustand (Savestate) automatisch laden - + Cheats Cheats - + Game Boy model Game Boy-Modell - - - + + + Autodetect Automatisch erkennen - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model Super Game Boy-Modell - + Game Boy Color model Game Boy Color-Modell - + Default BG colors: Standard-Hintergrundfarben: - + Default sprite colors 1: Standard-Sprite-Farben 1: - + Default sprite colors 2: Standard-Sprite-Farben 2: - + Super Game Boy borders Super Game Boy-Rahmen - + Camera driver: Kamera-Treiber: - + Library: Bibliothek: - + Show when no game open Anzeigen, wenn kein Spiel geöffnet ist - + Clear cache Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - - Rewind affects save data - Rücklauf beeinflusst -Speicherdaten - - - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - - - + + + + + + + + + Browse Durchsuchen @@ -4510,22 +4461,22 @@ in Arbeitsspeicher vorladen wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -4535,95 +4486,95 @@ wenn vorhanden BIOS - + Pause when inactive Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen - + Allow opposing input directions Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren - + Bilinear filtering Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + Autofire interval: Autofeuer-Intervall: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: @@ -4638,31 +4589,31 @@ wenn vorhanden Datei mit SGB-BIOS: - + Save games Spielstände - - - - - + + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches @@ -4778,47 +4729,12 @@ wenn vorhanden Presets Vorgaben - - - High Quality - Hohe Qualität - - - - YouTube - YouTube - WebM WebM - - - Lossless - Verlustfrei - - - - 1080p - 1080p - - - - 720p - 720p - - - - 480p - 480p - - - - Native - Nativ - Format @@ -4840,8 +4756,39 @@ wenn vorhanden MP4 - PNG - PNG + + High &Quality + Hohe &Qualität + + + + &YouTube + &YouTube + + + + &Lossless + Ver&lustfrei + + + + &1080p + &1080p + + + + &720p + &720p + + + + &480p + &480p + + + + &Native + &Nativ @@ -4865,76 +4812,81 @@ wenn vorhanden + VP9 + VP9 + + + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Unkomprimiert - + Bitrate (kbps) Bitrate (kbps) - + VBR VBR - + ABR ABR - + Dimensions Abmessungen - + : : - + × × - + Lock aspect ratio Seitenverhältnis sperren - + Show advanced Erweiterte Optionen anzeigen diff --git a/src/platform/qt/ts/medusa-emu-es.ts b/src/platform/qt/ts/medusa-emu-es.ts index 662b54668..cae9fd4f7 100644 --- a/src/platform/qt/ts/medusa-emu-es.ts +++ b/src/platform/qt/ts/medusa-emu-es.ts @@ -168,7 +168,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Enter command (try `help` for more info) - Ingresa un comando (intenta `help` para más información) + Ingresa un comando (prueba con `help` para más información) @@ -1249,22 +1249,22 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Error al abrir el archivo de guardado: %1 - + Failed to open game file: %1 Error al abrir el archivo del juego: %1 - + Failed to open snapshot file for reading: %1 Error al leer del archivo de captura: %1 - + Failed to open snapshot file for writing: %1 Error al escribir al archivo de captura: %1 @@ -2764,27 +2764,27 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::LoadSaveState - + Load State Cargar estado - + Save State Guardar estado - + Empty Vacío - + Corrupted Dañado - + Slot %1 Espacio %1 @@ -2832,7 +2832,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Map Addr. - Dirección de mapa + Dir de mapa @@ -2899,7 +2899,6 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - All Todo @@ -2909,32 +2908,32 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Cargar TBL - + Save selected memory Guardar memoria seleccionada - + Failed to open output file: %1 Error al abrir el archivo de salida: %1 - + Load memory Cargar memoria - + Failed to open input file: %1 Error al abrir el archivo de entrada: %1 - + TBL TBL - + ISO-8859-1 ISO-8859-1 @@ -3108,59 +3107,59 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::SettingsView - - + + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (forzar versión 1.x) - + None (Still Image) Nada (imagen estática) - + Keyboard Teclado - + Controllers Controladores - + Shortcuts Atajos de teclado - - + + Shaders Shaders - + Select BIOS Seleccionar BIOS @@ -3219,17 +3218,17 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::VideoView - + Failed to open output video file: %1 Error al abrir el archivo de video de salida: %1 - + Native (%0x%1) Native (%0x%1) - + Select output file Seleccionar archivo de salida @@ -3280,54 +3279,65 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - - + + Select save Seleccionar guardado - + + mGBA savestate files (%1) + Archivos de estado de guardado de mGBA (%1) + + + + + Select savestate + Elegir estado de guardado + + + Select patch Seleccionar parche - + Patches (*.ips *.ups *.bps) Parches (*.ips *.ups *.bps) - + Select image Seleccionar imagen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Archivo de imagen (*.png *.gif *.jpg *.jpeg);;Todos los archivos (*) - - + + GameShark saves (*.sps *.xps) Guardados de GameShark (*.sps *.xps) - + Select video log Seleccionar video-registro - + Video logs (*.mvl) Video-registros (*.mvl) - + Crash Error fatal - + The game has crashed with the following error: %1 @@ -3336,648 +3346,655 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. %1 - + Couldn't Load No se pudo cargar - + Could not load game. Are you sure it's in the correct format? No se pudo cargar el juego. ¿Estás seguro de que está en el formato correcto? - + Unimplemented BIOS call Llamada a BIOS no implementada - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Este juego utiliza una llamada al BIOS que no se ha implementado. Utiliza el BIOS oficial para obtener la mejor experiencia. - + Really make portable? ¿Hacer "portable"? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Esto hará que el emulador cargue su configuración desde el mismo directorio que el ejecutable. ¿Quieres continuar? - + Restart needed Reinicio necesario - + Some changes will not take effect until the emulator is restarted. Algunos cambios no surtirán efecto hasta que se reinicie el emulador. - + - Player %1 of %2 - Jugador %1 de %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 fps) - %4 - + &File &Archivo - + Load &ROM... Cargar &ROM... - + Load ROM in archive... Cargar ROM desde contenedor... - + Add folder to library... Agregar carpeta a la biblioteca... - + Load alternate save... Cargar guardado alternativo... - + Load temporary save... Cargar guardado temporal... - + Load &patch... Cargar &parche... - + Boot BIOS Arrancar BIOS - + Replace ROM... Reemplazar ROM... - + ROM &info... &Información de la ROM... - + Recent Recientes - + Make portable Hacer "portable" - + &Load state Ca&rgar estado - + F10 F10 - + + Load state file... + Cargar archivo de estado... + + + &Save state Guardar e&stado - + Shift+F10 Shift+F10 - + + Save state file... + Guardar archivo de estado... + + + Quick load Cargado rápido - + Quick save Guardado rápido - + Load recent Cargar reciente - + Save recent Guardar reciente - + Undo load state Deshacer cargar estado - + F11 F11 - + Undo save state Deshacer guardar estado - + Shift+F11 Shift+F11 - - + + State &%1 Estado &%1 - + F%1 F%1 - + Shift+F%1 Shift+F%1 - + Load camera image... Cargar imagen para la cámara... - + Import GameShark Save Importar guardado de GameShark - + Export GameShark Save Exportar guardado de GameShark - + New multiplayer window Nueva ventana multijugador - + About Acerca de - + E&xit Salir (&X) - + &Emulation &Emulación - + &Reset &Reinicializar - + Ctrl+R Ctrl+R - + Sh&utdown Apagar (&U) - + Yank game pak Tirar del cartucho - + &Pause &Pausar - + Ctrl+P Ctrl+P - + &Next frame Cuadro siguie&nte - + Ctrl+N Ctrl+N - + Fast forward (held) Avance rápido (mantener) - + &Fast forward &Avance rápido - + Shift+Tab Shift+Tab - + Fast forward speed Velocidad de avance rápido - + Unbounded Sin límite - + %0x %0x - + Rewind (held) Rebobinar (mantener) - + Re&wind Re&bobinar - + ~ ~ - + Step backwards Paso hacia atrás - + Ctrl+B Ctrl+B - + Sync to &video Sincronizar a &video - + Sync to &audio Sincronizar a au&dio - + Solar sensor Sensor solar - + Increase solar level Subir nivel - + Decrease solar level Bajar nivel - + Brightest solar level Más claro - + Darkest solar level Más oscuro - + Brightness %1 Brillo %1 - + Audio/&Video Audio/&video - + Frame size Tamaño del cuadro - + %1x %1x - + Toggle fullscreen Pantalla completa - + Lock aspect ratio Bloquear proporción de aspecto - + Force integer scaling Forzar escala a enteros - + Bilinear filtering Filtro bilineal - + Frame&skip &Salto de cuadros - + Mute Silenciar - + FPS target Objetivo de FPS - + + Native (59.7275) + Nativo (59,7275) + + 15 - 15 + 15 - 30 - 30 + 30 - 45 - 45 + 45 - Native (59.7) - Nativo (59.7) + Nativo (59.7) - 60 - 60 + 60 - 90 - 90 + 90 - 120 - Bilineal120 + Bilineal120 - 240 - 240 + 240 - + Take &screenshot Tomar pan&tallazo - + F12 F12 - + Record output... Grabar salida... - + Record GIF... Grabar GIF... - + Record video log... Grabar video-registro... - + Stop video log Detener video-registro - + Game Boy Printer... Game Boy Printer... - + Video layers Capas de video - + Audio channels Canales de audio - + Adjust layer placement... Ajustar ubicación de capas... - + &Tools Herramien&tas - + View &logs... Ver re&gistros... - + Game &overrides... Ajustes específic&os por juego... - + Game &Pak sensors... Sensores del Game &Pak... - + &Cheats... Tru&cos... - + Settings... Ajustes... - + Open debugger console... Abrir consola de depuración... - + Start &GDB server... Iniciar servidor &GDB... - + View &palette... Ver &paleta... - + View &sprites... Ver &sprites... - + View &tiles... Ver &tiles... - + View &map... Ver &mapa... - + View memory... Ver memoria... - + Search memory... Buscar memoria... - + View &I/O registers... Ver registros &I/O... - + Exit fullscreen Salir de pantalla completa - + GameShark Button (held) Botón GameShark (mantener) - + Autofire Disparo automático - + Autofire A Disparo automático A - + Autofire B Disparo automático B - + Autofire L Disparo automático L - + Autofire R Disparo automático R - + Autofire Start Disparo automático Start - + Autofire Select Disparo automático Select - + Autofire Up Disparo automático Arriba - + Autofire Right Disparo automático Derecha - + Autofire Down Disparo automático Abajo - + Autofire Left Disparo automático Izquierda @@ -4259,382 +4276,387 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + Mute Silenciar - + + Fast forward volume: + Vol. durante av. rápido: + + + Display driver: Sistema de video: - + Frameskip: Salto de cuadros: - + Skip every Saltar cada - - + + frames cuadros - + FPS target: Objetivo de FPS: - + frames per second cuadros por segundo - + Sync: Sincronizar con: - + Video Video - + Audio Audio - + Lock aspect ratio Bloquear proporción de aspecto - + Bilinear filtering Filtro bilineal - + Force integer scaling - Forzar escala a enteros + Forzar escalado de enteros - + Language Idioma - + English English - + Library: Biblioteca: - + List view Lista - + Tree view Árbol - + Show when no game open Mostrar cuando no haya un juego abierto - + Clear cache Limpiar caché - + Allow opposing input directions Permitir direcciones opuestas al mismo tiempo - + Suspend screensaver Suspender protector de pantalla - + Pause when inactive Pausar al no estar activo - + Show FPS in title bar Mostrar FPS en la barra de título - + Automatically save cheats Guardar trucos automáticamente - + Automatically load cheats Cargar trucos automáticamente - + Automatically save state Guardar estado automáticamente - + Automatically load state Cargar estado automáticamente - + Fast forward speed: Velocidad de avance rápido: - + × × - + Unbounded Sin límite - + Enable rewind Habilitar el rebobinar - + Rewind history: Historial de rebobinado: - + Idle loops: Bucles inactivos: - + Run all Ejecutarlos todos - + Remove known Eliminar los conocidos - + Detect and remove Detectar y eliminar - + Savestate extra data: Guardar datos extra con el estado: - - + + Screenshot Pantallazo - - + + Save data Datos de guardado - - + + Cheat codes Trucos - + Load extra data: Cargar datos extra con el estado: - Rewind affects save data - El rebobinar afecta los datos de guardado + El rebobinar afecta los datos de guardado - + Preload entire ROM into memory Cargar ROM completa a la memoria - + Autofire interval: Intervalo de disparo automático: - + GB BIOS file: Archivo BIOS GB: - - - - - - - - - + + + + + + + + + Browse Examinar - + Use BIOS file if found Usar archivo BIOS si fue encontrado - + Skip BIOS intro Saltar animación de entrada del BIOS - + GBA BIOS file: Archivo BIOS GBA: - + GBC BIOS file: Archivo BIOS GBC: - + SGB BIOS file: SGB BIOS file: - + Save games Datos de guardado - - - - - + + + + + Same directory as the ROM Al mismo directorio que la ROM - + Save states Estados de guardado - + Screenshots Pantallazos - + Patches Parches - + Cheats Trucos - + Game Boy model Modelo de Game Boy - - - + + + Autodetect Detección automática - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model Modelo de Super Game Boy - + Game Boy Color model Modelo de Game Boy Color - + Default BG colors: Colores de fondo por defecto: - + Super Game Boy borders Bordes de Super Game Boy - + Camera driver: Controlador de cámara: - + Default sprite colors 1: Colores de sprite 1 por defecto: - + Default sprite colors 2: Colores de sprite 2 por defecto: @@ -4751,14 +4773,12 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Ajustes predefinidos - High Quality - Alta calidad + Alta calidad - YouTube - YouTube + YouTube @@ -4767,29 +4787,24 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. WebM - Lossless - Sin pérdidas + Sin pérdidas - 1080p - 1080p + 1080p - 720p - 720p + 720p - 480p - 480p + 480p - Native - Nativa + Nativa @@ -4815,6 +4830,41 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. PNG PNG + + + High &Quality + Alta &calidad + + + + &YouTube + &YouTube + + + + &Lossless + Sin pér&didas + + + + &1080p + &1080p + + + + &720p + &720p + + + + &480p + &480p + + + + &Native + &NAtivo + h.264 @@ -4837,76 +4887,81 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + VP9 + VP9 + + + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Sin comprimir - + Bitrate (kbps) Tasa de bits (kbps) - + VBR VBR - + ABR ABR - + Dimensions Dimensiones - + : : - + × × - + Lock aspect ratio Bloquear proporción de aspecto - + Show advanced Mostrar ajustes avanzados diff --git a/src/platform/switch/CMakeLists.txt b/src/platform/switch/CMakeLists.txt index f33d6a937..6666b8a01 100644 --- a/src/platform/switch/CMakeLists.txt +++ b/src/platform/switch/CMakeLists.txt @@ -50,4 +50,5 @@ add_custom_target(${BINARY_NAME}.nro ALL ${ELF2NRO} ${BINARY_NAME}.elf ${BINARY_NAME}.nro --romfs=romfs.bin --nacp=control.nacp --icon="${CMAKE_CURRENT_SOURCE_DIR}/icon.jpg" DEPENDS ${BINARY_NAME}.elf control.nacp ${CMAKE_CURRENT_SOURCE_DIR}/icon.jpg romfs.bin) +install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.nro DESTINATION . COMPONENT ${BINARY_NAME}-switch) diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 55be2f910..6c1018712 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -75,6 +75,13 @@ static GLuint tex; static color_t* frameBuffer; static struct mAVStream stream; +static struct mSwitchRumble { + struct mRumble d; + int up; + int down; + HidVibrationValue value; +} rumble; +static struct mRotationSource rotation = {0}; static int audioBufferActive; static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000))); static AudioOutBuffer audoutBuffer[N_BUFFERS]; @@ -82,6 +89,8 @@ static int enqueuedBuffers; static bool frameLimiter = true; static unsigned framecount = 0; static unsigned framecap = 10; +static u32 vibrationDeviceHandles[4]; +static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f }; static bool initEgl() { s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); @@ -226,6 +235,8 @@ static void _setup(struct mGUIRunner* runner) { _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R); runner->core->setVideoBuffer(runner->core, frameBuffer, 256); + runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d); + runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setAVStream(runner->core, &stream); } @@ -237,6 +248,18 @@ static void _gameLoaded(struct mGUIRunner* runner) { blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio); mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap); + + rumble.up = 0; + rumble.down = 0; +} + +static void _gameUnloaded(struct mGUIRunner* runner) { + HidVibrationValue values[4]; + memcpy(&values[0], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[1], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[2], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[3], &vibrationStop, sizeof(rumble.value)); + hidSendVibrationValues(vibrationDeviceHandles, values, 4); } static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) { @@ -299,6 +322,24 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); _drawTex(runner, width, height, faded); + + HidVibrationValue values[4]; + if (rumble.up) { + rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down); + rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down); + memcpy(&values[0], &rumble.value, sizeof(rumble.value)); + memcpy(&values[1], &rumble.value, sizeof(rumble.value)); + memcpy(&values[2], &rumble.value, sizeof(rumble.value)); + memcpy(&values[3], &rumble.value, sizeof(rumble.value)); + } else { + memcpy(&values[0], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[1], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[2], &vibrationStop, sizeof(rumble.value)); + memcpy(&values[3], &vibrationStop, sizeof(rumble.value)); + } + hidSendVibrationValues(vibrationDeviceHandles, values, 4); + rumble.up = 0; + rumble.down = 0; } static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) { @@ -355,6 +396,36 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig ++enqueuedBuffers; } +void _setRumble(struct mRumble* rumble, int enable) { + struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble; + if (enable) { + ++sr->up; + } else { + ++sr->down; + } +} + +int32_t _readTiltX(struct mRotationSource* source) { + UNUSED(source); + SixAxisSensorValues sixaxis; + hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1); + return sixaxis.accelerometer.x * 3e8f; +} + +int32_t _readTiltY(struct mRotationSource* source) { + UNUSED(source); + SixAxisSensorValues sixaxis; + hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1); + return sixaxis.accelerometer.y * -3e8f; +} + +int32_t _readGyroZ(struct mRotationSource* source) { + UNUSED(source); + SixAxisSensorValues sixaxis; + hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1); + return sixaxis.gyroscope.z * 1.1e9f; +} + static int _batteryState(void) { u32 charge; int state = 0; @@ -455,6 +526,24 @@ int main(int argc, char* argv[]) { glEnableVertexAttribArray(offsetLocation); glBindVertexArray(0); + rumble.d.setRumble = _setRumble; + rumble.value.freq_low = 120.0; + rumble.value.freq_high = 180.0; + hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR); + hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR); + + u32 handles[4]; + hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR); + hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER); + hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD); + hidStartSixAxisSensor(handles[0]); + hidStartSixAxisSensor(handles[1]); + hidStartSixAxisSensor(handles[2]); + hidStartSixAxisSensor(handles[3]); + rotation.readTiltX = _readTiltX; + rotation.readTiltY = _readTiltY; + rotation.readGyroZ = _readGyroZ; + stream.videoDimensionsChanged = NULL; stream.postVideoFrame = NULL; stream.postAudioFrame = NULL; @@ -528,6 +617,23 @@ int main(int argc, char* argv[]) { "10", "11", "12", "13", "14", "15", "20", "30" }, + .stateMappings = (const struct GUIVariant[]) { + GUI_V_U(3), + GUI_V_U(4), + GUI_V_U(5), + GUI_V_U(6), + GUI_V_U(7), + GUI_V_U(8), + GUI_V_U(9), + GUI_V_U(10), + GUI_V_U(11), + GUI_V_U(12), + GUI_V_U(13), + GUI_V_U(14), + GUI_V_U(15), + GUI_V_U(20), + GUI_V_U(30), + }, .nStates = 15 }, }, @@ -535,11 +641,11 @@ int main(int argc, char* argv[]) { .setup = _setup, .teardown = NULL, .gameLoaded = _gameLoaded, - .gameUnloaded = NULL, + .gameUnloaded = _gameUnloaded, .prepareForFrame = _prepareForFrame, .drawFrame = _drawFrame, .drawScreenshot = _drawScreenshot, - .paused = NULL, + .paused = _gameUnloaded, .unpaused = _gameLoaded, .incrementScreenMode = NULL, .setFrameLimiter = _setFrameLimiter, @@ -577,6 +683,11 @@ int main(int argc, char* argv[]) { glDeleteProgram(program); glDeleteVertexArrays(1, &vao); + hidStopSixAxisSensor(handles[0]); + hidStopSixAxisSensor(handles[1]); + hidStopSixAxisSensor(handles[2]); + hidStopSixAxisSensor(handles[3]); + psmExit(); audoutExit(); deinitEgl(); diff --git a/src/platform/wii/CMakeLists.txt b/src/platform/wii/CMakeLists.txt index 165dba60b..4caf73d10 100644 --- a/src/platform/wii/CMakeLists.txt +++ b/src/platform/wii/CMakeLists.txt @@ -40,5 +40,7 @@ add_custom_target(run ${WIILOAD} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol DEPENDS ${BINARY_NAME}.dol) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/meta.xml.in ${CMAKE_CURRENT_BINARY_DIR}/meta.xml) + +install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/meta.xml DESTINATION . COMPONENT ${BINARY_NAME}-wii) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol DESTINATION . RENAME boot.dol COMPONENT ${BINARY_NAME}-wii) diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 1885f5c37..8c37c1f4c 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -195,18 +195,18 @@ static void reconfigureScreen(struct mGUIRunner* runner) { break; } - free(framebuffer[0]); - free(framebuffer[1]); - VIDEO_SetBlack(true); VIDEO_Configure(vmode); + free(framebuffer[0]); + free(framebuffer[1]); + framebuffer[0] = SYS_AllocateFramebuffer(vmode); framebuffer[1] = SYS_AllocateFramebuffer(vmode); - VIDEO_ClearFrameBuffer(vmode, framebuffer[0], COLOR_BLACK); - VIDEO_ClearFrameBuffer(vmode, framebuffer[1], COLOR_BLACK); + VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[0]), COLOR_BLACK); + VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[1]), COLOR_BLACK); - VIDEO_SetNextFramebuffer(framebuffer[whichFb]); + VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb])); VIDEO_Flush(); VIDEO_WaitVSync(); if (vmode->viTVMode & VI_NON_INTERLACE) { @@ -469,9 +469,49 @@ int main(int argc, char* argv[]) { "Bilinear (pixelated)", }, .nStates = 3 - } + }, + { + .title = "Horizontal stretch", + .data = "stretchWidth", + .submenu = 0, + .state = 7, + .validStates = (const char*[]) { + "1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x" + }, + .stateMappings = (const struct GUIVariant[]) { + GUI_V_F(0.5f), + GUI_V_F(0.6f), + GUI_V_F(1.f / 3.f), + GUI_V_F(0.7f), + GUI_V_F(0.75f), + GUI_V_F(0.8f), + GUI_V_F(0.9f), + GUI_V_F(1.0f), + }, + .nStates = 8 + }, + { + .title = "Vertical stretch", + .data = "stretchHeight", + .submenu = 0, + .state = 6, + .validStates = (const char*[]) { + "1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x" + }, + .stateMappings = (const struct GUIVariant[]) { + GUI_V_F(0.5f), + GUI_V_F(0.6f), + GUI_V_F(1.f / 3.f), + GUI_V_F(0.7f), + GUI_V_F(0.75f), + GUI_V_F(0.8f), + GUI_V_F(0.9f), + GUI_V_F(1.0f), + }, + .nStates = 8 + }, }, - .nConfigExtra = 3, + .nConfigExtra = 5, .setup = _setup, .teardown = 0, .gameLoaded = _gameLoaded, @@ -517,6 +557,15 @@ int main(int argc, char* argv[]) { _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT); + + float stretch = 0; + if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) { + wStretch = fminf(1.0f, fmaxf(0.5f, stretch)); + } + if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) { + hStretch = fminf(1.0f, fmaxf(0.5f, stretch)); + } + if (argc > 1) { size_t i; for (i = 0; runner.keySources[i].id; ++i) { @@ -595,7 +644,7 @@ static void _drawStart(void) { static void _drawEnd(void) { GX_CopyDisp(framebuffer[whichFb], GX_TRUE); GX_DrawDone(); - VIDEO_SetNextFramebuffer(framebuffer[whichFb]); + VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb])); VIDEO_Flush(); whichFb = !whichFb; @@ -862,8 +911,8 @@ void _drawFrame(struct mGUIRunner* runner, bool faded) { GX_LoadTexObj(&rescaleTex, GX_TEXMAP0); } - int hfactor = vmode->fbWidth / (corew * wAdjust); - int vfactor = vmode->efbHeight / (coreh * hAdjust); + int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust); + int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust); if (hfactor > vfactor) { scaleFactor = vfactor; } else { diff --git a/src/platform/windows/setup/setup.ico b/src/platform/windows/setup/setup.ico new file mode 100644 index 000000000..7b382e808 Binary files /dev/null and b/src/platform/windows/setup/setup.ico differ diff --git a/src/platform/windows/setup/setup.iss.in b/src/platform/windows/setup/setup.iss.in new file mode 100644 index 000000000..c3771eadb --- /dev/null +++ b/src/platform/windows/setup/setup.iss.in @@ -0,0 +1,140 @@ +#define AppName "${PROJECT_NAME}" +#define AppName2 "${BINARY_NAME}" +#define VerMajor ${LIB_VERSION_MAJOR} +#define VerMinor ${LIB_VERSION_MINOR} +#define VerRev ${LIB_VERSION_PATCH} +#define VerBuild ${GIT_REV} +#define Release ${IS_RELEASE} +#define WinBits "${WIN_BITS}" +#define VersionString "${VERSION_STRING}" +#define CleanVersionString "${CLEAN_VERSION_STRING}" +#define SetupDir "${SETUP_DIR}" +#define BinDir "${BIN_DIR}" +#define ResDir "${RES_DIR}" + +#define FullVersion ParseVersion('{#AppName}.exe', VerMajor, VerMinor, VerRev, VerBuild) +#define AppVer Str(VerMajor) + "." + Str(VerMinor) + "." + Str(VerRev) + +[Setup] +SourceDir={#BinDir} +SetupIconFile={#SetupDir}\setup.ico +WizardImageFile={#SetupDir}\wizard-image.bmp + +AppName={#AppName} +AppVersion={#AppVer} +AppPublisher=Jeffrey Pfau +AppPublisherURL=https://mgba.io +AppSupportURL=https://mgba.io +AppUpdatesURL=https://mgba.io +AppReadmeFile={#BinDir}\README.html +OutputDir=.\ +DefaultDirName={pf}\{#AppName} +DefaultGroupName={#AppName} +AllowNoIcons=yes +DirExistsWarning=no +ChangesAssociations=True +AppendDefaultDirName=False +UninstallDisplayIcon={app}\{#AppName}.exe +MinVersion=0,6.0 +AlwaysShowDirOnReadyPage=True +UsePreviousSetupType=True +UsePreviousTasks=True +AlwaysShowGroupOnReadyPage=True +LicenseFile={#BinDir}\LICENSE.txt +#if Release + #define IsRelease = 'yes' + AppVerName={#AppName} {#AppVer} +#else + #define IsRelease = 'no' + AppVerName={#AppName} {#VersionString} (Development build) +#endif +#if '{#WinBits}' == '64' + ArchitecturesInstallIn64BitMode=x64 + ArchitecturesAllows=x64 +#endif +OutputBaseFilename={#AppName}-setup-{#CleanVersionString}-win{#WinBits} +UsePreviousLanguage=False +DisableWelcomePage=False +VersionInfoDescription={#AppName} is an open-source Game Boy Advance emulator +VersionInfoCopyright= 20132018 Jeffrey Pfau +VersionInfoProductName={#AppName} +VersionInfoVersion={#AppVer} +Compression=lzma2/ultra64 +SolidCompression=True +VersionInfoTextVersion={#AppVer} +VersionInfoProductVersion={#AppVer} +VersionInfoProductTextVersion={#AppVer} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" +Name: "gbfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy}"; GroupDescription: "{cm:FileAssoc}" +Name: "gbcfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy Color}"; GroupDescription: "{cm:FileAssoc}" +Name: "sgbfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Super Game Boy}"; GroupDescription: "{cm:FileAssoc}" +Name: "gbafileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy Advance}"; GroupDescription: "{cm:FileAssoc}" + +[Files] +Source: "{#BinDir}\qt\{#AppName}.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#BinDir}\sdl\{#AppName2}-sdl.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#BinDir}\CHANGES.txt"; DestDir: "{app}\"; Flags: ignoreversion isreadme +Source: "{#BinDir}\LICENSE.txt"; DestDir: "{app}\"; Flags: ignoreversion +Source: "{#ResDir}\nointro.dat"; DestDir: "{app}\"; Flags: ignoreversion +Source: "{#BinDir}\README.html"; DestDir: "{app}\"; Flags: ignoreversion isreadme; Languages: english italian spanish +Source: "{#BinDir}\README_DE.html"; DestDir: "{app}\"; DestName: "LIESMICH.html"; Flags: ignoreversion isreadme; Languages: german +Source: "{#ResDir}\shaders\*"; DestDir: "{app}\shaders\"; Flags: ignoreversion recursesubdirs +Source: "{#ResDir}\licenses\*"; DestDir: "{app}\licenses\"; Flags: ignoreversion recursesubdirs + +[Icons] +Name: "{commonstartmenu}\{#AppName}"; Filename: "{app}\{#AppName}.exe" +Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppName}.exe"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#AppName}.exe"; Description: "{cm:LaunchProgram,{#AppName}}"; Flags: nowait postinstall skipifsilent + +[Dirs] +Name: "{app}" + +[CustomMessages] +english.FileAssoc=Register file associations +french.FileAssoc=Register file associations +italian.FileAssoc=Register file associations +spanish.FileAssoc=Register file associations +german.FileAssoc=Dateierweiterungen registrieren + +[Registry] +Root: HKCR; Subkey: ".gb"; ValueType: string; ValueName: ""; ValueData: "Game Boy ROM"; Flags: uninsdeletevalue; Tasks: gbfileassoc +Root: HKCR; Subkey: ".gb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbfileassoc +Root: HKCR; Subkey: ".gb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbfileassoc +Root: HKCR; Subkey: ".gbc"; ValueType: string; ValueName: ""; ValueData: "Game Boy Color ROM"; Flags: uninsdeletevalue; Tasks: gbcfileassoc +Root: HKCR; Subkey: ".gbc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbcfileassoc +Root: HKCR; Subkey: ".gbc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbcfileassoc +Root: HKCR; Subkey: ".sgb"; ValueType: string; ValueName: ""; ValueData: "Super Game Boy ROM"; Flags: uninsdeletevalue; Tasks: sgbfileassoc +Root: HKCR; Subkey: ".sgb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: sgbfileassoc +Root: HKCR; Subkey: ".sgb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: sgbfileassoc +Root: HKCR; Subkey: ".gba"; ValueType: string; ValueName: ""; ValueData: "Game Boy Advance ROM"; Flags: uninsdeletevalue; Tasks: gbafileassoc +Root: HKCR; Subkey: ".gba\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbafileassoc +Root: HKCR; Subkey: ".gba\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbafileassoc + +[Code] +var + noReleaseWarning: String; + +procedure InitializeWizard(); + begin + if ExpandConstant('{#IsRelease}') = 'no' then + begin + if ExpandConstant('{language}') = 'english' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.'; + if ExpandConstant('{language}') = 'french' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.'; + if ExpandConstant('{language}') = 'italian' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.'; + if ExpandConstant('{language}') = 'spanish' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.'; + if ExpandConstant('{language}') = 'german' then noReleaseWarning := 'Sie mchten eine Entwicklerversion von {#AppName} installieren.' + #13#10#13#10 + 'Entwicklerversionen knnen bislang noch nicht endeckte Fehler beinhalten. Bitte melden Sie alle Fehler, die Sie finden knnen, auf der GitHub-Projektseite.'; + MsgBox(noReleaseWarning, mbInformation, MB_OK); + end; + end; +end. diff --git a/src/platform/windows/setup/wizard-image.bmp b/src/platform/windows/setup/wizard-image.bmp new file mode 100644 index 000000000..7c6d90404 Binary files /dev/null and b/src/platform/windows/setup/wizard-image.bmp differ