Merge branch 'master' into port/3ds
jump to
@@ -1,14 +1,134 @@
-0.2.0: (Future) +0.3.0: (Future) +Features: + - Ability to hide individual background layers, or OBJs + - Ability to mute individual audio channels + - Palette viewer and exporter + - Volume control + - More shortcuts are editable (e.g. quick save/load) + - Rewind now shows the frame after rewinding + - Import/Export of GameShark/Action Replay snapshots + - Add "Step backwards" item for single increment rewind + - Deadzone estimation for game controllers + - Analog inputs can be used for shortcuts + - Menu items for specific solar sensor brightness levels + - Remappable controls for tilt and gyroscope sensors + - Status messages for actions taken while a game is running (e.g. save/load state) + - Memory inspector + - Screensaver can now be suspended while a game is running + - Load/save the most recent savestate slot + - Support varible speed (PWM) rumble + - Ability to cap fast forward speed + - Finer control over FPS target + - Holdable shortcut for rewinding one frame at a time + - Ability to boot directly into the BIOS +Bugfixes: + - ARM7: Fix SWI and IRQ timings + - GBA Audio: Force audio FIFOs to 32-bit + - GBA Memory: Improve Thumb open bus behavior + - VFS: Fix resource leaks if some allocations fail + - Video: Fix an issue with very long filenames + - GBA Video: Blended sprites should never have other effects applied + - GBA: Fix crash if a 512kb flash save is loaded when a game has a 1Mb flash override + - Qt: Better cleanup when a game crashes + - Qt: Fix open ROM dialog filtering for archive formats + - ARM7: Fix Thumb MUL timing + - Qt: Cap the maximum number of multiplayer windows + - Qt: Fix maximum year in sensor override + - GBA: Cap audio FIFO read size during deserialization + - GBA: Check for corrupted savestates when loading + - GBA: Check for improperly sized savestates when loading + - GBA: Check for savestates made from differently sized ROMs + - GBA Video: Fix out-of-bounds tiles in mosaic + - GBA Memory: Fix potential DMA issue when loading a savestate + - GBA Audio: Fix audio pitch changing when adjusting buffer size + - SDL: Fix SDL build when OpenGL is missing + - ARM7: Fix timing of multiplies to use N cycles + - GBA: Fix calls to endian-independent loadstores + - GBA Video: Fix windows not affecting sprites + - VFS: Fix line-reading to return proper values +Misc: + - Qt: Handle saving input settings better + - Debugger: Free watchpoints in addition to breakpoints + - Qt: Move GL frame drawing back onto its own thread + - GBA: Add status log level + - GBA Thread: Add functionality for running callbacks on the GBA thread + - Qt: Fast forward (held) option moved from Other to Emulation menu + - All: Add --help flag for command line programs + - Qt: Show version info in window title + - All: Fix sanitize-deb script to set file permissions properly if run as (fake)root + - GBA SIO: Add a dummy driver for Normal mode + - GBA: GBARewind now returns how many states it has rewound + - All: Enable static linking for Windows + - All: Enable static linking for OS X + - Qt: Migrate multiplayer window handling into GBAApp + - Qt: Unified file opening and saving with last location + - Qt: Fix windows being resizable when they shouldn't have been + - Qt: Only hide cursor in full screen + - Perf: Ability to load savestates immediately on launch + - Qt: Replace pause-after-frame mutex with an atomic + - Util: Allow disabling the threading code entirely + - GBA: SIO logging layer + - Qt: Add application icon and XDG desktop files + - GBA Thread: Split GBASync into a separate file + - SDL: Properly check for initialization + - SDL: Clean up initialization functions + - All: Threads are now named + - Qt: Rename "Fullscreen" to "Toggle fullscreen" + - Qt: Don't save window size when entering fullscreen + - Qt: Make the default fullscreen binding for Windows be Alt-Enter + +0.2.1: (2015-05-13) +Bugfixes: + - All: Fix sanitize-deb script not cleaning up after itself + - All: Fix dependencies for libavcodec on Debian-derived platforms + - ARM7: Handle writeback for PC in addressing modes 2 and 3 + - ARM7: Make illegal instruction decoding consistent between ARM and Thumb + - ARM7: Fix ARM multiply instructions when PC is a destination register + - Debugger: Fix use-after-free in breakpoint clearing code + - Debugger: Fix boundary conditions in tab completion + - GBA: Fix timers not updating timing when writing to only the reload register + - GBA: Fix rewind boundary conditions + - GBA: Add initial I/O register settings for background matrix registers + - GBA: Fix hang when loading a savestate if sync to video is enabled + - GBA: Handle out-of-bounds I/O access + - GBA: Fix bounds-checking on EEPROM access + - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio + - GBA BIOS: Initialize a variable that may be uninitialized in very rare cases + - GBA Memory: Allow SRAM to be 64kB + - GBA Memory: Fix 32-bit loads from unaddress cartridge space + - GBA Memory: Fix jumping to invalid memory when switching from Thumb to ARM + - GBA Video: Fix second frame mode 5 + - Perf: Fix race condition if a game crashes immediately on start + - Qt: Fix Display object leak when closing a window + - Qt: Fix .deb dependencies + - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning + - Qt: Fix window not regaining focus after exiting savestate window + - Qt: Fix regression where video would not record if the game had already started + - Qt: Fix potential crash if a gamepad causes focus to change + - Qt: Fix controller axis querying + - Qt: Fix multiplayer windows opening as the wrong size + - Qt: Fix controllers sometimes not loading the right profile + - SDL: Fix boundary conditions for joystick adjustments + - SDL: Allocate properly sized input maps + - SDL: Fix potential build issues when Qt and SDL2 are in use + - Util: Fix resource leak in UTF-8 handling code + - Util: Fix a null-pointer issue when attempting to delete a key + - VFS: Fix resource leaks if some allocations fail + - Video: Fix an issue with very long filenames +Misc: + - GBA Memory: Soft-crash if jumping past the end of a ROM + - Qt: Show multiplayer numbers in window title + - Qt: Solar sensor can have shortcuts set + +0.2.0: (2015-04-03) Features: - Support for gamepad axes, e.g. analog sticks or triggers - Add scale presets for up to 6x - - Debugger: Add CLI "frame", frame advance command - Settings window - Bilinear resampling option - Add option to skip BIOS start screen - List of recently opened games - Support for games using the Solar Sensor - - Debugger: Add CLI functions for writing to memory - Better audio resampling via blip-buf - Game Pak overrides dialog for setting savetype and sensor values - Support for games using the tilt sensor@@ -23,56 +143,73 @@ - Configurable game overrides
- Support loading 7-Zip files - Drag and drop game loading - Cheat code support + - Runtime configurable audio driver + - Libretro core for use with RetroArch and other front-ends + - Controller profiles for setting different bindings for different controllers + - Ability to lock aspect ratio + - Local link cable support + - Ability to switch which game controller is in use per instance + - Ability to prevent opposing directional input + - Warning dialog if an unimplemented BIOS feature is called + - Debugger: Add CLI "frame", frame advance command + - Debugger: Add CLI functions for writing to memory - Debugger: Add CLI functions for examining memory regions - - Runtime configurable audio driver - Debugger: Add CLI function for writing a register - - Libretro core for use with RetroArch and other front-ends Bugfixes: - ARM7: Extend prefetch by one stage + - ARM7: Fix cycle counting for loads + - Debugger: Disassembly now lists PSR bitmasks (fixes #191) + - GBA: Fix savestate loading of DISPSTAT and WAITCNT registers + - GBA: Initialize gba.sync to null + - GBA: Fix timer initialization - GBA Audio: Support 16-bit writes to FIFO audio - GBA Audio: Audio buffer sizes are now correct sizes for both sample rates - GBA BIOS: Fix BIOS prefetch after returning from an IRQ - GBA BIOS: Fix BIOS prefetch after reset + - GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184) + - GBA BIOS: Fix BIOS decompression routines with invalid source addresses - GBA Memory: Fix alignment of open bus 8- and 16-bit loads + - GBA Memory: Fix I cycles that had been moved to ARM7 core + - GBA Memory: Fix cycle counting for 32-bit load/stores + - GBA RR: Fix fallthrough error when reading tags from a movie - GBA Thread: Fix possible hang when loading an archive + - GBA Thread: Fix possible deadlock in video sync - Perf: Fix crash when the GBA thread fails to start - - SDL: Properly clean up if a game doesn't launch - - Debugger: Disassembly now lists PSR bitmasks (fixes #191) - - GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184) - - GBA RR: Fix fallthrough error when reading tags from a movie - - GBA Thread: Fix possible deadlock in video sync - - GBA: Fix savestate loading of DISPSTAT and WAITCNT registers - Qt: Fix crash starting a GDB stub if a game isn't loaded - Qt: Fix crash when adjusting settings after closing a game - Qt: Fix crash when starting GDB stub after closing a game - Qt: Fix patch loading while a game is running + - Qt: Fix crash when loading a game after stopping GDB server + - Qt: Pause game while open file dialogs are open (fixes #6 on GitHub) + - Qt: Fix crash when attempting to pause if a game is not running + - SDL: Properly clean up if a game doesn't launch - Util: Fix sockets on Windows - - Qt: Fix crash when loading a game after stopping GDB server - - GBA BIOS: Fix BIOS decompression routines with invalid source addresses - - GBA: Initialize gba.sync to null Misc: - - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - - GBA Memory: Simplify memory API and use fixed bus width - - GBA Video: Start video at the last scanline instead of the first + - All: Enable link-time optimization - Debugger: Watchpoints now work on STM/LDM instructions - - GBA: Improve accuracy of event timing - Debugger: Clean up GDB stub network interfacing - Debugger: Simplify debugger state machine to play nicer with the GBA thread loop - Debugger: Merge Thumb BL instructions when disassembling - Debugger: Clean up debugger interface, removing obsolete state (fixes #67) - Debugger: Watchpoints now report address watched (fixes #68) + - Debugger: Add support for soft breakpoints + - Debugger: Make I/O register names be addresses instead of values + - Debugger: Rename read/write commands + - GBA: Improve accuracy of event timing - GBA: Add API for getting Configuration structs for overrides and input - GBA: Refactor gba-sensors and gba-gpio into gba-hardware - GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory - - Debugger: Add support for soft breakpoints - - Util: Use proper locale for reading and writing float values - - Debugger: Make I/O register names be addresses instead of values - - Debugger: Rename read/write commands + - GBA: Move A/V stream interface into core + - GBA: Savestates now take into account savedata state machines (fixes #109) + - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples + - GBA Memory: Simplify memory API and use fixed bus width + - GBA Thread: Make GBASyncWaitFrameStart time out + - GBA Video: Start video at the last scanline instead of the first - Qt: Optimize logo drawing - Qt: Move frame upload back onto main thread - - All: Enable link-time optimization - - GBA Thread: Make GBASyncWaitFrameStart time out - - GBA: Move A/V stream interface into core + - Qt: Remember window position + - Qt: Double-clicking on the window toggles full screen + - Util: Use proper locale for reading and writing float values 0.1.1: (2015-01-24) Bugfixes:
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 2.6) project(mGBA C) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu99") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99") set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger") set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger") set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")@@ -16,19 +16,22 @@ set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") +set(BUILD_GL ON CACHE STRING "Build with OpenGL") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) +file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c) file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) -file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) +file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) +set(VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-mem.c) source_group("ARM core" FILES ${ARM_SRC}) -source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC}) -source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) -source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) +source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) +source_group("GBA supervisor" FILES ${GBA_CHEATS_SRC} ${GBA_SV_SRC} ${GBA_RR_SRC}) +source_group("Utilities" FILES ${UTIL_SRC}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) include_directories(${CMAKE_SOURCE_DIR}/src)@@ -70,11 +73,52 @@ endfunction()
# Version information set(LIB_VERSION_MAJOR 0) -set(LIB_VERSION_MINOR 2) +set(LIB_VERSION_MINOR 3) set(LIB_VERSION_PATCH 0) -set(LIB_VERSION_ABI 0.2) +set(LIB_VERSION_ABI 0.3) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) +execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(GIT_REV STREQUAL "") + set(GIT_REV -1) +endif() +if(NOT GIT_TAG STREQUAL "") + set(VERSION_STRING ${GIT_TAG}) +elseif(GIT_BRANCH STREQUAL "") + set(VERSION_STRING ${LIB_VERSION_STRING}) +else() + if(GIT_BRANCH STREQUAL "master") + set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT}) + else() + set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT}) + endif() + + if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH) + set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING}) + endif() +endif() + +add_custom_target(version-info ALL touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in + COMMAND ${CMAKE_COMMAND} + -DGIT_COMMIT=${GIT_COMMIT} + -DGIT_COMMIT_SHORT=${GIT_COMMIT_SHORT} + -DGIT_BRANCH=${GIT_BRANCH} + -DGIT_REV=${GIT_REV} + -DBINARY_NAME=${BINARY_NAME} + -DPROJECT_NAME=${PROJECT_NAME} + -DVERSION_STRING=${VERSION_STRING} + -D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -P ${CMAKE_SOURCE_DIR}/version.cmake + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +include(${CMAKE_SOURCE_DIR}/version.cmake) +list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c) + # Advanced settings set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization") set(BUILD_PGO OFF CACHE BOOL "Build with profiling-guided optimization")@@ -94,15 +138,22 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PGO_POST_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_POST_FLAGS}") endif() -add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}") - # Feature dependencies set(FEATURES) if(CMAKE_SYSTEM_NAME MATCHES .*BSD) set(LIBEDIT_LIBRARIES -ledit) + if (CMAKE_SYSTEM_NAME STREQUAL OpenBSD) + list(APPEND LIBEDIT_LIBRARIES -ltermcap) + endif() else() find_feature(USE_CLI_DEBUGGER "libedit") endif() +if(BUILD_GL) + find_package(OpenGL QUIET) + if(NOT OPENGL_FOUND) + set(BUILD_GL OFF) + endif() +endif() find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") find_feature(USE_PNG "ZLIB;PNG") find_feature(USE_LIBZIP "libzip")@@ -110,15 +161,23 @@ find_feature(USE_MAGICK "MagickWand")
# Platform support if(WIN32) + set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}") add_definitions(-D_WIN32_WINNT=0x0600) list(APPEND OS_LIB ws2_32) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c) source_group("Windows-specific code" FILES ${OS_SRC}) elseif(UNIX) add_definitions(-DUSE_PTHREADS) - if(NOT APPLE) + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_definitions(-D_GNU_SOURCE) + endif() + if(NOT APPLE AND NOT HAIKU) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") endif() + + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) source_group("POSIX-specific code" FILES ${OS_SRC}) elseif(3DS)@@ -129,7 +188,12 @@ source_group("3DS-specific code" FILES ${OS_SRC})
endif() if(APPLE) + add_definitions(-D_DARWIN_C_SOURCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") +endif() + +if(NOT HAIKU) + list(APPEND OS_LIB m) endif() if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO)@@ -137,17 +201,22 @@ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") endif() -if(BUILD_BBB OR BUILD_RASPI) +if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA) if(NOT BUILD_EGL) add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5) endif() endif() -if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") +if(BUILD_PANDORA) + add_definitions(-DBUILD_PANDORA) +endif() + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*") enable_language(ASM) endif() include(CheckFunctionExists) +check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux")@@ -159,6 +228,10 @@ endif()
check_function_exists(newlocale HAVE_NEWLOCALE) check_function_exists(freelocale HAVE_FREELOCALE) check_function_exists(uselocale HAVE_USELOCALE) + +if(HAVE_STRDUP) + add_definitions(-DHAVE_STRDUP) +endif() if(HAVE_STRNDUP) add_definitions(-DHAVE_STRNDUP)@@ -214,12 +287,16 @@ string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES}) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}") set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra") + if(APPLE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework VideoDecodeAcceleration -framework CoreVideo") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework VideoDecodeAcceleration -framework CoreVideo") + endif() endif() if(USE_BLIP) - list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c") + list(APPEND THIRD_PARTY_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c") add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF) else() add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)@@ -252,12 +329,14 @@ include_directories(AFTER ${LIBZIP_INCLUDE_DIRS})
link_directories(${LIBZIP_LIBRARY_DIRS}) list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES}) list(APPEND FEATURES LIBZIP) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-zip.c) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2") endif() if (USE_LZMA) include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma) add_definitions(-D_7ZIP_PPMD_SUPPPORT) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c) set(LZMA_SRC ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c@@ -289,10 +368,12 @@ # Binaries
set(CORE_SRC ${ARM_SRC} ${GBA_SRC} + ${GBA_CHEATS_SRC} ${GBA_RR_SRC} ${GBA_SV_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} + ${SIO_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC}@@ -310,24 +391,40 @@ if(BUILD_SHARED)
add_library(${BINARY_NAME} SHARED ${SRC}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) - target_compile_definitions(${BINARY_NAME}-static PRIVATE ${FEATURE_DEFINES}) + set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME}) + add_dependencies(${BINARY_NAME}-static version-info) endif() else() add_library(${BINARY_NAME} STATIC ${SRC}) endif() -target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB}) -target_compile_definitions(${BINARY_NAME} PRIVATE ${FEATURE_DEFINES}) +add_dependencies(${BINARY_NAME} version-info) + +target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME}) -set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI}) +if(UNIX AND NOT APPLE) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-48.png DESTINATION share/icons/hicolor/48x48/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-64.png DESTINATION share/icons/hicolor/64x64/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-96.png DESTINATION share/icons/hicolor/96x96/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) + install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) +endif() +set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}") + +if(BUILD_GL) + add_definitions(-DBUILD_GL) +endif() if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) - set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "") - target_compile_definitions(${BINARY_NAME}_libretro PRIVATE COLOR_16_BIT;COLOR_5_6_5 PUBLIC "" INTERFACE "") - target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB}) + set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5") + target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) endif() if(BUILD_SDL)@@ -358,6 +455,7 @@ add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx)
endif() # Packaging +set(CPACK_PACKAGE_VERSION ${VERSION_STRING}) set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${LIB_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${LIB_VERSION_PATCH})
@@ -0,0 +1,196 @@
+Contribution Guidelines +======================= + +In order to contribute to mGBA, there are a few things to be mindful of so as to ease the process. + +Filing issues +------------- +New issues should be filed on the [mGBA GitHub Issues tracker](http://mgba.io/i/). When filing issues, please include the following information: + +* The build you are using. For recent builds, this is visible in the title bar. For example, `0.3-2134-4ec19aa`. On older builds, such as 0.2.1, this is not present, so please specify the version you downloaded or built. If present, this contains the version, branch name (if not `master`), a revision number and a truncated revision hash. For this example, it means that it's version 0.3, on `master`, commit number 2134 and revision hash `4ec19aa`. Additionally, `-dirty` will be appended if there are local changes that haven't been commited. +* The operating system you're using, for example Windows 7 32-bit or Ubuntu 15.04 64-bit. +* Your CPU and graphics card (usually not necessary). For example, Core i5-3570K and AMD Radeon R9 280X. + +Please also describe the issue in as much detail as possible, including the name of the games you have reproduced the issue on, and how you managed to enter the buggy state. If applicable, savestates can be renamed to be .png files and attached to the issue directly. + +Filing pull requests +-------------------- +When filing a pull request, please make sure you adhere to the coding style as outlined below, and are aware of the requirements for licensing. Furthermore, please make sure all commits in the pull request have coherent commit messages as well as the name of the component being modified in the commit message. + +Some components are as follows: + +* ARM7: The ARM core +* GBA: GBA code + * GBA Memory: Memory-specific + * GBA Video: Video, rendering + * GBA Audio: Audio processing + * GBA SIO: Serial I/O, multiplayer, link + * GBA Hardware: Extra devices, e.g. gyro, light sensor + * GBA RR: Rerecording features + * GBA Thread: Thread-layer abstractions + * GBA BIOS: High-level BIOS +* Qt: Qt port-related code +* SDL: SDL port-related code (including as used in other ports) +* Video: Video recording code +* Util: Common utility code +* Tools: Miscellaneous tools +* Debugger: Included debugging functionality +* All: Changes that don't touch specific components but affect the project overall + + +Coding Style +------------ +mGBA aims to have a consistent, clean codebase, so when contributing code to mGBA, please adhere to the following rules. If a pull request has style errors, you will be asked to fix them before the PR will be accepted. + +### Naming + +Variable names, including parameters, should all be in camelCase. File-scoped static variables must start with an underscore. + +C struct names should start with a capital letter, and functions relating to these structs should start with the name of the class (including the capital letter) and be in camelCase after. C struct should not be `typedef`ed. + +Functions not associated with structs should be in camelCase throughout. Static functions not associated with structs must start with an underscore. + +Enum values and `#define`s should be all caps with underscores. + +Good: + + static int _localVariable; + + struct LocalStruct { + void (*methodName)(struct LocalStruct struct, param); + + int memberName; + }; + + enum { + ENUM_ITEM_1, + ENUM_ITEM_2 + }; + + void LocalStructCreate(struct LocalStruct* struct); + + void functionName(int argument); + + static void _LocalStructUse(struct LocalStruct* struct); + static void _function2(int argument2); + +C++ classes should be confined to namespaces. For the Qt port, this namespace is called `QGBA`. + +Class names should be handled similarly to C structs. Fields should be prefixed according to their scoping: + +* `m_` for non-static member. +* `s_` for static member. + +### Braces + +Braces do not go on their own lines, apart from the terminating brace. There should be a single space between the condition clause and the brace. Furthermore, braces must be used even for single-line blocks. + +Good: + + if (condition) { + block; + } else if (condition2) { + block2; + } else { + block3; + } + +Bad (separate line): + + if (condition) + { + block; + } + else if (condition2) + { + block2; + } + else + { + block3; + } + +Bad (missing braces): + + if (condition) + statement; + else if (condition2) + statement2; + else + statement3; + +Bad (missing space): + + if (condition){ + block; + } + +### Spacing + +Indentation should be done using tabs and should match the level of braces. Alignment within a line should be done sparingly, but only done with spaces. + +### Header guards + +For C headers guards, the define should be the filename (including H), all-caps, with underscores instead of punctuation. + +Good: + + #ifndef FILE_NAME_H + #define FILE_NAME_H + + // Header + + #endif + +There should be no comment on the `#endif`. + +For Qt (C++ header guards), the define should start with `QGBA_` and not include `_H`, but is otherwise the same. This is mostly for legacy reasons., and may change in the future. + +Good: + + #ifndef QGBA_FILE_NAME + #define QGBA_FILE_NAME + + // Header + + #endif + +### Other + +Block statements such as `if`, `while` and `for` should have a space between the type of block and the parenthesis. + +Good: + + while (condition) { + block; + } + +Bad: + + while(condition) { + block; + } + +In C code, use `0` instead of `NULL`. This is mostly for legacy reasons and may change in the future. C code should also use `bool` types and values `true` and `false` instead of `1` and `0` where applicable. In C++ code, use `nullptr` instead of `NULL` or `0`. + +If a statement has no body, putting braces is not required, and a semicolon can be used. This is not required, but is suggested. + +Good: + + while (f()); + +Bad: + + while (f()) {} + + +For infinite loops that `break` statements internally, `while (true)` is preferred over `for (;;)`. + +Licensing +--------- + +mGBA is licensed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). This entails a few things when it comes to adding code to mGBA. + +* New code to mGBA will be licensed under the MPL 2.0 license. +* GPL-licensed code cannot be added to mGBA upstream, but can be linked with mGBA when compiled. +* MIT, BSD, CC0, etc., code can be added to mGBA upstream, but preferably in the `third-party` section if applicable.
@@ -3,7 +3,7 @@ ====
mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack. -Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/). +Up-to-date news and downloads can be found at [mgba.io](http://mgba.io/). ![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)@@ -13,11 +13,12 @@
- Near full Game Boy Advance hardware support[<sup>[1]</sup>](#missing). - Fast emulation. Known to run at full speed even on low end hardware, such as netbooks. - Qt and SDL ports for a heavy-weight and a light-weight frontend. +- Local (same computer) link cable support. - Save type detection, even for flash memory size[<sup>[2]</sup>](#flashdetect). - Real-time clock support, even without configuration. - A built-in BIOS implementation, and ability to load external BIOS files. - Turbo/fast-forward support by holding Tab. -- Frameskip, configurable up to 9. +- Frameskip, configurable up to 10. - Screenshot support. - Cheat code support. - 9 savestate slots. Savestates are also viewable as screenshots.@@ -30,12 +31,13 @@ - Configurable emulation rewinding.
### Planned features -- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)). -- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)). +- Networked multiplayer link cable support ([Bug #1](http://mgba.io/b/1)). +- Dolphin/JOY bus link cable support ([Bug #73](http://mgba.io/b/73)). - Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker)) -- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)). -- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)). -- libretro core for RetroArch and OpenEmu ([Bug #86](https://endrift.com/mgba/bugs/show_bug.cgi?id=86)). +- Lua support for scripting ([Bug #62](http://mgba.io/b/62)). +- A comprehensive debug suite ([Bug #132](http://mgba.io/b/132)). +- OpenEmu core. +- e-Reader support. ([Bug #171](http://mgba.io/b/171)) Supported Platforms@@ -46,7 +48,7 @@ - OS X 10.7 (Lion)[<sup>[3]</sup>](#osxver) or newer
- Linux - FreeBSD -Other Unix-like platforms work as well, but are untested. +Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported. ### System requirements@@ -72,7 +74,7 @@
Compiling --------- -Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows: +Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows: mkdir build cd build@@ -99,18 +101,18 @@ ---------
<a name="missing">[1]</a> Currently missing features are -- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5)) -- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9)) -- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141)) -- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142)) -- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195)) -- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199)) +- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) +- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) +- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141)) +- Audio channel reset flags ([Bug #142](http://mgba.io/b/142)) +- Game Pak prefetch ([Bug #195](http://mgba.io/b/195)) +- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199)) -<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered. +<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. <a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older. -[downloads]: https://endrift.com/mgba/downloads.html +[downloads]: http://mgba.io/downloads.html [source]: https://github.com/mgba-emu/mgba/ Copyright
@@ -0,0 +1,11 @@
+[Desktop Entry] +Version=1.0 +Icon=mgba +Exec=mgba-qt %f +Terminal=false +Type=Application +Name=mGBA +GenericName=Game Boy Advance Emulator +Categories=Game;Emulator; +MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; +
@@ -0,0 +1,28 @@
+IDI_ICON1 ICON DISCARDABLE "${CMAKE_SOURCE_DIR}/res/mgba.ico" + +#include <windows.h> + +VS_VERSION_INFO VERSIONINFO +FILEVERSION ${WIN32_VERSION},0 +PRODUCTVERSION ${WIN32_VERSION},0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "endrift" + VALUE "FileDescription", "mGBA Game Boy Advance emulator" + VALUE "FileVersion", "${LIB_VERSION_STRING}.0" + VALUE "InternalName", "${BINARY_NAME}" + VALUE "LegalCopyright", "(c) 2013 - 2015 Jeffrey Pfau" + VALUE "OriginalFilename", "${BINARY_NAME}" + VALUE "ProductName", "${PROJECT_NAME}" + VALUE "ProductVersion", "${BINARY_NAME}" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END
@@ -164,10 +164,10 @@ cpu->gprs[ARM_LR] = cpu->gprs[ARM_PC] - instructionWidth + WORD_SIZE_ARM;
cpu->gprs[ARM_PC] = BASE_IRQ; int currentCycles = 0; ARM_WRITE_PC; - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); _ARMSetMode(cpu, MODE_ARM); cpu->spsr = cpsr; cpu->cpsr.i = 1; + cpu->cycles += currentCycles; } void ARMRaiseSWI(struct ARMCore* cpu) {@@ -184,10 +184,10 @@ cpu->gprs[ARM_LR] = cpu->gprs[ARM_PC] - instructionWidth;
cpu->gprs[ARM_PC] = BASE_SWI; int currentCycles = 0; ARM_WRITE_PC; - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); _ARMSetMode(cpu, MODE_ARM); cpu->spsr = cpsr; cpu->cpsr.i = 1; + cpu->cycles += currentCycles; } static inline void ARMStep(struct ARMCore* cpu) {
@@ -380,8 +380,12 @@ DEFINE_DECODER_ARM(MRC, ILL, info->operandFormat = ARM_OPERAND_NONE;)
// Begin miscellaneous definitions -DEFINE_DECODER_ARM(BKPT, BKPT, info->operandFormat = ARM_OPERAND_NONE;) // Not strictly in ARMv4T, but here for convenience -DEFINE_DECODER_ARM(ILL, ILL, info->operandFormat = ARM_OPERAND_NONE;) // Illegal opcode +DEFINE_DECODER_ARM(BKPT, BKPT, + info->operandFormat = ARM_OPERAND_NONE; + info->traps = 1;) // Not strictly in ARMv4T, but here for convenience +DEFINE_DECODER_ARM(ILL, ILL, + info->operandFormat = ARM_OPERAND_NONE; + info->traps = 1;) // Illegal opcode DEFINE_DECODER_ARM(MSR, MSR, info->affectsCPSR = 1;
@@ -281,8 +281,13 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POPR, ARM_SP, LDM, ARM_MEMORY_INCREMENT_AFTER, 1 << ARM_PC)
DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSH, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 0) DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSHR, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 1 << ARM_LR) -DEFINE_THUMB_DECODER(ILL, ILL, info->traps = 1;) -DEFINE_THUMB_DECODER(BKPT, BKPT, info->traps = 1;) +DEFINE_THUMB_DECODER(ILL, ILL, + info->operandFormat = ARM_OPERAND_NONE; + info->traps = 1;) + +DEFINE_THUMB_DECODER(BKPT, BKPT, + info->operandFormat = ARM_OPERAND_NONE; + info->traps = 1;) DEFINE_THUMB_DECODER(B, B, int16_t immediate = (opcode & 0x07FF) << 5;
@@ -233,7 +233,12 @@ #define ADDR_MODE_2_RN (cpu->gprs[rn])
#define ADDR_MODE_2_RM (cpu->gprs[rm]) #define ADDR_MODE_2_IMMEDIATE (opcode & 0x00000FFF) #define ADDR_MODE_2_INDEX(U_OP, M) (cpu->gprs[rn] U_OP M) -#define ADDR_MODE_2_WRITEBACK(ADDR) (cpu->gprs[rn] = ADDR) +#define ADDR_MODE_2_WRITEBACK(ADDR) \ + cpu->gprs[rn] = ADDR; \ + if (UNLIKELY(rn == ARM_PC)) { \ + ARM_WRITE_PC; \ + } + #define ADDR_MODE_2_LSL (cpu->gprs[rm] << ADDR_MODE_2_I) #define ADDR_MODE_2_LSR (ADDR_MODE_2_I_TEST ? ((uint32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : 0) #define ADDR_MODE_2_ASR (ADDR_MODE_2_I_TEST ? ((int32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : ((int32_t) cpu->gprs[rm]) >> 31)@@ -254,7 +259,7 @@
#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address; #define ARM_LOAD_POST_BODY \ - ++currentCycles; \ + currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \ if (rd == ARM_PC) { \ ARM_WRITE_PC; \ }@@ -322,13 +327,13 @@ int rd = (opcode >> 12) & 0xF; \
int rdHi = (opcode >> 16) & 0xF; \ int rs = (opcode >> 8) & 0xF; \ int rm = opcode & 0xF; \ - UNUSED(rdHi); \ + if (rdHi == ARM_PC || rd == ARM_PC) { \ + return; \ + } \ ARM_WAIT_MUL(cpu->gprs[rs]); \ BODY; \ S_BODY; \ - if (rd == ARM_PC) { \ - ARM_WRITE_PC; \ - }) + currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32) #define DEFINE_MULTIPLY_INSTRUCTION_ARM(NAME, BODY, S_BODY) \ DEFINE_MULTIPLY_INSTRUCTION_EX_ARM(NAME, BODY, ) \@@ -562,14 +567,14 @@ ARM_STORE_POST_BODY;)
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(LDM, load, - ++currentCycles; + currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; if (rs & 0x8000) { ARM_WRITE_PC; }) DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(STM, store, - currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32) + ARM_STORE_POST_BODY;) DEFINE_INSTRUCTION_ARM(SWP, int rm = opcode & 0xF;
@@ -41,7 +41,8 @@ THUMB_SUBTRACTION_S(m, n, D)
#define THUMB_PREFETCH_CYCLES (1 + cpu->memory.activeSeqCycles16) -#define THUMB_LOAD_POST_BODY ++currentCycles; +#define THUMB_LOAD_POST_BODY \ + currentCycles += 1 + cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; #define THUMB_STORE_POST_BODY \ currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;@@ -230,7 +231,7 @@ DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(NEG, THUMB_SUBTRACTION(cpu->gprs[rd], 0, cpu->gprs[rn]))
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(CMP2, int32_t aluOut = cpu->gprs[rd] - cpu->gprs[rn]; THUMB_SUBTRACTION_S(cpu->gprs[rd], cpu->gprs[rn], aluOut)) DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(CMN, int32_t aluOut = cpu->gprs[rd] + cpu->gprs[rn]; THUMB_ADDITION_S(cpu->gprs[rd], cpu->gprs[rn], aluOut)) DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(ORR, cpu->gprs[rd] = cpu->gprs[rd] | cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd])) -DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MUL, ARM_WAIT_MUL(cpu->gprs[rn]); cpu->gprs[rd] *= cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd])) +DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MUL, ARM_WAIT_MUL(cpu->gprs[rd]); cpu->gprs[rd] *= cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]); currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16) DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(BIC, cpu->gprs[rd] = cpu->gprs[rd] & ~cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd])) DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MVN, cpu->gprs[rd] = ~cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]))
@@ -8,68 +8,9 @@ #define MACROS_H
#include "util/common.h" -#if defined(__PPC__) || defined(__POWERPC__) -#define LOAD_32(DEST, ADDR, ARR) { \ - uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ - asm("lwbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ -} - -#define LOAD_16(DEST, ADDR, ARR) { \ - uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ - asm("lhbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ -} - -#define STORE_32(SRC, ADDR, ARR) { \ - uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ - asm("stwbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ -} - -#define STORE_16(SRC, ADDR, ARR) { \ - uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ - asm("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ -} -#else -#define LOAD_32(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2] -#define LOAD_16(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1] -#define STORE_32(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC -#define STORE_16(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC -#endif - -#define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START)) -#define CHECK_BITS(SRC, START, END) ((SRC) & MAKE_MASK(START, END)) -#define EXT_BITS(SRC, START, END) (((SRC) >> (START)) & ((1 << ((END) - (START))) - 1)) -#define INS_BITS(SRC, START, END, BITS) (CLEAR_BITS(SRC, START, END) | (((BITS) << (START)) & MAKE_MASK(START, END))) -#define CLEAR_BITS(SRC, START, END) ((SRC) & ~MAKE_MASK(START, END)) -#define FILL_BITS(SRC, START, END) ((SRC) | MAKE_MASK(START, END)) - -#define DECL_BITFIELD(NAME, TYPE) typedef TYPE NAME - -#define DECL_BITS(TYPE, FIELD, START, SIZE) \ - __attribute__((unused)) static inline TYPE TYPE ## Is ## FIELD (TYPE src) { \ - return CHECK_BITS(src, (START), (START) + (SIZE)); \ - } \ - __attribute__((unused)) static inline TYPE TYPE ## Get ## FIELD (TYPE src) { \ - return EXT_BITS(src, (START), (START) + (SIZE)); \ - } \ - __attribute__((unused)) static inline TYPE TYPE ## Clear ## FIELD (TYPE src) { \ - return CLEAR_BITS(src, (START), (START) + (SIZE)); \ - } \ - __attribute__((unused)) static inline TYPE TYPE ## Fill ## FIELD (TYPE src) { \ - return FILL_BITS(src, (START), (START) + (SIZE)); \ - } \ - __attribute__((unused)) static inline TYPE TYPE ## Set ## FIELD (TYPE src, TYPE bits) { \ - return INS_BITS(src, (START), (START) + (SIZE), bits); \ - } - -#define DECL_BIT(TYPE, FIELD, BIT) DECL_BITS(TYPE, FIELD, BIT, 1) - -#define LIKELY(X) __builtin_expect(!!(X), 1) -#define UNLIKELY(X) __builtin_expect(!!(X), 0) - -#define ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31))) +#define LOAD_32 LOAD_32LE +#define LOAD_16 LOAD_16LE +#define STORE_32 STORE_32LE +#define STORE_16 STORE_16LE #endif
@@ -519,6 +519,7 @@ return;
} uint32_t address = dv->intValue; ARMDebuggerClearBreakpoint(&debugger->d, address); + ARMDebuggerClearWatchpoint(&debugger->d, address); } static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {@@ -816,7 +817,7 @@ return CC_ERROR;
} const char* commandPtr; - int cmd = 0, len = 0; + size_t cmd = 0, len = 0; const char* name = 0; for (commandPtr = li->buffer; commandPtr <= li->cursor; ++commandPtr, ++len) { for (; (name = _debuggerCommands[cmd].name); ++cmd) {@@ -832,7 +833,7 @@ }
if (!name) { return CC_ERROR; } - if (_debuggerCommands[cmd + 1].name && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) { + if (_debuggerCommands[cmd + 1].name && strlen(_debuggerCommands[cmd + 1].name) >= len - 1 && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) { --len; const char* next = 0; int i;@@ -842,6 +843,9 @@ break;
} next = _debuggerCommands[i].name; } + if (!next) { + return CC_ERROR; + } for (; name[len]; ++len) { if (name[len] != next[len]) {@@ -861,7 +865,7 @@
static void _cliDebuggerInit(struct ARMDebugger* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; // TODO: get argv[0] - cliDebugger->elstate = el_init(BINARY_NAME, stdin, stdout, stderr); + cliDebugger->elstate = el_init(binaryName, stdin, stdout, stderr); el_set(cliDebugger->elstate, EL_PROMPT, _prompt); el_set(cliDebugger->elstate, EL_EDITOR, "emacs");
@@ -149,11 +149,14 @@
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) { struct DebugBreakpoint** previous = &debugger->breakpoints; struct DebugBreakpoint* breakpoint; - for (; (breakpoint = *previous); previous = &breakpoint->next) { + struct DebugBreakpoint** next; + while ((breakpoint = *previous)) { + next = &breakpoint->next; if (breakpoint->address == address) { - *previous = breakpoint->next; + *previous = *next; free(breakpoint); } + previous = next; } }@@ -169,12 +172,15 @@ }
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) { struct DebugWatchpoint** previous = &debugger->watchpoints; - struct DebugWatchpoint* breakpoint; - for (; (breakpoint = *previous); previous = &breakpoint->next) { - if (breakpoint->address == address) { - *previous = breakpoint->next; - free(breakpoint); + struct DebugWatchpoint* watchpoint; + struct DebugWatchpoint** next; + while ((watchpoint = *previous)) { + next = &watchpoint->next; + if (watchpoint->address == address) { + *previous = *next; + free(watchpoint); } + previous = next; } if (!debugger->watchpoints) { ARMDebuggerRemoveMemoryShim(debugger);
@@ -91,7 +91,7 @@
bool (*setSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); bool (*clearSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode); - __attribute__((format (printf, 3, 4))) + ATTRIBUTE_FORMAT(printf, 3, 4) void (*log)(struct ARMDebugger*, enum DebuggerLogLevel, const char* format, ...); };
@@ -464,6 +464,8 @@ stub->d.entered = _gdbStubEntered;
stub->d.custom = _gdbStubPoll; stub->d.log = 0; stub->untilPoll = GDB_STUB_INTERVAL; + stub->lineAck = GDB_ACK_PENDING; + stub->shouldBlock = false; } bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
@@ -14,7 +14,12 @@
const unsigned GBA_AUDIO_SAMPLES = 2048; const unsigned BLIP_BUFFER_SIZE = 0x4000; const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); +const int GBA_AUDIO_VOLUME_MAX = 0x100; #define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128) + +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF +static const int CLOCKS_PER_FRAME = 0x400; +#endif static bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value); static int32_t _updateSquareChannel(struct GBAAudioSquareControl* envelope, int duty);@@ -41,6 +46,14 @@ blip_set_rates(audio->right, GBA_ARM7TDMI_FREQUENCY, 96000);
#endif CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); + + audio->forceDisableCh[0] = false; + audio->forceDisableCh[1] = false; + audio->forceDisableCh[2] = false; + audio->forceDisableCh[3] = false; + audio->forceDisableChA = false; + audio->forceDisableChB = false; + audio->masterVolume = GBA_AUDIO_VOLUME_MAX; } void GBAAudioReset(struct GBAAudio* audio) {@@ -480,30 +493,6 @@ }
} } -void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value) { - struct CircleBuffer* fifo; - switch (address) { - case REG_FIFO_A_LO: - case REG_FIFO_A_HI: - fifo = &audio->chA.fifo; - break; - case REG_FIFO_B_LO: - case REG_FIFO_B_HI: - fifo = &audio->chB.fifo; - break; - default: - GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", address); - return; - } - int i; - for (i = 0; i < 2; ++i) { - while (!CircleBufferWrite8(fifo, value >> (8 * i))) { - int8_t dummy; - CircleBufferRead8(fifo, &dummy); - } - } -} - void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { struct GBAAudioFIFO* channel; if (fifoId == 0) {@@ -514,11 +503,16 @@ } else {
GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", fifoId); return; } - if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t)) { + if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; - dma->nextCount = 4; - dma->nextEvent = 0; - GBAMemoryUpdateDMAs(audio->p, -cycles); + if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { + dma->nextCount = 4; + dma->nextEvent = 0; + dma->reg = GBADMARegisterSetWidth(dma->reg, 1); + GBAMemoryUpdateDMAs(audio->p, -cycles); + } else { + channel->dmaSource = 0; + } } CircleBufferRead8(&channel->fifo, &channel->sample); }@@ -738,7 +732,7 @@ sample = 0x3FF;
} else if (sample < 0) { sample = 0; } - return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5; + return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume) >> 3; } static void _sample(struct GBAAudio* audio) {@@ -746,55 +740,67 @@ int16_t sampleLeft = 0;
int16_t sampleRight = 0; int psgShift = 5 - audio->volume; - if (audio->ch1Left) { - sampleLeft += audio->ch1.sample; - } + if (audio->playingCh1 && !audio->forceDisableCh[0]) { + if (audio->ch1Left) { + sampleLeft += audio->ch1.sample; + } - if (audio->ch1Right) { - sampleRight += audio->ch1.sample; + if (audio->ch1Right) { + sampleRight += audio->ch1.sample; + } } - if (audio->ch2Left) { - sampleLeft += audio->ch2.sample; - } + if (audio->playingCh2 && !audio->forceDisableCh[1]) { + if (audio->ch2Left) { + sampleLeft += audio->ch2.sample; + } - if (audio->ch2Right) { - sampleRight += audio->ch2.sample; + if (audio->ch2Right) { + sampleRight += audio->ch2.sample; + } } - if (audio->ch3Left) { - sampleLeft += audio->ch3.sample; - } + if (audio->playingCh3 && !audio->forceDisableCh[2]) { + if (audio->ch3Left) { + sampleLeft += audio->ch3.sample; + } - if (audio->ch3Right) { - sampleRight += audio->ch3.sample; + if (audio->ch3Right) { + sampleRight += audio->ch3.sample; + } } - if (audio->ch4Left) { - sampleLeft += audio->ch4.sample; - } + if (audio->playingCh4 && !audio->forceDisableCh[3]) { + if (audio->ch4Left) { + sampleLeft += audio->ch4.sample; + } - if (audio->ch4Right) { - sampleRight += audio->ch4.sample; + if (audio->ch4Right) { + sampleRight += audio->ch4.sample; + } } sampleLeft = (sampleLeft * (1 + audio->volumeLeft)) >> psgShift; sampleRight = (sampleRight * (1 + audio->volumeRight)) >> psgShift; - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; - } + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; - } + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } sampleLeft = _applyBias(audio, sampleLeft);@@ -813,19 +819,23 @@ blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
audio->lastLeft = sampleLeft; audio->lastRight = sampleRight; audio->clock += audio->sampleInterval; - int clockNeeded = blip_clocks_needed(audio->left, audio->samples / 32); - if (audio->clock >= clockNeeded) { + if (audio->clock >= CLOCKS_PER_FRAME) { blip_end_frame(audio->left, audio->clock); blip_end_frame(audio->right, audio->clock); - audio->clock -= clockNeeded; + audio->clock -= CLOCKS_PER_FRAME; } } produced = blip_samples_avail(audio->left); #endif - if (audio->p->stream) { + if (audio->p->stream && audio->p->stream->postAudioFrame) { audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); } - GBASyncProduceAudio(audio->p->sync, produced >= audio->samples); + bool wait = produced >= audio->samples; + GBASyncProduceAudio(audio->p->sync, wait); + + if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) { + audio->p->stream->postAudioBuffer(audio->p->stream, audio); + } } void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {@@ -897,8 +907,12 @@ audio->nextCh4 = state->audio.ch4.nextEvent;
CircleBufferClear(&audio->chA.fifo); CircleBufferClear(&audio->chB.fifo); - int i; - for (i = 0; i < state->audio.fifoSize; ++i) { + size_t fifoSize = state->audio.fifoSize; + if (state->audio.fifoSize > CircleBufferCapacity(&audio->chA.fifo)) { + fifoSize = CircleBufferCapacity(&audio->chA.fifo); + } + size_t i; + for (i = 0; i < fifoSize; ++i) { CircleBufferWrite8(&audio->chA.fifo, state->audio.fifoA[i]); CircleBufferWrite8(&audio->chB.fifo, state->audio.fifoB[i]); }
@@ -11,6 +11,9 @@ #include "macros.h"
#include "util/circle-buffer.h" +#define RESAMPLE_NN 0 +#define RESAMPLE_BLIP_BUF 2 + #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF #include "third-party/blip_buf/blip_buf.h" #endif@@ -18,9 +21,7 @@
struct GBADMA; extern const unsigned GBA_AUDIO_SAMPLES; - -#define RESAMPLE_NN 0 -#define RESAMPLE_BLIP_BUF 2 +extern const int GBA_AUDIO_VOLUME_MAX; DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t); DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6);@@ -236,6 +237,11 @@ int32_t nextCh4;
int32_t nextSample; int32_t sampleInterval; + + bool forceDisableCh[4]; + bool forceDisableChA; + bool forceDisableChB; + int masterVolume; }; struct GBAStereoSample {@@ -268,7 +274,6 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value); void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value); -void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value); void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
@@ -88,8 +88,8 @@ while (i--) {
// [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ] // [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ] // [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] - ox = cpu->memory.load32(cpu, offset, 0) / 256.f; - oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f; + ox = (int32_t) cpu->memory.load32(cpu, offset, 0) / 256.f; + oy = (int32_t) cpu->memory.load32(cpu, offset + 4, 0) / 256.f; cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0); cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0); sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;@@ -238,6 +238,7 @@ }
switch (cpu->gprs[1] >> BASE_OFFSET) { default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination"); + // Fall through case REGION_WORKING_RAM: case REGION_WORKING_IRAM: case REGION_VRAM:@@ -253,6 +254,7 @@ }
switch (cpu->gprs[1] >> BASE_OFFSET) { default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination"); + // Fall through case REGION_WORKING_RAM: case REGION_WORKING_IRAM: case REGION_VRAM:@@ -269,6 +271,7 @@ }
switch (cpu->gprs[1] >> BASE_OFFSET) { default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination"); + // Fall through case REGION_WORKING_RAM: case REGION_WORKING_IRAM: case REGION_VRAM:@@ -286,6 +289,7 @@ }
switch (cpu->gprs[1] >> BASE_OFFSET) { default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination"); + // Fall through case REGION_WORKING_RAM: case REGION_WORKING_IRAM: case REGION_VRAM:@@ -327,7 +331,7 @@ int blocksRemaining = 0;
uint32_t disp; int bytes; int byte; - int halfword; + int halfword = 0; while (remaining > 0) { if (blocksRemaining) { if (blockheader & 0x80) {
@@ -5,8 +5,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cheats.h" +#include "gba/cheats/gameshark.h" +#include "gba/cheats/parv3.h" #include "gba/gba.h" -#include "gba/io.h" +#include "util/string.h" #include "util/vfs.h" #define MAX_LINE_LENGTH 128@@ -17,85 +19,6 @@ DEFINE_VECTOR(GBACheatList, struct GBACheat);
DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*); DEFINE_VECTOR(StringList, char*); -static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; -static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; - -static const uint8_t _gsa1T1[256] = { - 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94, - 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B, - 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45, - 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9, - 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA, - 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E, - 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79, - 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31, - 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B, - 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB, - 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77, - 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93, - 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25, - 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52, - 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F, - 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50 -}; - -static const uint8_t _gsa1T2[256] = { - 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6, - 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1, - 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B, - 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48, - 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6, - 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8, - 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE, - 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39, - 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B, - 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4, - 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5, - 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90, - 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75, - 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76, - 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2, - 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C -}; - -static const uint8_t _par3T1[256] = { - 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A, - 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45, - 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7, - 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5, - 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54, - 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A, - 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB, - 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC, - 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2, - 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2, - 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31, - 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35, - 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11, - 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C, - 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA, - 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30 -}; - -static const uint8_t _par3T2[256] = { - 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA, - 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F, - 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93, - 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B, - 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD, - 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC, - 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F, - 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9, - 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1, - 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B, - 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC, - 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA, - 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8, - 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D, - 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C, - 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC -}; - static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) { switch (width) { case 1:@@ -122,225 +45,10 @@ break;
} } -static int _hexDigit(char digit) { - switch (digit) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return digit - '0'; - - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - return digit - 'a' + 10; - - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - return digit - 'A' + 10; - - default: - return -1; - } -} - -static const char* _hex32(const char* line, uint32_t* out) { - uint32_t value = 0; - int i; - for (i = 0; i < 8; ++i, ++line) { - char digit = *line; - value <<= 4; - int nybble = _hexDigit(digit); - if (nybble < 0) { - return 0; - } - value |= nybble; - } - *out = value; - return line; -} - -static const char* _hex16(const char* line, uint16_t* out) { - uint16_t value = 0; - *out = 0; - int i; - for (i = 0; i < 4; ++i, ++line) { - char digit = *line; - value <<= 4; - int nybble = _hexDigit(digit); - if (nybble < 0) { - return 0; - } - value |= nybble; - } - *out = value; - return line; -} - -static void _registerLine(struct GBACheatSet* cheats, const char* line) { +void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) { *StringListAppend(&cheats->lines) = strdup(line); } -// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm -static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { - uint32_t sum = 0xC6EF3720; - int i; - for (i = 0; i < 32; ++i) { - *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); - *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); - sum -= 0x9E3779B9; - } -} - -static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) { - int x, y; - int s0 = params >> 8; - int s1 = params & 0xFF; - for (y = 0; y < 4; ++y) { - for (x = 0; x < 4; ++x) { - uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF]; - seeds[y] <<= 8; - seeds[y] |= z; - } - } -} - -static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) { - cheats->gsaVersion = 1; - switch (version) { - case 1: - memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t)); - break; - case 3: - memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t)); - break; - } -} - -static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - enum GBAGameSharkType type = op1 >> 28; - struct GBACheat* cheat = 0; - - if (cheats->incompleteCheat) { - if (cheats->remainingAddresses > 0) { - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op1; - cheat->operand = cheats->incompleteCheat->operand; - cheat->repeat = 1; - --cheats->remainingAddresses; - } - if (cheats->remainingAddresses > 0) { - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op2; - cheat->operand = cheats->incompleteCheat->operand; - cheat->repeat = 1; - --cheats->remainingAddresses; - } - if (cheats->remainingAddresses == 0) { - cheats->incompleteCheat = 0; - } - return true; - } - - switch (type) { - case GSA_ASSIGN_1: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 1; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_4: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_LIST: - cheats->remainingAddresses = (op1 & 0xFFFF) - 1; - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op2; - cheats->incompleteCheat = cheat; - break; - case GSA_PATCH: - cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1; - cheats->romPatches[0].newValue = op2; - cheats->romPatches[0].applied = false; - cheats->romPatches[0].exists = true; - return true; - case GSA_BUTTON: - // TODO: Implement button - return false; - case GSA_IF_EQ: - if (op1 == 0xDEADFACE) { - _reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); - return true; - } - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_IF_EQ_RANGE: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - cheat->address = op2 & 0x0FFFFFFF; - cheat->operand = op1 & 0xFFFF; - cheat->repeat = (op1 >> 16) & 0xFF; - return true; - case GSA_HOOK: - if (cheats->hook) { - return false; - } - cheats->hook = malloc(sizeof(*cheats->hook)); - cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); - cheats->hook->mode = MODE_THUMB; - cheats->hook->refs = 1; - cheats->hook->reentries = 0; - return true; - default: - return false; - } - cheat->operand = op2; - cheat->repeat = 1; - return true; -} - -static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - // TODO - UNUSED(cheats); - UNUSED(op1); - UNUSED(op2); - UNUSED(_par3T1); - UNUSED(_par3T2); - return false; -} - static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) { if (!device->p || !cheats->hook) { return;@@ -415,6 +123,8 @@ void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
GBACheatListInit(&set->list, 4); StringListInit(&set->lines, 4); set->incompleteCheat = 0; + set->incompletePatch = 0; + set->currentBlock = 0; set->gsaVersion = 0; set->remainingAddresses = 0; set->hook = 0;@@ -476,201 +186,35 @@ _unpatchROM(device, cheats);
_removeBreakpoint(device, cheats); } -bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { - char line[14] = "XXXXXXXX XXXX"; - snprintf(line, sizeof(line), "%08X %04X", op1, op2); - _registerLine(cheats, line); - - enum GBACodeBreakerType type = op1 >> 28; - struct GBACheat* cheat = 0; - - if (cheats->incompleteCheat) { - cheats->incompleteCheat->repeat = op1 & 0xFFFF; - cheats->incompleteCheat->addressOffset = op2; - cheats->incompleteCheat->operandOffset = 0; - cheats->incompleteCheat = 0; - return true; - } - - switch (type) { - case CB_GAME_ID: - // TODO: Run checksum - return true; - case CB_HOOK: - if (cheats->hook) { - return false; - } - cheats->hook = malloc(sizeof(*cheats->hook)); - cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); - cheats->hook->mode = MODE_THUMB; - cheats->hook->refs = 1; - cheats->hook->reentries = 0; - return true; - case CB_OR_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_OR; - cheat->width = 2; - break; - case CB_ASSIGN_1: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 1; - break; - case CB_FILL: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - cheats->incompleteCheat = cheat; - break; - case CB_FILL_8: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); - return false; - case CB_AND_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_AND; - cheat->width = 2; - break; - case CB_IF_EQ: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - break; - case CB_ASSIGN_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - break; - case CB_ENCRYPT: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); - return false; - case CB_IF_NE: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_NE; - cheat->width = 2; - break; - case CB_IF_GT: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_GT; - cheat->width = 2; - break; - case CB_IF_LT: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_LT; - cheat->width = 2; - break; - case CB_IF_SPECIAL: - switch (op1 & 0x0FFFFFFF) { - case 0x20: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_AND; - cheat->width = 2; - cheat->address = BASE_IO | REG_JOYSTAT; - cheat->operand = op2; - cheat->repeat = 1; - return true; - default: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); - return false; - } - case CB_ADD_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ADD; - cheat->width = 2; - break; - case CB_IF_AND: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_AND; - cheat->width = 2; - break; - } - - cheat->address = op1 & 0x0FFFFFFF; - cheat->operand = op2; - cheat->repeat = 1; - return true; -} - -bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { - uint32_t op1; - uint16_t op2; - line = _hex32(line, &op1); - if (!line) { - return false; - } - while (*line == ' ') { - ++line; - } - line = _hex16(line, &op2); - if (!line) { - return false; - } - return GBACheatAddCodeBreaker(cheats, op1, op2); -} - -bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { - uint32_t o1 = op1; - uint32_t o2 = op2; - char line[18] = "XXXXXXXX XXXXXXXX"; - snprintf(line, sizeof(line), "%08X %08X", op1, op2); - _registerLine(set, line); - - switch (set->gsaVersion) { - case 0: - _setGameSharkVersion(set, 1); - // Fall through - case 1: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addGSA1(set, o1, o2); - } - return false; -} - -bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { - uint32_t op1; - uint32_t op2; - line = _hex32(line, &op1); - if (!line) { - return false; - } - while (*line == ' ') { - ++line; - } - line = _hex32(line, &op2); - if (!line) { - return false; - } - return GBACheatAddGameShark(cheats, op1, op2); -} - bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { uint32_t o1 = op1; uint32_t o2 = op2; char line[18] = "XXXXXXXX XXXXXXXX"; snprintf(line, sizeof(line), "%08X %08X", op1, op2); - _registerLine(set, line); + GBACheatRegisterLine(set, line); switch (set->gsaVersion) { case 0: // Try to detect GameShark version - _decryptGameShark(&o1, &o2, _gsa1S); + GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds); if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) { - _setGameSharkVersion(set, 1); - return _addGSA1(set, o1, o2); + GBACheatSetGameSharkVersion(set, 1); + return GBACheatAddGameSharkRaw(set, o1, o2); } o1 = op1; o2 = op2; - _decryptGameShark(&o1, &o2, _par3S); + GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds); if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) { - _setGameSharkVersion(set, 3); - return _addPAR3(set, o1, o2); + GBACheatSetGameSharkVersion(set, 3); + return GBACheatAddProActionReplayRaw(set, o1, o2); } break; case 1: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addGSA1(set, o1, o2); + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddGameSharkRaw(set, o1, o2); case 3: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addPAR3(set, o1, o2); + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddProActionReplayRaw(set, o1, o2); } return false; }@@ -678,14 +222,14 @@
bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) { uint32_t op1; uint32_t op2; - line = _hex32(line, &op1); + line = hex32(line, &op1); if (!line) { return false; } while (*line == ' ') { ++line; } - line = _hex32(line, &op2); + line = hex32(line, &op2); if (!line) { return false; }@@ -726,7 +270,7 @@ }
if (set && !reset) { GBACheatSetCopyProperties(newSet, set); } else { - _setGameSharkVersion(newSet, gsaVersion); + GBACheatSetGameSharkVersion(newSet, gsaVersion); } reset = false; set = newSet;@@ -755,7 +299,7 @@ set = malloc(sizeof(*set));
GBACheatSetInit(set, 0); set->enabled = !nextDisabled; nextDisabled = false; - _setGameSharkVersion(set, gsaVersion); + GBACheatSetGameSharkVersion(set, gsaVersion); } GBACheatAddLine(set, cheat); break;@@ -819,21 +363,21 @@ bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
uint32_t op1; uint16_t op2; uint16_t op3; - line = _hex32(line, &op1); + line = hex32(line, &op1); if (!line) { return false; } while (isspace(line[0])) { ++line; } - line = _hex16(line, &op2); + line = hex16(line, &op2); if (!line) { return false; } if (!line[0] || isspace(line[0])) { return GBACheatAddCodeBreaker(cheats, op1, op2); } - line = _hex16(line, &op3); + line = hex16(line, &op3); if (!line) { return false; }@@ -849,6 +393,7 @@ return;
} bool condition = true; int conditionRemaining = 0; + int negativeConditionRemaining = 0; _patchROM(device, cheats); size_t nCodes = GBACheatListSize(&cheats->list);@@ -859,6 +404,13 @@ --conditionRemaining;
if (!condition) { continue; } + } else if (negativeConditionRemaining > 0) { + conditionRemaining = negativeConditionRemaining - 1; + negativeConditionRemaining = 0; + condition = !condition; + if (!condition) { + continue; + } } else { condition = true; }@@ -872,6 +424,11 @@ for (; operationsRemaining; --operationsRemaining) {
switch (cheat->type) { case CHEAT_ASSIGN: value = operand; + performAssignment = true; + break; + case CHEAT_ASSIGN_INDIRECT: + value = operand; + address = _readMem(device->p->cpu, address + cheat->addressOffset, 4); performAssignment = true; break; case CHEAT_AND:@@ -889,34 +446,42 @@ break;
case CHEAT_IF_EQ: condition = _readMem(device->p->cpu, address, cheat->width) == operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_NE: condition = _readMem(device->p->cpu, address, cheat->width) != operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_LT: condition = _readMem(device->p->cpu, address, cheat->width) < operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_GT: condition = _readMem(device->p->cpu, address, cheat->width) > operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_ULT: condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_UGT: condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_AND: condition = _readMem(device->p->cpu, address, cheat->width) & operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_LAND: condition = _readMem(device->p->cpu, address, cheat->width) && operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; }
@@ -15,6 +15,7 @@ #define MAX_ROM_PATCHES 4
enum GBACheatType { CHEAT_ASSIGN, + CHEAT_ASSIGN_INDIRECT, CHEAT_AND, CHEAT_ADD, CHEAT_OR,@@ -85,6 +86,11 @@ PAR3_ACTION_DISABLE = 0xC0000000,
}; enum GBAActionReplay3Base { + PAR3_BASE_ASSIGN = 0x00000000, + PAR3_BASE_INDIRECT = 0x40000000, + PAR3_BASE_ADD = 0x80000000, + PAR3_BASE_OTHER = 0xC0000000, + PAR3_BASE_ASSIGN_1 = 0x00000000, PAR3_BASE_ASSIGN_2 = 0x02000000, PAR3_BASE_ASSIGN_4 = 0x04000000,@@ -119,7 +125,10 @@
enum { PAR3_COND = 0x38000000, PAR3_WIDTH = 0x06000000, - PAR3_ACTION = 0xC0000000 + PAR3_ACTION = 0xC0000000, + PAR3_BASE = 0xC0000000, + + PAR3_WIDTH_BASE = 25 }; struct GBACheat {@@ -128,6 +137,7 @@ int width;
uint32_t address; uint32_t operand; uint32_t repeat; + uint32_t negativeRepeat; int32_t addressOffset; int32_t operandOffset;@@ -148,8 +158,6 @@ struct GBACheatSet {
struct GBACheatHook* hook; struct GBACheatList list; - struct GBACheat* incompleteCheat; - struct GBACheatPatch { uint32_t address; int16_t newValue;@@ -157,6 +165,10 @@ int16_t oldValue;
bool applied; bool exists; } romPatches[MAX_ROM_PATCHES]; + + struct GBACheat* incompleteCheat; + struct GBACheatPatch* incompletePatch; + struct GBACheat* currentBlock; int gsaVersion; uint32_t gsaSeeds[4];@@ -196,6 +208,9 @@
bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2); bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line); +bool GBACheatAddProActionReplay(struct GBACheatSet*, uint32_t op1, uint32_t op2); +bool GBACheatAddProActionReplayLine(struct GBACheatSet*, const char* line); + bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2); bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line);@@ -206,4 +221,4 @@ bool GBACheatAddLine(struct GBACheatSet*, const char* line);
void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*); -#endif+#endif
@@ -0,0 +1,13 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_CHEATS_PRIVATE_H +#define GBA_CHEATS_PRIVATE_H + +#include "gba/cheats.h" + +void GBACheatRegisterLine(struct GBACheatSet* set, const char* line); + +#endif
@@ -0,0 +1,143 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gba/cheats.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/gba.h" +#include "gba/io.h" +#include "util/string.h" + +bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { + char line[14] = "XXXXXXXX XXXX"; + snprintf(line, sizeof(line), "%08X %04X", op1, op2); + GBACheatRegisterLine(cheats, line); + + enum GBACodeBreakerType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + cheats->incompleteCheat->repeat = op1 & 0xFFFF; + cheats->incompleteCheat->addressOffset = op2; + cheats->incompleteCheat->operandOffset = 0; + cheats->incompleteCheat = 0; + return true; + } + + switch (type) { + case CB_GAME_ID: + // TODO: Run checksum + return true; + case CB_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + case CB_OR_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_OR; + cheat->width = 2; + break; + case CB_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + break; + case CB_FILL: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case CB_FILL_8: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + case CB_AND_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_AND; + cheat->width = 2; + break; + case CB_IF_EQ: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + break; + case CB_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + break; + case CB_ENCRYPT: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); + return false; + case CB_IF_NE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_NE; + cheat->width = 2; + break; + case CB_IF_GT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_GT; + cheat->width = 2; + break; + case CB_IF_LT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_LT; + cheat->width = 2; + break; + case CB_IF_SPECIAL: + switch (op1 & 0x0FFFFFFF) { + case 0x20: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + cheat->address = BASE_IO | REG_JOYSTAT; + cheat->operand = op2; + cheat->repeat = 1; + return true; + default: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + } + case CB_ADD_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ADD; + cheat->width = 2; + break; + case CB_IF_AND: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + break; + } + + cheat->address = op1 & 0x0FFFFFFF; + cheat->operand = op2; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + return true; +} + +bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex16(line, &op2); + if (!line) { + return false; + } + return GBACheatAddCodeBreaker(cheats, op1, op2); +}
@@ -0,0 +1,224 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gameshark.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/cheats/parv3.h" +#include "gba/gba.h" +#include "util/string.h" + +const uint32_t GBACheatGameSharkSeeds[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; + +static const uint8_t _gsa1T1[256] = { + 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94, + 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B, + 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45, + 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9, + 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA, + 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E, + 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79, + 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31, + 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B, + 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB, + 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77, + 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93, + 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25, + 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52, + 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F, + 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50 +}; + +static const uint8_t _gsa1T2[256] = { + 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6, + 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1, + 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B, + 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48, + 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6, + 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8, + 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE, + 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39, + 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B, + 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4, + 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5, + 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90, + 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75, + 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76, + 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2, + 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C +}; + +// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm +void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { + uint32_t sum = 0xC6EF3720; + int i; + for (i = 0; i < 32; ++i) { + *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); + *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); + sum -= 0x9E3779B9; + } +} + +void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) { + int x, y; + int s0 = params >> 8; + int s1 = params & 0xFF; + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF]; + seeds[y] <<= 8; + seeds[y] |= z; + } + } +} + +void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version) { + cheats->gsaVersion = 1; + switch (version) { + case 1: + memcpy(cheats->gsaSeeds, GBACheatGameSharkSeeds, 4 * sizeof(uint32_t)); + break; + case 3: + memcpy(cheats->gsaSeeds, GBACheatProActionReplaySeeds, 4 * sizeof(uint32_t)); + break; + } +} + +bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAGameSharkType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses == 0) { + cheats->incompleteCheat = 0; + } + return true; + } + + switch (type) { + case GSA_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_LIST: + cheats->remainingAddresses = (op1 & 0xFFFF) - 1; + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheats->incompleteCheat = cheat; + break; + case GSA_PATCH: + cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1; + cheats->romPatches[0].newValue = op2; + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + return true; + case GSA_BUTTON: + // TODO: Implement button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); + return false; + case GSA_IF_EQ: + if (op1 == 0xDEADFACE) { + GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); + return true; + } + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_IF_EQ_RANGE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op2 & 0x0FFFFFFF; + cheat->operand = op1 & 0xFFFF; + cheat->repeat = (op1 >> 16) & 0xFF; + return true; + case GSA_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + default: + return false; + } + cheat->operand = op2; + cheat->repeat = 1; + return true; +} + +bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + GBACheatRegisterLine(set, line); + + switch (set->gsaVersion) { + case 0: + GBACheatSetGameSharkVersion(set, 1); + // Fall through + case 1: + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddGameSharkRaw(set, o1, o2); + } + return false; +} + +bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddGameShark(cheats, op1, op2); +}
@@ -0,0 +1,18 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_CHEATS_GAMESHARK_H +#define GBA_CHEATS_GAMESHARK_H + +#include "gba/cheats.h" + +extern const uint32_t GBACheatGameSharkSeeds[4]; + +void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds); +void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2); +void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version); +bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2); + +#endif
@@ -0,0 +1,324 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "parv3.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/cheats/gameshark.h" +#include "gba/gba.h" +#include "util/string.h" + +const uint32_t GBACheatProActionReplaySeeds[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; + +static const uint8_t _par3T1[256] = { + 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A, + 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45, + 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7, + 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5, + 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54, + 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A, + 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB, + 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC, + 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2, + 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2, + 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31, + 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35, + 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11, + 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C, + 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA, + 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30 +}; + +static const uint8_t _par3T2[256] = { + 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA, + 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F, + 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93, + 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B, + 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD, + 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC, + 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F, + 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9, + 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1, + 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B, + 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC, + 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA, + 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8, + 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D, + 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C, + 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC +}; +static uint32_t _parAddr(uint32_t x) { + return (x & 0xFFFFF) | ((x << 4) & 0x0F000000); +} + +static void _parEndBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + if (cheats->currentBlock->repeat) { + cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat; + } else { + cheats->currentBlock->repeat = size; + } + cheats->currentBlock = 0; +} + +static void _parElseBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + cheats->currentBlock->repeat = size; +} + +static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAActionReplay3Condition condition = op1 & PAR3_COND; + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + if (width > 4) { + // TODO: Always false conditions + return false; + } + if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) { + // TODO: Codes that disable + return false; + } + + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + cheat->addressOffset = 0; + cheat->operandOffset = 0; + + switch (op1 & PAR3_ACTION) { + case PAR3_ACTION_NEXT: + cheat->repeat = 1; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_NEXT_TWO: + cheat->repeat = 2; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_BLOCK: + cheat->repeat = 0; + cheat->negativeRepeat = 0; + if (cheats->currentBlock) { + _parEndBlock(cheats); + } + cheats->currentBlock = cheat; + break; + } + + switch (condition) { + case PAR3_COND_OTHER: + // We shouldn't be able to get here + GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code"); + cheat->type = CHEAT_IF_LAND; + cheat->operand = 0; + break; + case PAR3_COND_EQ: + cheat->type = CHEAT_IF_EQ; + break; + case PAR3_COND_NE: + cheat->type = CHEAT_IF_NE; + break; + case PAR3_COND_LT: + cheat->type = CHEAT_IF_LT; + break; + case PAR3_COND_GT: + cheat->type = CHEAT_IF_GT; + break; + case PAR3_COND_ULT: + cheat->type = CHEAT_IF_ULT; + break; + case PAR3_COND_UGT: + cheat->type = CHEAT_IF_UGT; + break; + case PAR3_COND_LAND: + cheat->type = CHEAT_IF_LAND; + break; + } + return true; +} + +static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { + struct GBACheat* cheat; + switch (op2 & 0xFF000000) { + case PAR3_OTHER_SLOWDOWN: + // TODO: Slowdown + return false; + case PAR3_OTHER_BUTTON_1: + case PAR3_OTHER_BUTTON_2: + case PAR3_OTHER_BUTTON_4: + // TODO: Button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); + return false; + // TODO: Fix overriding existing patches + case PAR3_OTHER_PATCH_1: + cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + cheats->incompletePatch = &cheats->romPatches[0]; + break; + case PAR3_OTHER_PATCH_2: + cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[1].applied = false; + cheats->romPatches[1].exists = true; + cheats->incompletePatch = &cheats->romPatches[1]; + break; + case PAR3_OTHER_PATCH_3: + cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[2].applied = false; + cheats->romPatches[2].exists = true; + cheats->incompletePatch = &cheats->romPatches[2]; + break; + case PAR3_OTHER_PATCH_4: + cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[3].applied = false; + cheats->romPatches[3].exists = true; + cheats->incompletePatch = &cheats->romPatches[3]; + break; + case PAR3_OTHER_ENDIF: + if (cheats->currentBlock) { + _parEndBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_ELSE: + if (cheats->currentBlock) { + _parElseBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_FILL_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 1; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 3; + cheats->incompleteCheat = cheat; + break; + } + return true; +} + +bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + if (cheats->incompletePatch) { + cheats->incompletePatch->newValue = op1; + cheats->incompletePatch = 0; + return true; + } + if (cheats->incompleteCheat) { + cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8)); + cheats->incompleteCheat->addressOffset = op2 >> 24; + cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF; + cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width; + cheats->incompleteCheat = 0; + return true; + } + + if (op2 == 0x001DC0DE) { + return true; + } + + switch (op1) { + case 0x00000000: + return _addPAR3Special(cheats, op2); + case 0xDEADFACE: + GBACheatReseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2); + return true; + } + + if (op1 >> 24 == 0xC4) { + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + } + + if (op1 & PAR3_COND) { + return _addPAR3Cond(cheats, op1, op2); + } + + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->operandOffset = 0; + cheat->addressOffset = 0; + cheat->repeat = 1; + + switch (op1 & PAR3_BASE) { + case PAR3_BASE_ASSIGN: + cheat->type = CHEAT_ASSIGN; + cheat->addressOffset = width; + if (width < 4) { + cheat->repeat = (op2 >> (width * 8)) + 1; + } + break; + case PAR3_BASE_INDIRECT: + cheat->type = CHEAT_ASSIGN_INDIRECT; + if (width < 4) { + cheat->addressOffset = (op2 >> (width * 8)) * width; + } + break; + case PAR3_BASE_ADD: + cheat->type = CHEAT_ADD; + break; + case PAR3_BASE_OTHER: + width = ((op1 >> 24) & 1) + 1; + cheat->type = CHEAT_ASSIGN; + cheat->address = BASE_IO | (op1 & OFFSET_MASK); + break; + } + + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + return true; +} + +bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + GBACheatRegisterLine(set, line); + + switch (set->gsaVersion) { + case 0: + GBACheatSetGameSharkVersion(set, 3); + // Fall through + case 1: + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddProActionReplayRaw(set, o1, o2); + } + return false; +} + +bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddProActionReplay(cheats, op1, op2); +}
@@ -0,0 +1,15 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_CHEATS_PARV3_H +#define GBA_CHEATS_PARV3_H + +#include "gba/cheats.h" + +extern const uint32_t GBACheatProActionReplaySeeds[4]; + +bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2); + +#endif
@@ -87,8 +87,12 @@
gba->idleOptimization = IDLE_LOOP_REMOVE; gba->idleLoop = IDLE_LOOP_NONE; gba->lastJump = 0; + gba->haltPending = false; gba->idleDetectionStep = 0; gba->idleDetectionFailures = 0; + + gba->realisticTiming = false; + gba->performingDMA = false; }@@ -430,6 +434,7 @@ }
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) { gba->timers[timer].reload = reload; + gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << gba->timers[timer].prescaleBits; } void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {@@ -464,7 +469,7 @@ currentTimer->nextEvent = INT_MAX;
} gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload; currentTimer->oldReload = currentTimer->reload; - currentTimer->lastEvent = 0; + currentTimer->lastEvent = gba->cpu->cycles; gba->timersEnabled |= 1 << timer; } else if (wasEnabled && !currentTimer->enable) { if (!currentTimer->countUp) {@@ -525,7 +530,7 @@ }
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) { struct GBAThread* threadContext = GBAThreadGetContext(); - enum GBALogLevel logLevel = -1; + enum GBALogLevel logLevel = GBA_LOG_ALL; if (gba) { logLevel = gba->logLevel;@@ -628,10 +633,18 @@ return true;
} void GBAGetGameCode(struct GBA* gba, char* out) { + if (!gba->memory.rom) { + out[0] = '\0'; + return; + } memcpy(out, &((struct GBACartridge*) gba->memory.rom)->id, 4); } void GBAGetGameTitle(struct GBA* gba, char* out) { + if (!gba->memory.rom) { + strncpy(out, "(BIOS)", 12); + return; + } memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12); }@@ -730,14 +743,13 @@ }
} } + if (gba->stream) { + gba->stream->postVideoFrame(gba->stream, gba->video.renderer); + } struct GBAThread* thread = GBAThreadGetContext(); if (!thread) { return; - } - - if (gba->stream) { - gba->stream->postVideoFrame(gba->stream, gba->video.renderer); } if (thread->frameCallback) {
@@ -45,8 +45,10 @@ GBA_LOG_STUB = 0x20,
GBA_LOG_GAME_ERROR = 0x100, GBA_LOG_SWI = 0x200, + GBA_LOG_STATUS = 0x400, + GBA_LOG_SIO = 0x800, - GBA_LOG_ALL = 0x33F, + GBA_LOG_ALL = 0xF3F, #ifdef NDEBUG GBA_LOG_DANGER = GBA_LOG_ERROR@@ -99,6 +101,7 @@
struct GBAAVStream { void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right); + void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*); }; struct GBATimer {@@ -156,10 +159,13 @@
enum GBAIdleLoopOptimization idleOptimization; uint32_t idleLoop; uint32_t lastJump; + bool haltPending; int idleDetectionStep; int idleDetectionFailures; int32_t cachedRegisters[16]; bool taintedRegisters[16]; + + bool realisticTiming; }; struct GBACartridge {@@ -211,10 +217,10 @@
void GBAFrameStarted(struct GBA* gba); void GBAFrameEnded(struct GBA* gba); -__attribute__((format (printf, 3, 4))) +ATTRIBUTE_FORMAT(printf,3,4) void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...); -__attribute__((format (printf, 3, 4))) +ATTRIBUTE_FORMAT(printf, 3, 4) void GBADebuggerLogShim(struct ARMDebugger* debugger, enum DebuggerLogLevel level, const char* format, ...); #endif
@@ -7,8 +7,6 @@ #include "hardware.h"
#include "gba/serialize.h" -#include <time.h> - static void _readPins(struct GBACartridgeHardware* hw); static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);@@ -81,8 +79,8 @@
hw->rtc.bitsRead = 0; hw->rtc.bits = 0; hw->rtc.commandActive = 0; - hw->rtc.command.packed = 0; - hw->rtc.control.packed = 0x40; + hw->rtc.command = 0; + hw->rtc.control = 0x40; memset(hw->rtc.time, 0, sizeof(hw->rtc.time)); }@@ -139,14 +137,14 @@ hw->rtc.transferStep = 2;
} break; case 2: - if (!hw->p0) { + if (!(hw->pinState & 1)) { hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); - hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead; + hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead; } else { - if (hw->p2) { + if (hw->pinState & 4) { // GPIO direction should always != reading - if (hw->dir1) { - if (hw->rtc.command.reading) { + if (hw->direction & 2) { + if (RTCCommandDataIsReading(hw->rtc.command)) { GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode"); } ++hw->rtc.bitsRead;@@ -160,7 +158,7 @@ if (hw->rtc.bitsRead == 8) {
--hw->rtc.bytesRemaining; if (hw->rtc.bytesRemaining <= 0) { hw->rtc.commandActive = 0; - hw->rtc.command.reading = 0; + hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command); } hw->rtc.bitsRead = 0; }@@ -169,7 +167,7 @@ } else {
hw->rtc.bitsRead = 0; hw->rtc.bytesRemaining = 0; hw->rtc.commandActive = 0; - hw->rtc.command.reading = 0; + hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command); hw->rtc.transferStep = 0; } }@@ -180,16 +178,16 @@
void _rtcProcessByte(struct GBACartridgeHardware* hw) { --hw->rtc.bytesRemaining; if (!hw->rtc.commandActive) { - union RTCCommandData command; - command.packed = hw->rtc.bits; - if (command.magic == 0x06) { + RTCCommandData command; + command = hw->rtc.bits; + if (RTCCommandDataGetMagic(command) == 0x06) { hw->rtc.command = command; - hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command]; + hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)]; hw->rtc.commandActive = hw->rtc.bytesRemaining > 0; - switch (command.command) { + switch (RTCCommandDataGetCommand(command)) { case RTC_RESET: - hw->rtc.control.packed = 0; + hw->rtc.control = 0; break; case RTC_DATETIME: case RTC_TIME:@@ -203,12 +201,12 @@ } else {
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits); } } else { - switch (hw->rtc.command.command) { + switch (RTCCommandDataGetCommand(hw->rtc.command)) { case RTC_CONTROL: - hw->rtc.control.packed = hw->rtc.bits; + hw->rtc.control = hw->rtc.bits; break; case RTC_FORCE_IRQ: - GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command); + GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command)); break; case RTC_RESET: case RTC_DATETIME:@@ -221,15 +219,15 @@ hw->rtc.bits = 0;
hw->rtc.bitsRead = 0; if (!hw->rtc.bytesRemaining) { hw->rtc.commandActive = 0; - hw->rtc.command.reading = 0; + hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command); } } unsigned _rtcOutput(struct GBACartridgeHardware* hw) { uint8_t outputByte = 0; - switch (hw->rtc.command.command) { + switch (RTCCommandDataGetCommand(hw->rtc.command)) { case RTC_CONTROL: - outputByte = hw->rtc.control.packed; + outputByte = hw->rtc.control; break; case RTC_DATETIME: case RTC_TIME:@@ -262,7 +260,7 @@ hw->rtc.time[0] = _rtcBCD(date.tm_year - 100);
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1); hw->rtc.time[2] = _rtcBCD(date.tm_mday); hw->rtc.time[3] = _rtcBCD(date.tm_wday); - if (hw->rtc.control.hour24) { + if (RTCControlIsHour24(hw->rtc.control)) { hw->rtc.time[4] = _rtcBCD(date.tm_hour); } else { hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);@@ -288,11 +286,11 @@ }
void _gyroReadPins(struct GBACartridgeHardware* hw) { struct GBARotationSource* gyro = hw->p->rotationSource; - if (!gyro) { + if (!gyro || !gyro->readGyroZ) { return; } - if (hw->p0) { + if (hw->pinState & 1) { if (gyro->sample) { gyro->sample(gyro); }@@ -302,14 +300,14 @@ // Normalize to ~12 bits, focused on 0x6C0
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative } - if (hw->gyroEdge && !hw->p1) { + if (hw->gyroEdge && !(hw->pinState & 2)) { // Write bit on falling edge unsigned bit = hw->gyroSample >> 15; hw->gyroSample <<= 1; _outputPins(hw, bit << 2); } - hw->gyroEdge = hw->p1; + hw->gyroEdge = !!(hw->pinState & 2); } // == Rumble@@ -324,7 +322,7 @@ if (!rumble) {
return; } - rumble->setRumble(rumble, hw->p3); + rumble->setRumble(rumble, !!(hw->pinState & 8)); } // == Light sensor@@ -337,11 +335,11 @@ hw->lightSample = 0xFF;
} void _lightReadPins(struct GBACartridgeHardware* hw) { - if (hw->p2) { + if (hw->pinState & 4) { // Boktai chip select return; } - if (hw->p1) { + if (hw->pinState & 2) { struct GBALuminanceSource* lux = hw->p->luminanceSource; GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset"); hw->lightCounter = 0;@@ -352,10 +350,10 @@ } else {
hw->lightSample = 0xFF; } } - if (hw->p0 && hw->lightEdge) { + if ((hw->pinState & 1) && hw->lightEdge) { ++hw->lightCounter; } - hw->lightEdge = !hw->p0; + hw->lightEdge = !(hw->pinState & 1); bool sendBit = hw->lightCounter >= hw->lightSample; _outputPins(hw, sendBit << 3);
@@ -8,6 +8,10 @@ #define GBA_HARDWARE_H
#include "util/common.h" +#include "macros.h" + +#include <time.h> + #define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL) struct GBARotationSource {@@ -52,16 +56,10 @@ GPIO_WRITE_ONLY = 0,
GPIO_READ_WRITE = 1 }; -union RTCControl { - struct { - unsigned : 3; - unsigned minIRQ : 1; - unsigned : 2; - unsigned hour24 : 1; - unsigned poweroff : 1; - }; - uint8_t packed; -}; +DECL_BITFIELD(RTCControl, uint32_t); +DECL_BIT(RTCControl, MinIRQ, 3); +DECL_BIT(RTCControl, Hour24, 6); +DECL_BIT(RTCControl, Poweroff, 7); enum RTCCommand { RTC_RESET = 0,@@ -71,29 +69,29 @@ RTC_CONTROL = 4,
RTC_TIME = 6 }; -union RTCCommandData { - struct { - unsigned magic : 4; - enum RTCCommand command : 3; - unsigned reading : 1; - }; - uint8_t packed; -}; +DECL_BITFIELD(RTCCommandData, uint32_t); +DECL_BITS(RTCCommandData, Magic, 0, 4); +DECL_BITS(RTCCommandData, Command, 4, 3); +DECL_BIT(RTCCommandData, Reading, 7); +#pragma pack(push, 1) struct GBARTC { - int bytesRemaining; - int transferStep; - int bitsRead; - int bits; - int commandActive; - union RTCCommandData command; - union RTCControl control; + int32_t bytesRemaining; + int32_t transferStep; + int32_t bitsRead; + int32_t bits; + int32_t commandActive; + RTCCommandData command; + RTCControl control; uint8_t time[7]; -} __attribute__((packed)); +}; +#pragma pack(pop) struct GBARumble { void (*setRumble)(struct GBARumble*, int enable); }; + +DECL_BITFIELD(GPIOPin, uint16_t); struct GBACartridgeHardware { struct GBA* p;@@ -101,25 +99,8 @@ int devices;
enum GPIODirection readWrite; uint16_t* gpioBase; - union { - struct { - unsigned p0 : 1; - unsigned p1 : 1; - unsigned p2 : 1; - unsigned p3 : 1; - }; - uint16_t pinState; - }; - - union { - struct { - unsigned dir0 : 1; - unsigned dir1 : 1; - unsigned dir2 : 1; - unsigned dir3 : 1; - }; - uint16_t direction; - }; + uint16_t pinState; + uint16_t direction; struct GBARTC rtc;
@@ -24,7 +24,7 @@ };
struct GBAAxisSave { struct Configuration* config; - uint32_t type; + const char* sectionName; }; struct GBAAxisEnumerate {@@ -44,6 +44,11 @@ "Down",
"R", "L" }; + +static void _makeSectionName(char* sectionName, size_t len, uint32_t type) { + snprintf(sectionName, len, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[len - 1] = '\0'; +} static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) { const char* strValue = ConfigurationGetValue(config, section, key);@@ -90,7 +95,7 @@ map->maps = malloc(sizeof(*map->maps));
map->numMaps = 1; impl = &map->maps[0]; impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + impl->map = calloc(GBA_KEY_MAX, sizeof(int)); TableInit(&impl->axes, 2, free); } else { impl = _lookupMap(map, type);@@ -105,7 +110,7 @@ }
} if (impl) { impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + impl->map = calloc(GBA_KEY_MAX, sizeof(int)); } else { map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2); for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {@@ -115,18 +120,14 @@ }
map->numMaps *= 2; impl = &map->maps[m]; impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + impl->map = calloc(GBA_KEY_MAX, sizeof(int)); } TableInit(&impl->axes, 2, free); } return impl; } -static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - +static void _loadKey(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey key, const char* keyName) { char keyKey[KEY_NAME_MAX]; snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); keyKey[KEY_NAME_MAX - 1] = '\0';@@ -138,11 +139,7 @@ }
GBAInputBindKey(map, type, value, key); } -static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - +static void _loadAxis(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey direction, const char* axisName) { char axisKey[KEY_NAME_MAX]; snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName); axisKey[KEY_NAME_MAX - 1] = '\0';@@ -179,11 +176,7 @@ }
GBAInputBindAxis(map, type, axis, &realDescription); } -static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - +static void _saveKey(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config, enum GBAKey key, const char* keyName) { char keyKey[KEY_NAME_MAX]; snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); keyKey[KEY_NAME_MAX - 1] = '\0';@@ -195,11 +188,7 @@
ConfigurationSetValue(config, sectionName, keyKey, keyValue); } -static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - +static void _clearAxis(const char* sectionName, struct Configuration* config, const char* axisName) { char axisKey[KEY_NAME_MAX]; snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName); axisKey[KEY_NAME_MAX - 1] = '\0';@@ -214,10 +203,7 @@ static void _saveAxis(uint32_t axis, void* dp, void* up) {
struct GBAAxisSave* user = up; const struct GBAAxis* description = dp; - uint32_t type = user->type; - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; + const char* sectionName = user->sectionName; if (description->lowDirection != GBA_KEY_NONE) { const char* keyName = GBAKeyNames[description->lowDirection];@@ -271,6 +257,64 @@ description->lowDirection = GBA_KEY_NONE;
} } +static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) { + _loadKey(map, type, sectionName, config, GBA_KEY_A, "A"); + _loadKey(map, type, sectionName, config, GBA_KEY_B, "B"); + _loadKey(map, type, sectionName, config, GBA_KEY_L, "L"); + _loadKey(map, type, sectionName, config, GBA_KEY_R, "R"); + _loadKey(map, type, sectionName, config, GBA_KEY_START, "Start"); + _loadKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select"); + _loadKey(map, type, sectionName, config, GBA_KEY_UP, "Up"); + _loadKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down"); + _loadKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left"); + _loadKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right"); + + _loadAxis(map, type, sectionName, config, GBA_KEY_A, "A"); + _loadAxis(map, type, sectionName, config, GBA_KEY_B, "B"); + _loadAxis(map, type, sectionName, config, GBA_KEY_L, "L"); + _loadAxis(map, type, sectionName, config, GBA_KEY_R, "R"); + _loadAxis(map, type, sectionName, config, GBA_KEY_START, "Start"); + _loadAxis(map, type, sectionName, config, GBA_KEY_SELECT, "Select"); + _loadAxis(map, type, sectionName, config, GBA_KEY_UP, "Up"); + _loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down"); + _loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left"); + _loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right"); +} + +static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) { + _saveKey(map, type, sectionName, config, GBA_KEY_A, "A"); + _saveKey(map, type, sectionName, config, GBA_KEY_B, "B"); + _saveKey(map, type, sectionName, config, GBA_KEY_L, "L"); + _saveKey(map, type, sectionName, config, GBA_KEY_R, "R"); + _saveKey(map, type, sectionName, config, GBA_KEY_START, "Start"); + _saveKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select"); + _saveKey(map, type, sectionName, config, GBA_KEY_UP, "Up"); + _saveKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down"); + _saveKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left"); + _saveKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right"); + + _clearAxis(sectionName, config, "A"); + _clearAxis(sectionName, config, "B"); + _clearAxis(sectionName, config, "L"); + _clearAxis(sectionName, config, "R"); + _clearAxis(sectionName, config, "Start"); + _clearAxis(sectionName, config, "Select"); + _clearAxis(sectionName, config, "Up"); + _clearAxis(sectionName, config, "Down"); + _clearAxis(sectionName, config, "Left"); + _clearAxis(sectionName, config, "Right"); + + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return; + } + struct GBAAxisSave save = { + config, + sectionName + }; + TableEnumerate(&impl->axes, _saveAxis, &save); +} + void GBAInputMapInit(struct GBAInputMap* map) { map->maps = 0; map->numMaps = 0;@@ -414,59 +458,68 @@ TableEnumerate(&impl->axes, _enumerateAxis, &enumUser);
} void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) { - _loadKey(map, type, config, GBA_KEY_A, "A"); - _loadKey(map, type, config, GBA_KEY_B, "B"); - _loadKey(map, type, config, GBA_KEY_L, "L"); - _loadKey(map, type, config, GBA_KEY_R, "R"); - _loadKey(map, type, config, GBA_KEY_START, "Start"); - _loadKey(map, type, config, GBA_KEY_SELECT, "Select"); - _loadKey(map, type, config, GBA_KEY_UP, "Up"); - _loadKey(map, type, config, GBA_KEY_DOWN, "Down"); - _loadKey(map, type, config, GBA_KEY_LEFT, "Left"); - _loadKey(map, type, config, GBA_KEY_RIGHT, "Right"); + char sectionName[SECTION_NAME_MAX]; + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + _loadAll(map, type, sectionName, config); +} - _loadAxis(map, type, config, GBA_KEY_A, "A"); - _loadAxis(map, type, config, GBA_KEY_B, "B"); - _loadAxis(map, type, config, GBA_KEY_L, "L"); - _loadAxis(map, type, config, GBA_KEY_R, "R"); - _loadAxis(map, type, config, GBA_KEY_START, "Start"); - _loadAxis(map, type, config, GBA_KEY_SELECT, "Select"); - _loadAxis(map, type, config, GBA_KEY_UP, "Up"); - _loadAxis(map, type, config, GBA_KEY_DOWN, "Down"); - _loadAxis(map, type, config, GBA_KEY_LEFT, "Left"); - _loadAxis(map, type, config, GBA_KEY_RIGHT, "Right"); +void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) { + char sectionName[SECTION_NAME_MAX]; + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + _saveAll(map, type, sectionName, config); } -void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) { - _saveKey(map, type, config, GBA_KEY_A, "A"); - _saveKey(map, type, config, GBA_KEY_B, "B"); - _saveKey(map, type, config, GBA_KEY_L, "L"); - _saveKey(map, type, config, GBA_KEY_R, "R"); - _saveKey(map, type, config, GBA_KEY_START, "Start"); - _saveKey(map, type, config, GBA_KEY_SELECT, "Select"); - _saveKey(map, type, config, GBA_KEY_UP, "Up"); - _saveKey(map, type, config, GBA_KEY_DOWN, "Down"); - _saveKey(map, type, config, GBA_KEY_LEFT, "Left"); - _saveKey(map, type, config, GBA_KEY_RIGHT, "Right"); +void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + _loadAll(map, type, sectionName, config); +} + +void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + _saveAll(map, type, sectionName, config); +} + +const char* GBAInputGetPreferredDevice(const struct Configuration* config, uint32_t type, int playerId) { + char sectionName[SECTION_NAME_MAX]; + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + + char deviceId[KEY_NAME_MAX]; + snprintf(deviceId, sizeof(deviceId), "device%i", playerId); + return ConfigurationGetValue(config, sectionName, deviceId); +} + +void GBAInputSetPreferredDevice(struct Configuration* config, uint32_t type, int playerId, const char* deviceName) { + char sectionName[SECTION_NAME_MAX]; + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + + char deviceId[KEY_NAME_MAX]; + snprintf(deviceId, sizeof(deviceId), "device%i", playerId); + return ConfigurationSetValue(config, sectionName, deviceId, deviceName); +} - _clearAxis(type, config, "A"); - _clearAxis(type, config, "B"); - _clearAxis(type, config, "L"); - _clearAxis(type, config, "R"); - _clearAxis(type, config, "Start"); - _clearAxis(type, config, "Select"); - _clearAxis(type, config, "Up"); - _clearAxis(type, config, "Down"); - _clearAxis(type, config, "Left"); - _clearAxis(type, config, "Right"); +const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + if (profile) { + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + const char* value = ConfigurationGetValue(config, sectionName, key); + if (value) { + return value; + } + } + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + return ConfigurationGetValue(config, sectionName, key); +} - const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); - if (!impl) { - return; +void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + if (profile) { + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + ConfigurationSetValue(config, sectionName, key, value); } - struct GBAAxisSave save = { - config, - type - }; - TableEnumerate(&impl->axes, _saveAxis, &save); + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + ConfigurationSetValue(config, sectionName, key, value); }
@@ -45,4 +45,13 @@
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*); void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*); +void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile); +void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile); + +const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId); +void GBAInputSetPreferredDevice(struct Configuration*, uint32_t type, int playerId, const char* deviceName); + +const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile); +void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile); + #endif
@@ -286,6 +286,10 @@ gba->memory.io[REG_DISPCNT >> 1] = 0x0080;
gba->memory.io[REG_RCNT >> 1] = RCNT_INITIAL; gba->memory.io[REG_KEYINPUT >> 1] = 0x3FF; gba->memory.io[REG_SOUNDBIAS >> 1] = 0x200; + gba->memory.io[REG_BG2PA >> 1] = 0x100; + gba->memory.io[REG_BG2PD >> 1] = 0x100; + gba->memory.io[REG_BG3PA >> 1] = 0x100; + gba->memory.io[REG_BG3PD >> 1] = 0x100; } void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {@@ -366,23 +370,14 @@ case REG_WAVE_RAM3_HI:
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); break; - // TODO: Confirm this behavior on real hardware case REG_FIFO_A_LO: case REG_FIFO_B_LO: - if (gba->performingDMA) { - GBAAudioWriteFIFO16(&gba->audio, address, value); - } else { - GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); - } + GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); break; case REG_FIFO_A_HI: case REG_FIFO_B_HI: - if (gba->performingDMA) { - GBAAudioWriteFIFO16(&gba->audio, address, value); - } else { - GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); - } + GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); break; // DMA@@ -494,6 +489,10 @@ // Some bad interrupt libraries will write to this
break; default: GBALog(gba, GBA_LOG_STUB, "Stub I/O register write: %03x", address); + if (address >= REG_MAX) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Write to unused I/O register: %03X", address); + return; + } break; } }@@ -567,7 +566,7 @@ gba->memory.io[(address >> 1) + 1] = value >> 16;
} uint16_t GBAIORead(struct GBA* gba, uint32_t address) { - gba->lastJump = -1; // IO reads need to invalidate detected idle loops + gba->haltPending = false; // IO reads need to invalidate detected idle loops switch (address) { case REG_TM0CNT_LO: GBATimerUpdateRegister(gba, 0);@@ -648,6 +647,10 @@ // Some bad interrupt libraries will read from this
break; default: GBALog(gba, GBA_LOG_STUB, "Stub I/O register read: %03x", address); + if (address >= REG_MAX) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Read from unused I/O register: %03X", address); + return 0; // TODO: Reuse LOAD_BAD + } break; } return gba->memory.io[address >> 1];@@ -701,5 +704,6 @@ if (gba->timers[i].enable) {
gba->timersEnabled |= 1 << i; } } + GBAMemoryUpdateDMAs(gba, 0); GBAHardwareDeserialize(&gba->memory.hw, state); }
@@ -17,6 +17,7 @@
#define IDLE_LOOP_THRESHOLD 10000 static uint32_t _popcount32(unsigned bits); +static void _pristineCow(struct GBA* gba); static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region);@@ -46,6 +47,7 @@ gba->memory.fullBios = 0;
gba->memory.wram = 0; gba->memory.iwram = 0; gba->memory.rom = 0; + gba->memory.romSize = 0; gba->memory.hw.p = gba; int i;@@ -197,8 +199,13 @@ struct GBAMemory* memory = &gba->memory;
int newRegion = address >> BASE_OFFSET; if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) { - if (address == gba->lastJump && address == gba->idleLoop) { - GBAHalt(gba); + if (address == gba->idleLoop) { + if (gba->haltPending) { + gba->haltPending = false; + GBAHalt(gba); + } else { + gba->haltPending = true; + } } else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) { if (address == gba->lastJump) { switch (gba->idleDetectionStep) {@@ -225,7 +232,7 @@ }
} gba->lastJump = address; - if (newRegion == memory->activeRegion) { + if (newRegion == memory->activeRegion && (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize)) { return; }@@ -233,33 +240,37 @@ if (memory->activeRegion == REGION_BIOS) {
memory->biosPrefetch = cpu->prefetch[1]; } memory->activeRegion = newRegion; - switch (address & ~OFFSET_MASK) { - case BASE_BIOS: + switch (newRegion) { + case REGION_BIOS: cpu->memory.activeRegion = memory->bios; cpu->memory.activeMask = SIZE_BIOS - 1; break; - case BASE_WORKING_RAM: + case REGION_WORKING_RAM: cpu->memory.activeRegion = memory->wram; cpu->memory.activeMask = SIZE_WORKING_RAM - 1; break; - case BASE_WORKING_IRAM: + case REGION_WORKING_IRAM: cpu->memory.activeRegion = memory->iwram; cpu->memory.activeMask = SIZE_WORKING_IRAM - 1; break; - case BASE_VRAM: + case REGION_VRAM: cpu->memory.activeRegion = (uint32_t*) gba->video.renderer->vram; cpu->memory.activeMask = 0x0000FFFF; break; - case BASE_CART0: - case BASE_CART0_EX: - case BASE_CART1: - case BASE_CART1_EX: - case BASE_CART2: - case BASE_CART2_EX: + case REGION_CART0: + case REGION_CART0_EX: + case REGION_CART1: + case REGION_CART1_EX: + case REGION_CART2: + case REGION_CART2_EX: cpu->memory.activeRegion = memory->rom; cpu->memory.activeMask = SIZE_CART0 - 1; - break; + if ((address & (SIZE_CART0 - 1)) < memory->romSize) { + break; + } + // Fall through default: + memory->activeRegion = 0; cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");@@ -274,13 +285,30 @@ cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[memory->activeRegion];
} #define LOAD_BAD \ - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \ if (gba->performingDMA) { \ value = gba->bus; \ } else { \ value = cpu->prefetch[1]; \ if (cpu->executionMode == MODE_THUMB) { \ - value |= value << 16; \ + /* http://ngemu.com/threads/gba-open-bus.170809/ */ \ + switch (cpu->gprs[ARM_PC] >> BASE_OFFSET) { \ + case REGION_BIOS: \ + case REGION_OAM: \ + /* This isn't right half the time, but we don't have $+6 handy */ \ + value <<= 16; \ + value |= cpu->prefetch[0]; \ + break; \ + case REGION_WORKING_IRAM: \ + /* This doesn't handle prefetch clobbering */ \ + if (cpu->gprs[ARM_PC] & 2) { \ + value |= cpu->prefetch[0] << 16; \ + } else { \ + value <<= 16; \ + value |= cpu->prefetch[0]; \ + } \ + default: \ + value |= value << 16; \ + } \ } \ }@@ -293,38 +321,39 @@ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load32: 0x%08X", address); \
value = memory->biosPrefetch; \ } \ } else { \ + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \ LOAD_BAD; \ } #define LOAD_WORKING_RAM \ - LOAD_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); \ + LOAD_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram); \ wait += waitstatesRegion[REGION_WORKING_RAM]; -#define LOAD_WORKING_IRAM LOAD_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); +#define LOAD_WORKING_IRAM LOAD_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram); #define LOAD_IO value = GBAIORead(gba, (address & (SIZE_IO - 1)) & ~2) | (GBAIORead(gba, (address & (SIZE_IO - 1)) | 2) << 16); #define LOAD_PALETTE_RAM \ - LOAD_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); \ + LOAD_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ ++wait; #define LOAD_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ - LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \ + LOAD_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ } else { \ - LOAD_32(value, address & 0x00017FFF, gba->video.renderer->vram); \ + LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ } \ ++wait; -#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw); +#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); #define LOAD_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \ - LOAD_32(value, address & (SIZE_CART0 - 1), memory->rom); \ + LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \ } else { \ GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ value = (address >> 1) & 0xFFFF; \ - value |= value << 16; \ + value |= ((address + 2) >> 1) << 16; \ } #define LOAD_SRAM \@@ -375,12 +404,13 @@ case REGION_CART_SRAM_MIRROR:
LOAD_SRAM; break; default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); LOAD_BAD; break; } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } // Unaligned 32-bit loads are "rotated" so they make some semblance of sense int rotate = (address & 3) << 3;@@ -404,39 +434,33 @@ LOAD_16(value, address & 2, &memory->biosPrefetch);
} } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (gba->performingDMA) { - LOAD_16(value, address & 2, &gba->bus); - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - LOAD_16(value, address & 2, &prefetch); - } + LOAD_BAD; + uint32_t v2 = value; + LOAD_16(value, address & 2, &v2); } break; case REGION_WORKING_RAM: - LOAD_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + LOAD_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram); wait = memory->waitstatesNonseq16[REGION_WORKING_RAM]; break; case REGION_WORKING_IRAM: - LOAD_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + LOAD_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram); break; case REGION_IO: - value = GBAIORead(gba, address & (SIZE_IO - 1)); + value = GBAIORead(gba, address & (SIZE_IO - 2)); break; case REGION_PALETTE_RAM: - LOAD_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); + LOAD_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette); break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + LOAD_16(value, address & 0x0001FFFE, gba->video.renderer->vram); } else { - LOAD_16(value, address & 0x00017FFF, gba->video.renderer->vram); + LOAD_16(value, address & 0x00017FFE, gba->video.renderer->vram); } break; case REGION_OAM: - LOAD_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw); + LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw); break; case REGION_CART0: case REGION_CART0_EX:@@ -445,7 +469,7 @@ case REGION_CART1_EX:
case REGION_CART2: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { - LOAD_16(value, address & (SIZE_CART0 - 1), memory->rom); + LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; \@@ -456,7 +480,7 @@ wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if (memory->savedata.type == SAVEDATA_EEPROM) { value = GBASavedataReadEEPROM(&memory->savedata); } else if ((address & (SIZE_CART0 - 1)) < memory->romSize) { - LOAD_16(value, address & (SIZE_CART0 - 1), memory->rom); + LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; \@@ -470,20 +494,14 @@ value |= value << 8;
break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (gba->performingDMA) { - LOAD_16(value, address & 2, &gba->bus); - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - LOAD_16(value, address & 2, &prefetch); - } + LOAD_BAD; + uint32_t v2 = value; + LOAD_16(value, address & 2, &v2); break; } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } // Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too. int rotate = (address & 1) << 3;@@ -493,49 +511,42 @@
uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; - uint8_t value = 0; + uint32_t value = 0; int wait = 0; switch (address >> BASE_OFFSET) { case REGION_BIOS: if (address < SIZE_BIOS) { if (memory->activeRegion == REGION_BIOS) { - value = ((int8_t*) memory->bios)[address]; + value = ((uint8_t*) memory->bios)[address]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address); value = ((uint8_t*) &memory->biosPrefetch)[address & 3]; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (gba->performingDMA) { - value = ((uint8_t*) &gba->bus)[address & 3]; - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - value = ((uint8_t*) &prefetch)[address & 3]; - } + LOAD_BAD; + value = ((uint8_t*) &value)[address & 3]; } break; case REGION_WORKING_RAM: - value = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)]; + value = ((uint8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)]; wait = memory->waitstatesNonseq16[REGION_WORKING_RAM]; break; case REGION_WORKING_IRAM: - value = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)]; + value = ((uint8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)]; break; case REGION_IO: value = (GBAIORead(gba, address & 0xFFFE) >> ((address & 0x0001) << 3)) & 0xFF; break; case REGION_PALETTE_RAM: - value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)]; + value = ((uint8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)]; break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; + value = ((uint8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; } else { - value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; + value = ((uint8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; } break; case REGION_OAM:@@ -549,7 +560,7 @@ case REGION_CART2:
case REGION_CART2_EX: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { - value = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; + value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); value = (address >> 1) & 0xFF; \@@ -572,57 +583,52 @@ } else {
GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address); value = 0xFF; } + value &= 0xFF; break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (gba->performingDMA) { - value = ((uint8_t*) &gba->bus)[address & 3]; - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - value = ((uint8_t*) &prefetch)[address & 3]; - } + LOAD_BAD; + value = ((uint8_t*) &value)[address & 3]; break; } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } return value; } #define STORE_WORKING_RAM \ - STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); \ + STORE_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram); \ wait += waitstatesRegion[REGION_WORKING_RAM]; #define STORE_WORKING_IRAM \ - STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + STORE_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram); #define STORE_IO \ - GBAIOWrite32(gba, address & (SIZE_IO - 1), value); + GBAIOWrite32(gba, address & (SIZE_IO - 4), value); #define STORE_PALETTE_RAM \ - STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); \ - gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16); \ + STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ + gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); \ ++wait; \ - gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value); #define STORE_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ - STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \ + STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ } else { \ - STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); \ + STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ } \ ++wait; #define STORE_OAM \ - STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw); \ + STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); \ gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 4)) >> 1); \ gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) >> 1) + 1); #define STORE_CART \ + wait += waitstatesRegion[address >> BASE_OFFSET]; \ GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address); #define STORE_SRAM \@@ -685,33 +691,33 @@ int wait = 0;
switch (address >> BASE_OFFSET) { case REGION_WORKING_RAM: - STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + STORE_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram); wait = memory->waitstatesNonseq16[REGION_WORKING_RAM]; break; case REGION_WORKING_IRAM: - STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + STORE_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram); break; case REGION_IO: - GBAIOWrite(gba, address & (SIZE_IO - 1), value); + GBAIOWrite(gba, address & (SIZE_IO - 2), value); break; case REGION_PALETTE_RAM: - STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); - gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); + STORE_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value); break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); } else { - STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram); + STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); } break; case REGION_OAM: - STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw); - gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); + STORE_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw); + gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 2)) >> 1); break; case REGION_CART0: - if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFF)) { - uint32_t reg = address & 0xFFFFFF; + if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFE)) { + uint32_t reg = address & 0xFFFFFE; GBAHardwareGPIOWrite(&memory->hw, reg, value); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad cartridge Store16: 0x%08X", address);@@ -777,7 +783,7 @@ case REGION_CART_SRAM_MIRROR:
if (memory->savedata.type == SAVEDATA_AUTODETECT) { if (address == SAVEDATA_FLASH_BASE) { GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame"); - GBASavedataInitFlash(&memory->savedata); + GBASavedataInitFlash(&memory->savedata, gba->realisticTiming); } else { GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame"); GBASavedataInitSRAM(&memory->savedata);@@ -811,36 +817,36 @@ int32_t oldValue = -1;
switch (address >> BASE_OFFSET) { case REGION_WORKING_RAM: - LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram); - STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 4), memory->wram); + STORE_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram); break; case REGION_WORKING_IRAM: - LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram); - STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 4), memory->iwram); + STORE_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram); break; case REGION_IO: GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch32: 0x%08X", address); break; case REGION_PALETTE_RAM: LOAD_32(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); - STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); - gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); - gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16); + STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value); + gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_32(oldValue, address & 0x0001FFFF, gba->video.renderer->vram); - STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); + LOAD_32(oldValue, address & 0x0001FFFC, gba->video.renderer->vram); + STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); } else { - LOAD_32(oldValue, address & 0x00017FFF, gba->video.renderer->vram); - STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); + LOAD_32(oldValue, address & 0x00017FFC, gba->video.renderer->vram); + STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); } break; case REGION_OAM: - LOAD_32(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw); - STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw); - gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); - gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 1)) + 2) >> 1); + LOAD_32(oldValue, address & (SIZE_OAM - 4), gba->video.oam.raw); + STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); + gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 4)) >> 1); + gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) + 2) >> 1); break; case REGION_CART0: case REGION_CART0_EX:@@ -848,18 +854,18 @@ case REGION_CART1:
case REGION_CART1_EX: case REGION_CART2: case REGION_CART2_EX: - if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { - LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); - STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom); - } else { - GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address); + _pristineCow(gba); + if ((address & (SIZE_CART0 - 4)) >= gba->memory.romSize) { + gba->memory.romSize = (address & (SIZE_CART0 - 4)) + 4; } + LOAD_32(oldValue, address & (SIZE_CART0 - 4), gba->memory.rom); + STORE_32(value, address & (SIZE_CART0 - 4), gba->memory.rom); break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: if (memory->savedata.type == SAVEDATA_SRAM) { - LOAD_32(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data); - STORE_32(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + LOAD_32(oldValue, address & (SIZE_CART_SRAM - 4), memory->savedata.data); + STORE_32(value, address & (SIZE_CART_SRAM - 4), memory->savedata.data); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); }@@ -880,34 +886,34 @@ int16_t oldValue = -1;
switch (address >> BASE_OFFSET) { case REGION_WORKING_RAM: - LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram); - STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 2), memory->wram); + STORE_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram); break; case REGION_WORKING_IRAM: - LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram); - STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 2), memory->iwram); + STORE_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram); break; case REGION_IO: GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch16: 0x%08X", address); break; case REGION_PALETTE_RAM: - LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); - STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); - gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); + LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 2), gba->video.palette); + STORE_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value); break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_16(oldValue, address & 0x0001FFFF, gba->video.renderer->vram); - STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + LOAD_16(oldValue, address & 0x0001FFFE, gba->video.renderer->vram); + STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); } else { - LOAD_16(oldValue, address & 0x00017FFF, gba->video.renderer->vram); - STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram); + LOAD_16(oldValue, address & 0x00017FFE, gba->video.renderer->vram); + STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); } break; case REGION_OAM: - LOAD_16(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw); - STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw); - gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); + LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw); + STORE_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw); + gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 2)) >> 1); break; case REGION_CART0: case REGION_CART0_EX:@@ -915,18 +921,18 @@ case REGION_CART1:
case REGION_CART1_EX: case REGION_CART2: case REGION_CART2_EX: - if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { - LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); - STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom); - } else { - GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address); + _pristineCow(gba); + if ((address & (SIZE_CART0 - 1)) >= gba->memory.romSize) { + gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2; } + LOAD_16(oldValue, address & (SIZE_CART0 - 2), gba->memory.rom); + STORE_16(value, address & (SIZE_CART0 - 2), gba->memory.rom); break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: if (memory->savedata.type == SAVEDATA_SRAM) { - LOAD_16(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data); - STORE_16(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + LOAD_16(oldValue, address & (SIZE_CART_SRAM - 2), memory->savedata.data); + STORE_16(value, address & (SIZE_CART_SRAM - 2), memory->savedata.data); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); }@@ -940,6 +946,63 @@ *old = oldValue;
} } +void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old) { + struct GBA* gba = (struct GBA*) cpu->master; + struct GBAMemory* memory = &gba->memory; + int8_t oldValue = -1; + + switch (address >> BASE_OFFSET) { + case REGION_WORKING_RAM: + oldValue = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)]; + ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)] = value; + break; + case REGION_WORKING_IRAM: + oldValue = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)]; + ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)] = value; + break; + case REGION_IO: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address); + break; + case REGION_PALETTE_RAM: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address); + break; + case REGION_VRAM: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address); + break; + case REGION_OAM: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address); + break; + case REGION_CART0: + case REGION_CART0_EX: + case REGION_CART1: + case REGION_CART1_EX: + case REGION_CART2: + case REGION_CART2_EX: + _pristineCow(gba); + if ((address & (SIZE_CART0 - 1)) >= gba->memory.romSize) { + gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2; + } + oldValue = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; + ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)] = value; + break; + case REGION_CART_SRAM: + case REGION_CART_SRAM_MIRROR: + if (memory->savedata.type == SAVEDATA_SRAM) { + oldValue = ((int8_t*) memory->savedata.data)[address & (SIZE_CART_SRAM - 1)]; + ((int8_t*) memory->savedata.data)[address & (SIZE_CART_SRAM - 1)] = value; + } else { + GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); + } + break; + default: + GBALog(gba, GBA_LOG_WARN, "Bad memory Patch8: 0x%08X", address); + break; + } + if (old) { + *old = oldValue; + } +} + #define LDM_LOOP(LDM) \ for (i = 0; i < 16; i += 4) { \ if (UNLIKELY(mask & (1 << i))) { \@@ -1180,9 +1243,9 @@ memory->waitstatesSeq16[REGION_CART0] = memory->waitstatesSeq16[REGION_CART0_EX] = GBA_ROM_WAITSTATES_SEQ[ws0seq];
memory->waitstatesSeq16[REGION_CART1] = memory->waitstatesSeq16[REGION_CART1_EX] = GBA_ROM_WAITSTATES_SEQ[ws1seq + 2]; memory->waitstatesSeq16[REGION_CART2] = memory->waitstatesSeq16[REGION_CART2_EX] = GBA_ROM_WAITSTATES_SEQ[ws2seq + 4]; - memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesSeq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0]; - memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesSeq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1]; - memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesSeq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2]; + memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0]; + memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1]; + memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2]; memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0_EX] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1; memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1_EX] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1;@@ -1476,3 +1539,12 @@ bits = bits - ((bits >> 1) & 0x55555555);
bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333); return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; } + +void _pristineCow(struct GBA* gba) { + if (gba->memory.rom != gba->pristineRom) { + return; + } + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); + memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize); + memset(((uint8_t*) gba->memory.rom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize); +}
@@ -61,7 +61,7 @@ SIZE_OAM = 0x00000400,
SIZE_CART0 = 0x02000000, SIZE_CART1 = 0x02000000, SIZE_CART2 = 0x02000000, - SIZE_CART_SRAM = 0x00008000, + SIZE_CART_SRAM = 0x00010000, SIZE_CART_FLASH512 = 0x00010000, SIZE_CART_FLASH1M = 0x00020000, SIZE_CART_EEPROM = 0x00002000@@ -154,6 +154,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter);
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old); void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old); +void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old); uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);
@@ -87,6 +87,12 @@ renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame; renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels; renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels; + + renderer->d.disableBG[0] = false; + renderer->d.disableBG[1] = false; + renderer->d.disableBG[2] = false; + renderer->d.disableBG[3] = false; + renderer->d.disableOBJ = false; } static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {@@ -522,6 +528,8 @@ } else {
softwareRenderer->windows[0].control.packed = 0xFF; } + GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); + int w; x = 0; for (w = 0; w < softwareRenderer->nWindows; ++w) {@@ -560,7 +568,7 @@ }
} #ifdef COLOR_16_BIT -#ifdef __arm__ +#ifdef __ARM_NEON _to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS); #else for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {@@ -599,10 +607,10 @@ }
} static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) { - renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt); - renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt); - renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt); - renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt); + renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0]; + renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1]; + renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2]; + renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3]; } static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {@@ -691,7 +699,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
int w; renderer->end = 0; int spriteLayers = 0; - if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt)) { + if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) { if (renderer->oamDirty) { _cleanOAM(renderer); }@@ -973,23 +981,27 @@ } else { \
BACKGROUND_TEXT_SELECT_CHARACTER; \ } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= 4 * baseX; \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ } else { \ - tileData >>= 4 * (7 - baseX); \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * baseX; \ + } else { \ + tileData >>= 4 * (7 - baseX); \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 12; \ + tileData |= tileData << 16; \ + tileData |= tileData << 20; \ + tileData |= tileData << 24; \ + tileData |= tileData << 28; \ + carryData = tileData; \ } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ } \ for (; length; ++tileX) { \ BACKGROUND_TEXT_SELECT_CHARACTER; \@@ -997,23 +1009,27 @@ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
tileData = carryData; \ for (; x < 8 && length; ++x, --length) { \ if (!mosaicWait) { \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= x * 4; \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ } else { \ - tileData >>= (7 - x) * 4; \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= x * 4; \ + } else { \ + tileData >>= (7 - x) * 4; \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 12; \ + tileData |= tileData << 16; \ + tileData |= tileData << 20; \ + tileData |= tileData << 24; \ + tileData |= tileData << 28; \ + carryData = tileData; \ } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ mosaicWait = mosaicH; \ } \ --mosaicWait; \@@ -1239,25 +1255,29 @@ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
tileData = carryData; \ for (x = 0; x < 8; ++x) { \ if (!mosaicWait) { \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (x - 4) * 8; \ - } else { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= x * 8; \ - } \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ } else { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= (7 - x) * 8; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (x - 4) * 8; \ + } else { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= x * 8; \ + } \ } else { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (3 - x) * 8; \ + if (x >= 4) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= (7 - x) * 8; \ + } else { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (3 - x) * 8; \ + } \ } \ + tileData &= 0xFF; \ + carryData = tileData; \ } \ - tileData &= 0xFF; \ - carryData = tileData; \ mosaicWait = mosaicH; \ } \ tileData |= tileData << 8; \@@ -1526,6 +1546,12 @@ color32 |= (color << 3) & 0xF8;
color32 |= (color << 6) & 0xF800; color32 |= (color << 9) & 0xF80000; color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; #endif mosaicWait = mosaicH; } else {@@ -1602,13 +1628,19 @@ for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
BACKGROUND_BITMAP_ITERATE(160, 128); if (!mosaicWait) { - LOAD_16(color, (offset + (localX >> 8) + (localY >> 8) * 160) << 1, renderer->d.vram); + LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); #ifndef COLOR_16_BIT unsigned color32 = 0; color32 |= (color << 9) & 0xF80000; color32 |= (color << 3) & 0xF8; color32 |= (color << 6) & 0xF800; color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; #endif mosaicWait = mosaicH; } else {@@ -1691,7 +1723,7 @@ #define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4; #define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ current = renderer->spriteLayer[outX]; \ if ((current & FLAG_ORDER_MASK) > flags) { \@@ -1703,7 +1735,7 @@ } \
} #define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ if (tileData) { \ renderer->row[outX] |= FLAG_OBJWIN; \@@ -1713,7 +1745,7 @@ #define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; #define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ current = renderer->spriteLayer[outX]; \ if ((current & FLAG_ORDER_MASK) > flags) { \@@ -1725,7 +1757,7 @@ } \
} #define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ if (tileData) { \ renderer->row[outX] |= FLAG_OBJWIN; \@@ -1744,9 +1776,15 @@ x >>= 23;
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT && renderer->target2Bd) { - // Hack: if a sprite is blended, then the variant palette is not used, but we don't know if it's blended in advance - variant = 0; + if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { + int target2 = renderer->target2Bd << 4; + target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); + target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); + target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); + target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); + if (GBAObjAttributesCGetPriority(sprite->c) < target2) { + variant = 0; + } } color_t* palette = &renderer->normalPalette[0x100]; if (variant) {@@ -1845,6 +1883,9 @@ bool objwinOnly = false;
if (objwinSlowPath) { objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed); objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed); + if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; + } if (objwinDisable) { for (x = renderer->start; x < renderer->end; ++x, ++pixel) {@@ -1874,6 +1915,8 @@ }
} return; } + } else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; } for (x = renderer->start; x < renderer->end; ++x, ++pixel) { uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
@@ -94,7 +94,12 @@ return false;
} void GBAVBMNextFrame(struct GBARRContext* rr) { - UNUSED(rr); + if (!rr->isPlaying(rr)) { + return; + } + + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR); } uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {@@ -105,6 +110,7 @@
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; uint16_t input; vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input)); + vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR); return input & 0x3FF; }
@@ -6,12 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "savedata.h" #include "gba/gba.h" +#include "gba/serialize.h" #include "util/memory.h" #include "util/vfs.h" #include <errno.h> #include <fcntl.h> + +#define FLASH_SETTLE_CYCLES 18000 static void _flashSwitchBank(struct GBASavedata* savedata, int bank); static void _flashErase(struct GBASavedata* savedata);@@ -112,7 +115,7 @@ }
return true; } -void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) { +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) { if (savedata->type != SAVEDATA_AUTODETECT) { struct VFile* vf = savedata->vf; GBASavedataDeinit(savedata);@@ -122,7 +125,7 @@ switch (type) {
case SAVEDATA_FLASH512: case SAVEDATA_FLASH1M: savedata->type = type; - GBASavedataInitFlash(savedata); + GBASavedataInitFlash(savedata, realisticTiming); break; case SAVEDATA_EEPROM: GBASavedataInitEEPROM(savedata);@@ -138,7 +141,7 @@ break;
} } -void GBASavedataInitFlash(struct GBASavedata* savedata) { +void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) { if (savedata->type == SAVEDATA_AUTODETECT) { savedata->type = SAVEDATA_FLASH512; }@@ -146,14 +149,17 @@ if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) {
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata"); return; } - size_t flashSize = SIZE_CART_FLASH512; + int32_t flashSize = SIZE_CART_FLASH512; + if (savedata->type == SAVEDATA_FLASH1M) { + flashSize = SIZE_CART_FLASH1M; + } off_t end; if (!savedata->vf) { end = 0; savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M); } else { end = savedata->vf->size(savedata->vf); - if (end < SIZE_CART_FLASH512) { + if (end < flashSize) { savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); flashSize = SIZE_CART_FLASH1M; }@@ -161,6 +167,8 @@ savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
} savedata->currentBank = savedata->data; + savedata->dust = 0; + savedata->realisticTiming = realisticTiming; if (end < SIZE_CART_FLASH512) { memset(&savedata->data[end], 0xFF, flashSize - end); }@@ -225,6 +233,10 @@ return FLASH_MFG_SANYO >> (address * 8);
} } } + if (savedata->dust > 0 && (address >> 12) == savedata->settling) { + --savedata->dust; + return 0x5F; + } return savedata->currentBank[address]; }@@ -323,10 +335,8 @@ case EEPROM_COMMAND_PENDING:
savedata->command <<= 1; savedata->command |= value & 0x1; if (savedata->command == EEPROM_COMMAND_WRITE) { - savedata->addressBits = writeSize - 64 - 2; savedata->writeAddress = 0; } else { - savedata->addressBits = writeSize - 2; savedata->readAddress = 0; } break;@@ -338,13 +348,14 @@ savedata->writeAddress <<= 1;
savedata->writeAddress |= (value & 0x1) << 6; } else if (writeSize == 1) { savedata->command = EEPROM_COMMAND_NULL; - savedata->writePending = 1; - } else { + } else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) { uint8_t current = savedata->data[savedata->writeAddress >> 3]; current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7))); current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7)); savedata->data[savedata->writeAddress >> 3] = current; ++savedata->writeAddress; + } else { + GBALog(0, GBA_LOG_GAME_ERROR, "Writing beyond end of EEPROM: %08X", (savedata->writeAddress >> 3)); } break; case EEPROM_COMMAND_READ_PENDING:@@ -369,7 +380,12 @@ }
--savedata->readBitsRemaining; if (savedata->readBitsRemaining < 64) { int step = 63 - savedata->readBitsRemaining; - uint8_t data = savedata->data[(savedata->readAddress + step) >> 3] >> (0x7 - (step & 0x7)); + uint32_t address = (savedata->readAddress + step) >> 3; + if (address >= SIZE_CART_EEPROM) { + GBALog(0, GBA_LOG_GAME_ERROR, "Reading beyond end of EEPROM: %08X", address); + return 0xFF; + } + uint8_t data = savedata->data[address] >> (0x7 - (step & 0x7)); if (!savedata->readBitsRemaining) { savedata->command = EEPROM_COMMAND_NULL; }@@ -378,6 +394,42 @@ }
return 0; } +void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) { + state->savedata.type = savedata->type; + state->savedata.command = savedata->command; + state->savedata.flashState = savedata->flashState; + state->savedata.flashBank = savedata->currentBank == &savedata->data[0x10000]; + state->savedata.readBitsRemaining = savedata->readBitsRemaining; + state->savedata.readAddress = savedata->readAddress; + state->savedata.writeAddress = savedata->writeAddress; + state->savedata.settlingSector = savedata->settling; + state->savedata.settlingDust = savedata->dust; + + UNUSED(includeData); // TODO +} + +void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData) { + if (state->savedata.type == SAVEDATA_FORCE_NONE) { + return; + } + if (savedata->type != state->savedata.type) { + GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming); + } + savedata->command = state->savedata.command; + savedata->flashState = state->savedata.flashState; + savedata->readBitsRemaining = state->savedata.readBitsRemaining; + savedata->readAddress = state->savedata.readAddress; + savedata->writeAddress = state->savedata.writeAddress; + savedata->settling = state->savedata.settlingSector; + savedata->dust = state->savedata.settlingDust; + + if (savedata->type == SAVEDATA_FLASH1M) { + _flashSwitchBank(savedata, state->savedata.flashBank); + } + + UNUSED(includeData); // TODO +} + void _flashSwitchBank(struct GBASavedata* savedata, int bank) { GBALog(0, GBA_LOG_DEBUG, "Performing flash bank switch to bank %i", bank); savedata->currentBank = &savedata->data[bank << 16];@@ -404,6 +456,10 @@ GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
size_t size = 0x1000; if (savedata->type == SAVEDATA_FLASH1M) { GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart); + } + savedata->settling = sectorStart >> 12; + if (savedata->realisticTiming) { + savedata->dust = FLASH_SETTLE_CYCLES; } memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size); }
@@ -13,10 +13,10 @@
enum SavedataType { SAVEDATA_AUTODETECT = -1, SAVEDATA_FORCE_NONE = 0, - SAVEDATA_SRAM, - SAVEDATA_FLASH512, - SAVEDATA_FLASH1M, - SAVEDATA_EEPROM + SAVEDATA_SRAM = 1, + SAVEDATA_FLASH512 = 2, + SAVEDATA_FLASH1M = 3, + SAVEDATA_EEPROM = 4 }; enum SavedataCommand {@@ -42,8 +42,8 @@ };
enum FlashStateMachine { FLASH_STATE_RAW = 0, - FLASH_STATE_START, - FLASH_STATE_CONTINUE + FLASH_STATE_START = 1, + FLASH_STATE_CONTINUE = 2, }; enum FlashManufacturer {@@ -67,13 +67,15 @@
int mapMode; struct VFile* realVf; - int readBitsRemaining; - int readAddress; - int writeAddress; - int writePending; - int addressBits; + int32_t readBitsRemaining; + uint32_t readAddress; + uint32_t writeAddress; uint8_t* currentBank; + + bool realisticTiming; + unsigned settling; + int dust; enum FlashStateMachine flashState; };@@ -84,9 +86,9 @@
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataUnmask(struct GBASavedata* savedata); bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out); -void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type); +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming); -void GBASavedataInitFlash(struct GBASavedata* savedata); +void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming); void GBASavedataInitEEPROM(struct GBASavedata* savedata); void GBASavedataInitSRAM(struct GBASavedata* savedata);@@ -95,5 +97,9 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8_t value);
uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata); void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize); + +struct GBASerializedState; +void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData); +void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData); #endif
@@ -29,8 +29,13 @@ state->versionMagic = GBA_SAVESTATE_MAGIC;
state->biosChecksum = gba->biosChecksum; state->romCrc32 = gba->romCrc32; - state->id = ((struct GBACartridge*) gba->memory.rom)->id; - memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + if (gba->memory.rom) { + state->id = ((struct GBACartridge*) gba->memory.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + } else { + state->id = 0; + memset(state->title, 0, sizeof(state->title)); + } memcpy(state->cpu.gprs, gba->cpu->gprs, sizeof(state->cpu.gprs)); state->cpu.cpsr = gba->cpu->cpsr;@@ -48,6 +53,7 @@ GBAMemorySerialize(&gba->memory, state);
GBAIOSerialize(gba, state); GBAVideoSerialize(&gba->video, state); GBAAudioSerialize(&gba->audio, state); + GBASavedataSerialize(&gba->memory.savedata, state, false); state->associatedStreamId = 0; if (gba->rr) {@@ -66,13 +72,57 @@ if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) {
return; } } - if (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title))) { + if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game"); return; + } else if (!gba->memory.rom && state->id != 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded"); + return; } if (state->romCrc32 != gba->romCrc32) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game"); } + if (state->cpu.cycles < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); + return; + } + if (state->video.eventDiff < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); + return; + } + if (state->video.nextHblank - state->video.eventDiff < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); + return; + } + if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); + return; + } + if (state->audio.eventDiff < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); + return; + } + if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative"); + return; + } + if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative"); + return; + } + if (state->audio.ch3.nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative"); + return; + } + if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative"); + return; + } + int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET); + if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) { + GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM"); + return; + } memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs)); gba->cpu->cpsr = state->cpu.cpsr; gba->cpu->spsr = state->cpu.spsr;@@ -111,17 +161,20 @@ GBAMemoryDeserialize(&gba->memory, state);
GBAIODeserialize(gba, state); GBAVideoDeserialize(&gba->video, state); GBAAudioDeserialize(&gba->audio, state); + GBASavedataDeserialize(&gba->memory.savedata, state, false); if (gba->rr) { gba->rr->stateLoaded(gba->rr, state); } } +#ifndef _3DS struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) { char suffix[5] = { '\0' }; snprintf(suffix, sizeof(suffix), ".ss%d", slot); return VDirOptionalOpenFile(dir, gba->activeFile, "savestate", suffix, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY); } +#endif #ifdef USE_PNG static bool _savePNGState(struct GBA* gba, struct VFile* vf) {@@ -133,20 +186,31 @@ return false;
} struct GBASerializedState* state = GBAAllocateState(); + if (!state) { + return false; + } png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + if (!png || !info) { + PNGWriteClose(png, info); + GBADeallocateState(state); + return false; + } uLongf len = compressBound(sizeof(*state)); void* buffer = malloc(len); - if (state && png && info && buffer) { - GBASerialize(gba, state); - compress(buffer, &len, (const Bytef*) state, sizeof(*state)); - PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); - PNGWriteCustomChunk(png, "gbAs", len, buffer); + if (!buffer) { + PNGWriteClose(png, info); + GBADeallocateState(state); + return false; } + GBASerialize(gba, state); + compress(buffer, &len, (const Bytef*) state, sizeof(*state)); + PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); + PNGWriteCustomChunk(png, "gbAs", len, buffer); PNGWriteClose(png, info); free(buffer); GBADeallocateState(state); - return state && png && info && buffer; + return true; } static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {@@ -176,13 +240,14 @@ PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
PNGReadFooter(png, end); PNGReadClose(png, info, end); gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels); - GBASyncPostFrame(gba->sync); + GBASyncForceFrame(gba->sync); free(pixels); return true; } #endif +#ifndef _3DS bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) { struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true); if (!vf) {@@ -190,6 +255,9 @@ return false;
} bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot); vf->close(vf); + if (success) { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot); + } return success; }@@ -201,8 +269,12 @@ }
threadContext->rewindBufferSize = 0; bool success = GBALoadStateNamed(threadContext->gba, vf); vf->close(vf); + if (success) { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot); + } return success; } +#endif bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) { if (!screenshot) {@@ -229,6 +301,9 @@ if (isPNG(vf)) {
return _loadPNGState(gba, vf); } #endif + if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) { + return false; + } struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ); if (!state) { return false;@@ -254,6 +329,18 @@ state = GBAAllocateState();
thread->rewindBuffer[offset] = state; } GBASerialize(thread->gba, state); + + if (thread->rewindScreenBuffer) { + unsigned stride; + uint8_t* pixels = 0; + thread->gba->video.renderer->getPixels(thread->gba->video.renderer, &stride, (void*) &pixels); + if (pixels) { + size_t y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + memcpy(&thread->rewindScreenBuffer[(offset * VIDEO_VERTICAL_PIXELS + y) * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL], &pixels[y * stride * BYTES_PER_PIXEL], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + } + } + } thread->rewindBufferSize = thread->rewindBufferSize == thread->rewindBufferCapacity ? thread->rewindBufferCapacity : thread->rewindBufferSize + 1; thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; }@@ -271,33 +358,40 @@ for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
GBADeallocateState(threadContext->rewindBuffer[i]); } free(threadContext->rewindBuffer); + free(threadContext->rewindScreenBuffer); } threadContext->rewindBufferCapacity = newCapacity; if (threadContext->rewindBufferCapacity > 0) { threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*)); + threadContext->rewindScreenBuffer = calloc(threadContext->rewindBufferCapacity, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); } else { threadContext->rewindBuffer = 0; + threadContext->rewindScreenBuffer = 0; } } -void GBARewind(struct GBAThread* thread, int nStates) { +int GBARewind(struct GBAThread* thread, int nStates) { if (nStates > thread->rewindBufferSize || nStates < 0) { nStates = thread->rewindBufferSize; } if (nStates == 0) { - return; + return 0; } int offset = thread->rewindBufferWriteOffset - nStates; if (offset < 0) { - offset += thread->rewindBufferSize; + offset += thread->rewindBufferCapacity; } struct GBASerializedState* state = thread->rewindBuffer[offset]; if (!state) { - return; + return 0; } - thread->rewindBufferSize -= nStates - 1; - thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; + thread->rewindBufferSize -= nStates; + thread->rewindBufferWriteOffset = offset; GBADeserialize(thread->gba, state); + if (thread->rewindScreenBuffer) { + thread->gba->video.renderer->putPixels(thread->gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, &thread->rewindScreenBuffer[offset * VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL]); + } + return nStates; } void GBARewindAll(struct GBAThread* thread) {
@@ -129,7 +129,7 @@ * | 0x0028C - 0x0028F: DMA next event
* 0x00290 - 0x002C3: GPIO state * | 0x00290 - 0x00291: Pin state * | 0x00292 - 0x00293: Direction state - * | 0x00294 - 0x002B6: RTC state (see gba-hardware.h for format) + * | 0x00294 - 0x002B6: RTC state (see hardware.h for format) * | 0x002B7 - 0x002B7: GPIO devices * | bit 0: Has RTC values * | bit 1: Has rumble value (reserved)@@ -150,7 +150,21 @@ * | 0x002C0 - 0x002C0: Light sample
* | 0x002C1 - 0x002C3: Flags * | bits 0 - 1: Tilt state machine * | bits 2 - 31: Reserved - * 0x002C4 - 0x002F3: Reserved (leave zero) + * 0x002C4 - 0x002DF: Reserved (leave zero) + * 0x002E0 - 0x002EF: Savedata state + * | 0x002E0 - 0x002E0: Savedata type + * | 0x002E1 - 0x002E1: Savedata command (see savedata.h) + * | 0x002E2 - 0x002E2: Flags + * | bits 0 - 1: Flash state machine + * | bits 2 - 3: Reserved + * | bit 4: Flash bank + * | bits 5 - 7: Reserved + * | 0x002E3 - 0x002E3: Reserved + * | 0x002E4 - 0x002E7: EEPROM read bits remaining + * | 0x002E8 - 0x002EB: EEPROM read address + * | 0x002EC - 0x002EF: EEPROM write address + * | 0x002F0 - 0x002F1: Flash settling sector + * | 0x002F2 - 0x002F3: Flash settling remaining * 0x002F4 - 0x002FF: Prefetch * | 0x002F4 - 0x002F7: GBA BIOS bus prefetch * | 0x002F8 - 0x002FB: CPU prefecth (decode slot)@@ -217,7 +231,7 @@ uint8_t fifoB[32];
int32_t nextEvent; int32_t eventDiff; int32_t nextSample; - int32_t fifoSize; + uint32_t fifoSize; unsigned ch1Volume : 4; unsigned ch1Dead : 1; unsigned ch1Hi : 1;@@ -271,7 +285,22 @@ unsigned tiltState : 2;
unsigned : 22; } hw; - uint32_t reservedHardware[12]; + uint32_t reservedHardware[7]; + + struct { + unsigned type : 8; + unsigned command : 8; + unsigned flashState : 2; + unsigned : 2; + unsigned flashBank : 1; + unsigned : 3; + unsigned : 8; + int32_t readBitsRemaining; + uint32_t readAddress; + uint32_t writeAddress; + uint16_t settlingSector; + uint16_t settlingDust; + } savedata; uint32_t biosPrefetch; uint32_t cpuPrefetch[2];@@ -306,7 +335,7 @@ void GBADeallocateState(struct GBASerializedState* state);
void GBARecordFrame(struct GBAThread* thread); void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int newInterval); -void GBARewind(struct GBAThread* thread, int nStates); +int GBARewind(struct GBAThread* thread, int nStates); void GBARewindAll(struct GBAThread* thread); #endif
@@ -0,0 +1,260 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "sharkport.h" + +#include "gba/gba.h" +#include "util/vfs.h" + +static const char* const SHARKPORT_HEADER = "SharkPortSave"; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) { + union { + char c[0x1C]; + int32_t i; + } buffer; + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + int32_t size; + LOAD_32(size, 0, &buffer.i); + if (size != (int32_t) strlen(SHARKPORT_HEADER)) { + return false; + } + if (vf->read(vf, buffer.c, size) < size) { + return false; + } + if (memcmp(SHARKPORT_HEADER, buffer.c, size) != 0) { + return false; + } + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + LOAD_32(size, 0, &buffer.i); + if (size != 0x000F0000) { + // What is this value? + return false; + } + + // Skip first three fields + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + LOAD_32(size, 0, &buffer.i); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + LOAD_32(size, 0, &buffer.i); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + LOAD_32(size, 0, &buffer.i); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + // Read payload + if (vf->read(vf, &buffer.i, 4) < 4) { + return false; + } + LOAD_32(size, 0, &buffer.i); + if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) { + return false; + } + char* payload = malloc(size); + if (vf->read(vf, payload, size) < size) { + goto cleanup; + } + + struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; + memcpy(buffer.c, &cart->title, 16); + buffer.c[0x10] = 0; + buffer.c[0x11] = 0; + buffer.c[0x12] = cart->checksum; + buffer.c[0x13] = cart->maker; + buffer.c[0x14] = 1; + buffer.c[0x15] = 0; + buffer.c[0x16] = 0; + buffer.c[0x17] = 0; + buffer.c[0x18] = 0; + buffer.c[0x19] = 0; + buffer.c[0x1A] = 0; + buffer.c[0x1B] = 0; + if (memcmp(buffer.c, payload, 0x1C) != 0) { + goto cleanup; + } + + uint32_t checksum; + if (vf->read(vf, &buffer.i, 4) < 4) { + goto cleanup; + } + LOAD_32(checksum, 0, &buffer.i); + + if (testChecksum) { + uint32_t calcChecksum = 0; + int i; + for (i = 0; i < size; ++i) { + calcChecksum += ((int32_t) payload[i]) << (calcChecksum % 24); + } + + if (calcChecksum != checksum) { + goto cleanup; + } + } + + uint32_t copySize = size - 0x1C; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + if (copySize > SIZE_CART_SRAM) { + copySize = SIZE_CART_SRAM; + } + break; + case SAVEDATA_FLASH512: + if (copySize > SIZE_CART_FLASH512) { + GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); + } + // Fall through + case SAVEDATA_FLASH1M: + if (copySize > SIZE_CART_FLASH1M) { + copySize = SIZE_CART_FLASH1M; + } + break; + case SAVEDATA_EEPROM: + if (copySize > SIZE_CART_EEPROM) { + copySize = SAVEDATA_EEPROM; + } + break; + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: + goto cleanup; + } + + memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + + free(payload); + return true; + +cleanup: + free(payload); + return false; +} + + +bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { + union { + char c[0x1C]; + int32_t i; + } buffer; + int32_t size = strlen(SHARKPORT_HEADER); + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + if (vf->write(vf, SHARKPORT_HEADER, size) < size) { + return false; + } + + size = 0x000F0000; + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + + const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; + size = sizeof(cart->title); + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + if (vf->write(vf, cart->title, size) < 4) { + return false; + } + + time_t t = time(0); + struct tm* tm = localtime(&t); + size = strftime(&buffer.c[4], sizeof(buffer.c) - 4, "%m/%d/%Y %I:%M:%S %p", tm); + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, buffer.c, size + 4) < size + 4) { + return false; + } + + // Last field is blank + size = 0; + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + + // Write payload + size = 0x1C; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + size += SIZE_CART_SRAM; + break; + case SAVEDATA_FLASH512: + size += SIZE_CART_FLASH512; + break; + case SAVEDATA_FLASH1M: + size += SIZE_CART_FLASH1M; + break; + case SAVEDATA_EEPROM: + size += SIZE_CART_EEPROM; + break; + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: + return false; + } + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + size -= 0x1C; + + memcpy(buffer.c, &cart->title, 16); + buffer.c[0x10] = 0; + buffer.c[0x11] = 0; + buffer.c[0x12] = cart->checksum; + buffer.c[0x13] = cart->maker; + buffer.c[0x14] = 1; + buffer.c[0x15] = 0; + buffer.c[0x16] = 0; + buffer.c[0x17] = 0; + buffer.c[0x18] = 0; + buffer.c[0x19] = 0; + buffer.c[0x1A] = 0; + buffer.c[0x1B] = 0; + if (vf->write(vf, buffer.c, 0x1C) < 0x1C) { + return false; + } + + uint32_t checksum = 0; + int i; + for (i = 0; i < 0x1C; ++i) { + checksum += buffer.c[i] << (checksum % 24); + } + + if (vf->write(vf, gba->memory.savedata.data, size) < size) { + return false; + } + + for (i = 0; i < size; ++i) { + checksum += ((char) gba->memory.savedata.data[i]) << (checksum % 24); + } + + STORE_32(checksum, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { + return false; + } + + return true; +}
@@ -0,0 +1,17 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_SHARKPORT_H +#define GBA_SHARKPORT_H + +#include "util/common.h" + +struct GBA; +struct VFile; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum); +bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf); + +#endif
@@ -131,11 +131,30 @@ }
} void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { + if ((value ^ sio->siocnt) & 0x3000) { + sio->siocnt = value & 0x3000; + _switchMode(sio); + } if (sio->activeDriver && sio->activeDriver->writeRegister) { value = sio->activeDriver->writeRegister(sio->activeDriver, REG_SIOCNT, value); + } else { + // Dummy drivers + switch (sio->mode) { + case SIO_NORMAL_8: + case SIO_NORMAL_32: + value |= 0x0004; + if ((value & 0x4080) == 0x4080) { + // TODO: Test this on hardware to see if this is correct + GBARaiseIRQ(sio->p, IRQ_SIO); + } + value &= ~0x0080; + break; + default: + // TODO + break; + } } sio->siocnt = value; - _switchMode(sio); } void GBASIOWriteSIOMLT_SEND(struct GBASIO* sio, uint16_t value) {
@@ -30,11 +30,11 @@
struct GBASIODriver { struct GBASIO* p; - int (*init)(struct GBASIODriver* driver); + bool (*init)(struct GBASIODriver* driver); void (*deinit)(struct GBASIODriver* driver); - int (*load)(struct GBASIODriver* driver); - int (*unload)(struct GBASIODriver* driver); - int (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); + bool (*load)(struct GBASIODriver* driver); + bool (*unload)(struct GBASIODriver* driver); + uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles); };
@@ -0,0 +1,201 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "lockstep.h" + +#include "gba/gba.h" +#include "gba/io.h" + +#define LOCKSTEP_INCREMENT 2048 + +static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); +static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver); +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles); + +void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { + lockstep->players[0] = 0; + lockstep->players[1] = 0; + lockstep->players[2] = 0; + lockstep->players[3] = 0; + lockstep->multiRecv[0] = 0xFFFF; + lockstep->multiRecv[1] = 0xFFFF; + lockstep->multiRecv[2] = 0xFFFF; + lockstep->multiRecv[3] = 0xFFFF; + lockstep->attached = 0; + lockstep->loaded = 0; + lockstep->transferActive = false; + lockstep->waiting = 0; + lockstep->nextEvent = LOCKSTEP_INCREMENT; + ConditionInit(&lockstep->barrier); + MutexInit(&lockstep->mutex); +} + +void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) { + ConditionDeinit(&lockstep->barrier); + MutexDeinit(&lockstep->mutex); +} + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) { + node->d.init = GBASIOLockstepNodeInit; + node->d.deinit = GBASIOLockstepNodeDeinit; + node->d.load = GBASIOLockstepNodeLoad; + node->d.unload = GBASIOLockstepNodeUnload; + node->d.writeRegister = GBASIOLockstepNodeWriteRegister; + node->d.processEvents = GBASIOLockstepNodeProcessEvents; +} + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == MAX_GBAS) { + return false; + } + lockstep->players[lockstep->attached] = node; + node->p = lockstep; + node->id = lockstep->attached; + ++lockstep->attached; + return true; +} + +void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == 0) { + return; + } + int i; + for (i = 0; i < lockstep->attached; ++i) { + if (lockstep->players[i] != node) { + continue; + } + for (++i; i < lockstep->attached; ++i) { + lockstep->players[i - 1] = lockstep->players[i]; + lockstep->players[i - 1]->id = i - 1; + } + --lockstep->attached; + break; + } +} + +bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent = LOCKSTEP_INCREMENT; + node->d.p->multiplayerControl.slave = node->id > 0; + GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Node init", node->id); + return true; +} + +void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) { + UNUSED(driver); +} + +bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->state = LOCKSTEP_IDLE; + MutexLock(&node->p->mutex); + ++node->p->loaded; + node->d.p->rcnt |= 3; + if (node->id) { + node->d.p->rcnt |= 4; + node->d.p->multiplayerControl.slave = 1; + } + MutexUnlock(&node->p->mutex); + return true; +} + +bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + MutexLock(&node->p->mutex); + --node->p->loaded; + ConditionWake(&node->p->barrier); + MutexUnlock(&node->p->mutex); + return true; +} + +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + if (address == REG_SIOCNT) { + GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value); + if (value & 0x0080) { + if (!node->id) { + GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Transfer initiated", node->id); + MutexLock(&node->p->mutex); + node->p->transferActive = true; + node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1]; + node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; + MutexUnlock(&node->p->mutex); + } else { + value &= ~0x0080; + } + } + value &= 0xFF83; + value |= driver->p->siocnt & 0x00FC; + } else if (address == REG_SIOMLT_SEND) { + GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value); + } + return value; +} + +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent -= cycles; + while (node->nextEvent <= 0) { + MutexLock(&node->p->mutex); + ++node->p->waiting; + if (node->p->waiting < node->p->loaded) { + ConditionWait(&node->p->barrier, &node->p->mutex); + } else { + if (node->p->transferActive) { + node->p->transferCycles -= node->p->nextEvent; + if (node->p->transferCycles > 0) { + if (node->p->transferCycles < LOCKSTEP_INCREMENT) { + node->p->nextEvent = node->p->transferCycles; + } + } else { + node->p->nextEvent = LOCKSTEP_INCREMENT; + node->p->transferActive = false; + int i; + for (i = 0; i < node->p->attached; ++i) { + node->p->multiRecv[i] = node->p->players[i]->multiSend; + node->p->players[i]->state = LOCKSTEP_FINISHED; + } + for (; i < MAX_GBAS; ++i) { + node->p->multiRecv[i] = 0xFFFF; + } + } + } + node->p->waiting = 0; + ConditionWake(&node->p->barrier); + } + if (node->state == LOCKSTEP_FINISHED) { + GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Finishing transfer: %04x %04x %04x %04x", node->id, node->p->multiRecv[0], node->p->multiRecv[1], node->p->multiRecv[2], node->p->multiRecv[3]); + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0]; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1]; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; + node->d.p->rcnt |= 1; + node->state = LOCKSTEP_IDLE; + if (node->d.p->multiplayerControl.irq) { + GBARaiseIRQ(node->d.p->p, IRQ_SIO); + } + node->d.p->multiplayerControl.id = node->id; + node->d.p->multiplayerControl.busy = 0; + } else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) { + node->state = LOCKSTEP_STARTED; + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; + node->d.p->rcnt &= ~1; + if (node->id) { + node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; + node->d.p->multiplayerControl.busy = 1; + } + } + node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached; + node->nextEvent += node->p->nextEvent; + MutexUnlock(&node->p->mutex); + } + return node->nextEvent; +}
@@ -0,0 +1,52 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef SIO_LOCKSTEP_H +#define SIO_LOCKSTEP_H + +#include "gba/sio.h" + +#include "util/threading.h" + +enum LockstepState { + LOCKSTEP_IDLE = 0, + LOCKSTEP_STARTED = 1, + LOCKSTEP_FINISHED = 2 +}; + +struct GBASIOLockstep { + struct GBASIOLockstepNode* players[MAX_GBAS]; + int attached; + int loaded; + + uint16_t multiRecv[MAX_GBAS]; + bool transferActive; + int32_t transferCycles; + int32_t nextEvent; + + int waiting; + Mutex mutex; + Condition barrier; +}; + +struct GBASIOLockstepNode { + struct GBASIODriver d; + struct GBASIOLockstep* p; + + int32_t nextEvent; + uint16_t multiSend; + enum LockstepState state; + int id; +}; + +void GBASIOLockstepInit(struct GBASIOLockstep*); +void GBASIOLockstepDeinit(struct GBASIOLockstep*); + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*); + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); +void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); + +#endif
@@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "config.h" #include "util/formatting.h" +#include "util/string.h" +#include "util/vfs.h" #include <sys/stat.h>@@ -13,9 +15,6 @@ #ifdef _WIN32
#include <windows.h> #include <shlobj.h> #include <strsafe.h> -#define PATH_SEP "\\" -#else -#define PATH_SEP "/" #endif #define SECTION_NAME_MAX 128@@ -131,12 +130,12 @@ #ifndef _WIN32
char* home = getenv("HOME"); snprintf(out, outLength, "%s/.config", home); mkdir(out, 0755); - snprintf(out, outLength, "%s/.config/%s", home, BINARY_NAME); + snprintf(out, outLength, "%s/.config/%s", home, binaryName); mkdir(out, 0755); #else char home[MAX_PATH]; SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home); - snprintf(out, outLength, "%s\\%s", home, PROJECT_NAME); + snprintf(out, outLength, "%s\\%s", home, projectName); CreateDirectoryA(out, NULL); #endif }@@ -181,6 +180,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
_lookupCharValue(config, "bios", &opts->bios); _lookupIntValue(config, "logLevel", &opts->logLevel); _lookupIntValue(config, "frameskip", &opts->frameskip); + _lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); _lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);@@ -190,6 +190,9 @@ opts->audioBuffers = audioBuffers;
} int fakeBool; + if (_lookupIntValue(config, "useBios", &fakeBool)) { + opts->useBios = fakeBool; + } if (_lookupIntValue(config, "audioSync", &fakeBool)) { opts->audioSync = fakeBool; }@@ -202,6 +205,12 @@ }
if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { opts->resampleVideo = fakeBool; } + if (_lookupIntValue(config, "suspendScreensaver", &fakeBool)) { + opts->suspendScreensaver = fakeBool; + } + if (_lookupIntValue(config, "mute", &fakeBool)) { + opts->mute = fakeBool; + } if (_lookupIntValue(config, "skipBios", &fakeBool)) { opts->skipBios = fakeBool; }@@ -229,6 +238,7 @@
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) { ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios); ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios); + ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios); ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel); ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);@@ -241,8 +251,11 @@ ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width); ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height); + ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume); + ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute); ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio); ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo); + ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver); switch (opts->idleOptimization) { case IDLE_LOOP_IGNORE:
@@ -21,6 +21,7 @@
struct GBAOptions { char* bios; bool skipBios; + bool useBios; int logLevel; int frameskip; bool rewindEnable;@@ -34,6 +35,10 @@ int width;
int height; bool lockAspectRatio; bool resampleVideo; + bool suspendScreensaver; + + int volume; + bool mute; bool videoSync; bool audioSync;
@@ -0,0 +1,81 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "export.h" + +#include "gba/video.h" +#include "util/vfs.h" + +bool GBAExportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) { + if (entries > 0xFFFF) { + return false; + } + uint32_t chunkSize = 4 + 4 * entries; + uint32_t size = chunkSize + 12; + + // Header + if (vf->write(vf, "RIFF", 4) < 4) { + return false; + } + if (VFileWrite32LE(vf, size) < 4) { + return false; + } + if (vf->write(vf, "PAL ", 4) < 4) { + return false; + } + + // Data chunk + if (vf->write(vf, "data", 4) < 4) { + return false; + } + if (VFileWrite32LE(vf, chunkSize) < 4) { + return false; + } + if (VFileWrite16LE(vf, 0x0300) < 2) { + return false; + } + if (VFileWrite16LE(vf, entries) < 2) { + return false; + } + + size_t i; + for (i = 0; i < entries; ++i) { + uint8_t block[4] = { + GBA_R8(colors[i]), + GBA_G8(colors[i]), + GBA_B8(colors[i]), + 0 + }; + if (vf->write(vf, block, 4) < 4) { + return false; + } + } + + return true; +} + +bool GBAExportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors) { + if (entries > 256) { + return false; + } + size_t i; + for (i = 0; i < entries; ++i) { + uint8_t block[3] = { + GBA_R8(colors[i]), + GBA_G8(colors[i]), + GBA_B8(colors[i]), + }; + if (vf->write(vf, block, 3) < 3) { + return false; + } + } + for (; i < 256; ++i) { + uint8_t block[3] = { 0, 0, 0 }; + if (vf->write(vf, block, 3) < 3) { + return false; + } + } + return true; +}
@@ -0,0 +1,16 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_EXPORT_H +#define GBA_EXPORT_H + +#include "util/common.h" + +struct VFile; + +bool GBAExportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); +bool GBAExportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors); + +#endif
@@ -11,6 +11,10 @@
#include "util/configuration.h" static const struct GBACartridgeOverride _overrides[] = { + // Advance Wars + { "AWRE", SAVEDATA_FLASH512, HW_NONE, 0x8038810 }, + { "AWRP", SAVEDATA_FLASH512, HW_NONE, 0x8038810 }, + // Boktai: The Sun is in Your Hand { "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, { "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },@@ -21,6 +25,13 @@ { "U32J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
{ "U32E", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, { "U32P", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + // Dragon Ball Z - The Legacy of Goku + { "ALGP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE }, + + // Dragon Ball Z - Taiketsu + { "BDBE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE }, + { "BDBP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE }, + // Drill Dozer { "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE }, { "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },@@ -28,6 +39,9 @@
// Final Fantasy Tactics Advance { "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 }, + // F-Zero - Climax + { "BFTJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + // Golden Sun: The Lost Age { "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },@@ -37,6 +51,9 @@
// Mega Man Battle Network { "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E }, + // Mega Man Zero + { "AZCE", SAVEDATA_SRAM, HW_NONE, 0x80004E8 }, + // Metal Slug Advance { "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 },@@ -59,13 +76,13 @@ { "AXPD", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
{ "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, // Pokemon Emerald - { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, { "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, - { "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEP", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPEI", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPES", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPED", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, // Pokemon Mystery Dungeon { "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },@@ -77,28 +94,47 @@ // Pokemon FireRed
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, { "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, { "BPRP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, // Pokemon LeafGreen { "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, { "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, { "BPGP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, // RockMan EXE 4.5 - Real Operation { "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE }, + // Rocky + { "AR8E", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE }, + { "AROP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE }, + + // Sennen Kazoku + { "BKAJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + // Shin Bokura no Taiyou: Gyakushuu no Sabata { "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, // Super Mario Advance 2 + { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, + { "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, // Super Mario Advance 3 + { "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, { "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, + { "A3AP", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, // Super Mario Advance 4 - { "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, - { "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, - { "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "AX4J", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, + { "AX4E", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, + { "AX4P", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, // Top Gun - Combat Zones { "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },@@ -120,7 +156,7 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOverride* override) {
override->savetype = SAVEDATA_AUTODETECT; override->hardware = HW_NONE; override->idleLoop = IDLE_LOOP_NONE; - bool found; + bool found = false; if (override->id[0] == 'F') { // Classic NES Series@@ -224,7 +260,7 @@ }
void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) { if (override->savetype != SAVEDATA_AUTODETECT) { - GBASavedataForceType(&gba->memory.savedata, override->savetype); + GBASavedataForceType(&gba->memory.savedata, override->savetype, gba->realisticTiming); } if (override->hardware != HW_NO_OVERRIDE) {
@@ -0,0 +1,126 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "thread.h" + +static void _changeVideoSync(struct GBASync* sync, bool frameOn) { + // Make sure the video thread can process events while the GBA thread is paused + MutexLock(&sync->videoFrameMutex); + if (frameOn != sync->videoFrameOn) { + sync->videoFrameOn = frameOn; + ConditionWake(&sync->videoFrameAvailableCond); + } + MutexUnlock(&sync->videoFrameMutex); +} + +void GBASyncPostFrame(struct GBASync* sync) { + if (!sync) { + return; + } + + MutexLock(&sync->videoFrameMutex); + ++sync->videoFramePending; + --sync->videoFrameSkip; + if (sync->videoFrameSkip < 0) { + do { + ConditionWake(&sync->videoFrameAvailableCond); + if (sync->videoFrameWait) { + ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); + } + } while (sync->videoFrameWait && sync->videoFramePending); + } + MutexUnlock(&sync->videoFrameMutex); +} + +void GBASyncForceFrame(struct GBASync* sync) { + if (!sync) { + return; + } + + MutexLock(&sync->videoFrameMutex); + ConditionWake(&sync->videoFrameAvailableCond); + MutexUnlock(&sync->videoFrameMutex); +} + +bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { + if (!sync) { + return true; + } + + MutexLock(&sync->videoFrameMutex); + ConditionWake(&sync->videoFrameRequiredCond); + if (!sync->videoFrameOn && !sync->videoFramePending) { + return false; + } + if (sync->videoFrameOn) { + if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) { + return false; + } + } + sync->videoFramePending = 0; + sync->videoFrameSkip = frameskip; + return true; +} + +void GBASyncWaitFrameEnd(struct GBASync* sync) { + if (!sync) { + return; + } + + MutexUnlock(&sync->videoFrameMutex); +} + +bool GBASyncDrawingFrame(struct GBASync* sync) { + if (!sync) { + return true; + } + + return sync->videoFrameSkip <= 0; +} + +void GBASyncSetVideoSync(struct GBASync* sync, bool wait) { + if (!sync) { + return; + } + + _changeVideoSync(sync, wait); +} + +void GBASyncProduceAudio(struct GBASync* sync, bool wait) { + if (!sync) { + return; + } + + if (sync->audioWait && wait) { + // TODO loop properly in event of spurious wakeups + ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex); + } + MutexUnlock(&sync->audioBufferMutex); +} + +void GBASyncLockAudio(struct GBASync* sync) { + if (!sync) { + return; + } + + MutexLock(&sync->audioBufferMutex); +} + +void GBASyncUnlockAudio(struct GBASync* sync) { + if (!sync) { + return; + } + + MutexUnlock(&sync->audioBufferMutex); +} + +void GBASyncConsumeAudio(struct GBASync* sync) { + if (!sync) { + return; + } + + ConditionWake(&sync->audioRequiredCond); + MutexUnlock(&sync->audioBufferMutex); +}
@@ -0,0 +1,39 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_SYNC_H +#define GBA_SYNC_H + +#include "util/common.h" + +#include "util/threading.h" + +struct GBASync { + int videoFramePending; + bool videoFrameWait; + int videoFrameSkip; + bool videoFrameOn; + Mutex videoFrameMutex; + Condition videoFrameAvailableCond; + Condition videoFrameRequiredCond; + + bool audioWait; + Condition audioRequiredCond; + Mutex audioBufferMutex; +}; + +void GBASyncPostFrame(struct GBASync* sync); +void GBASyncForceFrame(struct GBASync* sync); +bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip); +void GBASyncWaitFrameEnd(struct GBASync* sync); +bool GBASyncDrawingFrame(struct GBASync* sync); +void GBASyncSetVideoSync(struct GBASync* sync, bool wait); + +void GBASyncProduceAudio(struct GBASync* sync, bool wait); +void GBASyncLockAudio(struct GBASync* sync); +void GBASyncUnlockAudio(struct GBASync* sync); +void GBASyncConsumeAudio(struct GBASync* sync); + +#endif
@@ -25,6 +25,7 @@ #include <signal.h>
static const float _defaultFPSTarget = 60.f; +#ifndef DISABLE_THREADING #ifdef USE_PTHREADS static pthread_key_t _contextKey; static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;@@ -45,7 +46,6 @@ return TRUE;
} #endif -#ifndef DISABLE_THREADING static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) { MutexLock(&threadContext->stateMutex); threadContext->state = newState;@@ -92,20 +92,8 @@ threadContext->state = THREAD_PAUSING;
if (!onThread) { _waitUntilNotState(threadContext, THREAD_PAUSING); } -} -#endif - -static void _changeVideoSync(struct GBASync* sync, bool frameOn) { - // Make sure the video thread can process events while the GBA thread is paused - MutexLock(&sync->videoFrameMutex); - if (frameOn != sync->videoFrameOn) { - sync->videoFrameOn = frameOn; - ConditionWake(&sync->videoFrameAvailableCond); - } - MutexUnlock(&sync->videoFrameMutex); } -#ifndef DISABLE_THREADING static THREAD_ENTRY _GBAThreadRun(void* context) { #ifdef USE_PTHREADS pthread_once(&_contextOnce, _createTLS);@@ -118,10 +106,12 @@ struct ARMCore cpu;
struct Patch patch; struct GBACheatDevice cheatDevice; struct GBAThread* threadContext = context; - struct ARMComponent* components[GBA_COMPONENT_MAX] = {}; + struct ARMComponent* components[GBA_COMPONENT_MAX] = {0}; struct GBARRContext* movie = 0; int numComponents = GBA_COMPONENT_MAX; + ThreadSetName("CPU Thread"); + #if !defined(_WIN32) && defined(USE_PTHREADS) sigset_t signals; sigemptyset(&signals);@@ -133,6 +123,7 @@ ARMSetComponents(&cpu, &gba.d, numComponents, components);
ARMInit(&cpu); gba.sync = &threadContext->sync; threadContext->gba = &gba; + threadContext->cpu = &cpu; gba.logLevel = threadContext->logLevel; gba.logHandler = threadContext->logHandler; gba.stream = threadContext->stream;@@ -166,15 +157,15 @@ if (threadContext->hasOverride) {
GBAOverrideApply(&gba, &threadContext->override); } - if (threadContext->bios && GBAIsBIOS(threadContext->bios)) { - GBALoadBIOS(&gba, threadContext->bios); - } - if (threadContext->patch && loadPatch(threadContext->patch, &patch)) { GBAApplyPatch(&gba, &patch); } } + if (threadContext->bios && GBAIsBIOS(threadContext->bios)) { + GBALoadBIOS(&gba, threadContext->bios); + } + if (threadContext->movie) { struct VDir* movieDir = VDirOpen(threadContext->movie); #ifdef USE_LIBZIP@@ -233,6 +224,15 @@ }
GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers); + if (threadContext->volume == 0) { + threadContext->volume = GBA_AUDIO_VOLUME_MAX; + } + if (threadContext->mute) { + gba.audio.masterVolume = 0; + } else { + gba.audio.masterVolume = threadContext->volume; + } + gba.keySource = &threadContext->activeKeys; if (threadContext->startCallback) {@@ -265,6 +265,13 @@ if (threadContext->state == THREAD_INTERRUPTING) {
threadContext->state = THREAD_INTERRUPTED; ConditionWake(&threadContext->stateCond); } + if (threadContext->state == THREAD_RUN_ON) { + if (threadContext->run) { + threadContext->run(threadContext); + } + threadContext->state = threadContext->savedState; + ConditionWake(&threadContext->stateCond); + } if (threadContext->state == THREAD_RESETING) { threadContext->state = THREAD_RUNNING; resetScheduled = 1;@@ -310,8 +317,14 @@ return 0;
} void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) { - threadContext->bios = VFileOpen(opts->bios, O_RDONLY); + if (opts->useBios) { + threadContext->bios = VFileOpen(opts->bios, O_RDONLY); + } else { + threadContext->bios = 0; + } threadContext->frameskip = opts->frameskip; + threadContext->volume = opts->volume; + threadContext->mute = opts->mute; threadContext->logLevel = opts->logLevel; if (opts->rewindEnable) { threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;@@ -366,6 +379,7 @@ threadContext->sync.videoFrameOn = true;
threadContext->sync.videoFrameSkip = 0; threadContext->rewindBuffer = 0; + threadContext->rewindScreenBuffer = 0; int newCapacity = threadContext->rewindBufferCapacity; int newInterval = threadContext->rewindBufferInterval; threadContext->rewindBufferCapacity = 0;@@ -376,7 +390,9 @@ if (!threadContext->fpsTarget) {
threadContext->fpsTarget = _defaultFPSTarget; } - if (threadContext->rom && !GBAIsROM(threadContext->rom)) { + bool bootBios = threadContext->bootBios && threadContext->bios; + + if (threadContext->rom && (!GBAIsROM(threadContext->rom) || bootBios)) { threadContext->rom->close(threadContext->rom); threadContext->rom = 0; }@@ -403,7 +419,7 @@ }
} - if (!threadContext->rom) { + if (!threadContext->rom && !bootBios) { threadContext->state = THREAD_SHUTDOWN; return false; }@@ -516,6 +532,7 @@ GBADeallocateState(threadContext->rewindBuffer[i]);
} } free(threadContext->rewindBuffer); + free(threadContext->rewindScreenBuffer); if (threadContext->rom) { threadContext->rom->close(threadContext->rom);@@ -581,29 +598,44 @@ }
MutexUnlock(&threadContext->stateMutex); } +void GBARunOnThread(struct GBAThread* threadContext, void (*run)(struct GBAThread*)) { + MutexLock(&threadContext->stateMutex); + threadContext->run = run; + _waitOnInterrupt(threadContext); + threadContext->savedState = threadContext->state; + threadContext->state = THREAD_RUN_ON; + threadContext->gba->cpu->nextEvent = 0; + ConditionWake(&threadContext->stateCond); + _waitUntilNotState(threadContext, THREAD_RUN_ON); + MutexUnlock(&threadContext->stateMutex); +} + void GBAThreadPause(struct GBAThread* threadContext) { - bool frameOn = true; + bool frameOn = threadContext->sync.videoFrameOn; MutexLock(&threadContext->stateMutex); _waitOnInterrupt(threadContext); if (threadContext->state == THREAD_RUNNING) { _pauseThread(threadContext, false); + threadContext->frameWasOn = frameOn; frameOn = false; } MutexUnlock(&threadContext->stateMutex); - _changeVideoSync(&threadContext->sync, frameOn); + GBASyncSetVideoSync(&threadContext->sync, frameOn); } void GBAThreadUnpause(struct GBAThread* threadContext) { + bool frameOn = threadContext->sync.videoFrameOn; MutexLock(&threadContext->stateMutex); _waitOnInterrupt(threadContext); if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) { threadContext->state = THREAD_RUNNING; ConditionWake(&threadContext->stateCond); + frameOn = threadContext->frameWasOn; } MutexUnlock(&threadContext->stateMutex); - _changeVideoSync(&threadContext->sync, true); + GBASyncSetVideoSync(&threadContext->sync, frameOn); } bool GBAThreadIsPaused(struct GBAThread* threadContext) {@@ -616,19 +648,21 @@ return isPaused;
} void GBAThreadTogglePause(struct GBAThread* threadContext) { - bool frameOn = true; + bool frameOn = threadContext->sync.videoFrameOn; MutexLock(&threadContext->stateMutex); _waitOnInterrupt(threadContext); if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) { threadContext->state = THREAD_RUNNING; ConditionWake(&threadContext->stateCond); + frameOn = threadContext->frameWasOn; } else if (threadContext->state == THREAD_RUNNING) { _pauseThread(threadContext, false); + threadContext->frameWasOn = frameOn; frameOn = false; } MutexUnlock(&threadContext->stateMutex); - _changeVideoSync(&threadContext->sync, frameOn); + GBASyncSetVideoSync(&threadContext->sync, frameOn); } void GBAThreadPauseFromThread(struct GBAThread* threadContext) {@@ -641,7 +675,7 @@ frameOn = false;
} MutexUnlock(&threadContext->stateMutex); - _changeVideoSync(&threadContext->sync, frameOn); + GBASyncSetVideoSync(&threadContext->sync, frameOn); } #ifdef USE_PTHREADS@@ -664,9 +698,12 @@ struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); + bool success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); PNGWriteClose(png, info); vf->close(vf); + if (success) { + GBALog(threadContext->gba, GBA_LOG_STATUS, "Screenshot saved"); + } } #endif@@ -675,111 +712,3 @@ struct GBAThread* GBAThreadGetContext(void) {
return 0; } #endif - -void GBASyncPostFrame(struct GBASync* sync) { - if (!sync) { - return; - } - - MutexLock(&sync->videoFrameMutex); - ++sync->videoFramePending; - --sync->videoFrameSkip; - if (sync->videoFrameSkip < 0) { - do { - ConditionWake(&sync->videoFrameAvailableCond); - if (sync->videoFrameWait) { - ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); - } - } while (sync->videoFrameWait && sync->videoFramePending); - } - MutexUnlock(&sync->videoFrameMutex); -} - -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { - if (!sync) { - return true; - } - - MutexLock(&sync->videoFrameMutex); - ConditionWake(&sync->videoFrameRequiredCond); - if (!sync->videoFrameOn && !sync->videoFramePending) { - return false; - } - if (sync->videoFrameOn) { - if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) { - return false; - } - } - sync->videoFramePending = 0; - sync->videoFrameSkip = frameskip; - return true; -} - -void GBASyncWaitFrameEnd(struct GBASync* sync) { - if (!sync) { - return; - } - - MutexUnlock(&sync->videoFrameMutex); -} - -bool GBASyncDrawingFrame(struct GBASync* sync) { - if (!sync) { - return true; - } - - return sync->videoFrameSkip <= 0; -} - -void GBASyncSuspendDrawing(struct GBASync* sync) { - if (!sync) { - return; - } - - _changeVideoSync(sync, false); -} - -void GBASyncResumeDrawing(struct GBASync* sync) { - if (!sync) { - return; - } - - _changeVideoSync(sync, true); -} - -void GBASyncProduceAudio(struct GBASync* sync, bool wait) { - if (!sync) { - return; - } - - if (sync->audioWait && wait) { - // TODO loop properly in event of spurious wakeups - ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex); - } - MutexUnlock(&sync->audioBufferMutex); -} - -void GBASyncLockAudio(struct GBASync* sync) { - if (!sync) { - return; - } - - MutexLock(&sync->audioBufferMutex); -} - -void GBASyncUnlockAudio(struct GBASync* sync) { - if (!sync) { - return; - } - - MutexUnlock(&sync->audioBufferMutex); -} - -void GBASyncConsumeAudio(struct GBASync* sync) { - if (!sync) { - return; - } - - ConditionWake(&sync->audioRequiredCond); - MutexUnlock(&sync->audioBufferMutex); -}
@@ -11,6 +11,7 @@
#include "gba/gba.h" #include "gba/input.h" #include "gba/supervisor/overrides.h" +#include "gba/supervisor/sync.h" #include "util/threading.h"@@ -28,26 +29,13 @@ THREAD_INTERRUPTED,
THREAD_INTERRUPTING, THREAD_PAUSED, THREAD_PAUSING, + THREAD_RUN_ON, THREAD_RESETING, THREAD_EXITING, THREAD_SHUTDOWN, THREAD_CRASHED }; -struct GBASync { - int videoFramePending; - bool videoFrameWait; - int videoFrameSkip; - bool videoFrameOn; - Mutex videoFrameMutex; - Condition videoFrameAvailableCond; - Condition videoFrameRequiredCond; - - bool audioWait; - Condition audioRequiredCond; - Mutex audioBufferMutex; -}; - struct GBAThread { // Output enum ThreadState state;@@ -71,6 +59,7 @@ int activeKeys;
struct GBAAVStream* stream; struct Configuration* overrides; enum GBAIdleLoopOptimization idleOptimization; + bool bootBios; bool hasOverride; struct GBACartridgeOverride override;@@ -80,6 +69,8 @@ int frameskip;
float fpsTarget; size_t audioBuffers; bool skipBios; + int volume; + bool mute; // Threading state Thread thread;@@ -88,6 +79,7 @@ Mutex stateMutex;
Condition stateCond; enum ThreadState savedState; int interruptDepth; + bool frameWasOn; GBALogHandler logHandler; int logLevel;@@ -95,6 +87,7 @@ ThreadCallback startCallback;
ThreadCallback cleanCallback; ThreadCallback frameCallback; void* userData; + void (*run)(struct GBAThread*); struct GBASync sync;@@ -104,6 +97,7 @@ int rewindBufferInterval;
int rewindBufferNext; struct GBASerializedState** rewindBuffer; int rewindBufferWriteOffset; + uint8_t* rewindScreenBuffer; struct GBACheatDevice* cheats; };@@ -122,6 +116,8 @@
bool GBAThreadIsActive(struct GBAThread* threadContext); void GBAThreadInterrupt(struct GBAThread* threadContext); void GBAThreadContinue(struct GBAThread* threadContext); + +void GBARunOnThread(struct GBAThread* threadContext, void (*run)(struct GBAThread*)); void GBAThreadPause(struct GBAThread* threadContext); void GBAThreadUnpause(struct GBAThread* threadContext);@@ -133,18 +129,5 @@
#ifdef USE_PNG void GBAThreadTakeScreenshot(struct GBAThread* threadContext); #endif - -void GBASyncPostFrame(struct GBASync* sync); -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip); -void GBASyncWaitFrameEnd(struct GBASync* sync); -bool GBASyncDrawingFrame(struct GBASync* sync); - -void GBASyncSuspendDrawing(struct GBASync* sync); -void GBASyncResumeDrawing(struct GBASync* sync); - -void GBASyncProduceAudio(struct GBASync* sync, bool wait); -void GBASyncLockAudio(struct GBASync* sync); -void GBASyncUnlockAudio(struct GBASync* sync); -void GBASyncConsumeAudio(struct GBASync* sync); #endif
@@ -9,7 +9,7 @@ #include "gba/gba.h"
#include "gba/io.h" #include "gba/serialize.h" #include "gba/supervisor/rr.h" -#include "gba/supervisor/thread.h" +#include "gba/supervisor/sync.h" #include "util/memory.h"
@@ -17,6 +17,14 @@ #else
#define BYTES_PER_PIXEL 4 #endif +#define GBA_R5(X) ((X) & 0x1F) +#define GBA_G5(X) (((X) >> 5) & 0x1F) +#define GBA_B5(X) (((X) >> 10) & 0x1F) + +#define GBA_R8(X) (((X) << 3) & 0xF8) +#define GBA_G8(X) (((X) >> 2) & 0xF8) +#define GBA_B8(X) (((X) >> 7) & 0xF8) + enum { VIDEO_CYCLES_PER_PIXEL = 4,@@ -167,6 +175,9 @@
uint16_t* palette; uint16_t* vram; union GBAOAM* oam; + + bool disableBG[4]; + bool disableOBJ; }; struct GBAVideo {
@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/memory.h" +#define asm __asm__ + #include <3ds.h> void* anonymousMemoryMap(size_t size) {
@@ -3,8 +3,15 @@ *
* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef N3DS_VFS_H +#define N3DS_VFS_H + #include "util/vfs.h" +#define asm __asm__ + #include <3ds.h> struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags); + +#endif
@@ -17,6 +17,7 @@ #include "debugger/gdb-stub.h"
#endif #include "gba/video.h" +#include "util/string.h" #include <fcntl.h> #include <getopt.h>@@ -43,6 +44,7 @@ #endif
#ifdef USE_GDB_STUB { "gdb", no_argument, 0, 'g' }, #endif + { "help", no_argument, 0, 'h' }, { "movie", required_argument, 0, 'v' }, { "patch", required_argument, 0, 'p' }, { 0, 0, 0, 0 }@@ -53,7 +55,7 @@
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) { int ch; char options[64] = - "b:c:Dl:p:s:v:" + "b:c:Dhl:p:s:v:" #ifdef USE_CLI_DEBUGGER "d" #endif@@ -93,6 +95,9 @@ }
opts->debuggerType = DEBUGGER_GDB; break; #endif + case 'h': + opts->showHelp = true; + break; case 'l': GBAConfigSetDefaultValue(config, "logLevel", optarg); break;@@ -117,7 +122,7 @@ }
argc -= optind; argv += optind; if (argc != 1) { - return false; + return opts->showHelp; } opts->fname = strdup(argv[0]); return true;
@@ -30,6 +30,7 @@ char* movie;
enum DebuggerType debuggerType; bool debugAtStart; + bool showHelp; }; struct SubParser {
@@ -33,6 +33,7 @@ av_register_all();
encoder->d.postVideoFrame = _ffmpegPostVideoFrame; encoder->d.postAudioFrame = _ffmpegPostAudioFrame; + encoder->d.postAudioBuffer = 0; encoder->audioCodec = 0; encoder->videoCodec = 0;@@ -201,7 +202,8 @@ #ifndef USE_LIBAV
avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile); #else encoder->context = avformat_alloc_context(); - strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename)); + strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename) - 1); + encoder->context->filename[sizeof(encoder->context->filename) - 1] = '\0'; encoder->context->oformat = oformat; #endif
@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef FFMPEG_ENCODER #define FFMPEG_ENCODER -#include "gba/supervisor/thread.h" +#include "gba/gba.h" #include <libavformat/avformat.h>
@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "imagemagick-gif-encoder.h" #include "gba/video.h" +#include "util/string.h" static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); static void _magickPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);@@ -15,6 +16,7 @@ encoder->wand = 0;
encoder->d.postVideoFrame = _magickPostVideoFrame; encoder->d.postAudioFrame = _magickPostAudioFrame; + encoder->d.postAudioBuffer = 0; encoder->frameskip = 2; }
@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IMAGEMAGICK_GIF_ENCODER #define IMAGEMAGICK_GIF_ENCODER -#include "gba/supervisor/thread.h" +#include "gba/gba.h" #define MAGICKCORE_HDRI_ENABLE 0 #define MAGICKCORE_QUANTUM_DEPTH 8
@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "libretro.h" +#include "util/common.h" + #include "gba/gba.h" #include "gba/renderers/video-software.h" #include "gba/serialize.h"@@ -12,16 +14,18 @@ #include "gba/supervisor/overrides.h"
#include "gba/video.h" #include "util/vfs.h" +#define SAMPLES 1024 + static retro_environment_t environCallback; static retro_video_refresh_t videoCallback; -static retro_audio_sample_t audioCallback; +static retro_audio_sample_batch_t audioCallback; static retro_input_poll_t inputPollCallback; static retro_input_state_t inputCallback; static retro_log_printf_t logCallback; static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); -static void _postAudioFrame(struct GBAAVStream*, int16_t left, int16_t right); +static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio); static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); static struct GBA gba;@@ -37,8 +41,8 @@ unsigned retro_api_version(void) {
return RETRO_API_VERSION; } -void retro_set_environment(retro_environment_t environ) { - environCallback = environ; +void retro_set_environment(retro_environment_t env) { + environCallback = env; } void retro_set_video_refresh(retro_video_refresh_t video) {@@ -46,11 +50,11 @@ videoCallback = video;
} void retro_set_audio_sample(retro_audio_sample_t audio) { - audioCallback = audio; + UNUSED(audio); } void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) { - UNUSED(audioBatch); + audioCallback = audioBatch; } void retro_set_input_poll(retro_input_poll_t inputPoll) {@@ -64,8 +68,8 @@
void retro_get_system_info(struct retro_system_info* info) { info->need_fullpath = false; info->valid_extensions = "gba"; - info->library_version = PROJECT_VERSION; - info->library_name = PROJECT_NAME; + info->library_version = projectVersion; + info->library_name = projectName; info->block_extract = false; }@@ -117,7 +121,8 @@ } else {
logCallback = 0; } - stream.postAudioFrame = _postAudioFrame; + stream.postAudioFrame = 0; + stream.postAudioBuffer = _postAudioBuffer; stream.postVideoFrame = _postVideoFrame; GBACreate(&gba);@@ -133,6 +138,13 @@ GBAVideoSoftwareRendererCreate(&renderer);
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); renderer.outputBufferStride = 256; GBAVideoAssociateRenderer(&gba.video, &renderer.d); + + GBAAudioResizeBuffer(&gba.audio, SAMPLES); + +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 32768); + blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 32768); +#endif } void retro_deinit(void) {@@ -160,7 +172,6 @@ int frameCount = gba.video.frameCounter;
while (gba.video.frameCounter == frameCount) { ARMRunLoop(&cpu); } - videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * 256); } void retro_reset(void) {@@ -316,9 +327,22 @@ }
logCallback(retroLevel, "%s\n", message); } -static void _postAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) { +static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) { UNUSED(stream); - audioCallback(left, right); + int16_t samples[SAMPLES * 2]; +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + blip_read_samples(audio->left, samples, SAMPLES, true); + blip_read_samples(audio->right, samples + 1, SAMPLES, true); +#else + int16_t samplesR[SAMPLES]; + GBAAudioCopy(audio, &samples[SAMPLES], samplesR, SAMPLES); + size_t i; + for (i = 0; i < SAMPLES; ++i) { + samples[i * 2] = samples[SAMPLES + i]; + samples[i * 2 + 1] = samplesR[i]; + } +#endif + audioCallback(samples, SAMPLES); } static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
@@ -0,0 +1,121 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gl.h" + +#include "gba/video.h" + +static const GLint _glVertices[] = { + 0, 0, + 256, 0, + 256, 256, + 0, 256 +}; + +static const GLint _glTexCoords[] = { + 0, 0, + 1, 0, + 1, 1, + 0, 1 +}; + +static void GBAGLContextInit(struct VideoBackend* v, WHandle handle) { + UNUSED(handle); + struct GBAGLContext* context = (struct GBAGLContext*) v; + glGenTextures(1, &context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +#ifndef _WIN32 + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif + +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + +static void GBAGLContextDeinit(struct VideoBackend* v) { + struct GBAGLContext* context = (struct GBAGLContext*) v; + glDeleteTextures(1, &context->tex); +} + +static void GBAGLContextResized(struct VideoBackend* v, int w, int h) { + int drawW = w; + int drawH = h; + if (v->lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); +} + +static void GBAGLContextClear(struct VideoBackend* v) { + UNUSED(v); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); +} + +void GBAGLContextDrawFrame(struct VideoBackend* v) { + struct GBAGLContext* context = (struct GBAGLContext*) v; + glEnable(GL_TEXTURE_2D); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_INT, 0, _glVertices); + glTexCoordPointer(2, GL_INT, 0, _glTexCoords); + glMatrixMode (GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glBindTexture(GL_TEXTURE_2D, context->tex); + if (v->filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +} + +void GBAGLContextPostFrame(struct VideoBackend* v, const void* frame) { + struct GBAGLContext* context = (struct GBAGLContext*) v; + glBindTexture(GL_TEXTURE_2D, context->tex); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); +#endif +} + +void GBAGLContextCreate(struct GBAGLContext* context) { + context->d.init = GBAGLContextInit; + context->d.deinit = GBAGLContextDeinit; + context->d.resized = GBAGLContextResized; + context->d.swap = 0; + context->d.clear = GBAGLContextClear; + context->d.postFrame = GBAGLContextPostFrame; + context->d.drawFrame = GBAGLContextDrawFrame; + context->d.setMessage = 0; + context->d.clearMessage = 0; +}
@@ -0,0 +1,25 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GL_H +#define GL_H + +#ifdef __APPLE__ +#include <OpenGL/gl.h> +#else +#include <GL/gl.h> +#endif + +#include "platform/video-backend.h" + +struct GBAGLContext { + struct VideoBackend d; + + GLuint tex; +}; + +void GBAGLContextCreate(struct GBAGLContext*); + +#endif
@@ -7,8 +7,11 @@ #include "gba/supervisor/thread.h"
#include "gba/supervisor/config.h" #include "gba/gba.h" #include "gba/renderers/video-software.h" +#include "gba/serialize.h" #include "platform/commandline.h" +#include "util/string.h" +#include "util/vfs.h" #include <errno.h> #include <fcntl.h>@@ -16,27 +19,31 @@ #include <signal.h>
#include <inttypes.h> #include <sys/time.h> -#define PERF_OPTIONS "F:NPS:" +#define PERF_OPTIONS "F:L:NPS:" #define PERF_USAGE \ "\nBenchmark options:\n" \ " -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -N Disable video rendering entirely\n" \ " -P CSV output, useful for parsing\n" \ - " -S SEC Run for SEC in-game seconds before exiting" + " -S SEC Run for SEC in-game seconds before exiting\n" \ + " -L FILE Load a savestate when starting the test" struct PerfOpts { bool noVideo; bool csv; unsigned duration; unsigned frames; + char* savestate; }; static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet); static void _GBAPerfShutdown(int signal); static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg); +static void _loadSavestate(struct GBAThread* context); static struct GBAThread* _thread; static bool _dispatchExiting = false; +static struct VFile* _savestate = 0; int main(int argc, char** argv) { signal(SIGINT, _GBAPerfShutdown);@@ -44,7 +51,7 @@
struct GBAVideoSoftwareRenderer renderer; GBAVideoSoftwareRendererCreate(&renderer); - struct PerfOpts perfOpts = { false, false, 0, 0 }; + struct PerfOpts perfOpts = { false, false, 0, 0, 0 }; struct SubParser subparser = { .usage = PERF_USAGE, .parse = _parsePerfOpts,@@ -62,12 +69,13 @@ };
GBAConfigLoadDefaults(&config, &opts); struct GBAArguments args; - if (!parseArguments(&args, &config, argc, argv, &subparser)) { + bool parsed = parseArguments(&args, &config, argc, argv, &subparser); + if (!parsed || args.showHelp) { usage(argv[0], PERF_USAGE); freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config); - return 1; + return !parsed; } renderer.outputBuffer = malloc(256 * 256 * 4);@@ -79,6 +87,13 @@
if (!perfOpts.noVideo) { context.renderer = &renderer.d; } + if (perfOpts.savestate) { + _savestate = VFileOpen(perfOpts.savestate, O_RDONLY); + free(perfOpts.savestate); + } + if (_savestate) { + context.startCallback = _loadSavestate; + } context.debugger = createDebugger(&args, &context); context.overrides = GBAConfigGetOverrides(&config);@@ -95,7 +110,14 @@
if (!didStart) { goto cleanup; } + GBAThreadInterrupt(&context); + if (GBAThreadHasCrashed(&context)) { + GBAThreadJoin(&context); + goto cleanup; + } + GBAGetGameCode(context.gba, gameCode); + GBAThreadContinue(&context); int frames = perfOpts.frames; if (!frames) {@@ -126,6 +148,9 @@ printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
} cleanup: + if (_savestate) { + _savestate->close(_savestate); + } GBAConfigFreeOpts(&opts); freeArguments(&args); GBAConfigDeinit(&config);@@ -197,7 +222,16 @@ return true;
case 'S': opts->duration = strtoul(arg, 0, 10); return !errno; + case 'L': + opts->savestate = strdup(arg); + return true; default: return false; } } + +static void _loadSavestate(struct GBAThread* context) { + GBALoadStateNamed(context->gba, _savestate); + _savestate->close(_savestate); + _savestate = 0; +}
@@ -0,0 +1,90 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef POSIX_THREADING_H +#define POSIX_THREADING_H + +#include "util/common.h" + +#include <pthread.h> +#include <sys/time.h> +#ifdef __FreeBSD__ +#include <pthread_np.h> +#endif + +#define THREAD_ENTRY void* +typedef THREAD_ENTRY (*ThreadEntry)(void*); + +typedef pthread_t Thread; +typedef pthread_mutex_t Mutex; +typedef pthread_cond_t Condition; + +static inline int MutexInit(Mutex* mutex) { + return pthread_mutex_init(mutex, 0); +} + +static inline int MutexDeinit(Mutex* mutex) { + return pthread_mutex_destroy(mutex); +} + +static inline int MutexLock(Mutex* mutex) { + return pthread_mutex_lock(mutex); +} + +static inline int MutexUnlock(Mutex* mutex) { + return pthread_mutex_unlock(mutex); +} + +static inline int ConditionInit(Condition* cond) { + return pthread_cond_init(cond, 0); +} + +static inline int ConditionDeinit(Condition* cond) { + return pthread_cond_destroy(cond); +} + +static inline int ConditionWait(Condition* cond, Mutex* mutex) { + return pthread_cond_wait(cond, mutex); +} + +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + struct timespec ts; + struct timeval tv; + + gettimeofday(&tv, 0); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = (tv.tv_usec + timeoutMs * 1000L) * 1000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_nsec -= 1000000000L; + ++ts.tv_sec; + } + + return pthread_cond_timedwait(cond, mutex, &ts); +} + +static inline int ConditionWake(Condition* cond) { + return pthread_cond_broadcast(cond); +} + +static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { + return pthread_create(thread, 0, entry, context); +} + +static inline int ThreadJoin(Thread thread) { + return pthread_join(thread, 0); +} + +static inline int ThreadSetName(const char* name) { +#ifdef __APPLE__ + return pthread_setname_np(name); +#elif defined(__FreeBSD__) + pthread_set_name_np(pthread_self(), name); + return 0; +#else + return pthread_setname_np(pthread_self(), name); +#endif +} + +#endif
@@ -17,12 +17,13 @@ AudioDevice::AudioDevice(QObject* parent)
: QIODevice(parent) , m_context(nullptr) , m_drift(0) + , m_ratio(1.f) { setOpenMode(ReadOnly); } void AudioDevice::setFormat(const QAudioFormat& format) { - if (!GBAThreadIsActive(m_context)) { + if (!m_context || !GBAThreadIsActive(m_context)) { return; } #if RESAMPLE_LIBRARY == RESAMPLE_NN
@@ -19,7 +19,7 @@ }
using namespace QGBA; -#ifdef BUILD_QT_MULTIMEDIA +#ifndef BUILD_SDL AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::QT_MULTIMEDIA; #else AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::SDL;@@ -48,6 +48,8 @@ }
AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) + , m_context(nullptr) + , m_samples(GBA_AUDIO_SAMPLES) { }
@@ -33,6 +33,10 @@ }
} void AudioProcessorQt::start() { + if (!input()) { + return; + } + if (!m_device) { m_device = new AudioDevice(this); }
@@ -22,6 +22,10 @@ GBASDLDeinitAudio(&m_audio);
} void AudioProcessorSDL::start() { + if (!input()) { + return; + } + if (m_audio.thread) { GBASDLResumeAudio(&m_audio); } else {
@@ -4,10 +4,14 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() endif() set(PLATFORM_SRC) +set(QT_STATIC OFF) if(BUILD_SDL) if(NOT SDL_FOUND AND NOT SDL2_FOUND)@@ -27,7 +31,6 @@
find_package(Qt5Multimedia) find_package(Qt5OpenGL) find_package(Qt5Widgets) -find_package(OpenGL) if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND) message(WARNING "Cannot find Qt modules")@@ -35,12 +38,22 @@ set(BUILD_QT OFF PARENT_SCOPE)
return() endif() +list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) + +get_target_property(QT_TYPE Qt5::Core TYPE) +if(QT_TYPE STREQUAL STATIC_LIBRARY) + set(QT_STATIC ON) + add_definitions(-DQT_STATIC) +endif() + set(SOURCE_FILES AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp ConfigController.cpp Display.cpp + DisplayGL.cpp + DisplayQt.cpp GBAApp.cpp GBAKeyEditor.cpp GIFView.cpp@@ -51,12 +64,18 @@ InputController.cpp
KeyEditor.cpp LoadSaveState.cpp LogView.cpp + MemoryModel.cpp + MemoryView.cpp + MessagePainter.cpp + MultiplayerController.cpp OverrideView.cpp + PaletteView.cpp SavestateButton.cpp SensorView.cpp SettingsView.cpp ShortcutController.cpp ShortcutView.cpp + Swatch.cpp Window.cpp VFileDevice.cpp VideoView.cpp)@@ -66,14 +85,16 @@ CheatsView.ui
GIFView.ui LoadSaveState.ui LogView.ui + MemoryView.ui OverrideView.ui + PaletteView.ui SensorView.ui SettingsView.ui ShortcutView.ui VideoView.ui) set(QT_LIBRARIES) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5") set(AUDIO_SRC) if(BUILD_SDL)@@ -84,9 +105,12 @@ if(Qt5Multimedia_FOUND)
list(APPEND AUDIO_SRC AudioProcessorQt.cpp AudioDevice.cpp) + if (WIN32 AND QT_STATIC) + list(APPEND QT_LIBRARIES qtaudio_windows strmiids winmm) + endif() list(APPEND QT_LIBRARIES Qt5::Multimedia) add_definitions(-DBUILD_QT_MULTIMEDIA) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5") endif() if(NOT AUDIO_SRC)@@ -106,18 +130,28 @@ set_source_files_properties(${CMAKE_SOURCE_DIR}/res/mgba.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
qt5_add_resources(RESOURCES resources.qrc) if(WIN32) - list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc) + configure_file(${CMAKE_SOURCE_DIR}/res/mgba.rc.in ${CMAKE_BINARY_DIR}/res/mgba.rc) + list(APPEND RESOURCES ${CMAKE_BINARY_DIR}/res/mgba.rc) + if(QT_STATIC) + list(APPEND QT_LIBRARIES qwindows imm32) + endif() endif() add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES}) -target_compile_definitions(${BINARY_NAME}-qt PRIVATE ${FEATURE_DEFINES}) -set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in) +set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}") list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE) install(TARGETS ${BINARY_NAME}-qt RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt BUNDLE DESTINATION Applications COMPONENT ${BINARY_NAME}-qt) +if(UNIX AND NOT APPLE) + find_program(DESKTOP_FILE_INSTALL desktop-file-install) + if(DESKTOP_FILE_INSTALL) + install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/applications/\")") + endif() +endif() if(APPLE OR WIN32) set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) endif()
@@ -5,12 +5,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CheatsModel.h" +#include "VFileDevice.h" + #include <QFont> #include <QSet> extern "C" { #include "gba/cheats.h" -#include "util/vfs.h" } using namespace QGBA;@@ -201,7 +202,7 @@ endInsertRows();
} void CheatsModel::loadFile(const QString& path) { - VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY); + VFile* vf = VFileDevice::open(path, O_RDONLY); if (!vf) { return; }@@ -212,7 +213,7 @@ vf->close(vf);
} void CheatsModel::saveFile(const QString& path) { - VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_TRUNC | O_CREAT | O_WRONLY); + VFile* vf = VFileDevice::open(path, O_TRUNC | O_CREAT | O_WRONLY); if (!vf) { return; }
@@ -5,10 +5,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CheatsView.h" +#include "GBAApp.h" #include "GameController.h" #include <QClipboard> -#include <QFileDialog> extern "C" { #include "gba/cheats.h"@@ -38,6 +38,10 @@ });
connect(m_ui.addGSA, &QPushButton::clicked, [this]() { enterCheat(GBACheatAddGameSharkLine); + }); + + connect(m_ui.addPAR, &QPushButton::clicked, [this]() { + enterCheat(GBACheatAddProActionReplayLine); }); connect(m_ui.addCB, &QPushButton::clicked, [this]() {@@ -60,14 +64,14 @@ return false;
} void CheatsView::load() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file")); if (!filename.isEmpty()) { m_model.loadFile(filename); } } void CheatsView::save() { - QString filename = QFileDialog::getSaveFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file")); if (!filename.isEmpty()) { m_model.saveFile(filename); }@@ -113,4 +117,4 @@ m_model.endAppendRow();
} m_controller->threadContinue(); m_ui.codeEntry->clear(); -}+}
@@ -37,9 +37,6 @@ </widget>
</item> <item row="8" column="1" colspan="2"> <widget class="QPushButton" name="addPAR"> - <property name="enabled"> - <bool>false</bool> - </property> <property name="text"> <string>Add Pro Action Replay</string> </property>
@@ -23,8 +23,11 @@ : QObject(parent)
{ } -void ConfigOption::connect(std::function<void(const QVariant&)> slot) { - m_slot = slot; +void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) { + m_slots[parent] = slot; + QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() { + m_slots.remove(parent); + }); } QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) {@@ -33,6 +36,9 @@ action->setCheckable(true);
QObject::connect(action, &QAction::triggered, [this, value]() { emit valueChanged(value); }); + QObject::connect(parent, &QAction::destroyed, [this, action, value]() { + m_actions.removeAll(qMakePair(action, value)); + }); parent->addAction(action); m_actions.append(qMakePair(action, value)); return action;@@ -47,6 +53,9 @@ QAction* action = new QAction(text, parent);
action->setCheckable(true); QObject::connect(action, &QAction::triggered, [this, action]() { emit valueChanged(action->isChecked()); + }); + QObject::connect(parent, &QAction::destroyed, [this, action]() { + m_actions.removeAll(qMakePair(action, 1)); }); parent->addAction(action); m_actions.append(qMakePair(action, 1));@@ -76,7 +85,10 @@ bool signalsEnabled = action.first->blockSignals(true);
action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled); } - m_slot(value); + std::function<void(const QVariant&)> slot; + foreach(slot, m_slots.values()) { + slot(value); + } } ConfigController::ConfigController(QObject* parent)@@ -96,18 +108,19 @@ m_opts.audioSync = GameController::AUDIO_SYNC;
m_opts.videoSync = GameController::VIDEO_SYNC; m_opts.fpsTarget = 60; m_opts.audioBuffers = 2048; - m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; + m_opts.volume = GBA_AUDIO_VOLUME_MAX; + m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS; m_opts.rewindEnable = false; m_opts.rewindBufferInterval = 0; m_opts.rewindBufferCapacity = 0; + m_opts.useBios = true; + m_opts.suspendScreensaver = true; GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigLoad(&m_config); GBAConfigMap(&m_config, &m_opts); } ConfigController::~ConfigController() { - write(); - GBAConfigDeinit(&m_config); GBAConfigFreeOpts(&m_opts); }
@@ -32,11 +32,11 @@
public: ConfigOption(QObject* parent = nullptr); - void connect(std::function<void(const QVariant&)>); + void connect(std::function<void(const QVariant&)>, QObject* parent = nullptr); - QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = 0); - QAction* addValue(const QString& text, const char* value, QMenu* parent = 0); - QAction* addBoolean(const QString& text, QMenu* parent = 0); + QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr); + QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr); + QAction* addBoolean(const QString& text, QMenu* parent = nullptr); public slots: void setValue(bool value);@@ -49,7 +49,7 @@ signals:
void valueChanged(const QVariant& value); private: - std::function<void(const QVariant&)> m_slot; + QMap<QObject*, std::function<void(const QVariant&)>> m_slots; QList<QPair<QAction*, QVariant>> m_actions; };@@ -79,6 +79,8 @@
Configuration* overrides() { return GBAConfigGetOverrides(&m_config); } void saveOverride(const GBACartridgeOverride&); + Configuration* input() { return GBAConfigGetInput(&m_config); } + public slots: void setOption(const char* key, bool value); void setOption(const char* key, int value);@@ -90,10 +92,7 @@
void write(); private: - Configuration* configuration() { return &m_config.configTable; } Configuration* defaults() { return &m_config.defaultsTable; } - - friend class InputController; // TODO: Do this without friends GBAConfig m_config; GBAOptions m_opts;
@@ -5,283 +5,48 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Display.h" -#include <QApplication> -#include <QResizeEvent> +#include "DisplayGL.h" +#include "DisplayQt.h" extern "C" { -#include "gba/supervisor/thread.h" +#include "gba/video.h" } using namespace QGBA; -static const GLint _glVertices[] = { - 0, 0, - 256, 0, - 256, 256, - 0, 256 -}; +#ifdef BUILD_GL +Display::Driver Display::s_driver = Display::Driver::OPENGL; +#else +Display::Driver Display::s_driver = Display::Driver::QT; +#endif -static const GLint _glTexCoords[] = { - 0, 0, - 1, 0, - 1, 1, - 0, 1 -}; - -Display::Display(QGLFormat format, QWidget* parent) - : QGLWidget(format, parent) - , m_painter(nullptr) - , m_started(false) -{ - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - setAutoBufferSwap(false); - setCursor(Qt::BlankCursor); -} +Display* Display::create(QWidget* parent) { +#ifdef BUILD_GL + QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); + format.setSwapInterval(1); +#endif -void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) { - if (m_started) { - return; - } - m_painter = new Painter(this); - m_painter->setContext(thread); - m_painter->setBacking(buffer); - m_context = thread; - doneCurrent(); - m_painter->start(); - m_started = true; + switch (s_driver) { +#ifdef BUILD_GL + case Driver::OPENGL: + return new DisplayGL(format, parent); +#endif - lockAspectRatio(m_lockAspectRatio); - filter(m_filter); -} + case Driver::QT: + return new DisplayQt(parent); -void Display::stopDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->stop(); - m_started = false; - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::pauseDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->pause(); - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::unpauseDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->unpause(); - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::forceDraw() { - if (m_started) { - m_painter->forceDraw(); - } -} - -void Display::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; - if (m_started) { - m_painter->lockAspectRatio(lock); - } -} - -void Display::filter(bool filter) { - m_filter = filter; - if (m_started) { - m_painter->filter(filter); - } -} - -#ifdef USE_PNG -void Display::screenshot() { - GBAThreadInterrupt(m_context); - GBAThreadTakeScreenshot(m_context); - GBAThreadContinue(m_context); -} + default: +#ifdef BUILD_GL + return new DisplayGL(format, parent); +#else + return new DisplayQt(parent); #endif - -void Display::initializeGL() { - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - swapBuffers(); -} - -void Display::resizeEvent(QResizeEvent* event) { - if (m_started) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - m_painter->resize(event->size()); - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); } } -Painter::Painter(Display* parent) - : m_gl(parent) - , m_lockAspectRatio(false) - , m_filter(false) +Display::Display(QWidget* parent) + : QWidget(parent) { - m_size = parent->size(); -} - -void Painter::setContext(GBAThread* context) { - m_context = context; -} - -void Painter::setBacking(const uint32_t* backing) { - m_backing = backing; -} - -void Painter::resize(const QSize& size) { - m_size = size; - forceDraw(); - forceDraw(); -} - -void Painter::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; - forceDraw(); - forceDraw(); -} - -void Painter::filter(bool filter) { - m_filter = filter; - m_gl->makeCurrent(); - if (m_filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - m_gl->doneCurrent(); - forceDraw(); -} - -void Painter::start() { - m_gl->makeCurrent(); - glEnable(GL_TEXTURE_2D); - glGenTextures(1, &m_tex); - glBindTexture(GL_TEXTURE_2D, m_tex); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if (m_filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, 240, 160, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - m_gl->doneCurrent(); - - m_drawTimer = new QTimer; - m_drawTimer->moveToThread(QThread::currentThread()); - m_drawTimer->setInterval(0); - connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw())); - m_drawTimer->start(); -} - -void Painter::draw() { - m_gl->makeCurrent(); - GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip); - performDraw(); - GBASyncWaitFrameEnd(&m_context->sync); - m_gl->swapBuffers(); - m_gl->doneCurrent(); -} - -void Painter::forceDraw() { - m_gl->makeCurrent(); - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glClear(GL_COLOR_BUFFER_BIT); - performDraw(); - m_gl->swapBuffers(); - m_gl->doneCurrent(); -} - -void Painter::stop() { - m_drawTimer->stop(); - delete m_drawTimer; - m_gl->makeCurrent(); - glDeleteTextures(1, &m_tex); - glClear(GL_COLOR_BUFFER_BIT); - m_gl->swapBuffers(); - m_gl->doneCurrent(); - m_gl->context()->moveToThread(QApplication::instance()->thread()); -} - -void Painter::pause() { - m_drawTimer->stop(); - // Make sure both buffers are filled - forceDraw(); - forceDraw(); -} - -void Painter::unpause() { - m_drawTimer->start(); -} - -void Painter::performDraw() { - int w = m_size.width() * m_gl->devicePixelRatio(); - int h = m_size.height() * m_gl->devicePixelRatio(); -#ifndef Q_OS_MAC - // TODO: This seems to cause framerates to drag down to 120 FPS on OS X, - // even if the emulator can go faster. Look into why. - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glClear(GL_COLOR_BUFFER_BIT); -#endif - int drawW = w; - int drawH = h; - if (m_lockAspectRatio) { - if (w * 2 > h * 3) { - drawW = h * 3 / 2; - } else if (w * 2 < h * 3) { - drawH = w * 2 / 3; - } - } - glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); -#endif -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); -#endif - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - if (m_context->sync.videoFrameWait) { - glFlush(); - } + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -6,77 +6,42 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_DISPLAY #define QGBA_DISPLAY -#include <QGLWidget> -#include <QThread> -#include <QTimer> +#include <QWidget> struct GBAThread; namespace QGBA { -class Painter; -class Display : public QGLWidget { +class Display : public QWidget { Q_OBJECT public: - Display(QGLFormat format, QWidget* parent = nullptr); - -public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context); - void stopDrawing(); - void pauseDrawing(); - void unpauseDrawing(); - void forceDraw(); - void lockAspectRatio(bool lock); - void filter(bool filter); -#ifdef USE_PNG - void screenshot(); + enum class Driver { + QT = 0, +#ifdef BUILD_GL + OPENGL = 1, #endif + }; -protected: - virtual void initializeGL() override; - virtual void paintEvent(QPaintEvent*) override {}; - virtual void resizeEvent(QResizeEvent*) override; + Display(QWidget* parent = nullptr); -private: - Painter* m_painter; - bool m_started; - GBAThread* m_context; - bool m_lockAspectRatio; - bool m_filter; -}; + static Display* create(QWidget* parent = nullptr); + static void setDriver(Driver driver) { s_driver = driver; } -class Painter : public QObject { -Q_OBJECT - -public: - Painter(Display* parent); +public slots: + virtual void startDrawing(GBAThread* context) = 0; + virtual void stopDrawing() = 0; + virtual void pauseDrawing() = 0; + virtual void unpauseDrawing() = 0; + virtual void forceDraw() = 0; + virtual void lockAspectRatio(bool lock) = 0; + virtual void filter(bool filter) = 0; + virtual void framePosted(const uint32_t*) = 0; - void setContext(GBAThread*); - void setBacking(const uint32_t*); - -public slots: - void forceDraw(); - void draw(); - void start(); - void stop(); - void pause(); - void unpause(); - void resize(const QSize& size); - void lockAspectRatio(bool lock); - void filter(bool filter); + virtual void showMessage(const QString& message) = 0; private: - void performDraw(); - - QTimer* m_drawTimer; - GBAThread* m_context; - const uint32_t* m_backing; - GLuint m_tex; - QGLWidget* m_gl; - QSize m_size; - bool m_lockAspectRatio; - bool m_filter; + static Driver s_driver; }; }
@@ -0,0 +1,251 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "DisplayGL.h" + +#include <QApplication> +#include <QResizeEvent> + +extern "C" { +#include "gba/supervisor/thread.h" +} + +using namespace QGBA; + +DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) + : Display(parent) + , m_gl(new EmptyGLWidget(format, this)) + , m_painter(new PainterGL(m_gl)) + , m_drawThread(nullptr) + , m_lockAspectRatio(false) + , m_filter(false) + , m_context(nullptr) +{ +} + +DisplayGL::~DisplayGL() { + delete m_painter; +} + +void DisplayGL::startDrawing(GBAThread* thread) { + if (m_drawThread) { + return; + } + m_painter->setContext(thread); + m_context = thread; + m_painter->resize(size()); + m_gl->move(0, 0); + m_drawThread = new QThread(this); + m_drawThread->setObjectName("Painter Thread"); + m_gl->context()->doneCurrent(); + m_gl->context()->moveToThread(m_drawThread); + m_painter->moveToThread(m_drawThread); + connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start())); + m_drawThread->start(); + GBASyncSetVideoSync(&m_context->sync, false); + + lockAspectRatio(m_lockAspectRatio); + filter(m_filter); + resizePainter(); +} + +void DisplayGL::stopDrawing() { + if (m_drawThread) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + } + QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); + m_drawThread->exit(); + m_drawThread = nullptr; + if (GBAThreadIsActive(m_context)) { + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::pauseDrawing() { + if (m_drawThread) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + } + QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); + if (GBAThreadIsActive(m_context)) { + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::unpauseDrawing() { + if (m_drawThread) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + } + QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); + if (GBAThreadIsActive(m_context)) { + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::forceDraw() { + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "forceDraw"); + } +} + +void DisplayGL::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock)); + } +} + +void DisplayGL::filter(bool filter) { + m_filter = filter; + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter)); + } +} + +void DisplayGL::framePosted(const uint32_t* buffer) { + if (m_drawThread && buffer) { + QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer)); + } +} + +void DisplayGL::showMessage(const QString& message) { + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "showMessage", Q_ARG(const QString&, message)); + } +} + +void DisplayGL::resizeEvent(QResizeEvent*) { + resizePainter(); +} + +void DisplayGL::resizePainter() { + m_gl->resize(size()); + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size())); + } +} + +PainterGL::PainterGL(QGLWidget* parent) + : m_gl(parent) + , m_active(false) + , m_context(nullptr) + , m_messagePainter(nullptr) +{ + GBAGLContextCreate(&m_backend); + m_backend.d.swap = [](VideoBackend* v) { + PainterGL* painter = static_cast<PainterGL*>(v->user); + painter->m_gl->swapBuffers(); + }; + m_backend.d.user = this; + m_backend.d.filter = false; + m_backend.d.lockAspectRatio = false; +} + +void PainterGL::setContext(GBAThread* context) { + m_context = context; +} + +void PainterGL::setBacking(const uint32_t* backing) { + m_gl->makeCurrent(); + m_backend.d.postFrame(&m_backend.d, backing); + if (m_active) { + draw(); + } + m_gl->doneCurrent(); +} + +void PainterGL::resize(const QSize& size) { + m_size = size; + if (m_active) { + m_messagePainter->resize(size, m_backend.d.lockAspectRatio); + forceDraw(); + } +} + +void PainterGL::lockAspectRatio(bool lock) { + m_backend.d.lockAspectRatio = lock; + if (m_active) { + m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio); + forceDraw(); + } +} + +void PainterGL::filter(bool filter) { + m_backend.d.filter = filter; + if (m_active) { + forceDraw(); + } +} + +void PainterGL::start() { + m_messagePainter = new MessagePainter(this); + m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio); + m_gl->makeCurrent(); + m_backend.d.init(&m_backend.d, reinterpret_cast<WHandle>(m_gl->winId())); + m_gl->doneCurrent(); + m_active = true; +} + +void PainterGL::draw() { + if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) { + m_painter.begin(m_gl->context()->device()); + performDraw(); + m_painter.end(); + GBASyncWaitFrameEnd(&m_context->sync); + m_backend.d.swap(&m_backend.d); + } else { + GBASyncWaitFrameEnd(&m_context->sync); + } +} + +void PainterGL::forceDraw() { + m_painter.begin(m_gl->context()->device()); + performDraw(); + m_painter.end(); + m_backend.d.swap(&m_backend.d); +} + +void PainterGL::stop() { + m_active = false; + m_gl->makeCurrent(); + m_backend.d.clear(&m_backend.d); + m_backend.d.swap(&m_backend.d); + m_backend.d.deinit(&m_backend.d); + m_gl->doneCurrent(); + m_gl->context()->moveToThread(m_gl->thread()); + m_messagePainter->clearMessage(); + delete m_messagePainter; + m_messagePainter = nullptr; + moveToThread(m_gl->thread()); +} + +void PainterGL::pause() { + m_active = false; + // Make sure both buffers are filled + forceDraw(); + forceDraw(); +} + +void PainterGL::unpause() { + m_active = true; +} + +void PainterGL::performDraw() { + m_painter.beginNativePainting(); + float r = m_gl->devicePixelRatio(); + m_backend.d.resized(&m_backend.d, m_size.width() * r, m_size.height() * r); + m_backend.d.drawFrame(&m_backend.d); + m_painter.endNativePainting(); + m_messagePainter->paint(&m_painter); +} + +void PainterGL::showMessage(const QString& message) { + m_messagePainter->showMessage(message); +}
@@ -0,0 +1,105 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DISPLAY_GL +#define QGBA_DISPLAY_GL + +#include "Display.h" + +#include "MessagePainter.h" + +#include <QGLWidget> +#include <QThread> +#include <QTimer> + +extern "C" { +#include "platform/opengl/gl.h" +} + +struct GBAThread; + +namespace QGBA { + +class EmptyGLWidget : public QGLWidget { +public: + EmptyGLWidget(const QGLFormat& format, QWidget* parent) : QGLWidget(format, parent) { setAutoBufferSwap(false); } + +protected: + void paintEvent(QPaintEvent*) override {} + void resizeEvent(QResizeEvent*) override {} +}; + +class PainterGL; +class DisplayGL : public Display { +Q_OBJECT + +public: + DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); + ~DisplayGL(); + +public slots: + void startDrawing(GBAThread* context) override; + void stopDrawing() override; + void pauseDrawing() override; + void unpauseDrawing() override; + void forceDraw() override; + void lockAspectRatio(bool lock) override; + void filter(bool filter) override; + void framePosted(const uint32_t*) override; + + void showMessage(const QString& message) override; + +protected: + virtual void paintEvent(QPaintEvent*) override {} + virtual void resizeEvent(QResizeEvent*) override; + +private: + void resizePainter(); + + QGLWidget* m_gl; + PainterGL* m_painter; + QThread* m_drawThread; + GBAThread* m_context; + bool m_lockAspectRatio; + bool m_filter; +}; + +class PainterGL : public QObject { +Q_OBJECT + +public: + PainterGL(QGLWidget* parent); + + void setContext(GBAThread*); + +public slots: + void setBacking(const uint32_t*); + void forceDraw(); + void draw(); + void start(); + void stop(); + void pause(); + void unpause(); + void resize(const QSize& size); + void lockAspectRatio(bool lock); + void filter(bool filter); + + void showMessage(const QString& message); + +private: + void performDraw(); + + QPainter m_painter; + QGLWidget* m_gl; + bool m_active; + GBAThread* m_context; + GBAGLContext m_backend; + QSize m_size; + MessagePainter* m_messagePainter; +}; + +} + +#endif
@@ -0,0 +1,81 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "DisplayQt.h" + +#include <QPainter> + +using namespace QGBA; + +DisplayQt::DisplayQt(QWidget* parent) + : Display(parent) + , m_backing(nullptr) + , m_lockAspectRatio(false) + , m_filter(false) +{ +} + +void DisplayQt::startDrawing(GBAThread*) { +} + +void DisplayQt::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + update(); +} + +void DisplayQt::filter(bool filter) { + m_filter = filter; + update(); +} + +void DisplayQt::framePosted(const uint32_t* buffer) { + update(); + if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) { + return; + } +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB16); +#else + m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB555); +#endif +#else + m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB32); +#endif +} + +void DisplayQt::showMessage(const QString& message) { + m_messagePainter.showMessage(message); +} + +void DisplayQt::paintEvent(QPaintEvent*) { + QPainter painter(this); + painter.fillRect(QRect(QPoint(), size()), Qt::black); + if (m_filter) { + painter.setRenderHint(QPainter::SmoothPixmapTransform); + } + QSize s = size(); + QSize ds = s; + if (m_lockAspectRatio) { + if (s.width() * 2 > s.height() * 3) { + ds.setWidth(s.height() * 3 / 2); + } else if (s.width() * 2 < s.height() * 3) { + ds.setHeight(s.width() * 2 / 3); + } + } + QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); + QRect full(origin, ds); + +#ifdef COLOR_5_6_5 + painter.drawImage(full, m_backing, QRect(0, 0, 240, 160)); +#else + painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, 240, 160)); +#endif + m_messagePainter.paint(&painter); +} + +void DisplayQt::resizeEvent(QResizeEvent*) { + m_messagePainter.resize(size(), m_lockAspectRatio); +}
@@ -0,0 +1,50 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DISPLAY_QT +#define QGBA_DISPLAY_QT + +#include "Display.h" +#include "MessagePainter.h" + +#include <QImage> +#include <QTimer> + +struct GBAThread; + +namespace QGBA { + +class DisplayQt : public Display { +Q_OBJECT + +public: + DisplayQt(QWidget* parent = nullptr); + +public slots: + void startDrawing(GBAThread* context) override; + void stopDrawing() override {} + void pauseDrawing() override {} + void unpauseDrawing() override {} + void forceDraw() override { update(); } + void lockAspectRatio(bool lock) override; + void filter(bool filter) override; + void framePosted(const uint32_t*) override; + + void showMessage(const QString& message) override; + +protected: + virtual void paintEvent(QPaintEvent*) override; + virtual void resizeEvent(QResizeEvent*) override;; + +private: + QImage m_backing; + bool m_lockAspectRatio; + bool m_filter; + MessagePainter m_messagePainter; +}; + +} + +#endif
@@ -6,9 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GBAApp.h" #include "AudioProcessor.h" +#include "Display.h" #include "GameController.h" +#include "Window.h" +#include <QFileInfo> #include <QFileOpenEvent> +#include <QIcon> extern "C" { #include "platform/commandline.h"@@ -17,43 +21,144 @@ }
using namespace QGBA; +static GBAApp* g_app = nullptr; + GBAApp::GBAApp(int& argc, char* argv[]) : QApplication(argc, argv) - , m_window(&m_configController) + , m_windows{} { + g_app = this; + #ifdef BUILD_SDL SDL_Init(SDL_INIT_NOPARACHUTE); #endif + + setWindowIcon(QIcon(":/res/mgba-1024.png")); SocketSubsystemInit(); + qRegisterMetaType<const uint32_t*>("const uint32_t*"); + + QApplication::setApplicationName(projectName); + QApplication::setApplicationVersion(projectVersion); - QApplication::setApplicationName(PROJECT_NAME); - QApplication::setApplicationVersion(PROJECT_VERSION); + Window* w = new Window(&m_configController); + m_windows[0] = w; #ifndef Q_OS_MAC - m_window.show(); + w->show(); #endif GBAArguments args; if (m_configController.parseArguments(&args, argc, argv)) { - m_window.argumentsPassed(&args); + w->argumentsPassed(&args); } else { - m_window.loadConfig(); + w->loadConfig(); } freeArguments(&args); + Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("videoDriver").toInt())); AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt())); - m_window.controller()->reloadAudioDriver(); + w->controller()->reloadAudioDriver(); + w->controller()->setMultiplayerController(&m_multiplayer); #ifdef Q_OS_MAC - m_window.show(); + w->show(); #endif } bool GBAApp::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { - m_window.controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file()); + m_windows[0]->controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file()); return true; } return QApplication::event(event); } + +Window* GBAApp::newWindow() { + if (m_multiplayer.attached() >= MAX_GBAS) { + return nullptr; + } + Window* w = new Window(&m_configController, m_multiplayer.attached()); + m_windows[m_multiplayer.attached()] = w; + w->setAttribute(Qt::WA_DeleteOnClose); +#ifndef Q_OS_MAC + w->show(); +#endif + w->loadConfig(); + w->controller()->setMultiplayerController(&m_multiplayer); +#ifdef Q_OS_MAC + w->show(); +#endif + return w; +} + +GBAApp* GBAApp::app() { + return g_app; +} + +void GBAApp::interruptAll() { + for (int i = 0; i < MAX_GBAS; ++i) { + if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) { + continue; + } + m_windows[i]->controller()->threadInterrupt(); + } +} + +void GBAApp::continueAll() { + for (int i = 0; i < MAX_GBAS; ++i) { + if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) { + continue; + } + m_windows[i]->controller()->threadContinue(); + } +} + +QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) { + interruptAll(); + QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter); + continueAll(); + if (!filename.isEmpty()) { + m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + } + return filename; +} + +QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) { + interruptAll(); + QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter); + continueAll(); + if (!filename.isEmpty()) { + m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + } + return filename; +} + +QFileDialog* GBAApp::getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter) { + FileDialog* dialog = new FileDialog(this, owner, title, filter); + dialog->setAcceptMode(QFileDialog::AcceptOpen); + return dialog; +} + +QFileDialog* GBAApp::getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter) { + FileDialog* dialog = new FileDialog(this, owner, title, filter); + dialog->setAcceptMode(QFileDialog::AcceptSave); + return dialog; +} + +GBAApp::FileDialog::FileDialog(GBAApp* app, QWidget* parent, const QString& caption, const QString& filter) + : QFileDialog(parent, caption, app->m_configController.getQtOption("lastDirectory").toString(), filter) + , m_app(app) +{ +} + +int GBAApp::FileDialog::exec() { + m_app->interruptAll(); + bool didAccept = QFileDialog::exec() == QDialog::Accepted; + QStringList filenames = selectedFiles(); + if (!filenames.isEmpty()) { + m_app->m_configController.setQtOption("lastDirectory", QFileInfo(filenames[0]).dir().path()); + } + m_app->continueAll(); + return didAccept; +}
@@ -7,26 +7,57 @@ #ifndef QGBA_APP_H
#define QGBA_APP_H #include <QApplication> +#include <QFileDialog> #include "ConfigController.h" -#include "Window.h" +#include "MultiplayerController.h" + +extern "C" { +#include "gba/sio.h" +} namespace QGBA { class GameController; +class Window; class GBAApp : public QApplication { Q_OBJECT public: GBAApp(int& argc, char* argv[]); + static GBAApp* app(); + + Window* newWindow(); + + QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = QString()); + QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString()); + + QFileDialog* getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); + QFileDialog* getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); + +public slots: + void interruptAll(); + void continueAll(); protected: bool event(QEvent*); private: + class FileDialog : public QFileDialog { + public: + FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), const QString& filter = QString()); + virtual int exec() override; + + private: + GBAApp* m_app; + }; + + Window* newWindowInternal(); + ConfigController m_configController; - Window m_window; + Window* m_windows[MAX_GBAS]; + MultiplayerController m_multiplayer; }; }
@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GBAKeyEditor.h" +#include <QComboBox> #include <QPaintEvent> #include <QPainter> #include <QPushButton>@@ -20,9 +21,11 @@ const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
const qreal GBAKeyEditor::DPAD_WIDTH = 0.1; const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1; -GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent) +GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent) : QWidget(parent) + , m_profileSelect(nullptr) , m_type(type) + , m_profile(profile) , m_controller(controller) { setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);@@ -41,19 +44,27 @@ m_keyB = new KeyEditor(this);
m_keyL = new KeyEditor(this); m_keyR = new KeyEditor(this); - lookupBinding(map, m_keyDU, GBA_KEY_UP); - lookupBinding(map, m_keyDD, GBA_KEY_DOWN); - lookupBinding(map, m_keyDL, GBA_KEY_LEFT); - lookupBinding(map, m_keyDR, GBA_KEY_RIGHT); - lookupBinding(map, m_keySelect, GBA_KEY_SELECT); - lookupBinding(map, m_keyStart, GBA_KEY_START); - lookupBinding(map, m_keyA, GBA_KEY_A); - lookupBinding(map, m_keyB, GBA_KEY_B); - lookupBinding(map, m_keyL, GBA_KEY_L); - lookupBinding(map, m_keyR, GBA_KEY_R); + refresh(); #ifdef BUILD_SDL - lookupAxes(map); + if (type == SDL_BINDING_BUTTON) { + controller->recalibrateAxes(); + lookupAxes(map); + + m_profileSelect = new QComboBox(this); + m_profileSelect->addItems(controller->connectedGamepads(type)); + int activeGamepad = controller->gamepad(type); + if (activeGamepad > 0) { + m_profileSelect->setCurrentIndex(activeGamepad); + } + + connect(m_profileSelect, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this] (int i) { + m_controller->setGamepad(m_type, i); + m_profile = m_profileSelect->currentText(); + m_controller->loadProfile(m_type, m_profile); + refresh(); + }); + } #endif connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext()));@@ -128,6 +139,10 @@ setLocation(m_keyA, 0.826, 0.451);
setLocation(m_keyB, 0.667, 0.490); setLocation(m_keyL, 0.1, 0.1); setLocation(m_keyR, 0.9, 0.1); + + if (m_profileSelect) { + setLocation(m_profileSelect, 0.5, 0.7); + } } void GBAKeyEditor::paintEvent(QPaintEvent* event) {@@ -163,6 +178,30 @@ bindKey(m_keyB, GBA_KEY_B);
bindKey(m_keyL, GBA_KEY_L); bindKey(m_keyR, GBA_KEY_R); m_controller->saveConfiguration(m_type); + +#ifdef BUILD_SDL + if (m_profileSelect) { + m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText()); + } +#endif + + if (!m_profile.isNull()) { + m_controller->saveProfile(m_type, m_profile); + } +} + +void GBAKeyEditor::refresh() { + const GBAInputMap* map = m_controller->map(); + lookupBinding(map, m_keyDU, GBA_KEY_UP); + lookupBinding(map, m_keyDD, GBA_KEY_DOWN); + lookupBinding(map, m_keyDL, GBA_KEY_LEFT); + lookupBinding(map, m_keyDR, GBA_KEY_RIGHT); + lookupBinding(map, m_keySelect, GBA_KEY_SELECT); + lookupBinding(map, m_keyStart, GBA_KEY_START); + lookupBinding(map, m_keyA, GBA_KEY_A); + lookupBinding(map, m_keyB, GBA_KEY_B); + lookupBinding(map, m_keyL, GBA_KEY_L); + lookupBinding(map, m_keyR, GBA_KEY_R); } void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) {@@ -199,11 +238,15 @@ }
#endif void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { +#ifdef BUILD_SDL if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) { m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key); } else { +#endif m_controller->bindKey(m_type, keyEditor->value(), key); +#ifdef BUILD_SDL } +#endif } bool GBAKeyEditor::findFocus() {
@@ -15,6 +15,7 @@ extern "C" {
#include "gba/input.h" } +class QComboBox; class QTimer; namespace QGBA {@@ -26,7 +27,7 @@ class GBAKeyEditor : public QWidget {
Q_OBJECT public: - GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr); + GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr); public slots: void setAll();@@ -38,6 +39,7 @@
private slots: void setNext(); void save(); + void refresh(); #ifdef BUILD_SDL void setAxisValue(int axis, int32_t value); #endif@@ -61,6 +63,7 @@ #endif
KeyEditor* keyById(GBAKey); + QComboBox* m_profileSelect; QWidget* m_buttons; KeyEditor* m_keyDU; KeyEditor* m_keyDD;@@ -76,6 +79,7 @@ QList<KeyEditor*> m_keyOrder;
QList<KeyEditor*>::iterator m_currentKey; uint32_t m_type; + QString m_profile; InputController* m_controller; QPicture m_background;
@@ -7,7 +7,8 @@ #include "GIFView.h"
#ifdef USE_MAGICK -#include <QFileDialog> +#include "GBAApp.h" + #include <QMap> using namespace QGBA;@@ -48,7 +49,7 @@ m_ui.start->setEnabled(true);
} void GIFView::selectFile() { - QString filename = QFileDialog::getSaveFileName(this, tr("Select output file")); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file"), tr("Graphics Interchange Format (*.gif)")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); if (!ImageMagickGIFEncoderIsOpen(&m_encoder)) {
@@ -14,6 +14,9 @@ <property name="windowTitle">
<string>Record GIF</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> <item> <layout class="QGridLayout" name="gridLayout"> <item row="1" column="0">
@@ -7,6 +7,8 @@ #include "GameController.h"
#include "AudioProcessor.h" #include "InputController.h" +#include "MultiplayerController.h" +#include "VFileDevice.h" #include <QDateTime> #include <QThread>@@ -17,6 +19,7 @@ extern "C" {
#include "gba/audio.h" #include "gba/gba.h" #include "gba/serialize.h" +#include "gba/sharkport.h" #include "gba/renderers/video-software.h" #include "gba/supervisor/config.h" #include "util/vfs.h"@@ -32,15 +35,22 @@ : QObject(parent)
, m_drawContext(new uint32_t[256 * 256]) , m_threadContext() , m_activeKeys(0) + , m_inactiveKeys(0) , m_logLevels(0) , m_gameOpen(false) , m_audioThread(new QThread(this)) , m_audioProcessor(AudioProcessor::create()) + , m_pauseAfterFrame(false) , m_videoSync(VIDEO_SYNC) , m_audioSync(AUDIO_SYNC) + , m_fpsTarget(-1) , m_turbo(false) , m_turboForced(false) + , m_turboSpeed(-1) + , m_wasPaused(false) , m_inputController(nullptr) + , m_multiplayer(nullptr) + , m_stateSlot(1) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer);@@ -57,7 +67,7 @@ m_threadContext.renderer = &m_renderer->d;
m_threadContext.userData = this; m_threadContext.rewindBufferCapacity = 0; m_threadContext.cheats = &m_cheatDevice; - m_threadContext.logLevel = -1; + m_threadContext.logLevel = GBA_LOG_ALL; m_lux.p = this; m_lux.sample = [] (GBALuminanceSource* context) {@@ -90,10 +100,11 @@
m_threadContext.startCallback = [] (GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); controller->m_audioProcessor->setInput(context); - // Override the GBA object's log level to prevent stdout spew - context->gba->logLevel = GBA_LOG_FATAL; context->gba->luminanceSource = &controller->m_lux; context->gba->rtcSource = &controller->m_rtc; + context->gba->rumble = controller->m_inputController->rumble(); + context->gba->rotationSource = controller->m_inputController->rotationSource(); + controller->m_fpsTarget = context->fpsTarget; controller->gameStarted(context); };@@ -104,48 +115,86 @@ };
m_threadContext.frameCallback = [] (GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - controller->m_pauseMutex.lock(); - if (controller->m_pauseAfterFrame) { + if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { GBAThreadPauseFromThread(context); - controller->m_pauseAfterFrame = false; controller->gamePaused(&controller->m_threadContext); } - controller->m_pauseMutex.unlock(); - controller->frameAvailable(controller->m_drawContext); + if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { + controller->frameAvailable(controller->m_drawContext); + } else { + controller->frameAvailable(nullptr); + } }; m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { + static const char* stubMessage = "Stub software interrupt"; + if (!context) { + return; + } GameController* controller = static_cast<GameController*>(context->userData); + if (level == GBA_LOG_STUB && strncmp(stubMessage, format, strlen(stubMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int immediate = va_arg(argc, int); + controller->unimplementedBiosCall(immediate); + } if (level == GBA_LOG_FATAL) { QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args))); } else if (!(controller->m_logLevels & level)) { return; } - controller->postLog(level, QString().vsprintf(format, args)); + QString message(QString().vsprintf(format, args)); + if (level == GBA_LOG_STATUS) { + controller->statusPosted(message); + } + controller->postLog(level, message); }; + connect(&m_rewindTimer, &QTimer::timeout, [this]() { + GBARewind(&m_threadContext, 1); + emit rewound(&m_threadContext); + emit frameAvailable(m_drawContext); + }); + m_rewindTimer.setInterval(100); + + m_audioThread->setObjectName("Audio Thread"); m_audioThread->start(QThread::TimeCriticalPriority); m_audioProcessor->moveToThread(m_audioThread); connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start())); connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause())); connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause())); connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start())); - -#ifdef BUILD_SDL - connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(testSDLEvents())); -#endif + connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(pollEvents())); } GameController::~GameController() { m_audioThread->quit(); m_audioThread->wait(); disconnect(); + clearMultiplayerController(); closeGame(); GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; } +void GameController::setMultiplayerController(MultiplayerController* controller) { + if (controller == m_multiplayer) { + return; + } + clearMultiplayerController(); + m_multiplayer = controller; + controller->attachGame(this); +} + +void GameController::clearMultiplayerController() { + if (!m_multiplayer) { + return; + } + m_multiplayer->detachGame(this); + m_multiplayer = nullptr; +} + void GameController::setOverride(const GBACartridgeOverride& override) { m_threadContext.override = override; m_threadContext.hasOverride = true;@@ -156,7 +205,10 @@ setFrameskip(opts->frameskip);
setAudioSync(opts->audioSync); setVideoSync(opts->videoSync); setSkipBIOS(opts->skipBios); + setUseBIOS(opts->useBios); setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval); + setVolume(opts->volume); + setMute(opts->mute); threadInterrupt(); m_threadContext.idleOptimization = opts->idleOptimization;@@ -196,7 +248,18 @@ m_dirmode = dirmode;
openGame(); } -void GameController::openGame() { +void GameController::bootBIOS() { + closeGame(); + m_fname = QString(); + m_dirmode = false; + openGame(true); +} + +void GameController::openGame(bool biosOnly) { + if (biosOnly && (!m_useBios || m_bios.isNull())) { + return; + } + m_gameOpen = true; m_pauseAfterFrame = false;@@ -210,31 +273,40 @@ m_threadContext.sync.audioWait = m_audioSync;
} m_threadContext.gameDir = 0; - m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); - if (m_dirmode) { - m_threadContext.gameDir = VDirOpen(m_threadContext.fname); - m_threadContext.stateDir = m_threadContext.gameDir; + m_threadContext.bootBios = biosOnly; + if (biosOnly) { + m_threadContext.fname = nullptr; } else { - m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); + m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); + if (m_dirmode) { + m_threadContext.gameDir = VDirOpen(m_threadContext.fname); + m_threadContext.stateDir = m_threadContext.gameDir; + } else { + m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); #if USE_LIBZIP - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); - } + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); + } #endif #if USE_LZMA - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); - } + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); + } #endif + } } - if (!m_bios.isNull()) { - m_threadContext.bios = VFileOpen(m_bios.toLocal8Bit().constData(), O_RDONLY); + if (!m_bios.isNull() && m_useBios) { + m_threadContext.bios = VFileDevice::open(m_bios, O_RDONLY); + } else { + m_threadContext.bios = nullptr; } if (!m_patch.isNull()) { - m_threadContext.patch = VFileOpen(m_patch.toLocal8Bit().constData(), O_RDONLY); + m_threadContext.patch = VFileDevice::open(m_patch, O_RDONLY); } + + m_inputController->recalibrateAxes(); if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false;@@ -263,10 +335,39 @@ m_patch = path;
} } +void GameController::importSharkport(const QString& path) { + if (!m_gameOpen) { + return; + } + VFile* vf = VFileDevice::open(path, O_RDONLY); + if (!vf) { + return; + } + threadInterrupt(); + GBASavedataImportSharkPort(m_threadContext.gba, vf, false); + threadContinue(); + vf->close(vf); +} + +void GameController::exportSharkport(const QString& path) { + if (!m_gameOpen) { + return; + } + VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + threadInterrupt(); + GBASavedataExportSharkPort(m_threadContext.gba, vf); + threadContinue(); + vf->close(vf); +} + void GameController::closeGame() { if (!m_gameOpen) { return; } + m_rewindTimer.stop(); if (GBAThreadIsPaused(&m_threadContext)) { GBAThreadUnpause(&m_threadContext); }@@ -293,6 +394,7 @@
void GameController::crashGame(const QString& crashMessage) { closeGame(); emit gameCrashed(crashMessage); + emit gameStopped(&m_threadContext); } bool GameController::isPaused() {@@ -303,7 +405,7 @@ return GBAThreadIsPaused(&m_threadContext);
} void GameController::setPaused(bool paused) { - if (paused == GBAThreadIsPaused(&m_threadContext)) { + if (!m_gameOpen || m_rewindTimer.isActive() || paused == GBAThreadIsPaused(&m_threadContext)) { return; } if (paused) {@@ -332,10 +434,12 @@ }
} void GameController::frameAdvance() { - m_pauseMutex.lock(); - m_pauseAfterFrame = true; - setPaused(false); - m_pauseMutex.unlock(); + if (m_rewindTimer.isActive()) { + return; + } + if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { + setPaused(false); + } } void GameController::setRewind(bool enable, int capacity, int interval) {@@ -362,22 +466,62 @@ } else {
GBARewind(&m_threadContext, states); } threadContinue(); + emit rewound(&m_threadContext); + emit frameAvailable(m_drawContext); +} + +void GameController::startRewinding() { + if (!m_gameOpen || m_rewindTimer.isActive()) { + return; + } + m_wasPaused = isPaused(); + setPaused(true); + m_rewindTimer.start(); +} + +void GameController::stopRewinding() { + if (!m_rewindTimer.isActive()) { + return; + } + m_rewindTimer.stop(); + setPaused(m_wasPaused); } void GameController::keyPressed(int key) { int mappedKey = 1 << key; m_activeKeys |= mappedKey; + if (!m_inputController->allowOpposing()) { + if ((m_activeKeys & 0x30) == 0x30) { + m_inactiveKeys |= mappedKey ^ 0x30; + m_activeKeys ^= mappedKey ^ 0x30; + } + if ((m_activeKeys & 0xC0) == 0xC0) { + m_inactiveKeys |= mappedKey ^ 0xC0; + m_activeKeys ^= mappedKey ^ 0xC0; + } + } updateKeys(); } void GameController::keyReleased(int key) { int mappedKey = 1 << key; m_activeKeys &= ~mappedKey; + if (!m_inputController->allowOpposing()) { + if (mappedKey & 0x30) { + m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey); + m_inactiveKeys &= ~0x30; + } + if (mappedKey & 0xC0) { + m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey); + m_inactiveKeys &= ~0xC0; + } + } updateKeys(); } void GameController::clearKeys() { m_activeKeys = 0; + m_inactiveKeys = 0; updateKeys(); }@@ -390,10 +534,13 @@ }
void GameController::setFPSTarget(float fps) { threadInterrupt(); + m_fpsTarget = fps; m_threadContext.fpsTarget = fps; + if (m_turbo && m_turboSpeed > 0) { + m_threadContext.fpsTarget *= m_turboSpeed; + } redoSamples(m_audioProcessor->getBufferSamples()); threadContinue(); - QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged"); } void GameController::setSkipBIOS(bool set) {@@ -402,18 +549,32 @@ m_threadContext.skipBios = set;
threadContinue(); } -void GameController::loadState(int slot) { +void GameController::setUseBIOS(bool use) { threadInterrupt(); - GBALoadState(&m_threadContext, m_threadContext.stateDir, slot); + m_useBios = use; threadContinue(); - emit stateLoaded(&m_threadContext); - emit frameAvailable(m_drawContext); +} + +void GameController::loadState(int slot) { + if (slot > 0) { + m_stateSlot = slot; + } + GBARunOnThread(&m_threadContext, [](GBAThread* context) { + GameController* controller = static_cast<GameController*>(context->userData); + GBALoadState(context, context->stateDir, controller->m_stateSlot); + controller->stateLoaded(context); + controller->frameAvailable(controller->m_drawContext); + }); } void GameController::saveState(int slot) { - threadInterrupt(); - GBASaveState(&m_threadContext, m_threadContext.stateDir, slot, true); - threadContinue(); + if (slot > 0) { + m_stateSlot = slot; + } + GBARunOnThread(&m_threadContext, [](GBAThread* context) { + GameController* controller = static_cast<GameController*>(context->userData); + GBASaveState(context, context->stateDir, controller->m_stateSlot, true); + }); } void GameController::setVideoSync(bool set) {@@ -438,30 +599,85 @@ void GameController::setFrameskip(int skip) {
m_threadContext.frameskip = skip; } +void GameController::setVolume(int volume) { + threadInterrupt(); + m_threadContext.volume = volume; + if (m_gameOpen) { + m_threadContext.gba->audio.masterVolume = volume; + } + threadContinue(); +} + +void GameController::setMute(bool mute) { + threadInterrupt(); + m_threadContext.mute = mute; + if (m_gameOpen) { + m_threadContext.gba->audio.masterVolume = mute ? 0 : m_threadContext.volume; + } + threadContinue(); +} + void GameController::setTurbo(bool set, bool forced) { if (m_turboForced && !forced) { + return; + } + if (m_turbo == set && m_turboForced == forced) { + // Don't interrupt the thread if we don't need to return; } m_turbo = set; m_turboForced = set && forced; + enableTurbo(); +} + +void GameController::setTurboSpeed(float ratio) { + m_turboSpeed = ratio; + enableTurbo(); +} + +void GameController::enableTurbo() { threadInterrupt(); - m_threadContext.sync.audioWait = set ? false : m_audioSync; - m_threadContext.sync.videoFrameWait = set ? false : m_videoSync; + if (!m_turbo) { + m_threadContext.fpsTarget = m_fpsTarget; + m_threadContext.sync.audioWait = m_audioSync; + m_threadContext.sync.videoFrameWait = m_videoSync; + } else if (m_turboSpeed <= 0) { + m_threadContext.fpsTarget = m_fpsTarget; + m_threadContext.sync.audioWait = false; + m_threadContext.sync.videoFrameWait = false; + } else { + m_threadContext.fpsTarget = m_fpsTarget * m_turboSpeed; + m_threadContext.sync.audioWait = true; + m_threadContext.sync.videoFrameWait = false; + } + redoSamples(m_audioProcessor->getBufferSamples()); threadContinue(); } void GameController::setAVStream(GBAAVStream* stream) { threadInterrupt(); m_threadContext.stream = stream; + if (m_gameOpen) { + m_threadContext.gba->stream = stream; + } threadContinue(); } void GameController::clearAVStream() { threadInterrupt(); m_threadContext.stream = nullptr; + if (m_gameOpen) { + m_threadContext.gba->stream = nullptr; + } threadContinue(); } +#ifdef USE_PNG +void GameController::screenshot() { + GBARunOnThread(&m_threadContext, GBAThreadTakeScreenshot); +} +#endif + void GameController::reloadAudioDriver() { QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); int samples = m_audioProcessor->getBufferSamples();@@ -489,6 +705,7 @@ m_luxLevel = i;
break; } } + emit luminanceValueChanged(m_luxValue); } void GameController::setLuminanceLevel(int level) {@@ -516,9 +733,8 @@ }
void GameController::updateKeys() { int activeKeys = m_activeKeys; -#ifdef BUILD_SDL activeKeys |= m_activeButtons; -#endif + activeKeys &= ~m_inactiveKeys; m_threadContext.activeKeys = activeKeys; }@@ -537,6 +753,7 @@ #endif
if (m_threadContext.gba) { GBAAudioResizeBuffer(&m_threadContext.gba->audio, m_threadContext.audioBuffers); } + QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged"); } void GameController::setLogLevel(int levels) {@@ -557,13 +774,11 @@ m_logLevels &= ~levels;
threadContinue(); } -#ifdef BUILD_SDL -void GameController::testSDLEvents() { +void GameController::pollEvents() { if (!m_inputController) { return; } - m_activeButtons = m_inputController->testSDLEvents(); + m_activeButtons = m_inputController->pollEvents(); updateKeys(); } -#endif
@@ -6,11 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_GAME_CONTROLLER #define QGBA_GAME_CONTROLLER +#include <QAtomicInt> #include <QFile> #include <QImage> #include <QObject> -#include <QMutex> #include <QString> +#include <QTimer> + +#include <memory> extern "C" { #include "gba/cheats.h"@@ -32,6 +35,7 @@ namespace QGBA {
class AudioProcessor; class InputController; +class MultiplayerController; class GameController : public QObject { Q_OBJECT@@ -59,6 +63,10 @@
void setInputController(InputController* controller) { m_inputController = controller; } void setOverrides(Configuration* overrides) { m_threadContext.overrides = overrides; } + void setMultiplayerController(MultiplayerController* controller); + MultiplayerController* multiplayerController() { return m_multiplayer; } + void clearMultiplayerController(); + void setOverride(const GBACartridgeOverride& override); void clearOverride() { m_threadContext.hasOverride = false; }@@ -78,37 +86,55 @@ void gameUnpaused(GBAThread*);
void gameCrashed(const QString& errorMessage); void gameFailed(); void stateLoaded(GBAThread*); + void rewound(GBAThread*); + void unimplementedBiosCall(int); + void luminanceValueChanged(int); + + void statusPosted(const QString& message); void postLog(int level, const QString& log); public slots: void loadGame(const QString& path, bool dirmode = false); void loadBIOS(const QString& path); void setSkipBIOS(bool); + void setUseBIOS(bool); void loadPatch(const QString& path); - void openGame(); + void importSharkport(const QString& path); + void exportSharkport(const QString& path); + void bootBIOS(); void closeGame(); void setPaused(bool paused); void reset(); void frameAdvance(); void setRewind(bool enable, int capacity, int interval); void rewind(int states = 0); + void startRewinding(); + void stopRewinding(); void keyPressed(int key); void keyReleased(int key); void clearKeys(); void setAudioBufferSamples(int samples); void setFPSTarget(float fps); - void loadState(int slot); - void saveState(int slot); + void loadState(int slot = 0); + void saveState(int slot = 0); void setVideoSync(bool); void setAudioSync(bool); void setFrameskip(int); + void setVolume(int); + void setMute(bool); void setTurbo(bool, bool forced = true); + void setTurboSpeed(float ratio = -1); void setAVStream(GBAAVStream*); void clearAVStream(); void reloadAudioDriver(); +#ifdef USE_PNG + void screenshot(); +#endif + void setLuminanceValue(uint8_t value); + uint8_t luminanceValue() const { return m_luxValue; } void setLuminanceLevel(int level); void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); } void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); }@@ -122,25 +148,23 @@ void enableLogLevel(int);
void disableLogLevel(int); private slots: + void openGame(bool bios = false); void crashGame(const QString& crashMessage); -#ifdef BUILD_SDL - void testSDLEvents(); - -private: - GBASDLEvents m_sdlEvents; - int m_activeButtons; -#endif + void pollEvents(); private: void updateKeys(); void redoSamples(int samples); + void enableTurbo(); uint32_t* m_drawContext; GBAThread m_threadContext; GBAVideoSoftwareRenderer* m_renderer; GBACheatDevice m_cheatDevice; int m_activeKeys; + int m_activeButtons; + int m_inactiveKeys; int m_logLevels; bool m_gameOpen;@@ -148,20 +172,27 @@ bool m_dirmode;
QString m_fname; QString m_bios; + bool m_useBios; QString m_patch; QThread* m_audioThread; AudioProcessor* m_audioProcessor; - QMutex m_pauseMutex; - bool m_pauseAfterFrame; + QAtomicInt m_pauseAfterFrame; bool m_videoSync; bool m_audioSync; + float m_fpsTarget; bool m_turbo; bool m_turboForced; + float m_turboSpeed; + QTimer m_rewindTimer; + bool m_wasPaused; + + int m_stateSlot; InputController* m_inputController; + MultiplayerController* m_multiplayer; struct GameControllerLux : GBALuminanceSource { GameController* p;
@@ -11,7 +11,7 @@ using namespace QGBA;
QEvent::Type GamepadAxisEvent::s_type = QEvent::None; -GamepadAxisEvent::GamepadAxisEvent(int axis, Direction direction, bool isNew, InputController* controller) +GamepadAxisEvent::GamepadAxisEvent(int axis, Direction direction, bool isNew, int type, InputController* controller) : QEvent(Type()) , m_axis(axis) , m_direction(direction)@@ -20,11 +20,9 @@ , m_controller(controller)
, m_key(GBA_KEY_NONE) { ignore(); -#ifdef BUILD_SDL if (controller) { - m_key = GBAInputMapAxis(controller->map(), SDL_BINDING_BUTTON, axis, direction * INT_MAX); + m_key = GBAInputMapAxis(controller->map(), type, axis, direction * INT_MAX); } -#endif } QEvent::Type GamepadAxisEvent::Type() {
@@ -24,7 +24,7 @@ POSITIVE = 1,
NEGATIVE = -1 }; - GamepadAxisEvent(int axis, Direction direction, bool isNew, InputController* controller = nullptr); + GamepadAxisEvent(int axis, Direction direction, bool isNew, int type, InputController* controller = nullptr); int axis() const { return m_axis; } Direction direction() const { return m_direction; }
@@ -12,18 +12,16 @@
QEvent::Type GamepadButtonEvent::s_downType = QEvent::None; QEvent::Type GamepadButtonEvent::s_upType = QEvent::None; -GamepadButtonEvent::GamepadButtonEvent(QEvent::Type type, int button, InputController* controller) - : QEvent(type) +GamepadButtonEvent::GamepadButtonEvent(QEvent::Type pressType, int button, int type, InputController* controller) + : QEvent(pressType) , m_button(button) , m_controller(controller) , m_key(GBA_KEY_NONE) { ignore(); -#ifdef BUILD_SDL if (controller) { - m_key = GBAInputMapKey(controller->map(), SDL_BINDING_BUTTON, button); + m_key = GBAInputMapKey(controller->map(), type, button); } -#endif } QEvent::Type GamepadButtonEvent::Down() {
@@ -18,7 +18,7 @@ class InputController;
class GamepadButtonEvent : public QEvent { public: - GamepadButtonEvent(Type type, int button, InputController* controller = nullptr); + GamepadButtonEvent(Type pressType, int button, int type, InputController* controller = nullptr); int value() const { return m_button; } GBAKey gbaKey() const { return m_key; }
@@ -19,24 +19,40 @@ }
using namespace QGBA; -InputController::InputController(QObject* parent) +#ifdef BUILD_SDL +int InputController::s_sdlInited = 0; +GBASDLEvents InputController::s_sdlEvents; +#endif + +InputController::InputController(int playerId, QObject* parent) : QObject(parent) + , m_playerId(playerId) , m_config(nullptr) , m_gamepadTimer(nullptr) +#ifdef BUILD_SDL + , m_playerAttached(false) +#endif + , m_allowOpposing(false) { GBAInputMapInit(&m_inputMap); #ifdef BUILD_SDL - m_sdlEvents.bindings = &m_inputMap; - GBASDLInitEvents(&m_sdlEvents); + if (s_sdlInited == 0) { + GBASDLInitEvents(&s_sdlEvents); + } + ++s_sdlInited; + m_sdlPlayer.bindings = &m_inputMap; GBASDLInitBindings(&m_inputMap); - SDL_JoystickEventState(SDL_QUERY); +#endif m_gamepadTimer = new QTimer(this); - connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); +#ifdef BUILD_SDL + connect(m_gamepadTimer, &QTimer::timeout, [this]() { + testGamepad(SDL_BINDING_BUTTON); + }); +#endif m_gamepadTimer->setInterval(50); m_gamepadTimer->start(); -#endif GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A); GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);@@ -54,27 +70,199 @@ InputController::~InputController() {
GBAInputMapDeinit(&m_inputMap); #ifdef BUILD_SDL - GBASDLDeinitEvents(&m_sdlEvents); + if (m_playerAttached) { + GBASDLDetachPlayer(&s_sdlEvents, &m_sdlPlayer); + } + + --s_sdlInited; + if (s_sdlInited == 0) { + GBASDLDeinitEvents(&s_sdlEvents); + } #endif } void InputController::setConfiguration(ConfigController* config) { m_config = config; + setAllowOpposing(config->getOption("allowOpposingDirections").toInt()); loadConfiguration(KEYBOARD); #ifdef BUILD_SDL + GBASDLEventsLoadConfig(&s_sdlEvents, config->input()); + if (!m_playerAttached) { + GBASDLAttachPlayer(&s_sdlEvents, &m_sdlPlayer); + m_playerAttached = true; + } loadConfiguration(SDL_BINDING_BUTTON); + loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON)); #endif } void InputController::loadConfiguration(uint32_t type) { - GBAInputMapLoad(&m_inputMap, type, m_config->configuration()); + GBAInputMapLoad(&m_inputMap, type, m_config->input()); +#ifdef BUILD_SDL + if (m_playerAttached) { + GBASDLPlayerLoadConfig(&m_sdlPlayer, m_config->input()); + } +#endif +} + +void InputController::loadProfile(uint32_t type, const QString& profile) { + GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData()); + recalibrateAxes(); +} + +void InputController::saveConfiguration() { + saveConfiguration(KEYBOARD); +#ifdef BUILD_SDL + saveConfiguration(SDL_BINDING_BUTTON); + saveProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON)); + if (m_playerAttached) { + GBASDLPlayerSaveConfig(&m_sdlPlayer, m_config->input()); + } + m_config->write(); +#endif } void InputController::saveConfiguration(uint32_t type) { - GBAInputMapSave(&m_inputMap, type, m_config->configuration()); + GBAInputMapSave(&m_inputMap, type, m_config->input()); + m_config->write(); +} + +void InputController::saveProfile(uint32_t type, const QString& profile) { + GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData()); m_config->write(); } +const char* InputController::profileForType(uint32_t type) { + UNUSED(type); +#ifdef BUILD_SDL + if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_JoystickName(m_sdlPlayer.joystick); +#else + return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick)); +#endif + } +#endif + return 0; +} + +QStringList InputController::connectedGamepads(uint32_t type) const { + UNUSED(type); + +#ifdef BUILD_SDL + if (type == SDL_BINDING_BUTTON) { + QStringList pads; + for (size_t i = 0; i < s_sdlEvents.nJoysticks; ++i) { + const char* name; +#if SDL_VERSION_ATLEAST(2, 0, 0) + name = SDL_JoystickName(s_sdlEvents.joysticks[i]); +#else + name = SDL_JoystickName(SDL_JoystickIndex(s_sdlEvents.joysticks[i])); +#endif + if (name) { + pads.append(QString(name)); + } else { + pads.append(QString()); + } + } + return pads; + } +#endif + + return QStringList(); +} + +int InputController::gamepad(uint32_t type) const { +#ifdef BUILD_SDL + if (type == SDL_BINDING_BUTTON) { + return m_sdlPlayer.joystickIndex; + } +#endif + return 0; +} + +void InputController::setGamepad(uint32_t type, int index) { +#ifdef BUILD_SDL + if (type == SDL_BINDING_BUTTON) { + GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index); + } +#endif +} + +void InputController::setPreferredGamepad(uint32_t type, const QString& device) { + if (!m_config) { + return; + } + GBAInputSetPreferredDevice(m_config->input(), type, m_playerId, device.toLocal8Bit().constData()); +} + +GBARumble* InputController::rumble() { +#ifdef BUILD_SDL + if (m_playerAttached) { + return &m_sdlPlayer.rumble.d; + } +#endif + return nullptr; +} + +GBARotationSource* InputController::rotationSource() { +#ifdef BUILD_SDL + if (m_playerAttached) { + return &m_sdlPlayer.rotation.d; + } +#endif + return nullptr; +} + +void InputController::registerTiltAxisX(int axis) { +#ifdef BUILD_SDL + if (m_playerAttached) { + m_sdlPlayer.rotation.axisX = axis; + } +#endif +} + +void InputController::registerTiltAxisY(int axis) { +#ifdef BUILD_SDL + if (m_playerAttached) { + m_sdlPlayer.rotation.axisY = axis; + } +#endif +} + +void InputController::registerGyroAxisX(int axis) { +#ifdef BUILD_SDL + if (m_playerAttached) { + m_sdlPlayer.rotation.gyroX = axis; + } +#endif +} + +void InputController::registerGyroAxisY(int axis) { +#ifdef BUILD_SDL + if (m_playerAttached) { + m_sdlPlayer.rotation.gyroY = axis; + } +#endif +} + +float InputController::gyroSensitivity() const { +#ifdef BUILD_SDL + if (m_playerAttached) { + return m_sdlPlayer.rotation.gyroSensitivity; + } +#endif + return 0; +} + +void InputController::setGyroSensitivity(float sensitivity) { +#ifdef BUILD_SDL + if (m_playerAttached) { + m_sdlPlayer.rotation.gyroSensitivity = sensitivity; + } +#endif +} + GBAKey InputController::mapKeyboard(int key) const { return GBAInputMapKey(&m_inputMap, KEYBOARD, key); }@@ -83,85 +271,119 @@ void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
return GBAInputBindKey(&m_inputMap, type, key, gbaKey); } -#ifdef BUILD_SDL -int InputController::testSDLEvents() { - SDL_Joystick* joystick = m_sdlEvents.joystick; - SDL_JoystickUpdate(); - int numButtons = SDL_JoystickNumButtons(joystick); +int InputController::pollEvents() { int activeButtons = 0; - int i; - for (i = 0; i < numButtons; ++i) { - GBAKey key = GBAInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i); - if (key == GBA_KEY_NONE) { - continue; - } - if (hasPendingEvent(key)) { - continue; - } - if (SDL_JoystickGetButton(joystick, i)) { - activeButtons |= 1 << key; - } - } - int numHats = SDL_JoystickNumHats(joystick); - for (i = 0; i < numHats; ++i) { - int hat = SDL_JoystickGetHat(joystick, i); - if (hat & SDL_HAT_UP) { - activeButtons |= 1 << GBA_KEY_UP; - } - if (hat & SDL_HAT_LEFT) { - activeButtons |= 1 << GBA_KEY_LEFT; - } - if (hat & SDL_HAT_DOWN) { - activeButtons |= 1 << GBA_KEY_DOWN; +#ifdef BUILD_SDL + if (m_playerAttached) { + SDL_Joystick* joystick = m_sdlPlayer.joystick; + SDL_JoystickUpdate(); + int numButtons = SDL_JoystickNumButtons(joystick); + int i; + for (i = 0; i < numButtons; ++i) { + GBAKey key = GBAInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i); + if (key == GBA_KEY_NONE) { + continue; + } + if (hasPendingEvent(key)) { + continue; + } + if (SDL_JoystickGetButton(joystick, i)) { + activeButtons |= 1 << key; + } } - if (hat & SDL_HAT_RIGHT) { - activeButtons |= 1 << GBA_KEY_RIGHT; + int numHats = SDL_JoystickNumHats(joystick); + for (i = 0; i < numHats; ++i) { + int hat = SDL_JoystickGetHat(joystick, i); + if (hat & SDL_HAT_UP) { + activeButtons |= 1 << GBA_KEY_UP; + } + if (hat & SDL_HAT_LEFT) { + activeButtons |= 1 << GBA_KEY_LEFT; + } + if (hat & SDL_HAT_DOWN) { + activeButtons |= 1 << GBA_KEY_DOWN; + } + if (hat & SDL_HAT_RIGHT) { + activeButtons |= 1 << GBA_KEY_RIGHT; + } } - } - int numAxes = SDL_JoystickNumAxes(joystick); - for (i = 0; i < numAxes; ++i) { - int value = SDL_JoystickGetAxis(joystick, i); + int numAxes = SDL_JoystickNumAxes(joystick); + for (i = 0; i < numAxes; ++i) { + int value = SDL_JoystickGetAxis(joystick, i); - enum GBAKey key = GBAInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value); - if (key != GBA_KEY_NONE) { - activeButtons |= 1 << key; + enum GBAKey key = GBAInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value); + if (key != GBA_KEY_NONE) { + activeButtons |= 1 << key; + } } } +#endif return activeButtons; } -QSet<int> InputController::activeGamepadButtons() { - SDL_Joystick* joystick = m_sdlEvents.joystick; - SDL_JoystickUpdate(); - int numButtons = SDL_JoystickNumButtons(joystick); +QSet<int> InputController::activeGamepadButtons(int type) { QSet<int> activeButtons; - int i; - for (i = 0; i < numButtons; ++i) { - if (SDL_JoystickGetButton(joystick, i)) { - activeButtons.insert(i); +#ifdef BUILD_SDL + if (m_playerAttached && type == SDL_BINDING_BUTTON) { + SDL_Joystick* joystick = m_sdlPlayer.joystick; + SDL_JoystickUpdate(); + int numButtons = SDL_JoystickNumButtons(joystick); + int i; + for (i = 0; i < numButtons; ++i) { + if (SDL_JoystickGetButton(joystick, i)) { + activeButtons.insert(i); + } } } +#endif return activeButtons; } -QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes() { - SDL_Joystick* joystick = m_sdlEvents.joystick; - SDL_JoystickUpdate(); - int numButtons = SDL_JoystickNumAxes(joystick); +void InputController::recalibrateAxes() { +#ifdef BUILD_SDL + if (m_playerAttached) { + SDL_Joystick* joystick = m_sdlPlayer.joystick; + SDL_JoystickUpdate(); + int numAxes = SDL_JoystickNumAxes(joystick); + if (numAxes < 1) { + return; + } + m_deadzones.resize(numAxes); + int i; + for (i = 0; i < numAxes; ++i) { + m_deadzones[i] = SDL_JoystickGetAxis(joystick, i); + } + } +#endif +} + +QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes(int type) { QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes; - int i; - for (i = 0; i < numButtons; ++i) { - int32_t axis = SDL_JoystickGetAxis(joystick, i); - if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) { - activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE)); +#ifdef BUILD_SDL + if (m_playerAttached && type == SDL_BINDING_BUTTON) { + SDL_Joystick* joystick = m_sdlPlayer.joystick; + SDL_JoystickUpdate(); + int numAxes = SDL_JoystickNumAxes(joystick); + if (numAxes < 1) { + return activeAxes; + } + m_deadzones.resize(numAxes); + int i; + for (i = 0; i < numAxes; ++i) { + int32_t axis = SDL_JoystickGetAxis(joystick, i); + axis -= m_deadzones[i]; + if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) { + activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE)); + } } } +#endif return activeAxes; } void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) { - const GBAAxis* old = GBAInputQueryAxis(&m_inputMap, SDL_BINDING_BUTTON, axis); + const GBAAxis* old = GBAInputQueryAxis(&m_inputMap, type, axis); GBAAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD }; if (old) { description = *old;@@ -169,26 +391,24 @@ }
switch (direction) { case GamepadAxisEvent::NEGATIVE: description.lowDirection = key; - description.deadLow = -AXIS_THRESHOLD; + description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD; break; case GamepadAxisEvent::POSITIVE: description.highDirection = key; - description.deadHigh = AXIS_THRESHOLD; + description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD; break; default: return; } - GBAInputBindAxis(&m_inputMap, SDL_BINDING_BUTTON, axis, &description); + GBAInputBindAxis(&m_inputMap, type, axis, &description); } -#endif -void InputController::testGamepad() { -#ifdef BUILD_SDL - auto activeAxes = activeGamepadAxes(); +void InputController::testGamepad(int type) { + auto activeAxes = activeGamepadAxes(type); auto oldAxes = m_activeAxes; m_activeAxes = activeAxes; - auto activeButtons = activeGamepadButtons(); + auto activeButtons = activeGamepadButtons(type); auto oldButtons = m_activeButtons; m_activeButtons = activeButtons;@@ -201,23 +421,30 @@ oldAxes.subtract(m_activeAxes);
for (auto& axis : m_activeAxes) { bool newlyAboveThreshold = activeAxes.contains(axis); - GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this); if (newlyAboveThreshold) { + GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this); postPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); } - } else if (oldAxes.contains(axis)) { - clearPendingEvent(event->gbaKey()); } + } + for (auto axis : oldAxes) { + GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this); + clearPendingEvent(event->gbaKey()); QApplication::sendEvent(QApplication::focusWidget(), event); + } + + if (!QApplication::focusWidget()) { + return; } activeButtons.subtract(oldButtons); oldButtons.subtract(m_activeButtons); for (int button : activeButtons) { - GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, this); + GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this); postPendingEvent(event->gbaKey()); QApplication::sendEvent(QApplication::focusWidget(), event); if (!event->isAccepted()) {@@ -225,11 +452,10 @@ clearPendingEvent(event->gbaKey());
} } for (int button : oldButtons) { - GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, this); + GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this); clearPendingEvent(event->gbaKey()); QApplication::sendEvent(QApplication::focusWidget(), event); } -#endif } void InputController::postPendingEvent(GBAKey key) {@@ -243,3 +469,17 @@
bool InputController::hasPendingEvent(GBAKey key) const { return m_pendingEvents.contains(key); } + +#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) +void InputController::suspendScreensaver() { + GBASDLSuspendScreensaver(&s_sdlEvents); +} + +void InputController::resumeScreensaver() { + GBASDLResumeScreensaver(&s_sdlEvents); +} + +void InputController::setScreensaverSuspendable(bool suspendable) { + GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable); +} +#endif
@@ -10,6 +10,7 @@ #include "GamepadAxisEvent.h"
#include <QObject> #include <QSet> +#include <QVector> class QTimer;@@ -31,12 +32,19 @@
public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(QObject* parent = nullptr); + InputController(int playerId = 0, QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config); + void saveConfiguration(); void loadConfiguration(uint32_t type); - void saveConfiguration(uint32_t type = KEYBOARD); + void loadProfile(uint32_t type, const QString& profile); + void saveConfiguration(uint32_t type); + void saveProfile(uint32_t type, const QString& profile); + const char* profileForType(uint32_t type); + + bool allowOpposing() const { return m_allowOpposing; } + void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; } GBAKey mapKeyboard(int key) const;@@ -44,18 +52,40 @@ void bindKey(uint32_t type, int key, GBAKey);
const GBAInputMap* map() const { return &m_inputMap; } -#ifdef BUILD_SDL + int pollEvents(); + static const int32_t AXIS_THRESHOLD = 0x3000; + QSet<int> activeGamepadButtons(int type); + QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(int type); + void recalibrateAxes(); - int testSDLEvents(); - QSet<int> activeGamepadButtons(); - QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(); + void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); + + QStringList connectedGamepads(uint32_t type) const; + int gamepad(uint32_t type) const; + void setGamepad(uint32_t type, int index); + void setPreferredGamepad(uint32_t type, const QString& device); + + void registerTiltAxisX(int axis); + void registerTiltAxisY(int axis); + void registerGyroAxisX(int axis); + void registerGyroAxisY(int axis); - void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); -#endif + float gyroSensitivity() const; + void setGyroSensitivity(float sensitivity); + + GBARumble* rumble(); + GBARotationSource* rotationSource(); public slots: - void testGamepad(); + void testGamepad(int type); + +#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) + // TODO: Move these to somewhere that makes sense + void suspendScreensaver(); + void resumeScreensaver(); + void setScreensaverSuspendable(bool); +#endif private: void postPendingEvent(GBAKey);@@ -64,10 +94,17 @@ bool hasPendingEvent(GBAKey) const;
GBAInputMap m_inputMap; ConfigController* m_config; + int m_playerId; + bool m_allowOpposing; #ifdef BUILD_SDL - GBASDLEvents m_sdlEvents; + static int s_sdlInited; + static GBASDLEvents s_sdlEvents; + GBASDLPlayer m_sdlPlayer; + bool m_playerAttached; #endif + + QVector<int> m_deadzones; QSet<int> m_activeButtons; QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes;
@@ -15,6 +15,7 @@
KeyEditor::KeyEditor(QWidget* parent) : QLineEdit(parent) , m_direction(GamepadAxisEvent::NEUTRAL) + , m_button(false) { setAlignment(Qt::AlignCenter); }
@@ -24,6 +24,7 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
: QWidget(parent) , m_controller(controller) , m_currentFocus(0) + , m_mode(LoadSave::LOAD) { m_ui.setupUi(this);@@ -43,6 +44,12 @@ loadState(i + 1);
m_slots[i]->installEventFilter(this); connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); }); } + + QAction* escape = new QAction(this); + escape->connect(escape, SIGNAL(triggered()), this, SLOT(close())); + escape->setShortcut(QKeySequence("Esc")); + escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); + addAction(escape); } void LoadSaveState::setMode(LoadSave mode) {@@ -55,13 +62,13 @@
bool LoadSaveState::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { int column = m_currentFocus % 3; - int row = m_currentFocus - column; + int row = m_currentFocus / 3; switch (static_cast<QKeyEvent*>(event)->key()) { case Qt::Key_Up: - row += 6; + row += 2; break; case Qt::Key_Down: - row += 3; + row += 1; break; case Qt::Key_Left: column += 2;@@ -80,9 +87,6 @@ case Qt::Key_8:
case Qt::Key_9: triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1); break; - case Qt::Key_Escape: - close(); - break; case Qt::Key_Enter: case Qt::Key_Return: triggerState(m_currentFocus + 1);@@ -91,8 +95,8 @@ default:
return false; } column %= 3; - row %= 9; - m_currentFocus = column + row; + row %= 3; + m_currentFocus = column + row * 3; m_slots[m_currentFocus]->setFocus(); return true; }
@@ -47,7 +47,6 @@ void triggerState(int slot);
Ui::LoadSaveState m_ui; GameController* m_controller; - InputController* m_inputController; SavestateButton* m_slots[NUM_SLOTS]; LoadSave m_mode;
@@ -12,6 +12,9 @@ using namespace QGBA;
LogView::LogView(QWidget* parent) : QWidget(parent) + , m_logLevel(0) + , m_lines(0) + , m_lineLimit(DEFAULT_LINE_LIMIT) { m_ui.setupUi(this); connect(m_ui.levelDebug, SIGNAL(toggled(bool)), this, SLOT(setLevelDebug(bool)));@@ -22,10 +25,10 @@ connect(m_ui.levelError, SIGNAL(toggled(bool)), this, SLOT(setLevelError(bool)));
connect(m_ui.levelFatal, SIGNAL(toggled(bool)), this, SLOT(setLevelFatal(bool))); connect(m_ui.levelGameError, SIGNAL(toggled(bool)), this, SLOT(setLevelGameError(bool))); connect(m_ui.levelSWI, SIGNAL(toggled(bool)), this, SLOT(setLevelSWI(bool))); + connect(m_ui.levelStatus, SIGNAL(toggled(bool)), this, SLOT(setLevelStatus(bool))); + connect(m_ui.levelSIO, SIGNAL(toggled(bool)), this, SLOT(setLevelSIO(bool))); connect(m_ui.clear, SIGNAL(clicked()), this, SLOT(clear())); connect(m_ui.maxLines, SIGNAL(valueChanged(int)), this, SLOT(setMaxLines(int))); - m_logLevel = 0; - m_lines = 0; m_ui.maxLines->setValue(DEFAULT_LINE_LIMIT); }@@ -56,6 +59,8 @@ m_ui.levelError->setCheckState(levels & GBA_LOG_ERROR ? Qt::Checked : Qt::Unchecked);
m_ui.levelFatal->setCheckState(levels & GBA_LOG_FATAL ? Qt::Checked : Qt::Unchecked); m_ui.levelGameError->setCheckState(levels & GBA_LOG_GAME_ERROR ? Qt::Checked : Qt::Unchecked); m_ui.levelSWI->setCheckState(levels & GBA_LOG_SWI ? Qt::Checked : Qt::Unchecked); + m_ui.levelStatus->setCheckState(levels & GBA_LOG_STATUS ? Qt::Checked : Qt::Unchecked); + m_ui.levelSIO->setCheckState(levels & GBA_LOG_SIO ? Qt::Checked : Qt::Unchecked); emit levelsSet(levels); }@@ -124,6 +129,22 @@ clearLevel(GBA_LOG_SWI);
} } +void LogView::setLevelStatus(bool set) { + if (set) { + setLevel(GBA_LOG_STATUS); + } else { + clearLevel(GBA_LOG_STATUS); + } +} + +void LogView::setLevelSIO(bool set) { + if (set) { + setLevel(GBA_LOG_SIO); + } else { + clearLevel(GBA_LOG_SIO); + } +} + void LogView::setMaxLines(int limit) { m_lineLimit = limit; while (m_lines > m_lineLimit) {@@ -149,6 +170,10 @@ case GBA_LOG_GAME_ERROR:
return tr("GAME ERROR"); case GBA_LOG_SWI: return tr("SWI"); + case GBA_LOG_STATUS: + return tr("STATUS"); + case GBA_LOG_SIO: + return tr("SIO"); } return QString(); }
@@ -40,6 +40,8 @@ void setLevelError(bool);
void setLevelFatal(bool); void setLevelGameError(bool); void setLevelSWI(bool); + void setLevelStatus(bool); + void setLevelSIO(bool); void setMaxLines(int);
@@ -106,6 +106,23 @@ <string>SW Interrupt</string>
</property> </widget> </item> + <item> + <widget class="QCheckBox" name="levelStatus"> + <property name="text"> + <string>Status</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="levelSIO"> + <property name="text"> + <string>Serial I/O</string> + </property> + </widget> + </item> </layout> </widget> </item>
@@ -0,0 +1,490 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MemoryModel.h" + +#include "GBAApp.h" +#include "GameController.h" + +#include <QAction> +#include <QApplication> +#include <QClipboard> +#include <QFontMetrics> +#include <QPainter> +#include <QScrollBar> +#include <QSlider> +#include <QWheelEvent> + +extern "C" { +#include "gba/memory.h" +} + +using namespace QGBA; + +MemoryModel::MemoryModel(QWidget* parent) + : QAbstractScrollArea(parent) + , m_cpu(nullptr) + , m_top(0) + , m_align(1) + , m_selection(0, 0) + , m_selectionAnchor(0) +{ + m_font.setFamily("Source Code Pro"); + m_font.setStyleHint(QFont::Monospace); + m_font.setPointSize(12); + QFontMetrics metrics(m_font); + m_cellHeight = metrics.height(); + m_letterWidth = metrics.averageCharWidth(); + + setFocusPolicy(Qt::StrongFocus); + setContextMenuPolicy(Qt::ActionsContextMenu); + + QAction* copy = new QAction(tr("Copy selection"), this); + copy->setShortcut(QKeySequence::Copy); + connect(copy, SIGNAL(triggered()), this, SLOT(copy())); + addAction(copy); + + QAction* save = new QAction(tr("Save selection"), this); + save->setShortcut(QKeySequence::Save); + connect(save, SIGNAL(triggered()), this, SLOT(save())); + addAction(save); + + static QString arg("%0"); + for (int i = 0; i < 256; ++i) { + QStaticText str(arg.arg(i, 2, 16, QChar('0')).toUpper()); + str.prepare(QTransform(), m_font); + m_staticNumbers.append(str); + } + + for (int i = 0; i < 128; ++i) { + QChar c(i); + if (!c.isPrint()) { + c = '.'; + } + QStaticText str = QStaticText(QString(c)); + str.prepare(QTransform(), m_font); + m_staticAscii.append(str); + } + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_margins = QMargins(metrics.width("0FFFFFF0 ") + 3, m_cellHeight + 1, metrics.width(" AAAAAAAAAAAAAAAA") + 3, 0); + m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight); + + connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) { + m_top = position; + update(); + }); + + setRegion(0, 0x10000000, tr("All")); +} + +void MemoryModel::setController(GameController* controller) { + m_cpu = controller->thread()->cpu; +} + +void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name) { + m_top = 0; + m_base = base; + m_size = size; + m_regionName = name; + m_regionName.prepare(QTransform(), m_font); + verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight); + verticalScrollBar()->setValue(0); + viewport()->update(); +} + +void MemoryModel::setAlignment(int width) { + if (width != 1 && width != 2 && width != 4) { + return; + } + m_align = width; + m_buffer = 0; + m_bufferedNybbles = 0; + viewport()->update(); +} + +void MemoryModel::jumpToAddress(const QString& hex) { + bool ok = false; + uint32_t i = hex.toInt(&ok, 16); + if (ok) { + jumpToAddress(i); + } +} + +void MemoryModel::jumpToAddress(uint32_t address) { + if (address >= 0x10000000) { + return; + } + if (address < m_base || address >= m_base + m_size) { + setRegion(0, 0x10000000, tr("All")); + } + m_top = (address - m_base) / 16; + boundsCheck(); + verticalScrollBar()->setValue(m_top); + m_buffer = 0; + m_bufferedNybbles = 0; +} + +void MemoryModel::copy() { + QClipboard* clipboard = QApplication::clipboard(); + if (!clipboard) { + return; + } + QByteArray bytestring; + QDataStream stream(&bytestring, QIODevice::WriteOnly); + serialize(&stream); + QString string; + string.reserve(bytestring.size() * 2); + static QString arg("%0"); + static QChar c0('0'); + for (uchar c : bytestring) { + string.append(arg.arg(c, 2, 16, c0).toUpper()); + } + clipboard->setText(string); +} + +void MemoryModel::save() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Save selected memory")); + if (filename.isNull()) { + return; + } + QFile outfile(filename); + if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + // TODO: Log + return; + } + QDataStream stream(&outfile); + serialize(&stream); +} + +void MemoryModel::serialize(QDataStream* stream) { + switch (m_align) { + case 1: + for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) { + *stream << (quint8) m_cpu->memory.load8(m_cpu, i, nullptr); + } + break; + case 2: + for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) { + *stream << (quint16) m_cpu->memory.load16(m_cpu, i, nullptr); + } + break; + case 4: + for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) { + *stream << (quint32) m_cpu->memory.load32(m_cpu, i, nullptr); + } + break; + } +} + +void MemoryModel::resizeEvent(QResizeEvent*) { + m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight); + verticalScrollBar()->setRange(0, (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight); + boundsCheck(); +} + +void MemoryModel::paintEvent(QPaintEvent* event) { + QPainter painter(viewport()); + QPalette palette; + painter.setFont(m_font); + painter.setPen(palette.color(QPalette::WindowText)); + static QChar c0('0'); + static QString arg("%0"); + QSizeF letterSize = QSizeF(m_letterWidth, m_cellHeight); + painter.drawStaticText(QPointF((m_margins.left() - m_regionName.size().width() - 1) / 2.0, 0), m_regionName); + painter.drawText(QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())), Qt::AlignHCenter, tr("ASCII")); + for (int x = 0; x < 16; ++x) { + painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter, QString::number(x, 16).toUpper()); + } + int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight; + for (int y = 0; y < height; ++y) { + int yp = m_cellHeight * y + m_margins.top(); + QString data = arg.arg((y + m_top) * 16 + m_base, 8, 16, c0).toUpper(); + painter.drawText(QRectF(QPointF(0, yp), QSizeF(m_margins.left(), m_cellHeight)), Qt::AlignHCenter, data); + switch (m_align) { + case 2: + for (int x = 0; x < 16; x += 2) { + uint32_t address = (y + m_top) * 16 + x + m_base; + if (isInSelection(address)) { + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 2, m_cellSize.height())), palette.highlight()); + painter.setPen(palette.color(QPalette::HighlightedText)); + if (isEditing(address)) { + drawEditingText(painter, QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp)); + continue; + } + } else { + painter.setPen(palette.color(QPalette::WindowText)); + } + uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), m_staticNumbers[b & 0xFF]); + } + break; + case 4: + for (int x = 0; x < 16; x += 4) { + uint32_t address = (y + m_top) * 16 + x + m_base; + if (isInSelection(address)) { + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 4, m_cellSize.height())), palette.highlight()); + painter.setPen(palette.color(QPalette::HighlightedText)); + if (isEditing(address)) { + drawEditingText(painter, QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp)); + continue; + } + } else { + painter.setPen(palette.color(QPalette::WindowText)); + } + uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 24) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 16) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[b & 0xFF]); + } + break; + case 1: + default: + for (int x = 0; x < 16; ++x) { + uint32_t address = (y + m_top) * 16 + x + m_base; + if (isInSelection(address)) { + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), palette.highlight()); + painter.setPen(palette.color(QPalette::HighlightedText)); + if (isEditing(address)) { + drawEditingText(painter, QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp)); + continue; + } + } else { + painter.setPen(palette.color(QPalette::WindowText)); + } + uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), m_staticNumbers[b]); + } + break; + } + painter.setPen(palette.color(QPalette::WindowText)); + for (int x = 0; x < 16; ++x) { + uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr); + painter.drawStaticText(QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp), b < 0x80 ? m_staticAscii[b] : m_staticAscii[0]); + } + } + painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height()); + painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), viewport()->size().height()); + painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top()); +} + +void MemoryModel::wheelEvent(QWheelEvent* event) { + m_top -= event->angleDelta().y() / 8; + boundsCheck(); + event->accept(); + verticalScrollBar()->setValue(m_top); + update(); +} + +void MemoryModel::mousePressEvent(QMouseEvent* event) { + if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) { + m_selection = qMakePair(0, 0); + return; + } + + QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top())); + uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; + if (event->button() == Qt::RightButton && isInSelection(address)) { + return; + } + m_selectionAnchor = address & ~(m_align - 1); + m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align); + m_buffer = 0; + m_bufferedNybbles = 0; + emit selectionChanged(m_selection.first, m_selection.second); + viewport()->update(); +} + +void MemoryModel::mouseMoveEvent(QMouseEvent* event) { + if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) { + return; + } + + QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top())); + uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; + if ((address & ~(m_align - 1)) < m_selectionAnchor) { + m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align); + } else { + m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align); + } + m_buffer = 0; + m_bufferedNybbles = 0; + emit selectionChanged(m_selection.first, m_selection.second); + viewport()->update(); +} + +void MemoryModel::keyPressEvent(QKeyEvent* event) { + if (m_selection.first >= m_selection.second) { + return; + } + int key = event->key(); + uint8_t nybble = 0; + switch (key) { + case Qt::Key_0: + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + nybble = key - Qt::Key_0; + break; + case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_C: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + nybble = key - Qt::Key_A + 10; + break; + case Qt::Key_Left: + adjustCursor(-m_align, event->modifiers() & Qt::ShiftModifier); + return; + case Qt::Key_Right: + adjustCursor(m_align, event->modifiers() & Qt::ShiftModifier); + return; + case Qt::Key_Up: + adjustCursor(-16, event->modifiers() & Qt::ShiftModifier); + return; + case Qt::Key_Down: + adjustCursor(16, event->modifiers() & Qt::ShiftModifier); + return; + default: + return; + } + m_buffer <<= 4; + m_buffer |= nybble; + ++m_bufferedNybbles; + if (m_bufferedNybbles == m_align * 2) { + switch (m_align) { + case 1: + GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr); + break; + case 2: + GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr); + break; + case 4: + GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr); + break; + } + m_bufferedNybbles = 0; + m_buffer = 0; + m_selection.first += m_align; + if (m_selection.second <= m_selection.first) { + m_selection.second = m_selection.first + m_align; + } + emit selectionChanged(m_selection.first, m_selection.second); + } + viewport()->update(); +} + +void MemoryModel::boundsCheck() { + if (m_top < 0) { + m_top = 0; + } else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) { + m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight; + } +} + +bool MemoryModel::isInSelection(uint32_t address) { + if (m_selection.first == m_selection.second) { + return false; + } + if (m_selection.second <= (address | (m_align - 1))) { + return false; + } + if (m_selection.first <= (address & ~(m_align - 1))) { + return true; + } + return false; +} + +bool MemoryModel::isEditing(uint32_t address) { + return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1)); +} + +void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) { + QPointF o(origin); + for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) { + if (nybbles > 1) { + uint8_t b = m_buffer >> ((nybbles - 2) * 4); + painter.drawStaticText(o, m_staticNumbers[b]); + } else { + int b = m_buffer & 0xF; + painter.drawStaticText(o, m_staticAscii[b + '0']); + } + o += QPointF(m_letterWidth * 2, 0); + } +} + +void MemoryModel::adjustCursor(int adjust, bool shift) { + if (m_selection.first >= m_selection.second) { + return; + } + int cursorPosition = m_top; + if (shift) { + if (m_selectionAnchor == m_selection.first) { + if (adjust < 0 && m_base - adjust > m_selection.second) { + adjust = m_base - m_selection.second + m_align; + } else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) { + adjust = m_base + m_size - m_selection.second; + } + adjust += m_selection.second; + if (adjust <= m_selection.first) { + m_selection.second = m_selection.first + m_align; + m_selection.first = adjust - m_align; + cursorPosition = m_selection.first; + } else { + m_selection.second = adjust; + cursorPosition = m_selection.second - m_align; + } + } else { + if (adjust < 0 && m_base - adjust > m_selection.first) { + adjust = m_base - m_selection.first; + } else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) { + adjust = m_base + m_size - m_selection.first - m_align; + } + adjust += m_selection.first; + if (adjust >= m_selection.second) { + m_selection.first = m_selection.second - m_align; + m_selection.second = adjust + m_align; + cursorPosition = adjust; + } else { + m_selection.first = adjust; + cursorPosition = m_selection.first; + } + } + cursorPosition = (cursorPosition - m_base) / 16; + } else { + if (m_selectionAnchor == m_selection.first) { + m_selectionAnchor = m_selection.second - m_align; + } else { + m_selectionAnchor = m_selection.first; + } + if (adjust < 0 && m_base - adjust > m_selectionAnchor) { + m_selectionAnchor = m_base; + } else if (adjust > 0 && m_selectionAnchor + adjust >= m_base + m_size) { + m_selectionAnchor = m_base + m_size - m_align; + } else { + m_selectionAnchor += adjust; + } + m_selection.first = m_selectionAnchor; + m_selection.second = m_selection.first + m_align; + cursorPosition = (m_selectionAnchor - m_base) / 16; + } + if (cursorPosition < m_top) { + m_top = cursorPosition; + } else if (cursorPosition >= m_top + viewport()->size().height() / m_cellHeight - 1) { + m_top = cursorPosition - viewport()->size().height() / m_cellHeight + 2; + } + emit selectionChanged(m_selection.first, m_selection.second); + viewport()->update(); +}
@@ -0,0 +1,84 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_MEMORY_MODEL +#define QGBA_MEMORY_MODEL + +#include <QAbstractScrollArea> +#include <QFont> +#include <QSize> +#include <QStaticText> +#include <QVector> + +struct ARMCore; + +namespace QGBA { + +class GameController; + +class MemoryModel : public QAbstractScrollArea { +Q_OBJECT + +public: + MemoryModel(QWidget* parent = nullptr); + + void setController(GameController* controller); + + void setRegion(uint32_t base, uint32_t size, const QString& name = QString()); + + void setAlignment(int); + int alignment() const { return m_align; } + +public slots: + void jumpToAddress(const QString& hex); + void jumpToAddress(uint32_t); + + void copy(); + void save(); + +signals: + void selectionChanged(uint32_t start, uint32_t end); + +protected: + void resizeEvent(QResizeEvent*) override; + void paintEvent(QPaintEvent*) override; + void wheelEvent(QWheelEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + void keyPressEvent(QKeyEvent*) override; + +private: + void boundsCheck(); + + bool isInSelection(uint32_t address); + bool isEditing(uint32_t address); + void drawEditingText(QPainter& painter, const QPointF& origin); + + void adjustCursor(int adjust, bool shift); + + void serialize(QDataStream* stream); + + ARMCore* m_cpu; + QFont m_font; + int m_cellHeight; + int m_letterWidth; + uint32_t m_base; + uint32_t m_size; + int m_top; + int m_align; + QMargins m_margins; + QVector<QStaticText> m_staticNumbers; + QVector<QStaticText> m_staticAscii; + QStaticText m_regionName; + QSizeF m_cellSize; + QPair<uint32_t, uint32_t> m_selection; + uint32_t m_selectionAnchor; + uint32_t m_buffer; + int m_bufferedNybbles; +}; + +} + +#endif
@@ -0,0 +1,109 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryView.h" + +#include "GameController.h" + +extern "C" { +#include "gba/memory.h" +} + +using namespace QGBA; + +MemoryView::MemoryView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + m_ui.hexfield->setController(controller); + + connect(m_ui.regions, SIGNAL(currentIndexChanged(int)), this, SLOT(setIndex(int))); + + connect(m_ui.width8, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(1); }); + connect(m_ui.width16, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(2); }); + connect(m_ui.width32, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(4); }); + connect(m_ui.setAddress, SIGNAL(valueChanged(const QString&)), m_ui.hexfield, SLOT(jumpToAddress(const QString&))); + + connect(m_ui.hexfield, SIGNAL(selectionChanged(uint32_t, uint32_t)), this, SLOT(updateSelection(uint32_t, uint32_t))); + + connect(controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(close())); + + connect(controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(update())); + connect(controller, SIGNAL(gamePaused(GBAThread*)), this, SLOT(update())); + connect(controller, SIGNAL(stateLoaded(GBAThread*)), this, SLOT(update())); + connect(controller, SIGNAL(rewound(GBAThread*)), this, SLOT(update())); +} + +void MemoryView::setIndex(int index) { + static struct { + const char* name; + uint32_t base; + uint32_t size; + } indexInfo[] = { + { "All", 0, 0x10000000 }, + { "BIOS", BASE_BIOS, SIZE_BIOS }, + { "EWRAM", BASE_WORKING_RAM, SIZE_WORKING_RAM }, + { "IWRAM", BASE_WORKING_IRAM, SIZE_WORKING_IRAM }, + { "MMIO", BASE_IO, SIZE_IO }, + { "Palette", BASE_PALETTE_RAM, SIZE_PALETTE_RAM }, + { "VRAM", BASE_VRAM, SIZE_VRAM }, + { "OAM", BASE_OAM, SIZE_OAM }, + { "ROM", BASE_CART0, SIZE_CART0 }, + { "ROM WS1", BASE_CART1, SIZE_CART1 }, + { "ROM WS2", BASE_CART2, SIZE_CART2 }, + { "SRAM", BASE_CART_SRAM, SIZE_CART_SRAM }, + }; + const auto& info = indexInfo[index]; + m_ui.hexfield->setRegion(info.base, info.size, info.name); +} + +void MemoryView::update() { + m_ui.hexfield->viewport()->update(); + updateStatus(); +} + +void MemoryView::updateSelection(uint32_t start, uint32_t end) { + m_selection.first = start; + m_selection.second = end; + updateStatus(); +} + +void MemoryView::updateStatus() { + int align = m_ui.hexfield->alignment(); + if (m_selection.first & (align - 1) || m_selection.second - m_selection.first != align) { + m_ui.sintVal->clear(); + m_ui.uintVal->clear(); + return; + } + ARMCore* cpu = m_controller->thread()->cpu; + union { + uint32_t u32; + int32_t i32; + uint16_t u16; + int16_t i16; + uint8_t u8; + int8_t i8; + } value; + switch (align) { + case 1: + value.u8 = cpu->memory.load8(cpu, m_selection.first, nullptr); + m_ui.sintVal->setText(QString::number(value.i8)); + m_ui.uintVal->setText(QString::number(value.u8)); + break; + case 2: + value.u16 = cpu->memory.load16(cpu, m_selection.first, nullptr); + m_ui.sintVal->setText(QString::number(value.i16)); + m_ui.uintVal->setText(QString::number(value.u16)); + break; + case 4: + value.u32 = cpu->memory.load32(cpu, m_selection.first, nullptr); + m_ui.sintVal->setText(QString::number(value.i32)); + m_ui.uintVal->setText(QString::number(value.u32)); + break; + } +}
@@ -0,0 +1,40 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_MEMORY_VIEW +#define QGBA_MEMORY_VIEW + +#include "MemoryModel.h" + +#include "ui_MemoryView.h" + +namespace QGBA { + +class GameController; + +class MemoryView : public QWidget { +Q_OBJECT + +public: + MemoryView(GameController* controller, QWidget* parent = nullptr); + +public slots: + void update(); + +private slots: + void setIndex(int); + void updateSelection(uint32_t start, uint32_t end); + void updateStatus(); + +private: + Ui::MemoryView m_ui; + + GameController* m_controller; + QPair<uint32_t, uint32_t> m_selection; +}; + +} + +#endif
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MemoryView</class> + <widget class="QWidget" name="MemoryView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>550</width> + <height>640</height> + </rect> + </property> + <property name="windowTitle"> + <string>Memory</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QComboBox" name="regions"> + <item> + <property name="text"> + <string>All</string> + </property> + </item> + <item> + <property name="text"> + <string>BIOS (16kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Working RAM (256kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Internal Working RAM (32kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Memory-Mapped I/O</string> + </property> + </item> + <item> + <property name="text"> + <string>Palette RAM (1kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Video RAM (96kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>OBJ Attribute Memory (1kiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Pak (32MiB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Pak (Waitstate 1)</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Pak (Waitstate 2)</string> + </property> + </item> + <item> + <property name="text"> + <string>Static RAM (64kiB)</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Inspect Address:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="setAddress"> + <property name="prefix"> + <string>0x</string> + </property> + <property name="maximum"> + <number>268435455</number> + </property> + <property name="singleStep"> + <number>16</number> + </property> + <property name="displayIntegerBase"> + <number>16</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Set Alignment:</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="width8"> + <property name="text"> + <string>1 Byte</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="width16"> + <property name="text"> + <string>2 Bytes</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="width32"> + <property name="text"> + <string>4 Bytes</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QGBA::MemoryModel" name="hexfield" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Signed Integer:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="sintVal"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Unsigned Integer:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="uintVal"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QGBA::MemoryModel</class> + <extends>QWidget</extends> + <header>MemoryModel.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
@@ -0,0 +1,77 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MessagePainter.h" + +#include <QPainter> + +#include <QDebug> + +extern "C" { +#include "gba/video.h" +} + +using namespace QGBA; + +MessagePainter::MessagePainter(QObject* parent) + : QObject(parent) + , m_messageTimer(this) +{ + m_messageFont.setFamily("Source Code Pro"); + m_messageFont.setStyleHint(QFont::Monospace); + m_messageFont.setPixelSize(13); + connect(&m_messageTimer, SIGNAL(timeout()), this, SLOT(clearMessage())); + m_messageTimer.setSingleShot(true); + m_messageTimer.setInterval(5000); + + clearMessage(); +} + +void MessagePainter::resize(const QSize& size, bool lockAspectRatio) { + int w = size.width(); + int h = size.height(); + int drawW = w; + int drawH = h; + if (lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + m_world.reset(); + m_world.translate((w - drawW) / 2, (h - drawH) / 2); + m_world.scale(qreal(drawW) / VIDEO_HORIZONTAL_PIXELS, qreal(drawH) / VIDEO_VERTICAL_PIXELS); + m_message.prepare(m_world, m_messageFont); +} + +void MessagePainter::paint(QPainter* painter) { + painter->setWorldTransform(m_world); + painter->setRenderHint(QPainter::Antialiasing); + painter->setFont(m_messageFont); + painter->setPen(Qt::black); + painter->translate(1, VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1); + const static int ITERATIONS = 11; + for (int i = 0; i < ITERATIONS; ++i) { + painter->save(); + painter->translate(cos(i * 2.0 * M_PI / ITERATIONS) * 0.8, sin(i * 2.0 * M_PI / ITERATIONS) * 0.8); + painter->drawStaticText(0, 0, m_message); + painter->restore(); + } + painter->setPen(Qt::white); + painter->drawStaticText(0, 0, m_message); +} + +void MessagePainter::showMessage(const QString& message) { + m_message.setText(message); + m_message.prepare(m_world, m_messageFont); + m_messageTimer.stop(); + m_messageTimer.start(); +} + +void MessagePainter::clearMessage() { + m_message.setText(QString()); + m_messageTimer.stop(); +}
@@ -0,0 +1,37 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_MESSAGE_PAINTER +#define QGBA_MESSAGE_PAINTER + +#include <QObject> +#include <QStaticText> +#include <QTimer> + +namespace QGBA { + +class MessagePainter : public QObject { +Q_OBJECT + +public: + MessagePainter(QObject* parent = nullptr); + + void resize(const QSize& size, bool lockAspectRatio); + void paint(QPainter* painter); + +public slots: + void showMessage(const QString& message); + void clearMessage(); + +private: + QStaticText m_message; + QTimer m_messageTimer; + QTransform m_world; + QFont m_messageFont; +}; + +} + +#endif
@@ -0,0 +1,71 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MultiplayerController.h" + +#include "GameController.h" + +using namespace QGBA; + +MultiplayerController::MultiplayerController() { + GBASIOLockstepInit(&m_lockstep); +} + +MultiplayerController::~MultiplayerController() { + GBASIOLockstepDeinit(&m_lockstep); +} + +bool MultiplayerController::attachGame(GameController* controller) { + MutexLock(&m_lockstep.mutex); + if (m_lockstep.attached == MAX_GBAS) { + MutexUnlock(&m_lockstep.mutex); + return false; + } + GBASIOLockstepNode* node = new GBASIOLockstepNode; + GBASIOLockstepNodeCreate(node); + GBASIOLockstepAttachNode(&m_lockstep, node); + MutexUnlock(&m_lockstep.mutex); + + controller->threadInterrupt(); + GBAThread* thread = controller->thread(); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI); + } + thread->sioDrivers.multiplayer = &node->d; + controller->threadContinue(); + return true; +} + +void MultiplayerController::detachGame(GameController* controller) { + controller->threadInterrupt(); + MutexLock(&m_lockstep.mutex); + GBAThread* thread = nullptr; + for (int i = 0; i < m_lockstep.attached; ++i) { + thread = controller->thread(); + if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) { + break; + } + thread = nullptr; + } + if (thread) { + GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(thread->sioDrivers.multiplayer); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_MULTI); + } + thread->sioDrivers.multiplayer = nullptr; + GBASIOLockstepDetachNode(&m_lockstep, node); + delete node; + } + MutexUnlock(&m_lockstep.mutex); + controller->threadContinue(); +} + +int MultiplayerController::attached() { + int num; + MutexLock(&m_lockstep.mutex); + num = m_lockstep.attached; + MutexUnlock(&m_lockstep.mutex); + return num; +}
@@ -0,0 +1,32 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_MULTIPLAYER_CONTROLLER +#define QGBA_MULTIPLAYER_CONTROLLER + +extern "C" { +#include "gba/sio/lockstep.h" +} + +namespace QGBA { + +class GameController; + +class MultiplayerController { +public: + MultiplayerController(); + ~MultiplayerController(); + + bool attachGame(GameController*); + void detachGame(GameController*); + + int attached(); + +private: + GBASIOLockstep m_lockstep; +}; + +} +#endif
@@ -143,6 +143,7 @@ m_ui.hwLight->setEnabled(!m_ui.hwAutodetect->isChecked());
m_ui.hwTilt->setEnabled(!m_ui.hwAutodetect->isChecked()); m_ui.hwRumble->setEnabled(!m_ui.hwAutodetect->isChecked()); + m_ui.hwAutodetect->setChecked(true); m_ui.hwRTC->setChecked(false); m_ui.hwGyro->setChecked(false); m_ui.hwLight->setChecked(false);@@ -150,7 +151,9 @@ m_ui.hwTilt->setChecked(false);
m_ui.hwRumble->setChecked(false); m_ui.idleLoop->setEnabled(true); + m_ui.idleLoop->clear(); - m_ui.clear->setEnabled(false); m_ui.save->setEnabled(false); + + updateOverrides(); }
@@ -20,6 +20,9 @@ <property name="windowTitle">
<string>Game Overrides</string> </property> <layout class="QGridLayout" name="gridLayout_3"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> <item row="2" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item>@@ -27,23 +30,7 @@ <spacer name="horizontalSpacer_2">
<property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> </spacer> - </item> - <item> - <widget class="QPushButton" name="clear"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Clear</string> - </property> - </widget> </item> <item> <widget class="QPushButton" name="save">@@ -200,12 +187,6 @@ <item row="1" column="1">
<spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> </property> </spacer> </item>
@@ -0,0 +1,106 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "PaletteView.h" + +#include "GBAApp.h" +#include "VFileDevice.h" + +#include <QFileDialog> +#include <QFontDatabase> + +extern "C" { +#include "gba/supervisor/export.h" +#include "util/vfs.h" +} + +using namespace QGBA; + +PaletteView::PaletteView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updatePalette())); + m_ui.bgGrid->setDimensions(QSize(16, 16)); + m_ui.objGrid->setDimensions(QSize(16, 16)); + m_ui.selected->setSize(64); + m_ui.selected->setDimensions(QSize(1, 1)); + updatePalette(); + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + m_ui.hexcode->setFont(font); + m_ui.value->setFont(font); + m_ui.index->setFont(font); + m_ui.r->setFont(font); + m_ui.g->setFont(font); + m_ui.b->setFont(font); + + connect(m_ui.bgGrid, SIGNAL(indexPressed(int)), this, SLOT(selectIndex(int))); + connect(m_ui.objGrid, &Swatch::indexPressed, [this] (int index) { selectIndex(index + 256); }); + connect(m_ui.exportBG, &QAbstractButton::clicked, [this] () { exportPalette(0, 256); }); + connect(m_ui.exportOBJ, &QAbstractButton::clicked, [this] () { exportPalette(256, 256); }); + + connect(controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(close())); +} + +void PaletteView::updatePalette() { + if (!m_controller->thread() || !m_controller->thread()->gba) { + return; + } + const uint16_t* palette = m_controller->thread()->gba->video.palette; + for (int i = 0; i < 256; ++i) { + m_ui.bgGrid->setColor(i, palette[i]); + m_ui.objGrid->setColor(i, palette[i + 256]); + } + m_ui.bgGrid->update(); + m_ui.objGrid->update(); +} + +void PaletteView::selectIndex(int index) { + uint16_t color = m_controller->thread()->gba->video.palette[index]; + m_ui.selected->setColor(0, color); + uint32_t r = GBA_R5(color); + uint32_t g = GBA_G5(color); + uint32_t b = GBA_B5(color); + uint32_t hexcode = (r << 19) | (g << 11) | (b << 3); + m_ui.hexcode->setText(tr("#%0").arg(hexcode, 6, 16, QChar('0'))); + m_ui.value->setText(tr("0x%0").arg(color, 4, 16, QChar('0'))); + m_ui.index->setText(tr("%0").arg(index, 3, 10, QChar('0'))); + m_ui.r->setText(tr("0x%0 (%1)").arg(r, 2, 16, QChar('0')).arg(r, 2, 10, QChar('0'))); + m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0'))); + m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0'))); +} + +void PaletteView::exportPalette(int start, int length) { + if (start >= 512) { + return; + } + if (start + length > 512) { + length = 512 - start; + } + m_controller->threadInterrupt(); + QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export palette"), tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); + if (!dialog->exec()) { + m_controller->threadContinue(); + return; + } + QString filename = dialog->selectedFiles()[0]; + VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + m_controller->threadContinue(); + return; + } + QString filter = dialog->selectedNameFilter(); + if (filter.contains("*.pal")) { + GBAExportPaletteRIFF(vf, length, &m_controller->thread()->gba->video.palette[start]); + } else if (filter.contains("*.act")) { + GBAExportPaletteACT(vf, length, &m_controller->thread()->gba->video.palette[start]); + } + vf->close(vf); + m_controller->threadContinue(); +}
@@ -0,0 +1,44 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_PALETTE_VIEW +#define QGBA_PALETTE_VIEW + +#include <QWidget> + +#include "GameController.h" +#include "Swatch.h" + +#include "ui_PaletteView.h" + +class GameController; + +namespace QGBA { + +class Swatch; + +class PaletteView : public QWidget { +Q_OBJECT + +public: + PaletteView(GameController* controller, QWidget* parent = nullptr); + +public slots: + void updatePalette(); + +private slots: + void selectIndex(int); + +private: + void exportPalette(int start, int length); + + Ui::PaletteView m_ui; + + GameController* m_controller; +}; + +} + +#endif
@@ -0,0 +1,331 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PaletteView</class> + <widget class="QWidget" name="PaletteView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>440</width> + <height>397</height> + </rect> + </property> + <property name="windowTitle"> + <string>Palette</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Background</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGBA::Swatch" name="bgGrid" native="true"> + <property name="minimumSize"> + <size> + <width>175</width> + <height>175</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Objects</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGBA::Swatch" name="objGrid" native="true"> + <property name="minimumSize"> + <size> + <width>175</width> + <height>175</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Selection</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QGBA::Swatch" name="selected" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_9"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Red</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Green</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Blue</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <property name="leftMargin"> + <number>8</number> + </property> + <item> + <widget class="QLabel" name="r"> + <property name="text"> + <string>0x00 (00)</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="g"> + <property name="text"> + <string>0x00 (00)</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="b"> + <property name="text"> + <string>0x00 (00)</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_11"> + <item> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>16-bit value</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Hex code</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Palette index</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_12"> + <property name="leftMargin"> + <number>8</number> + </property> + <item> + <widget class="QLabel" name="value"> + <property name="text"> + <string>0x0000</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="hexcode"> + <property name="text"> + <string>#000000</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="index"> + <property name="text"> + <string>000</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="exportBG"> + <property name="text"> + <string>Export BG</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="exportOBJ"> + <property name="text"> + <string>Export OBJ</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QGBA::Swatch</class> + <extends>QWidget</extends> + <header>Swatch.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
@@ -6,12 +6,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SensorView.h" #include "GameController.h" +#include "GamepadAxisEvent.h" +#include "InputController.h" using namespace QGBA; -SensorView::SensorView(GameController* controller, QWidget* parent) +SensorView::SensorView(GameController* controller, InputController* input, QWidget* parent) : QWidget(parent) , m_controller(controller) + , m_input(input) + , m_rotation(input->rotationSource()) { m_ui.setupUi(this);@@ -31,12 +35,91 @@ });
connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () { m_ui.time->setDateTime(QDateTime::currentDateTime()); }); - } + + connect(m_controller, SIGNAL(luminanceValueChanged(int)), this, SLOT(luminanceValueChanged(int))); + + m_timer.setInterval(2); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateSensors())); + if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) { + m_ui.tilt->hide(); + } else { + m_timer.start(); + } + + if (!m_rotation || !m_rotation->readGyroZ) { + m_ui.gyro->hide(); + } else { + m_timer.start(); + } + + jiggerer(m_ui.tiltSetX, &InputController::registerTiltAxisX); + jiggerer(m_ui.tiltSetY, &InputController::registerTiltAxisY); + jiggerer(m_ui.gyroSetX, &InputController::registerGyroAxisX); + jiggerer(m_ui.gyroSetY, &InputController::registerGyroAxisY); + + m_ui.gyroSensitivity->setValue(m_input->gyroSensitivity() / 1e8f); + connect(m_ui.gyroSensitivity, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this](double value) { + m_input->setGyroSensitivity(value * 1e8f); + }); +} + +void SensorView::jiggerer(QAbstractButton* button, void (InputController::*setter)(int)) { + connect(button, &QAbstractButton::toggled, [this, button, setter](bool checked) { + if (!checked) { + m_jiggered = nullptr; + } else { + m_jiggered = [this, button, setter](int axis) { + (m_input->*setter)(axis); + button->setChecked(false); + }; + } + }); + button->installEventFilter(this); +} + +bool SensorView::eventFilter(QObject*, QEvent* event) { + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); + gae->accept(); + if (m_jiggered && gae->direction() != GamepadAxisEvent::NEUTRAL && gae->isNew()) { + m_jiggered(gae->axis()); + } + return true; + } + return false; +} + +void SensorView::updateSensors() { + m_controller->threadInterrupt(); + if (m_rotation->sample && (!m_controller->isLoaded() || !(m_controller->thread()->gba->memory.hw.devices & (HW_GYRO | HW_TILT)))) { + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + m_rotation->sample(m_rotation); + } + if (m_rotation->readTiltX && m_rotation->readTiltY) { + float x = m_rotation->readTiltX(m_rotation); + float y = m_rotation->readTiltY(m_rotation); + m_ui.tiltX->setValue(x / 469762048.0f); // TODO: Document this value (0xE0 << 21) + m_ui.tiltY->setValue(y / 469762048.0f); + } + if (m_rotation->readGyroZ) { + m_ui.gyroView->setValue(m_rotation->readGyroZ(m_rotation)); + } + m_controller->threadContinue(); +} void SensorView::setLuminanceValue(int value) { - bool oldState; value = std::max(0, std::min(value, 255)); + m_controller->setLuminanceValue(value); +} +void SensorView::luminanceValueChanged(int value) { + bool oldState; oldState = m_ui.lightSpin->blockSignals(true); m_ui.lightSpin->setValue(value); m_ui.lightSpin->blockSignals(oldState);@@ -44,6 +127,4 @@
oldState = m_ui.lightSlide->blockSignals(true); m_ui.lightSlide->setValue(value); m_ui.lightSlide->blockSignals(oldState); - - m_controller->setLuminanceValue(value); }
@@ -6,28 +6,46 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_SENSOR_VIEW #define QGBA_SENSOR_VIEW +#include <QTimer> #include <QWidget> + +#include <functional> #include "ui_SensorView.h" + +struct GBARotationSource; namespace QGBA { class ConfigController; class GameController; +class GamepadAxisEvent; +class InputController; class SensorView : public QWidget { Q_OBJECT public: - SensorView(GameController* controller, QWidget* parent = nullptr); + SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr); + +protected: + bool eventFilter(QObject*, QEvent* event) override; private slots: + void updateSensors(); void setLuminanceValue(int); + void luminanceValueChanged(int); private: Ui::SensorView m_ui; + std::function<void(int)> m_jiggered; GameController* m_controller; + InputController* m_input; + GBARotationSource* m_rotation; + QTimer m_timer; + + void jiggerer(QAbstractButton*, void (InputController::*)(int)); }; }
@@ -6,8 +6,8 @@ <property name="geometry">
<rect> <x>0</x> <y>0</y> - <width>411</width> - <height>170</height> + <width>434</width> + <height>319</height> </rect> </property> <property name="sizePolicy">@@ -19,124 +19,296 @@ </property>
<property name="windowTitle"> <string>Sensors</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Realtime clock</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="2" column="0"> - <widget class="QRadioButton" name="timeFakeEpoch"> - <property name="text"> - <string>Start time at</string> - </property> - <attribute name="buttonGroup"> - <string notr="true">timeButtons</string> - </attribute> - </widget> - </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="timeFixed"> - <property name="text"> - <string>Fixed time</string> - </property> - <attribute name="buttonGroup"> - <string notr="true">timeButtons</string> - </attribute> - </widget> - </item> - <item row="0" column="0"> - <widget class="QRadioButton" name="timeNoOverride"> - <property name="text"> - <string>System time</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - <attribute name="buttonGroup"> - <string notr="true">timeButtons</string> - </attribute> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QDateTimeEdit" name="time"> - <property name="wrapping"> - <bool>true</bool> - </property> - <property name="maximumDate"> - <date> - <year>2100</year> - <month>1</month> - <day>2</day> - </date> - </property> - <property name="minimumDate"> - <date> - <year>2000</year> - <month>1</month> - <day>1</day> - </date> - </property> - <property name="currentSection"> - <enum>QDateTimeEdit::MonthSection</enum> - </property> - <property name="displayFormat"> - <string>MM/dd/yy hh:mm:ss AP</string> - </property> - <property name="timeSpec"> - <enum>Qt::UTC</enum> - </property> - </widget> - </item> - <item row="0" column="1" rowspan="3"> - <widget class="QPushButton" name="timeNow"> - <property name="text"> - <string>Now</string> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Realtime clock</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="0"> + <widget class="QRadioButton" name="timeFixed"> + <property name="text"> + <string>Fixed time</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="0" column="0"> + <widget class="QRadioButton" name="timeNoOverride"> + <property name="text"> + <string>System time</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="timeFakeEpoch"> + <property name="text"> + <string>Start time at</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="0" column="1" rowspan="3"> + <widget class="QPushButton" name="timeNow"> + <property name="text"> + <string>Now</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QDateTimeEdit" name="time"> + <property name="wrapping"> + <bool>true</bool> + </property> + <property name="maximumDate"> + <date> + <year>2099</year> + <month>12</month> + <day>31</day> + </date> + </property> + <property name="minimumDate"> + <date> + <year>2000</year> + <month>1</month> + <day>1</day> + </date> + </property> + <property name="currentSection"> + <enum>QDateTimeEdit::MonthSection</enum> + </property> + <property name="displayFormat"> + <string>MM/dd/yy hh:mm:ss AP</string> + </property> + <property name="timeSpec"> + <enum>Qt::UTC</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Light sensor</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Brightness</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="lightSpin"> + <property name="maximum"> + <number>255</number> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QSlider" name="lightSlide"> + <property name="maximum"> + <number>255</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksBelow</enum> + </property> + <property name="tickInterval"> + <number>16</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Light sensor</string> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="0" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Brightness</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QSpinBox" name="lightSpin"> - <property name="maximum"> - <number>255</number> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="QSlider" name="lightSlide"> - <property name="maximum"> - <number>255</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="tickPosition"> - <enum>QSlider::TicksBelow</enum> - </property> - <property name="tickInterval"> - <number>16</number> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QGroupBox" name="tilt"> + <property name="title"> + <string>Tilt sensor</string> + </property> + <layout class="QGridLayout" name="gridLayout_10"> + <item row="0" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_11"> + <item row="0" column="1"> + <widget class="QPushButton" name="tiltSetY"> + <property name="text"> + <string>Set Y</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QPushButton" name="tiltSetX"> + <property name="text"> + <string>Set X</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0" alignment="Qt::AlignHCenter"> + <widget class="QDoubleSpinBox" name="tiltX"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Ignored" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="minimum"> + <double>-1.000000000000000</double> + </property> + <property name="maximum"> + <double>1.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.010000000000000</double> + </property> + </widget> + </item> + <item row="1" column="1" alignment="Qt::AlignHCenter"> + <widget class="QDoubleSpinBox" name="tiltY"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Ignored" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="minimum"> + <double>-1.000000000000000</double> + </property> + <property name="maximum"> + <double>1.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.010000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="gyro"> + <property name="title"> + <string>Gyroscope</string> + </property> + <layout class="QGridLayout" name="gridLayout_12"> + <item row="0" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_17"> + <item row="0" column="1"> + <widget class="QPushButton" name="gyroSetY"> + <property name="text"> + <string>Set Y</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QPushButton" name="gyroSetX"> + <property name="text"> + <string>Set X</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Sensitivity</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDoubleSpinBox" name="gyroSensitivity"> + <property name="decimals"> + <number>0</number> + </property> + <property name="maximum"> + <double>1000.000000000000000</double> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QSlider" name="gyroView"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>-2147483647</number> + </property> + <property name="maximum"> + <number>2147483647</number> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="tracking"> + <bool>false</bool> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> </item> </layout> </widget>
@@ -7,8 +7,7 @@ #include "SettingsView.h"
#include "AudioProcessor.h" #include "ConfigController.h" - -#include <QFileDialog> +#include "GBAApp.h" using namespace QGBA;@@ -19,6 +18,7 @@ {
m_ui.setupUi(this); loadSetting("bios", m_ui.bios); + loadSetting("useBios", m_ui.useBios); loadSetting("skipBios", m_ui.skipBios); loadSetting("audioBuffers", m_ui.audioBufferSize); loadSetting("videoSync", m_ui.videoSync);@@ -26,10 +26,14 @@ loadSetting("audioSync", m_ui.audioSync);
loadSetting("frameskip", m_ui.frameskip); loadSetting("fpsTarget", m_ui.fpsTarget); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("volume", m_ui.volume); + loadSetting("mute", m_ui.mute); loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindBufferInterval", m_ui.rewindInterval); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); loadSetting("resampleVideo", m_ui.resampleVideo); + loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); + loadSetting("suspendScreensaver", m_ui.suspendScreensaver); QString idleOptimization = loadSetting("idleOptimization"); if (idleOptimization == "ignore") {@@ -40,17 +44,17 @@ } else if (idleOptimization == "detect") {
m_ui.idleOptimization->setCurrentIndex(2); } - int audioDriver = m_controller->getQtOption("audioDriver").toInt(); + QVariant audioDriver = m_controller->getQtOption("audioDriver"); #ifdef BUILD_QT_MULTIMEDIA m_ui.audioDriver->addItem(tr("Qt Multimedia"), static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)); - if (audioDriver == static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)) { + if (!audioDriver.isNull() && audioDriver.toInt() == static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)) { m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1); } #endif #ifdef BUILD_SDL m_ui.audioDriver->addItem(tr("SDL"), static_cast<int>(AudioProcessor::Driver::SDL)); - if (audioDriver == static_cast<int>(AudioProcessor::Driver::SDL)) { + if (audioDriver.isNull() || audioDriver.toInt() == static_cast<int>(AudioProcessor::Driver::SDL)) { m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1); } #endif@@ -60,7 +64,7 @@ connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig()));
} void SettingsView::selectBios() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS")); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS")); if (!filename.isEmpty()) { m_ui.bios->setText(filename); }@@ -68,6 +72,7 @@ }
void SettingsView::updateConfig() { saveSetting("bios", m_ui.bios); + saveSetting("useBios", m_ui.useBios); saveSetting("skipBios", m_ui.skipBios); saveSetting("audioBuffers", m_ui.audioBufferSize); saveSetting("videoSync", m_ui.videoSync);@@ -75,10 +80,14 @@ saveSetting("audioSync", m_ui.audioSync);
saveSetting("frameskip", m_ui.frameskip); saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); + saveSetting("volume", m_ui.volume); + saveSetting("mute", m_ui.mute); saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindBufferInterval", m_ui.rewindInterval); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); saveSetting("resampleVideo", m_ui.resampleVideo); + saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); + saveSetting("suspendScreensaver", m_ui.suspendScreensaver); switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) { case IDLE_LOOP_IGNORE:@@ -113,8 +122,16 @@ void SettingsView::saveSetting(const char* key, const QComboBox* field) {
saveSetting(key, field->lineEdit()); } +void SettingsView::saveSetting(const char* key, const QDoubleSpinBox* field) { + saveSetting(key, field->cleanText()); +} + void SettingsView::saveSetting(const char* key, const QLineEdit* field) { saveSetting(key, field->text()); +} + +void SettingsView::saveSetting(const char* key, const QSlider* field) { + saveSetting(key, QString::number(field->value())); } void SettingsView::saveSetting(const char* key, const QSpinBox* field) {@@ -128,16 +145,26 @@ }
void SettingsView::loadSetting(const char* key, QAbstractButton* field) { QString option = loadSetting(key); - field->setChecked(option != "0"); + field->setChecked(!option.isNull() && option != "0"); } void SettingsView::loadSetting(const char* key, QComboBox* field) { loadSetting(key, field->lineEdit()); } +void SettingsView::loadSetting(const char* key, QDoubleSpinBox* field) { + QString option = loadSetting(key); + field->setValue(option.toDouble()); +} + void SettingsView::loadSetting(const char* key, QLineEdit* field) { QString option = loadSetting(key); field->setText(option); +} + +void SettingsView::loadSetting(const char* key, QSlider* field) { + QString option = loadSetting(key); + field->setValue(option.toInt()); } void SettingsView::loadSetting(const char* key, QSpinBox* field) {
@@ -35,13 +35,17 @@ ConfigController* m_controller;
void saveSetting(const char* key, const QAbstractButton*); void saveSetting(const char* key, const QComboBox*); + void saveSetting(const char* key, const QDoubleSpinBox*); void saveSetting(const char* key, const QLineEdit*); + void saveSetting(const char* key, const QSlider*); void saveSetting(const char* key, const QSpinBox*); void saveSetting(const char* key, const QString&); void loadSetting(const char* key, QAbstractButton*); 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, QSpinBox*); QString loadSetting(const char* key); };
@@ -6,8 +6,8 @@ <property name="geometry">
<rect> <x>0</x> <y>0</y> - <width>355</width> - <height>501</height> + <width>673</width> + <height>366</height> </rect> </property> <property name="sizePolicy">@@ -20,22 +20,25 @@ <property name="windowTitle">
<string>Settings</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> <item> - <layout class="QFormLayout" name="formLayout"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::ExpandingFieldsGrow</enum> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>BIOS file:</string> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLineEdit" name="bios"> + <item row="0" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Audio driver:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="audioDriver"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <horstretch>0</horstretch>@@ -44,307 +47,373 @@ </sizepolicy>
</property> </widget> </item> - <item> - <widget class="QPushButton" name="biosBrowse"> + <item row="1" column="0"> + <widget class="QLabel" name="audioBufferSizeLabel"> <property name="text"> - <string>Browse</string> + <string>Audio buffer:</string> </property> </widget> </item> - </layout> - </item> - <item row="1" column="1"> - <widget class="QCheckBox" name="skipBios"> - <property name="text"> - <string>Skip BIOS intro</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QCheckBox" name="useBios"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Use BIOS file</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="Line" name="line_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_14"> - <property name="text"> - <string>Audio driver:</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QComboBox" name="audioDriver"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="audioBufferSizeLabel"> - <property name="text"> - <string>Audio buffer:</string> - </property> - </widget> - </item> - <item row="5" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_9"> - <item> - <widget class="QComboBox" name="audioBufferSize"> - <property name="editable"> - <bool>true</bool> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <item> + <widget class="QComboBox" name="audioBufferSize"> + <property name="editable"> + <bool>true</bool> + </property> + <property name="currentText"> + <string>2048</string> + </property> + <property name="currentIndex"> + <number>2</number> + </property> + <item> + <property name="text"> + <string>512</string> + </property> + </item> + <item> + <property name="text"> + <string>1024</string> + </property> + </item> + <item> + <property name="text"> + <string>2048</string> + </property> + </item> + <item> + <property name="text"> + <string>4096</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>samples</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Volume:</string> </property> - <property name="currentText"> - <string>2048</string> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QSlider" name="volume"> + <property name="maximum"> + <number>256</number> + </property> + <property name="pageStep"> + <number>16</number> + </property> + <property name="value"> + <number>256</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="mute"> + <property name="text"> + <string>Mute</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - <property name="currentIndex"> - <number>2</number> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Sync:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QCheckBox" name="videoSync"> + <property name="text"> + <string>Video</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="audioSync"> + <property name="text"> + <string>Audio</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Frameskip:</string> </property> + </widget> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_16"> <item> - <property name="text"> - <string>512</string> - </property> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Skip every</string> + </property> + </widget> </item> <item> - <property name="text"> - <string>1024</string> - </property> + <widget class="QSpinBox" name="frameskip"/> </item> <item> - <property name="text"> - <string>2048</string> - </property> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>frames</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>FPS target:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QDoubleSpinBox" name="fpsTarget"> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>240.000000000000000</double> + </property> + <property name="value"> + <double>60.000000000000000</double> + </property> + </widget> </item> <item> - <property name="text"> - <string>4096</string> - </property> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>frames per second</string> + </property> + </widget> </item> + </layout> + </item> + <item row="7" column="0" colspan="2"> + <widget class="Line" name="line_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> </widget> </item> - <item> - <widget class="QLabel" name="label_10"> + <item row="8" column="1"> + <widget class="QCheckBox" name="lockAspectRatio"> <property name="text"> - <string>samples</string> + <string>Lock aspect ratio</string> </property> </widget> </item> - </layout> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Sync:</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_10"> - <item> - <widget class="QCheckBox" name="videoSync"> + <item row="9" column="1"> + <widget class="QCheckBox" name="resampleVideo"> <property name="text"> - <string>Video</string> + <string>Resample video</string> </property> </widget> </item> - <item> - <widget class="QCheckBox" name="audioSync"> + <item row="10" column="1"> + <widget class="QCheckBox" name="suspendScreensaver"> <property name="text"> - <string>Audio</string> + <string>Suspend screensaver</string> + </property> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> </layout> </item> - <item row="7" column="0"> - <widget class="QLabel" name="label_9"> - <property name="text"> - <string>Frameskip:</string> + <item> + <widget class="Line" name="line_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> </widget> </item> - <item row="7" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_16"> - <item> - <widget class="QLabel" name="label_12"> + <item> + <layout class="QFormLayout" name="formLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> <property name="text"> - <string>Skip every</string> + <string>BIOS file:</string> </property> </widget> </item> - <item> - <widget class="QSpinBox" name="frameskip"/> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="bios"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="biosBrowse"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> </item> - <item> - <widget class="QLabel" name="label_13"> + <item row="1" column="1"> + <widget class="QCheckBox" name="skipBios"> <property name="text"> - <string>frames</string> + <string>Skip BIOS intro</string> </property> </widget> </item> - </layout> - </item> - <item row="9" column="0" colspan="2"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="10" column="1"> - <widget class="QCheckBox" name="lockAspectRatio"> - <property name="text"> - <string>Lock aspect ratio</string> - </property> - </widget> - </item> - <item row="11" column="1"> - <widget class="QCheckBox" name="resampleVideo"> - <property name="text"> - <string>Resample video</string> - </property> - </widget> - </item> - <item row="12" column="0" colspan="2"> - <widget class="Line" name="line_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="13" column="1"> - <widget class="QCheckBox" name="rewind"> - <property name="text"> - <string>Enable rewind</string> - </property> - </widget> - </item> - <item row="14" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Rewind interval:</string> - </property> - </widget> - </item> - <item row="14" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_12"> - <item> - <widget class="QLabel" name="label_5"> + <item row="2" column="1"> + <widget class="QCheckBox" name="useBios"> <property name="text"> - <string>Every</string> + <string>Use BIOS file</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item> - <widget class="QSpinBox" name="rewindInterval"/> + <item row="4" column="1"> + <widget class="QCheckBox" name="rewind"> + <property name="text"> + <string>Enable rewind</string> + </property> + </widget> </item> - <item> - <widget class="QLabel" name="label_6"> + <item row="5" column="0"> + <widget class="QLabel" name="label_4"> <property name="text"> - <string>frames</string> + <string>Create rewind state:</string> </property> </widget> </item> - </layout> - </item> - <item row="15" column="0"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>Rewind length:</string> - </property> - </widget> - </item> - <item row="15" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_13"> - <item> - <widget class="QSpinBox" name="rewindCapacity"/> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Every</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="rewindInterval"/> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>frames</string> + </property> + </widget> + </item> + </layout> </item> - <item> - <widget class="QLabel" name="label_7"> + <item row="6" column="0"> + <widget class="QLabel" name="label_8"> <property name="text"> - <string>intervals</string> + <string>Rewind history:</string> </property> </widget> </item> - </layout> - </item> - <item row="8" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>FPS target:</string> - </property> - </widget> - </item> - <item row="8" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QSpinBox" name="fpsTarget"> - <property name="maximum"> - <number>240</number> - </property> - <property name="value"> - <number>60</number> + <item row="6" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <item> + <widget class="QSpinBox" name="rewindCapacity"/> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>states</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="7" column="0" colspan="2"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item> - <widget class="QLabel" name="label_11"> + <item row="8" column="1"> + <widget class="QCheckBox" name="allowOpposingDirections"> <property name="text"> - <string>frames per second</string> + <string>Allow opposing input directions</string> </property> </widget> </item> - </layout> - </item> - <item row="17" column="1"> - <widget class="QComboBox" name="idleOptimization"> - <item> - <property name="text"> - <string>Run all</string> - </property> + <item row="9" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Idle loops</string> + </property> + </widget> </item> - <item> - <property name="text"> - <string>Remove known</string> - </property> + <item row="9" column="1"> + <widget class="QComboBox" name="idleOptimization"> + <item> + <property name="text"> + <string>Run all</string> + </property> + </item> + <item> + <property name="text"> + <string>Remove known</string> + </property> + </item> + <item> + <property name="text"> + <string>Detect and remove</string> + </property> + </item> + </widget> </item> - <item> - <property name="text"> - <string>Detect and remove</string> - </property> - </item> - </widget> - </item> - <item row="16" column="0" colspan="2"> - <widget class="Line" name="line_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="17" column="0"> - <widget class="QLabel" name="label_15"> - <property name="text"> - <string>Idle loops</string> - </property> - </widget> + </layout> </item> </layout> </item>
@@ -40,6 +40,16 @@ case 2:
if (item->button() >= 0) { return item->button(); } + if (item->axis() >= 0) { + char d = '\0'; + if (item->direction() == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (item->direction() == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + return QString("%1%2").arg(d).arg(item->axis()); + } break; } return QVariant();@@ -214,15 +224,48 @@ return;
} ShortcutItem* item = itemAt(index); int oldButton = item->button(); - item->setButton(button); if (oldButton >= 0) { m_buttons.take(oldButton); } + updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); + item->setButton(button); if (button >= 0) { m_buttons[button] = item; } if (m_config) { m_config->setQtOption(item->name(), button, BUTTON_SECTION); + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); +} + +void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutItem* item = itemAt(index); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + } + if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { + updateButton(index, -1); + m_axes[qMakePair(axis, direction)] = item; + } + item->setAxis(axis, direction); + if (m_config) { + char d = '\0'; + if (direction == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (direction == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); } emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); }@@ -286,6 +329,31 @@ }
event->accept(); return true; } + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); + auto item = m_axes.find(qMakePair(gae->axis(), gae->direction())); + if (item == m_axes.end()) { + return false; + } + if (gae->isNew()) { + QAction* action = item.value()->action(); + if (action && action->isEnabled()) { + action->trigger(); + } + } + ShortcutItem::Functions pair = item.value()->functions(); + if (gae->isNew()) { + if (pair.first) { + pair.first(); + } + } else { + if (pair.second) { + pair.second(); + } + } + event->accept(); + return true; + } return false; }@@ -311,6 +379,30 @@ m_buttons.take(oldButton);
} m_buttons[button.toInt()] = item; } + QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION); + if (!axis.isNull()) { + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + QString axisDesc = axis.toString(); + if (axisDesc.size() >= 2) { + GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; + if (axisDesc[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + } + if (axisDesc[0] == '+') { + direction = GamepadAxisEvent::POSITIVE; + } + bool ok; + int axis = axisDesc.mid(1).toInt(&ok); + if (ok) { + item->setAxis(axis, direction); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + } + m_axes[qMakePair(axis, direction)] = item; + } + } + } } QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {@@ -339,6 +431,8 @@ , m_shortcut(action->shortcut())
, m_menu(nullptr) , m_name(name) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { m_visibleName = action->text()@@ -354,6 +448,8 @@ , m_menu(nullptr)
, m_name(name) , m_visibleName(visibleName) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { }@@ -362,6 +458,8 @@ ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent)
: m_action(nullptr) , m_menu(menu) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { if (menu) {@@ -389,3 +487,8 @@ if (m_action) {
m_action->setShortcut(shortcut); } } + +void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { + m_axis = axis; + m_direction = direction; +}
@@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_SHORTCUT_MODEL #define QGBA_SHORTCUT_MODEL +#include "GamepadAxisEvent.h" + #include <QAbstractItemModel> #include <QKeySequence>@@ -26,6 +28,7 @@
private: constexpr static const char* const KEY_SECTION = "shortcutKey"; constexpr static const char* const BUTTON_SECTION = "shortcutButton"; + constexpr static const char* const AXIS_SECTION = "shortcutAxis"; class ShortcutItem { public:@@ -53,6 +56,9 @@ void addSubmenu(QMenu* menu);
int button() const { return m_button; } void setShortcut(const QKeySequence& sequence); void setButton(int button) { m_button = button; } + int axis() const { return m_axis; } + GamepadAxisEvent::Direction direction() const { return m_direction; } + void setAxis(int axis, GamepadAxisEvent::Direction direction); bool operator==(const ShortcutItem& other) const { return m_menu == other.m_menu && m_action == other.m_action; }@@ -64,6 +70,8 @@ Functions m_functions;
QString m_name; QString m_visibleName; int m_button; + int m_axis; + GamepadAxisEvent::Direction m_direction; QList<ShortcutItem> m_items; ShortcutItem* m_parent; };@@ -91,6 +99,7 @@ bool isMenuAt(const QModelIndex& index) const;
void updateKey(const QModelIndex& index, const QKeySequence& keySequence); void updateButton(const QModelIndex& index, int button); + void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction); void clearKey(const QModelIndex& index); void clearButton(const QModelIndex& index);@@ -108,6 +117,7 @@
ShortcutItem m_rootMenu; QMap<QMenu*, ShortcutItem*> m_menuMap; QMap<int, ShortcutItem*> m_buttons; + QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes; QMap<QKeySequence, ShortcutItem*> m_heldKeys; ConfigController* m_config; };
@@ -22,6 +22,7 @@ m_ui.keySequenceEdit->installEventFilter(this);
connect(m_ui.keySequenceEdit, SIGNAL(keySequenceChanged(const QKeySequence&)), this, SLOT(updateKey(const QKeySequence&))); connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); + connect(m_ui.keyEdit, SIGNAL(axisChanged(int, int)), this, SLOT(updateAxis(int, int))); connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(load(const QModelIndex&))); connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clear())); }@@ -29,15 +30,6 @@
void ShortcutView::setController(ShortcutController* controller) { m_controller = controller; m_ui.shortcutTable->setModel(controller); -} - -bool ShortcutView::event(QEvent* event) { - if (event->type() == GamepadButtonEvent::Down()) { - updateButton(static_cast<GamepadButtonEvent*>(event)->value()); - event->accept(); - return true; - } - return QWidget::event(event); } bool ShortcutView::eventFilter(QObject*, QEvent* event) {@@ -69,11 +61,15 @@ } else if (index.column() == 2) {
m_ui.gamepadButton->click(); } if (m_ui.gamepadButton->isChecked()) { + bool blockSignals = m_ui.keyEdit->blockSignals(true); m_ui.keyEdit->setFocus(); m_ui.keyEdit->setValueButton(-1); // There are no default bindings + m_ui.keyEdit->blockSignals(blockSignals); } else { + bool blockSignals = m_ui.keySequenceEdit->blockSignals(true); m_ui.keySequenceEdit->setFocus(); m_ui.keySequenceEdit->setKeySequence(sequence); + m_ui.keySequenceEdit->blockSignals(blockSignals); } }@@ -106,5 +102,12 @@ if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) {
return; } m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); +} + +void ShortcutView::updateAxis(int axis, int direction) { + if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + return; + } + m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, static_cast<GamepadAxisEvent::Direction>(direction)); }
@@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_SHORTCUT_VIEW #define QGBA_SHORTCUT_VIEW +#include "GamepadAxisEvent.h" + #include <QWidget> #include "ui_ShortcutView.h"@@ -23,7 +25,6 @@
void setController(ShortcutController* controller); protected: - virtual bool event(QEvent* event) override; virtual bool eventFilter(QObject* obj, QEvent* event) override; private slots:@@ -31,6 +32,7 @@ void load(const QModelIndex&);
void clear(); void updateKey(const QKeySequence&); void updateButton(int button); + void updateAxis(int axis, int direction); private: Ui::ShortcutView m_ui;
@@ -0,0 +1,66 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "Swatch.h" + +#include <QMouseEvent> +#include <QPainter> + +extern "C" { +#include "gba/video.h" +} + +using namespace QGBA; + +Swatch::Swatch(QWidget* parent) + : QWidget(parent) +{ + m_size = 10; + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); +} + +void Swatch::setSize(int size) { + m_size = size; + setDimensions(m_dims); +} + +void Swatch::setDimensions(const QSize& size) { + m_dims = size; + m_backing = QPixmap(size * (m_size + 1) - QSize(1, 1)); + m_backing.fill(Qt::transparent); + int elem = size.width() * size.height(); + m_colors.resize(elem); + for (int i = 0; i < elem; ++i) { + updateFill(i); + } +} + +void Swatch::setColor(int index, uint16_t color) { + m_colors[index].setRgb( + GBA_R8(color), + GBA_G8(color), + GBA_B8(color)); + updateFill(index); +} + +void Swatch::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.drawPixmap(QPoint(), m_backing); +} + +void Swatch::mousePressEvent(QMouseEvent* event) { + int x = event->x() / (m_size + 1); + int y = event->y() / (m_size + 1); + emit indexPressed(y * m_dims.width() + x); +} + +void Swatch::updateFill(int index) { + QPainter painter(&m_backing); + int x = index % m_dims.width(); + int y = index / m_dims.width(); + QRect r(x * (m_size + 1), y * (m_size + 1), m_size, m_size); + painter.fillRect(r, m_colors[index]); + update(r); +}
@@ -0,0 +1,45 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_SWATCH +#define QGBA_SWATCH + +#include <QColor> +#include <QWidget> +#include <QVector> + +namespace QGBA { + +class Swatch : public QWidget { +Q_OBJECT + +public: + Swatch(QWidget* parent = nullptr); + + void setDimensions(const QSize&); + void setSize(int size); + +public slots: + void setColor(int index, uint16_t); + +signals: + void indexPressed(int index); + +protected: + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent*) override; + +private: + int m_size; + QVector<QColor> m_colors; + QPixmap m_backing; + QSize m_dims; + + void updateFill(int index); +}; + +} + +#endif
@@ -5,10 +5,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "VFileDevice.h" -extern "C" { -#include "util/vfs.h" -} - using namespace QGBA; VFileDevice::VFileDevice(VFile* vf, QObject* parent)@@ -29,3 +25,7 @@
qint64 VFileDevice::size() const { return m_vf->size(m_vf); } + +VFile* VFileDevice::open(QString path, int mode) { + return VFileOpen(path.toLocal8Bit().constData(), mode); +}
@@ -8,7 +8,9 @@ #define QGBA_VFILE_DEVICE
#include <QFileDevice> -struct VFile; +extern "C" { +#include "util/vfs.h" +} namespace QGBA {@@ -18,13 +20,15 @@
public: VFileDevice(VFile* vf, QObject* parent = nullptr); + static VFile* open(QString path, int mode); + protected: virtual qint64 readData(char* data, qint64 maxSize) override; virtual qint64 writeData(const char* data, qint64 maxSize) override; virtual qint64 size() const override; private: - mutable VFile* m_vf; + VFile* m_vf; }; }
@@ -7,7 +7,8 @@ #include "VideoView.h"
#ifdef USE_FFMPEG -#include <QFileDialog> +#include "GBAApp.h" + #include <QMap> using namespace QGBA;@@ -212,7 +213,7 @@ validateSettings();
} void VideoView::selectFile() { - QString filename = QFileDialog::getSaveFileName(this, tr("Select output file")); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); }
@@ -5,25 +5,29 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Window.h" -#include <QFileDialog> -#include <QFileInfo> #include <QKeyEvent> #include <QKeySequence> #include <QMenuBar> #include <QMessageBox> #include <QMimeData> +#include <QPainter> #include <QStackedLayout> #include "CheatsView.h" #include "ConfigController.h" +#include "Display.h" #include "GameController.h" +#include "GBAApp.h" #include "GBAKeyEditor.h" #include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" #include "LoadSaveState.h" #include "LogView.h" +#include "MultiplayerController.h" +#include "MemoryView.h" #include "OverrideView.h" +#include "PaletteView.h" #include "SensorView.h" #include "SettingsView.h" #include "ShortcutController.h"@@ -36,13 +40,19 @@ }
using namespace QGBA; -Window::Window(ConfigController* config, QWidget* parent) +#ifdef __WIN32 +// This is a macro everywhere except MinGW, it seems +using std::isnan; +#endif + +Window::Window(ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) , m_logView(new LogView()) , m_stateWindow(nullptr) , m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) + , m_inputController(playerId) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif@@ -54,17 +64,16 @@ , m_gdbController(nullptr)
#endif , m_mruMenu(nullptr) , m_shortcutController(new ShortcutController(this)) + , m_playerId(playerId) { - setWindowTitle(PROJECT_NAME); setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); m_controller = new GameController(this); m_controller->setInputController(&m_inputController); m_controller->setOverrides(m_config->overrides()); + updateTitle(); - QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); - format.setSwapInterval(1); - m_display = new Display(format); + m_display = Display::create(this); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap@@ -76,10 +85,18 @@ m_screenWidget->setPixmap(m_logo);
m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); setCentralWidget(m_screenWidget); + QVariant windowPos = m_config->getQtOption("windowPos"); + if (!windowPos.isNull()) { + move(windowPos.toPoint()); + } + connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*))); + connect(m_controller, SIGNAL(gameStarted(GBAThread*)), &m_inputController, SLOT(suspendScreensaver())); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing())); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped())); + connect(m_controller, SIGNAL(gameStopped(GBAThread*)), &m_inputController, SLOT(resumeScreensaver())); connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw())); + connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw())); connect(m_controller, SIGNAL(gamePaused(GBAThread*)), m_display, SLOT(pauseDrawing())); #ifndef Q_OS_MAC connect(m_controller, SIGNAL(gamePaused(GBAThread*)), menuBar(), SLOT(show()));@@ -89,15 +106,20 @@ menuBar()->hide();
} }); #endif + connect(m_controller, SIGNAL(gamePaused(GBAThread*)), &m_inputController, SLOT(resumeScreensaver())); connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), m_display, SLOT(unpauseDrawing())); + connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), &m_inputController, SLOT(suspendScreensaver())); connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&))); connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame())); + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), m_display, SLOT(framePosted(const uint32_t*))); connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&))); connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed())); + connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int))); + connect(m_controller, SIGNAL(statusPosted(const QString&)), m_display, SLOT(showMessage(const QString&))); connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int))); - connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection); + connect(this, SIGNAL(startDrawing(GBAThread*)), m_display, SLOT(startDrawing(GBAThread*)), Qt::QueuedConnection); connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing())); connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame())); connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));@@ -105,7 +127,7 @@ connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); - m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL); + m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_shortcutController->setConfigController(m_config);@@ -172,6 +194,12 @@ if (opts->width && opts->height) {
resizeFrame(opts->width, opts->height); } + if (opts->fullscreen) { + enterFullScreen(); + } + + m_inputController.setScreensaverSuspendable(opts->suspendScreensaver); + m_mruFiles = m_config->getMRU(); updateMRU();@@ -179,86 +207,117 @@ m_inputController.setConfiguration(m_config);
} void Window::saveConfig() { + m_inputController.saveConfiguration(); m_config->write(); } void Window::selectROM() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"), m_config->getQtOption("lastDirectory").toString(), tr("Game Boy Advance ROMs (*.gba *.zip *.rom *.bin)")); + QStringList formats{ + "*.gba", +#ifdef USE_LIBZIP + "*.zip", +#endif +#ifdef USE_LZMA + "*.7z", +#endif + "*.rom", + "*.bin"}; + QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' '))); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); if (!filename.isEmpty()) { - m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadGame(filename); } } void Window::selectBIOS() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS"), m_config->getQtOption("lastDirectory").toString()); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS")); if (!filename.isEmpty()) { - m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_config->setOption("bios", filename); m_config->updateOption("bios"); + m_config->setOption("useBios", true); + m_config->updateOption("useBios"); m_controller->loadBIOS(filename); } } void Window::selectPatch() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), m_config->getQtOption("lastDirectory").toString(), tr("Patches (*.ips *.ups *.bps)")); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select patch"), tr("Patches (*.ips *.ups *.bps)")); if (!filename.isEmpty()) { - m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadPatch(filename); } } +void Window::openView(QWidget* widget) { + connect(this, SIGNAL(shutdown()), widget, SLOT(close())); + widget->setAttribute(Qt::WA_DeleteOnClose); + widget->show(); +} + +void Window::importSharkport() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)")); + if (!filename.isEmpty()) { + m_controller->importSharkport(filename); + } +} + +void Window::exportSharkport() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)")); + if (!filename.isEmpty()) { + m_controller->exportSharkport(filename); + } +} + void Window::openKeymapWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); - connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close())); - keyEditor->setAttribute(Qt::WA_DeleteOnClose); - keyEditor->show(); + openView(keyEditor); } void Window::openSettingsWindow() { SettingsView* settingsWindow = new SettingsView(m_config); - connect(this, SIGNAL(shutdown()), settingsWindow, SLOT(close())); connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); - settingsWindow->setAttribute(Qt::WA_DeleteOnClose); - settingsWindow->show(); + openView(settingsWindow); } void Window::openShortcutWindow() { +#ifdef BUILD_SDL + m_inputController.recalibrateAxes(); +#endif ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); - connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close())); - shortcutView->setAttribute(Qt::WA_DeleteOnClose); - shortcutView->show(); + openView(shortcutView); } void Window::openOverrideWindow() { OverrideView* overrideWindow = new OverrideView(m_controller, m_config); - connect(this, SIGNAL(shutdown()), overrideWindow, SLOT(close())); - overrideWindow->setAttribute(Qt::WA_DeleteOnClose); - overrideWindow->show(); + openView(overrideWindow); } void Window::openSensorWindow() { - SensorView* sensorWindow = new SensorView(m_controller); - connect(this, SIGNAL(shutdown()), sensorWindow, SLOT(close())); - sensorWindow->setAttribute(Qt::WA_DeleteOnClose); - sensorWindow->show(); + SensorView* sensorWindow = new SensorView(m_controller, &m_inputController); + openView(sensorWindow); } void Window::openCheatsWindow() { CheatsView* cheatsWindow = new CheatsView(m_controller); - connect(this, SIGNAL(shutdown()), cheatsWindow, SLOT(close())); - cheatsWindow->setAttribute(Qt::WA_DeleteOnClose); - cheatsWindow->show(); + openView(cheatsWindow); +} + +void Window::openPaletteWindow() { + PaletteView* paletteWindow = new PaletteView(m_controller); + openView(paletteWindow); +} + +void Window::openMemoryWindow() { + MemoryView* memoryWindow = new MemoryView(m_controller); + openView(memoryWindow); } #ifdef BUILD_SDL void Window::openGamepadWindow() { - GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON); - connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close())); - keyEditor->setAttribute(Qt::WA_DeleteOnClose); - keyEditor->show(); + const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); + GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile); + openView(keyEditor); } #endif@@ -331,17 +390,23 @@ event->accept();
} void Window::resizeEvent(QResizeEvent*) { - m_config->setOption("height", m_screenWidget->height()); - m_config->setOption("width", m_screenWidget->width()); + if (!isFullScreen()) { + m_config->setOption("height", m_screenWidget->height()); + m_config->setOption("width", m_screenWidget->width()); + } + m_config->setOption("fullscreen", isFullScreen()); } void Window::closeEvent(QCloseEvent* event) { emit shutdown(); + m_config->setQtOption("windowPos", pos()); + saveConfig(); QMainWindow::closeEvent(event); } void Window::focusOutEvent(QFocusEvent*) { m_controller->setTurbo(false, false); + m_controller->stopRewinding(); m_controller->clearKeys(); }@@ -367,17 +432,40 @@ event->accept();
m_controller->loadGame(url.path()); } +void Window::mouseDoubleClickEvent(QMouseEvent* event) { + if (event->button() != Qt::LeftButton) { + return; + } + toggleFullScreen(); +} + +void Window::enterFullScreen() { + if (isFullScreen()) { + return; + } + showFullScreen(); + setCursor(Qt::BlankCursor); +#ifndef Q_OS_MAC + if (m_controller->isLoaded() && !m_controller->isPaused()) { + menuBar()->hide(); + } +#endif +} + +void Window::exitFullScreen() { + if (!isFullScreen()) { + return; + } + unsetCursor(); + showNormal(); + menuBar()->show(); +} + void Window::toggleFullScreen() { if (isFullScreen()) { - showNormal(); - menuBar()->show(); + exitFullScreen(); } else { - showFullScreen(); -#ifndef Q_OS_MAC - if (m_controller->isLoaded() && !m_controller->isPaused()) { - menuBar()->hide(); - } -#endif + enterFullScreen(); } }@@ -385,7 +473,7 @@ void Window::gameStarted(GBAThread* context) {
char title[13] = { '\0' }; MutexLock(&context->stateMutex); if (context->state < THREAD_EXITING) { - emit startDrawing(m_controller->drawContext(), context); + emit startDrawing(context); GBAGetGameTitle(context->gba, title); } else { MutexUnlock(&context->stateMutex);@@ -395,8 +483,10 @@ MutexUnlock(&context->stateMutex);
foreach (QAction* action, m_gameActions) { action->setDisabled(false); } - appendMRU(context->fname); - setWindowTitle(tr(PROJECT_NAME " - %1").arg(title)); + if (context->fname) { + appendMRU(context->fname); + } + updateTitle(); attachWidget(m_display); #ifndef Q_OS_MAC@@ -405,6 +495,7 @@ menuBar()->hide();
} #endif + m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); }@@ -412,7 +503,7 @@ void Window::gameStopped() {
foreach (QAction* action, m_gameActions) { action->setDisabled(true); } - setWindowTitle(tr(PROJECT_NAME)); + updateTitle(); detachWidget(m_display); m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); m_screenWidget->setPixmap(m_logo);@@ -436,6 +527,19 @@ fail->setAttribute(Qt::WA_DeleteOnClose);
fail->show(); } +void Window::unimplementedBiosCall(int call) { + if (m_hitUnimplementedBiosCall) { + return; + } + m_hitUnimplementedBiosCall = true; + + QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Unimplemented BIOS call"), + tr("This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience."), + QMessageBox::Ok, this, Qt::Sheet); + fail->setAttribute(Qt::WA_DeleteOnClose); + fail->show(); +} + void Window::recordFrame() { m_frameList.append(QDateTime::currentDateTime()); while (m_frameList.count() > FRAME_LIST_SIZE) {@@ -444,16 +548,38 @@ }
} void Window::showFPS() { - char title[13] = { '\0' }; - GBAGetGameTitle(m_controller->thread()->gba, title); if (m_frameList.isEmpty()) { - setWindowTitle(tr(PROJECT_NAME " - %1").arg(title)); + updateTitle(); return; } qint64 interval = m_frameList.first().msecsTo(m_frameList.last()); float fps = (m_frameList.count() - 1) * 10000.f / interval; fps = round(fps) / 10.f; - setWindowTitle(tr(PROJECT_NAME " - %1 (%2 fps)").arg(title).arg(fps)); + updateTitle(fps); +} + +void Window::updateTitle(float fps) { + QString title; + + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + char gameTitle[13] = { '\0' }; + GBAGetGameTitle(m_controller->thread()->gba, gameTitle); + + title = (gameTitle); + } + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + title += tr(" - Player %1 of %2").arg(m_playerId + 1).arg(multiplayer->attached()); + } + m_controller->threadContinue(); + if (title.isNull()) { + setWindowTitle(tr("%1 - %2").arg(projectName).arg(projectVersion)); + } else if (isnan(fps)) { + setWindowTitle(tr("%1 - %2 - %3").arg(projectName).arg(title).arg(projectVersion)); + } else { + setWindowTitle(tr("%1 - %2 (%3 fps) - %4").arg(projectName).arg(title).arg(fps).arg(projectVersion)); + } } void Window::openStateWindow(LoadSave ls) {@@ -467,7 +593,7 @@ connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_stateWindow, SLOT(close()));
connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_screenWidget->layout()->removeWidget(m_stateWindow); m_stateWindow = nullptr; - setFocus(); + QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection); }); if (!wasPaused) { m_controller->setPaused(true);@@ -486,6 +612,7 @@ installEventFilter(m_shortcutController);
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS"); m_mruMenu = fileMenu->addMenu(tr("Recent"));@@ -505,25 +632,56 @@ addControlledAction(fileMenu, saveState, "saveState");
QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); + m_shortcutController->addMenu(quickLoadMenu); + m_shortcutController->addMenu(quickSaveMenu); + + QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); + connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState())); + m_gameActions.append(quickLoad); + addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); + + QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); + connect(quickSave, SIGNAL(triggered()), m_controller, SLOT(saveState())); + m_gameActions.append(quickSave); + addControlledAction(quickSaveMenu, quickSave, "quickSave"); + + quickLoadMenu->addSeparator(); + quickSaveMenu->addSeparator(); + int i; for (i = 1; i < 10; ++i) { - QAction* quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); + quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); quickLoad->setShortcut(tr("F%1").arg(i)); connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); m_gameActions.append(quickLoad); - addAction(quickLoad); - quickLoadMenu->addAction(quickLoad); + addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); - QAction* quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); + quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); m_gameActions.append(quickSave); - addAction(quickSave); - quickSaveMenu->addAction(quickSave); + addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } -#ifndef Q_OS_MAC fileMenu->addSeparator(); + QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu); + connect(importShark, SIGNAL(triggered()), this, SLOT(importSharkport())); + m_gameActions.append(importShark); + addControlledAction(fileMenu, importShark, "importShark"); + + QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu); + connect(exportShark, SIGNAL(triggered()), this, SLOT(exportSharkport())); + m_gameActions.append(exportShark); + addControlledAction(fileMenu, exportShark, "exportShark"); + + fileMenu->addSeparator(); + QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); + connect(multiWindow, &QAction::triggered, [this]() { + GBAApp::app()->newWindow(); + }); + addControlledAction(fileMenu, multiWindow, "multiWindow"); + +#ifndef Q_OS_MAC addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif@@ -567,6 +725,12 @@ addControlledAction(emulationMenu, frameAdvance, "frameAdvance");
emulationMenu->addSeparator(); + m_shortcutController->addFunctions(emulationMenu, [this]() { + m_controller->setTurbo(true, false); + }, [this]() { + m_controller->setTurbo(false, false); + }, QKeySequence(Qt::Key_Tab), tr("Fast forward (held)"), "holdFastForward"); + QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); turbo->setCheckable(true); turbo->setChecked(false);@@ -574,22 +738,82 @@ turbo->setShortcut(tr("Shift+Tab"));
connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool))); addControlledAction(emulationMenu, turbo, "fastForward"); + QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed")); + ConfigOption* ffspeed = m_config->addOption("fastForwardRatio"); + ffspeed->connect([this](const QVariant& value) { + m_controller->setTurboSpeed(value.toFloat()); + }, this); + ffspeed->addValue(tr("Unbounded"), -1.0f, ffspeedMenu); + ffspeed->setValue(QVariant(-1.0f)); + ffspeedMenu->addSeparator(); + for (i = 2; i < 11; ++i) { + ffspeed->addValue(tr("%0x").arg(i), i, ffspeedMenu); + } + m_config->updateOption("fastForwardRatio"); + + m_shortcutController->addFunctions(emulationMenu, [this]() { + m_controller->startRewinding(); + }, [this]() { + m_controller->stopRewinding(); + }, QKeySequence("~"), tr("Rewind (held)"), "holdRewind"); + QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); rewind->setShortcut(tr("`")); connect(rewind, SIGNAL(triggered()), m_controller, SLOT(rewind())); m_gameActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind"); + QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu); + frameRewind->setShortcut(tr("Ctrl+B")); + connect(frameRewind, &QAction::triggered, [this] () { + m_controller->rewind(1); + }); + m_gameActions.append(frameRewind); + addControlledAction(emulationMenu, frameRewind, "frameRewind"); + ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); - videoSync->connect([this](const QVariant& value) { m_controller->setVideoSync(value.toBool()); }); + videoSync->connect([this](const QVariant& value) { + m_controller->setVideoSync(value.toBool()); + }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); - audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); }); + audioSync->connect([this](const QVariant& value) { + m_controller->setAudioSync(value.toBool()); + }, this); m_config->updateOption("audioSync"); + emulationMenu->addSeparator(); + + QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); + m_shortcutController->addMenu(solarMenu); + QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); + connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); + addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); + + QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); + connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); + addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); + + QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); + connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); + addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); + + QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); + connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); + addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); + + solarMenu->addSeparator(); + for (int i = 0; i <= 10; ++i) { + QAction* setSolar = new QAction(tr("Brightness %1").arg(QString::number(i)), solarMenu); + connect(setSolar, &QAction::triggered, [this, i]() { + m_controller->setLuminanceLevel(i); + }); + addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i))); + } + QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); m_shortcutController->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size"));@@ -600,23 +824,35 @@ connect(setSize, &QAction::triggered, [this, i]() {
showNormal(); resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i); }); - addControlledAction(frameMenu, setSize, tr("frame%1x").arg(QString::number(i))); + addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i))); } - addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen"); + QKeySequence fullscreenKeys; +#ifdef Q_OS_WIN + fullscreenKeys = QKeySequence("Alt+Enter"); +#else + fullscreenKeys = QKeySequence("Ctrl+F"); +#endif + addControlledAction(frameMenu, frameMenu->addAction(tr("Toggle fullscreen"), this, SLOT(toggleFullScreen()), fullscreenKeys), "fullscreen"); ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); - lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); }); + lockAspectRatio->connect([this](const QVariant& value) { + m_display->lockAspectRatio(value.toBool()); + }, this); m_config->updateOption("lockAspectRatio"); ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Resample video"), avMenu); - resampleVideo->connect([this](const QVariant& value) { m_display->filter(value.toBool()); }); + resampleVideo->connect([this](const QVariant& value) { + m_display->filter(value.toBool()); + }, this); m_config->updateOption("resampleVideo"); QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip")); ConfigOption* skip = m_config->addOption("frameskip"); - skip->connect([this](const QVariant& value) { m_controller->setFrameskip(value.toInt()); }); + skip->connect([this](const QVariant& value) { + m_controller->setFrameskip(value.toInt()); + }, this); for (int i = 0; i <= 10; ++i) { skip->addValue(QString::number(i), i, skipMenu); }@@ -626,7 +862,9 @@ avMenu->addSeparator();
QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size")); ConfigOption* buffers = m_config->addOption("audioBuffers"); - buffers->connect([this](const QVariant& value) { emit audioBufferSamplesChanged(value.toInt()); }); + buffers->connect([this](const QVariant& value) { + emit audioBufferSamplesChanged(value.toInt()); + }, this); buffers->addValue(tr("512"), 512, buffersMenu); buffers->addValue(tr("768"), 768, buffersMenu); buffers->addValue(tr("1024"), 1024, buffersMenu);@@ -636,12 +874,15 @@ m_config->updateOption("audioBuffers");
avMenu->addSeparator(); - QMenu* target = avMenu->addMenu("FPS target"); + QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); - fpsTargetOption->connect([this](const QVariant& value) { emit fpsTargetChanged(value.toInt()); }); + fpsTargetOption->connect([this](const QVariant& value) { + emit fpsTargetChanged(value.toFloat()); + }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target); fpsTargetOption->addValue(tr("45"), 45, target); + fpsTargetOption->addValue(tr("Native (59.7)"), float(GBA_ARM7TDMI_FREQUENCY) / float(VIDEO_TOTAL_LENGTH), target); fpsTargetOption->addValue(tr("60"), 60, target); fpsTargetOption->addValue(tr("90"), 90, target); fpsTargetOption->addValue(tr("120"), 120, target);@@ -655,7 +896,7 @@
#ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot())); + connect(screenshot, SIGNAL(triggered()), m_controller, SLOT(screenshot())); m_gameActions.append(screenshot); addControlledAction(avMenu, screenshot, "screenshot"); #endif@@ -674,6 +915,50 @@ connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow()));
addControlledAction(avMenu, recordGIF, "recordGIF"); #endif + avMenu->addSeparator(); + QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); + + for (int i = 0; i < 4; ++i) { + QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); + enableBg->setCheckable(true); + enableBg->setChecked(true); + connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; }); + m_gameActions.append(enableBg); + addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); + } + + QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); + enableObj->setCheckable(true); + enableObj->setChecked(true); + connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; }); + m_gameActions.append(enableObj); + addControlledAction(videoLayers, enableObj, "enableOBJ"); + + QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); + + for (int i = 0; i < 4; ++i) { + QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); + enableCh->setCheckable(true); + enableCh->setChecked(true); + connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; }); + m_gameActions.append(enableCh); + addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1)); + } + + QAction* enableChA = new QAction(tr("Channel A"), audioChannels); + enableChA->setCheckable(true); + enableChA->setChecked(true); + connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; }); + m_gameActions.append(enableChA); + addControlledAction(audioChannels, enableChA, QString("enableChA")); + + QAction* enableChB = new QAction(tr("Channel B"), audioChannels); + enableChB->setCheckable(true); + enableChB->setChecked(true); + connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; }); + m_gameActions.append(enableChB); + addControlledAction(audioChannels, enableChB, QString("enableChB")); + QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); m_shortcutController->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu);@@ -699,23 +984,6 @@ addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
#endif toolsMenu->addSeparator(); - QAction* solarIncrease = new QAction(tr("Increase solar level"), toolsMenu); - connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); - addControlledAction(toolsMenu, solarIncrease, "increaseLuminanceLevel"); - - QAction* solarDecrease = new QAction(tr("Decrease solar level"), toolsMenu); - connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); - addControlledAction(toolsMenu, solarDecrease, "decreaseLuminanceLevel"); - - QAction* maxSolar = new QAction(tr("Brightest solar level"), toolsMenu); - connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); - addControlledAction(toolsMenu, maxSolar, "maxLuminanceLevel"); - - QAction* minSolar = new QAction(tr("Darkest solar level"), toolsMenu); - connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); - addControlledAction(toolsMenu, minSolar, "minLuminanceLevel"); - - toolsMenu->addSeparator(); addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts");@@ -729,25 +997,57 @@ connect(gamepad, SIGNAL(triggered()), this, SLOT(openGamepadWindow()));
addControlledAction(toolsMenu, gamepad, "remapGamepad"); #endif + toolsMenu->addSeparator(); + + QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); + connect(paletteView, SIGNAL(triggered()), this, SLOT(openPaletteWindow())); + m_gameActions.append(paletteView); + addControlledAction(toolsMenu, paletteView, "paletteWindow"); + + QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); + connect(memoryView, SIGNAL(triggered()), this, SLOT(openMemoryWindow())); + m_gameActions.append(memoryView); + addControlledAction(toolsMenu, memoryView, "memoryView"); + ConfigOption* skipBios = m_config->addOption("skipBios"); - skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); }); + skipBios->connect([this](const QVariant& value) { + m_controller->setSkipBIOS(value.toBool()); + }, this); + + ConfigOption* volume = m_config->addOption("volume"); + volume->connect([this](const QVariant& value) { + m_controller->setVolume(value.toInt()); + }, this); + + ConfigOption* mute = m_config->addOption("mute"); + mute->connect([this](const QVariant& value) { + m_controller->setMute(value.toBool()); + }, this); ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); - rewindEnable->connect([this](const QVariant& value) { m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + rewindEnable->connect([this](const QVariant& value) { + m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, this); ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity"); - rewindBufferCapacity->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + rewindBufferCapacity->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, this); ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); - rewindBufferInterval->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); }); + rewindBufferInterval->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); + }, this); - QMenu* other = new QMenu(tr("Other"), this); - m_shortcutController->addMenu(other); - m_shortcutController->addFunctions(other, [this]() { - m_controller->setTurbo(true, false); - }, [this]() { - m_controller->setTurbo(false, false); - }, QKeySequence(Qt::Key_Tab), tr("Fast Forward (held)"), "holdFastForward"); + ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); + allowOpposingDirections->connect([this](const QVariant& value) { + m_inputController.setAllowOpposing(value.toBool()); + }, this); + + QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); + connect(exitFullScreen, SIGNAL(triggered()), this, SLOT(exitFullScreen())); + exitFullScreen->setShortcut(QKeySequence("Esc")); + addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); foreach (QAction* action, m_gameActions) { action->setDisabled(true);@@ -794,8 +1094,14 @@ m_mruMenu->setEnabled(i > 0);
} QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) { - m_shortcutController->addAction(menu, action, name); + addHiddenAction(menu, action, name); menu->addAction(action); + return action; +} + +QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) { + m_shortcutController->addAction(menu, action, name); + action->setShortcutContext(Qt::WidgetShortcut); addAction(action); return action; }@@ -822,13 +1128,13 @@ m_aspectHeight = height;
} void WindowBackground::paintEvent(QPaintEvent*) { - QPainter painter(this); - painter.setRenderHint(QPainter::SmoothPixmapTransform); const QPixmap* logo = pixmap(); - painter.fillRect(QRect(QPoint(), size()), Qt::black); if (!logo) { return; } + QPainter painter(this); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + painter.fillRect(QRect(QPoint(), size()), Qt::black); QSize s = size(); QSize ds = s; if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) {
@@ -18,7 +18,6 @@ #include "gba/gba.h"
} #include "GDBController.h" -#include "Display.h" #include "InputController.h" #include "LoadSaveState.h"@@ -28,6 +27,7 @@
namespace QGBA { class ConfigController; +class Display; class GameController; class GIFView; class LogView;@@ -39,7 +39,7 @@ class Window : public QMainWindow {
Q_OBJECT public: - Window(ConfigController* config, QWidget* parent = nullptr); + Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr); virtual ~Window(); GameController* controller() { return m_controller; }@@ -50,7 +50,7 @@
void resizeFrame(int width, int height); signals: - void startDrawing(const uint32_t*, GBAThread*); + void startDrawing(GBAThread*); void shutdown(); void audioBufferSamplesChanged(int samples); void fpsTargetChanged(float target);@@ -59,9 +59,14 @@ public slots:
void selectROM(); void selectBIOS(); void selectPatch(); + void enterFullScreen(); + void exitFullScreen(); void toggleFullScreen(); void loadConfig(); void saveConfig(); + + void importSharkport(); + void exportSharkport(); void openKeymapWindow(); void openSettingsWindow();@@ -71,6 +76,9 @@ void openOverrideWindow();
void openSensorWindow(); void openCheatsWindow(); + void openPaletteWindow(); + void openMemoryWindow(); + #ifdef BUILD_SDL void openGamepadWindow(); #endif@@ -95,12 +103,14 @@ virtual void closeEvent(QCloseEvent*) override;
virtual void focusOutEvent(QFocusEvent*) override; virtual void dragEnterEvent(QDragEnterEvent*) override; virtual void dropEvent(QDropEvent*) override; + virtual void mouseDoubleClickEvent(QMouseEvent*) override; private slots: void gameStarted(GBAThread*); void gameStopped(); void gameCrashed(const QString&); void gameFailed(); + void unimplementedBiosCall(int); void recordFrame(); void showFPS();@@ -117,8 +127,13 @@ void detachWidget(QWidget* widget);
void appendMRU(const QString& fname); void updateMRU(); + + void openView(QWidget* widget); QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); + QAction* addHiddenAction(QMenu* menu, QAction* action, const QString& name); + + void updateTitle(float fps = NAN); GameController* m_controller; Display* m_display;@@ -134,6 +149,9 @@ QTimer m_fpsTimer;
QList<QString> m_mruFiles; QMenu* m_mruMenu; ShortcutController* m_shortcutController; + int m_playerId; + + bool m_hitUnimplementedBiosCall; #ifdef USE_FFMPEG VideoView* m_videoView;
@@ -6,6 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GBAApp.h" #include "Window.h" +#ifdef QT_STATIC +#include <QtPlugin> +#ifdef _WIN32 +Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin); +Q_IMPORT_PLUGIN (QWindowsAudioPlugin); +#endif +#endif + int main(int argc, char* argv[]) { QGBA::GBAApp application(argc, argv); return application.exec();
@@ -1,13 +1,12 @@
set(SDL_VERSION "2" CACHE STRING "Version of SDL to use (1.2 or 2)") -set(BUILD_GL ON CACHE STRING "Build with OpenGL") if (SDL_VERSION EQUAL "2") include(FindPkgConfig) pkg_search_module(SDL2 sdl2) if (SDL2_FOUND) - set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS} CACHE INTERNAL "") - set(SDL_LIBRARY ${SDL2_LIBRARIES} CACHE INTERNAL "") - set(SDLMAIN_LIBRARY "" CACHE INTERNAL "") + set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS}) + set(SDL_LIBRARY ${SDL2_LIBRARIES}) + set(SDLMAIN_LIBRARY "") link_directories(${SDL2_LIBDIR}) set(SDL_VERSION_DEBIAN "2-2.0-0") endif()@@ -17,6 +16,7 @@ if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND)
find_package(SDL 1.2) set(SDL_VERSION "1.2" PARENT_SCOPE) set(SDL_VERSION_DEBIAN "1.2debian") + set(USE_PIXMAN ON) endif() if (NOT SDL2_FOUND AND NOT SDL_FOUND)@@ -24,11 +24,28 @@ set(BUILD_SDL OFF PARENT_SCOPE)
return() endif() +find_feature(USE_PIXMAN "pixman-1") +if(USE_PIXMAN) + add_definitions(-DUSE_PIXMAN) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpixman-1.0" PARENT_SCOPE) +endif() + +if(WIN32) + list(APPEND SDL_LIBRARY imm32 version winmm) +elseif(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework AppKit -framework AudioUnit -framework Carbon -framework CoreAudio -framework ForceFeedback -framework IOKit") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE) +endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VERSION_DEBIAN}" PARENT_SCOPE) file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) -set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY}) -include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR}) +set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${PIXMAN-1_LIBRARIES}) +include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIR} ${SDL_INCLUDE_DIR}) + +set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE) +set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE) +set(SDLMAIN_LIBRARY "${SDLMAIN_LIBRARY}" PARENT_SCOPE) set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c)@@ -38,22 +55,24 @@ set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c)
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC}) - target_compile_definitions(${BINARY_NAME}-rpi PRIVATE ${FEATURE_DEFINES}) + set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY}) install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) endif() -if(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL) +if(BUILD_PANDORA) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c) +else() list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) -else() - list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) - add_definitions(-DBUILD_GL) - find_package(OpenGL REQUIRED) - include_directories(${OPENGL_INCLUDE_DIR}) + if(BUILD_GL) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) + include_directories(${OPENGL_INCLUDE_DIR}) + endif() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) -target_compile_definitions(${BINARY_NAME}-sdl PRIVATE ${FEATURE_DEFINES}) +set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
@@ -136,7 +136,7 @@ SDL_Event event;
while (context->state < THREAD_EXITING) { while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &renderer->events, &event); + GBASDLHandleEvent(context, &renderer->player, &event); } if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
@@ -6,30 +6,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "main.h" #include "gba/supervisor/thread.h" +#include "platform/opengl/gl.h" -#ifdef __APPLE__ -#include <OpenGL/gl.h> +static void _sdlSwap(struct VideoBackend* context) { + struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_SwapWindow(renderer->window); #else -#include <GL/gl.h> + SDL_GL_SwapBuffers(); #endif +} + +static void _doViewport(int w, int h, struct VideoBackend* v) { + v->resized(v, w, h); + v->clear(v); + v->swap(v); + v->clear(v); +} -#ifdef BUILD_GL -static const GLint _glVertices[] = { - 0, 0, - 256, 0, - 256, 256, - 0, 256 -}; +static bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer); -static const GLint _glTexCoords[] = { - 0, 0, - 1, 0, - 1, 1, - 0, 1 -}; -#endif +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLGLInit; + renderer->deinit = GBASDLGLDeinit; + renderer->runloop = GBASDLGLRunloop; +} -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { +bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { #ifndef COLOR_16_BIT SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);@@ -45,11 +50,11 @@ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
#endif #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->events.fullscreen)); + renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GL_CreateContext(renderer->window); SDL_GL_SetSwapInterval(1); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); - renderer->events.window = renderer->window; + renderer->player.window = renderer->window; #else SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); #ifdef COLOR_16_BIT@@ -59,88 +64,49 @@ SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
#endif #endif - renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); - renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; - glGenTextures(1, &renderer->tex); - glBindTexture(GL_TEXTURE_2D, renderer->tex); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - if (renderer->filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif - -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL); + renderer->d.outputBufferStride = 256; - - glViewport(0, 0, renderer->viewportWidth, renderer->viewportHeight); + GBAGLContextCreate(&renderer->gl); + renderer->gl.d.user = renderer; + renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; + renderer->gl.d.filter = renderer->filter; + renderer->gl.d.swap = _sdlSwap; + renderer->gl.d.init(&renderer->gl.d, 0); + _doViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); return true; } -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; + struct VideoBackend* v = &renderer->gl.d; - glEnable(GL_TEXTURE_2D); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode (GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1); while (context->state < THREAD_EXITING) { while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &renderer->events, &event); + GBASDLHandleEvent(context, &renderer->player, &event); #if SDL_VERSION_ATLEAST(2, 0, 0) // Event handling can change the size of the screen - if (renderer->events.windowUpdated) { + if (renderer->player.windowUpdated) { SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); - glViewport(0, 0, renderer->viewportWidth, renderer->viewportHeight); - renderer->events.windowUpdated = 0; + _doViewport(renderer->viewportWidth, renderer->viewportHeight, v); + renderer->player.windowUpdated = 0; } #endif } if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { - glBindTexture(GL_TEXTURE_2D, renderer->tex); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, renderer->d.outputBuffer); -#else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, renderer->d.outputBuffer); -#endif -#else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer); -#endif - if (context->sync.videoFrameWait) { - glFlush(); - } + v->postFrame(v, renderer->d.outputBuffer); } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + v->drawFrame(v); GBASyncWaitFrameEnd(&context->sync); -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GL_SwapWindow(renderer->window); -#else - SDL_GL_SwapBuffers(); -#endif + v->swap(v); } } -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { - UNUSED(renderer); +void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer) { + if (renderer->gl.d.deinit) { + renderer->gl.d.deinit(&renderer->gl.d); + } + free(renderer->d.outputBuffer); }
@@ -28,8 +28,8 @@ #include <sys/time.h>
#define PORT "sdl" -static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer); -static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer); +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); int main(int argc, char** argv) { struct SDLSoftwareRenderer renderer;@@ -45,6 +45,7 @@
struct GBAOptions opts = { .width = VIDEO_HORIZONTAL_PIXELS, .height = VIDEO_VERTICAL_PIXELS, + .useBios = true, .rewindEnable = true, .audioBuffers = 512, .videoSync = false,@@ -58,12 +59,13 @@
struct SubParser subparser; initParserForGraphics(&subparser, &graphicsOpts); - if (!parseArguments(&args, &config, argc, argv, &subparser)) { + bool parsed = parseArguments(&args, &config, argc, argv, &subparser); + if (!parsed || args.showHelp) { usage(argv[0], subparser.usage); freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config); - return 1; + return !parsed; } GBAConfigMap(&config, &opts);@@ -71,15 +73,24 @@
renderer.viewportWidth = opts.width; renderer.viewportHeight = opts.height; #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer.events.fullscreen = opts.fullscreen; - renderer.events.windowUpdated = 0; + renderer.player.fullscreen = opts.fullscreen; + renderer.player.windowUpdated = 0; #endif renderer.ratio = graphicsOpts.multiplier; + if (renderer.ratio == 0) { + renderer.ratio = 1; + } renderer.lockAspectRatio = opts.lockAspectRatio; renderer.filter = opts.resampleVideo; - if (!_GBASDLInit(&renderer)) { +#ifdef BUILD_GL + GBASDLGLCreate(&renderer); +#else + GBASDLSWCreate(&renderer); +#endif + + if (!GBASDLInit(&renderer)) { freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config);@@ -96,58 +107,73 @@
GBAMapOptionsToContext(&opts, &context); GBAMapArgumentsToContext(&args, &context); + bool didFail = false; + renderer.audio.samples = context.audioBuffers; - GBASDLInitAudio(&renderer.audio, &context); + if (!GBASDLInitAudio(&renderer.audio, &context)) { + didFail = true; + } - renderer.events.bindings = &inputMap; + renderer.player.bindings = &inputMap; GBASDLInitBindings(&inputMap); GBASDLInitEvents(&renderer.events); GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config)); + GBASDLAttachPlayer(&renderer.events, &renderer.player); + GBASDLPlayerLoadConfig(&renderer.player, GBAConfigGetInput(&config)); context.overrides = GBAConfigGetOverrides(&config); - int didFail = 0; - if (GBAThreadStart(&context)) { - GBASDLRunloop(&context, &renderer); - GBAThreadJoin(&context); - } else { - didFail = 1; - printf("Could not run game. Are you sure the file exists and is a Game Boy Advance game?\n"); - } + if (!didFail) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + GBASDLSetScreensaverSuspendable(&renderer.events, opts.suspendScreensaver); + GBASDLSuspendScreensaver(&renderer.events); +#endif + if (GBAThreadStart(&context)) { + renderer.runloop(&context, &renderer); + GBAThreadJoin(&context); + } else { + didFail = true; + printf("Could not run game. Are you sure the file exists and is a Game Boy Advance game?\n"); + } - if (GBAThreadHasCrashed(&context)) { - didFail = 1; - printf("The game crashed!\n"); +#if SDL_VERSION_ATLEAST(2, 0, 0) + GBASDLResumeScreensaver(&renderer.events); + GBASDLSetScreensaverSuspendable(&renderer.events, false); +#endif + + if (GBAThreadHasCrashed(&context)) { + didFail = true; + printf("The game crashed!\n"); + } } freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config); free(context.debugger); + GBASDLDetachPlayer(&renderer.events, &renderer.player); GBAInputMapDeinit(&inputMap); - _GBASDLDeinit(&renderer); + GBASDLDeinit(&renderer); return didFail; } -static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("Could not initialize video: %s\n", SDL_GetError()); return false; } - return GBASDLInit(renderer); + return renderer->init(renderer); } -static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { - free(renderer->d.outputBuffer); - +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { GBASDLDeinitEvents(&renderer->events); GBASDLDeinitAudio(&renderer->audio); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_DestroyWindow(renderer->window); #endif - GBASDLDeinit(renderer); + renderer->deinit(renderer); SDL_Quit();
@@ -12,11 +12,7 @@ #include "sdl-audio.h"
#include "sdl-events.h" #ifdef BUILD_GL -#ifdef __APPLE__ -#include <OpenGL/gl.h> -#else -#include <GL/gl.h> -#endif +#include "platform/opengl/gl.h" #endif #ifdef BUILD_RASPI@@ -29,19 +25,26 @@ #include <EGL/egl.h>
#include <bcm_host.h> #pragma GCC diagnostic pop +#endif + +#ifdef USE_PIXMAN +#include <pixman.h> #endif struct SDLSoftwareRenderer { struct GBAVideoSoftwareRenderer d; struct GBASDLAudio audio; struct GBASDLEvents events; + struct GBASDLPlayer player; + + bool (*init)(struct SDLSoftwareRenderer* renderer); + void (*runloop)(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); + void (*deinit)(struct SDLSoftwareRenderer* renderer); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; -#ifndef BUILD_GL - SDL_Texture* tex; + SDL_Texture* sdlTex; SDL_Renderer* sdlRenderer; -#endif #endif int viewportWidth;@@ -52,7 +55,12 @@ bool lockAspectRatio;
bool filter; #ifdef BUILD_GL - GLuint tex; + struct GBAGLContext gl; +#endif + +#ifdef USE_PIXMAN + pixman_image_t* pix; + pixman_image_t* screenpix; #endif #ifdef BUILD_RASPI@@ -68,11 +76,17 @@ GLuint bufferObject;
GLuint texLocation; GLuint positionLocation; #endif + +#ifdef BUILD_PANDORA + int fb; + int odd; + void* base[2]; +#endif }; -bool GBASDLInit(struct SDLSoftwareRenderer* renderer); -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer); +#ifdef BUILD_GL +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); #endif - +#endif
@@ -0,0 +1,109 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "main.h" + +#include "gba/supervisor/thread.h" + +#include <linux/omapfb.h> +#include <linux/fb.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { + SDL_SetVideoMode(800, 480, 16, SDL_FULLSCREEN); + + renderer->odd = 0; + renderer->fb = open("/dev/fb1", O_RDWR); + if (renderer->fb < 0) { + return false; + } + + struct omapfb_plane_info plane; + struct omapfb_mem_info mem; + if (ioctl(renderer->fb, OMAPFB_QUERY_PLANE, &plane) < 0) { + return false; + } + if (ioctl(renderer->fb, OMAPFB_QUERY_MEM, &mem) < 0) { + return false; + } + + if (plane.enabled) { + plane.enabled = 0; + ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane); + } + + mem.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4; + ioctl(renderer->fb, OMAPFB_SETUP_MEM, &mem); + + plane.enabled = 1; + plane.pos_x = 40; + plane.pos_y = 0; + plane.out_width = 720; + plane.out_height = 480; + ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane); + + struct fb_var_screeninfo info; + ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info); + info.xres = VIDEO_HORIZONTAL_PIXELS; + info.yres = VIDEO_VERTICAL_PIXELS; + info.xres_virtual = VIDEO_HORIZONTAL_PIXELS; + info.yres_virtual = VIDEO_VERTICAL_PIXELS * 2; + info.bits_per_pixel = 16; + ioctl(renderer->fb, FBIOPUT_VSCREENINFO, &info); + + renderer->odd = 0; + renderer->base[0] = mmap(0, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4, PROT_READ | PROT_WRITE, MAP_SHARED, renderer->fb, 0); + renderer->base[1] = (uint16_t*) renderer->base[0] + VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS; + + renderer->d.outputBuffer = renderer->base[0]; + renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; + return true; +} + +void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { + SDL_Event event; + + while (context->state < THREAD_EXITING) { + while (SDL_PollEvent(&event)) { + GBASDLHandleEvent(context, &renderer->player, &event); + } + + if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + int arg = 0; + ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); + + struct fb_var_screeninfo info; + ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info); + info.yoffset = VIDEO_VERTICAL_PIXELS * renderer->odd; + ioctl(renderer->fb, FBIOPAN_DISPLAY, &info); + + renderer->odd = !renderer->odd; + renderer->d.outputBuffer = renderer->base[renderer->odd]; + } + GBASyncWaitFrameEnd(&context->sync); + } +} + +void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { + munmap(renderer->base[0], VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); + + struct omapfb_plane_info plane; + struct omapfb_mem_info mem; + ioctl(renderer->fb, OMAPFB_QUERY_PLANE, &plane); + ioctl(renderer->fb, OMAPFB_QUERY_MEM, &mem); + + mem.size = 0; + ioctl(renderer->fb, OMAPFB_SETUP_MEM, &mem); + + plane.enabled = 0; + plane.pos_x = 0; + plane.pos_y = 0; + plane.out_width = 0; + plane.out_height = 0; + ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane); + + close(renderer->fb); +}
@@ -18,7 +18,7 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len);
bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - GBALog(0, GBA_LOG_ERROR, "Could not initialize SDL sound system"); + GBALog(0, GBA_LOG_ERROR, "Could not initialize SDL sound system: %s", SDL_GetError()); return false; }@@ -31,7 +31,13 @@ context->desiredSpec.userdata = context;
#if RESAMPLE_LIBRARY == RESAMPLE_NN context->drift = 0.f; #endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + context->deviceId = SDL_OpenAudioDevice(0, 0, &context->desiredSpec, &context->obtainedSpec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (context->deviceId == 0) { +#else if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) { +#endif GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; }@@ -43,25 +49,43 @@ if (context->samples > threadContext->audioBuffers) {
threadContext->audioBuffers = context->samples * 2; } +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_PauseAudioDevice(context->deviceId, 0); +#else SDL_PauseAudio(0); +#endif return true; } void GBASDLDeinitAudio(struct GBASDLAudio* context) { UNUSED(context); +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_PauseAudioDevice(context->deviceId, 1); + SDL_CloseAudioDevice(context->deviceId); +#else SDL_PauseAudio(1); SDL_CloseAudio(); +#endif SDL_QuitSubSystem(SDL_INIT_AUDIO); } void GBASDLPauseAudio(struct GBASDLAudio* context) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_PauseAudioDevice(context->deviceId, 1); +#else UNUSED(context); SDL_PauseAudio(1); +#endif + } void GBASDLResumeAudio(struct GBASDLAudio* context) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_PauseAudioDevice(context->deviceId, 0); +#else UNUSED(context); SDL_PauseAudio(0); +#endif } static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
@@ -25,6 +25,9 @@ #endif
#if RESAMPLE_LIBRARY == RESAMPLE_NN float drift; #endif +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_AudioDeviceID deviceId; +#endif struct GBAThread* thread; };
@@ -11,6 +11,8 @@ #include "gba/supervisor/rr.h"
#include "gba/serialize.h" #include "gba/video.h" #include "gba/renderers/video-software.h" +#include "util/configuration.h" +#include "util/formatting.h" #include "util/vfs.h" #if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)@@ -19,23 +21,103 @@ #else
#define GUI_MOD KMOD_CTRL #endif -static int _openContexts = 0; +#define GYRO_STEPS 100 +#define RUMBLE_PWM 35 + +#if SDL_VERSION_ATLEAST(2, 0, 0) +static void _GBASDLSetRumble(struct GBARumble* rumble, int enable); +#endif +static int32_t _GBASDLReadTiltX(struct GBARotationSource* rumble); +static int32_t _GBASDLReadTiltY(struct GBARotationSource* rumble); +static int32_t _GBASDLReadGyroZ(struct GBARotationSource* rumble); +static void _GBASDLRotationSample(struct GBARotationSource* source); bool GBASDLInitEvents(struct GBASDLEvents* context) { - if (!_openContexts && SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { - return false; +#if SDL_VERSION_ATLEAST(2, 0, 4) + SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); +#endif + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { + GBALog(0, GBA_LOG_ERROR, "SDL joystick initialization failed: %s", SDL_GetError()); } - ++_openContexts; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) { + GBALog(0, GBA_LOG_ERROR, "SDL haptic initialization failed: %s", SDL_GetError()); + } + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { + GBALog(0, GBA_LOG_ERROR, "SDL video initialization failed: %s", SDL_GetError()); + } +#endif + SDL_JoystickEventState(SDL_ENABLE); - context->joystick = SDL_JoystickOpen(0); + int nJoysticks = SDL_NumJoysticks(); + if (nJoysticks > 0) { + context->nJoysticks = nJoysticks; + context->joysticks = calloc(context->nJoysticks, sizeof(SDL_Joystick*)); +#if SDL_VERSION_ATLEAST(2, 0, 0) + context->haptic = calloc(context->nJoysticks, sizeof(SDL_Haptic*)); +#endif + size_t i; + for (i = 0; i < context->nJoysticks; ++i) { + context->joysticks[i] = SDL_JoystickOpen(i); +#if SDL_VERSION_ATLEAST(2, 0, 0) + context->haptic[i] = SDL_HapticOpenFromJoystick(context->joysticks[i]); +#endif + } + } else { + context->nJoysticks = 0; + context->joysticks = 0; + } + + context->playersAttached = 0; + + size_t i; + for (i = 0; i < MAX_PLAYERS; ++i) { + context->preferredJoysticks[i] = 0; + context->joysticksClaimed[i] = SIZE_MAX; + } + #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +#else + context->screensaverSuspendDepth = 0; #endif return true; } -void GBASDLInitBindings(struct GBAInputMap* inputMap) { +void GBASDLDeinitEvents(struct GBASDLEvents* context) { + size_t i; + for (i = 0; i < context->nJoysticks; ++i) { #if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_HapticClose(context->haptic[i]); +#endif + SDL_JoystickClose(context->joysticks[i]); + } + + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + +void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) { + context->preferredJoysticks[0] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 0); + context->preferredJoysticks[1] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 1); + context->preferredJoysticks[2] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 2); + context->preferredJoysticks[3] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 3); +} + +void GBASDLInitBindings(struct GBAInputMap* inputMap) { +#ifdef BUILD_PANDORA + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_PAGEDOWN, GBA_KEY_A); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_END, GBA_KEY_B); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RSHIFT, GBA_KEY_L); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RCTRL, GBA_KEY_R); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LALT, GBA_KEY_START); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LCTRL, GBA_KEY_SELECT); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT); + GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT); +#elif SDL_VERSION_ATLEAST(2, 0, 0) GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_X, GBA_KEY_A); GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_Z, GBA_KEY_B); GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_A, GBA_KEY_L);@@ -76,18 +158,177 @@ description = (struct GBAAxis) { GBA_KEY_DOWN, GBA_KEY_UP, 0x4000, -0x4000 };
GBAInputBindAxis(inputMap, SDL_BINDING_BUTTON, 1, &description); } -void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) { +bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player) { + player->joystick = 0; + player->joystickIndex = SIZE_MAX; + + if (events->playersAttached >= MAX_PLAYERS) { + return false; + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + player->rumble.d.setRumble = _GBASDLSetRumble; + CircleBufferInit(&player->rumble.history, RUMBLE_PWM); + player->rumble.level = 0; + player->rumble.p = player; +#endif + + player->rotation.d.readTiltX = _GBASDLReadTiltX; + player->rotation.d.readTiltY = _GBASDLReadTiltY; + player->rotation.d.readGyroZ = _GBASDLReadGyroZ; + player->rotation.d.sample = _GBASDLRotationSample; + player->rotation.axisX = 2; + player->rotation.axisY = 3; + player->rotation.gyroSensitivity = 2.2e9f; + player->rotation.gyroX = 0; + player->rotation.gyroY = 1; + player->rotation.zDelta = 0; + CircleBufferInit(&player->rotation.zHistory, sizeof(float) * GYRO_STEPS); + player->rotation.p = player; + + player->playerId = events->playersAttached; + size_t firstUnclaimed = SIZE_MAX; + + size_t i; + for (i = 0; i < events->nJoysticks; ++i) { + bool claimed = false; + + int p; + for (p = 0; p < events->playersAttached; ++p) { + if (events->joysticksClaimed[p] == i) { + claimed = true; + break; + } + } + if (claimed) { + continue; + } + + if (firstUnclaimed == SIZE_MAX) { + firstUnclaimed = i; + } + + const char* joystickName; +#if SDL_VERSION_ATLEAST(2, 0, 0) + joystickName = SDL_JoystickName(events->joysticks[i]); +#else + joystickName = SDL_JoystickName(SDL_JoystickIndex(events->joysticks[i])); +#endif + if (events->preferredJoysticks[player->playerId] && strcmp(events->preferredJoysticks[player->playerId], joystickName) == 0) { + player->joystickIndex = i; + break; + } + } + + if (player->joystickIndex == SIZE_MAX && firstUnclaimed != SIZE_MAX) { + player->joystickIndex = firstUnclaimed; + } + + if (player->joystickIndex != SIZE_MAX) { + player->joystick = events->joysticks[player->joystickIndex]; + events->joysticksClaimed[player->playerId] = player->joystickIndex; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + player->haptic = events->haptic[player->joystickIndex]; + if (player->haptic) { + SDL_HapticRumbleInit(player->haptic); + } +#endif + } + + ++events->playersAttached; + return true; +} + +void GBASDLDetachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player) { + events->joysticksClaimed[player->playerId] = SIZE_MAX; + CircleBufferDeinit(&player->rotation.zHistory); +} + +void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configuration* config) { GBAInputMapLoad(context->bindings, SDL_BINDING_KEY, config); - GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config); + if (context->joystick) { + GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config); +#if SDL_VERSION_ATLEAST(2, 0, 0) + const char* name = SDL_JoystickName(context->joystick); +#else + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); +#endif + GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, name); + + const char* value; + char* end; + int numAxes = SDL_JoystickNumAxes(context->joystick); + int axis; + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisX", name); + if (value) { + axis = strtol(value, &end, 0); + if (axis >= 0 && axis < numAxes && end && !*end) { + context->rotation.axisX = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisY", name); + if (value) { + axis = strtol(value, &end, 0); + if (axis >= 0 && axis < numAxes && end && !*end) { + context->rotation.axisY = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisX", name); + if (value) { + axis = strtol(value, &end, 0); + if (axis >= 0 && axis < numAxes && end && !*end) { + context->rotation.gyroX = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisY", name); + if (value) { + axis = strtol(value, &end, 0); + if (axis >= 0 && axis < numAxes && end && !*end) { + context->rotation.gyroY = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroSensitivity", name); + if (value) { + float sensitivity = strtof_u(value, &end); + if (end && !*end) { + context->rotation.gyroSensitivity = sensitivity; + } + } + } } -void GBASDLDeinitEvents(struct GBASDLEvents* context) { - SDL_JoystickClose(context->joystick); +void GBASDLPlayerSaveConfig(const struct GBASDLPlayer* context, struct Configuration* config) { + if (context->joystick) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + const char* name = SDL_JoystickName(context->joystick); +#else + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); +#endif + char value[12]; + snprintf(value, sizeof(value), "%i", context->rotation.axisX); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisX", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.axisY); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisY", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.gyroX); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisX", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.gyroY); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisY", value, name); + snprintf(value, sizeof(value), "%g", context->rotation.gyroSensitivity); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroSensitivity", value, name); + } +} - --_openContexts; - if (!_openContexts) { - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +void GBASDLPlayerChangeJoystick(struct GBASDLEvents* events, struct GBASDLPlayer* player, size_t index) { + if (player->playerId >= MAX_PLAYERS || index >= events->nJoysticks) { + return; } + events->joysticksClaimed[player->playerId] = index; + player->joystickIndex = index; + player->joystick = events->joysticks[index]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + player->haptic = events->haptic[index]; +#endif } static void _pauseAfterFrame(struct GBAThread* context) {@@ -95,10 +336,10 @@ context->frameCallback = 0;
GBAThreadPauseFromThread(context); } -static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_KeyboardEvent* event) { +static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_KeyboardEvent* event) { enum GBAKey key = GBA_KEY_NONE; if (!event->keysym.mod) { -#if SDL_VERSION_ATLEAST(2, 0, 0) +#if !defined(BUILD_PANDORA) && SDL_VERSION_ATLEAST(2, 0, 0) key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.scancode); #else key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym);@@ -140,6 +381,11 @@ GBAThreadInterrupt(context);
GBARewind(context, 10); GBAThreadContinue(context); return; +#ifdef BUILD_PANDORA + case SDLK_ESCAPE: + GBAThreadEnd(context); + return; +#endif default: if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) { switch (event->keysym.sym) {@@ -207,7 +453,7 @@ }
} } -static void _GBASDLHandleJoyButton(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_JoyButtonEvent* event) { +static void _GBASDLHandleJoyButton(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_JoyButtonEvent* event) { enum GBAKey key = 0; key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_BUTTON, event->button); if (key == GBA_KEY_NONE) {@@ -241,7 +487,7 @@ context->activeKeys &= ~((1 << GBA_KEY_UP) | (1 << GBA_KEY_LEFT) | (1 << GBA_KEY_DOWN) | (1 << GBA_KEY_RIGHT));
context->activeKeys |= key; } -static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_JoyAxisEvent* event) { +static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_JoyAxisEvent* event) { int keys = context->activeKeys; keys = GBAInputClearAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, keys);@@ -254,7 +500,7 @@ context->activeKeys = keys;
} #if SDL_VERSION_ATLEAST(2, 0, 0) -static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_WindowEvent* event) { +static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_WindowEvent* event) { UNUSED(context); switch (event->event) { case SDL_WINDOWEVENT_SIZE_CHANGED:@@ -264,7 +510,7 @@ }
} #endif -void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event) { +void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const union SDL_Event* event) { switch (event->type) { case SDL_QUIT: GBAThreadEnd(context);@@ -290,3 +536,102 @@ _GBASDLHandleJoyAxis(context, sdlContext, &event->jaxis);
break; } } + +#if SDL_VERSION_ATLEAST(2, 0, 0) +static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) { + struct GBASDLRumble* sdlRumble = (struct GBASDLRumble*) rumble; + if (!sdlRumble->p->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->haptic)) { + return; + } + sdlRumble->level += enable; + if (CircleBufferSize(&sdlRumble->history) == RUMBLE_PWM) { + int8_t oldLevel; + CircleBufferRead8(&sdlRumble->history, &oldLevel); + sdlRumble->level -= oldLevel; + } + CircleBufferWrite8(&sdlRumble->history, enable); + if (sdlRumble->level) { + SDL_HapticRumblePlay(sdlRumble->p->haptic, sdlRumble->level / (float) RUMBLE_PWM, 20); + } else { + SDL_HapticRumbleStop(sdlRumble->p->haptic); + } +} +#endif + +static int32_t _readTilt(struct GBASDLPlayer* player, int axis) { + return SDL_JoystickGetAxis(player->joystick, axis) * 0x3800; +} + +static int32_t _GBASDLReadTiltX(struct GBARotationSource* source) { + struct GBASDLRotation* rotation = (struct GBASDLRotation*) source; + return _readTilt(rotation->p, rotation->axisX); +} + +static int32_t _GBASDLReadTiltY(struct GBARotationSource* source) { + struct GBASDLRotation* rotation = (struct GBASDLRotation*) source; + return _readTilt(rotation->p, rotation->axisY); +} + +static int32_t _GBASDLReadGyroZ(struct GBARotationSource* source) { + struct GBASDLRotation* rotation = (struct GBASDLRotation*) source; + float z = rotation->zDelta; + return z * rotation->gyroSensitivity; +} + +static void _GBASDLRotationSample(struct GBARotationSource* source) { + struct GBASDLRotation* rotation = (struct GBASDLRotation*) source; + SDL_JoystickUpdate(); + + int x = SDL_JoystickGetAxis(rotation->p->joystick, rotation->gyroX); + int y = SDL_JoystickGetAxis(rotation->p->joystick, rotation->gyroY); + union { + float f; + int32_t i; + } theta = { .f = atan2f(y, x) - atan2f(rotation->oldY, rotation->oldX) }; + if (isnan(theta.f)) { + theta.f = 0.0f; + } else if (theta.f > M_PI) { + theta.f -= 2.0f * M_PI; + } else if (theta.f < -M_PI) { + theta.f += 2.0f * M_PI; + } + rotation->oldX = x; + rotation->oldY = y; + + float oldZ = 0; + if (CircleBufferSize(&rotation->zHistory) == GYRO_STEPS * sizeof(float)) { + CircleBufferRead32(&rotation->zHistory, (int32_t*) &oldZ); + } + CircleBufferWrite32(&rotation->zHistory, theta.i); + rotation->zDelta += theta.f - oldZ; +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +void GBASDLSuspendScreensaver(struct GBASDLEvents* events) { + if (events->screensaverSuspendDepth == 0 && events->screensaverSuspendable) { + SDL_DisableScreenSaver(); + } + ++events->screensaverSuspendDepth; +} + +void GBASDLResumeScreensaver(struct GBASDLEvents* events) { + --events->screensaverSuspendDepth; + if (events->screensaverSuspendDepth == 0 && events->screensaverSuspendable) { + SDL_EnableScreenSaver(); + } +} + +void GBASDLSetScreensaverSuspendable(struct GBASDLEvents* events, bool suspendable) { + bool wasSuspendable = events->screensaverSuspendable; + events->screensaverSuspendable = suspendable; + if (events->screensaverSuspendDepth > 0) { + if (suspendable && !wasSuspendable) { + SDL_DisableScreenSaver(); + } else if (!suspendable && wasSuspendable) { + SDL_EnableScreenSaver(); + } + } else { + SDL_EnableScreenSaver(); + } +} +#endif
@@ -7,33 +7,90 @@ #ifndef SDL_EVENTS_H
#define SDL_EVENTS_H #include "util/common.h" +#include "util/circle-buffer.h" #include "gba/supervisor/thread.h" #include <SDL.h> -#define SDL_BINDING_KEY 0x53444C4B -#define SDL_BINDING_BUTTON 0x53444C42 +#define SDL_BINDING_KEY 0x53444C4BU +#define SDL_BINDING_BUTTON 0x53444C42U + +#define MAX_PLAYERS 4 struct GBAVideoSoftwareRenderer; struct Configuration; struct GBASDLEvents { + SDL_Joystick** joysticks; + size_t nJoysticks; + const char* preferredJoysticks[MAX_PLAYERS]; + int playersAttached; + size_t joysticksClaimed[MAX_PLAYERS]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Haptic** haptic; + int screensaverSuspendDepth; + bool screensaverSuspendable; +#endif +}; + +struct GBASDLPlayer { + size_t playerId; struct GBAInputMap* bindings; SDL_Joystick* joystick; + size_t joystickIndex; #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; int fullscreen; int windowUpdated; + SDL_Haptic* haptic; + + struct GBASDLRumble { + struct GBARumble d; + struct GBASDLPlayer* p; + + int level; + struct CircleBuffer history; + } rumble; #endif + + struct GBASDLRotation { + struct GBARotationSource d; + struct GBASDLPlayer* p; + + // Tilt + int axisX; + int axisY; + + // Gyro + int gyroX; + int gyroY; + float gyroSensitivity; + struct CircleBuffer zHistory; + int oldX; + int oldY; + float zDelta; + } rotation; }; bool GBASDLInitEvents(struct GBASDLEvents*); void GBASDLDeinitEvents(struct GBASDLEvents*); -void GBASDLInitBindings(struct GBAInputMap* inputMap); +bool GBASDLAttachPlayer(struct GBASDLEvents*, struct GBASDLPlayer*); +void GBASDLDetachPlayer(struct GBASDLEvents*, struct GBASDLPlayer*); void GBASDLEventsLoadConfig(struct GBASDLEvents*, const struct Configuration*); +void GBASDLPlayerChangeJoystick(struct GBASDLEvents*, struct GBASDLPlayer*, size_t index); -void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event); +void GBASDLInitBindings(struct GBAInputMap* inputMap); +void GBASDLPlayerLoadConfig(struct GBASDLPlayer*, const struct Configuration*); +void GBASDLPlayerSaveConfig(const struct GBASDLPlayer*, struct Configuration*); + +void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const union SDL_Event* event); + +#if SDL_VERSION_ATLEAST(2, 0, 0) +void GBASDLSuspendScreensaver(struct GBASDLEvents*); +void GBASDLResumeScreensaver(struct GBASDLEvents*); +void GBASDLSetScreensaverSuspendable(struct GBASDLEvents*, bool suspendable); +#endif #endif
@@ -8,7 +8,17 @@
#include "gba/supervisor/thread.h" #include "util/arm-algo.h" -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLSWInit; + renderer->deinit = GBASDLSWDeinit; + renderer->runloop = GBASDLSWRunloop; +} + +bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer) { #if !SDL_VERSION_ATLEAST(2, 0, 0) #ifdef COLOR_16_BIT SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE);@@ -18,21 +28,21 @@ #endif
#endif #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->events.fullscreen)); + renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); - renderer->events.window = renderer->window; + renderer->player.window = renderer->window; renderer->sdlRenderer = SDL_CreateRenderer(renderer->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #else - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif #else - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif - SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else SDL_Surface* surface = SDL_GetVideoSurface();@@ -40,21 +50,39 @@ SDL_LockSurface(surface);
if (renderer->ratio == 1) { renderer->d.outputBuffer = surface->pixels; + renderer->d.outputBufferStride = surface->pitch / BYTES_PER_PIXEL; + } else { +#ifdef USE_PIXMAN + renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; #ifdef COLOR_16_BIT - renderer->d.outputBufferStride = surface->pitch / 2; +#ifdef COLOR_5_6_5 + pixman_format_code_t format = PIXMAN_r5g6b5; +#else + pixman_format_code_t format = PIXMAN_x1b5g5r5; +#endif #else - renderer->d.outputBufferStride = surface->pitch / 4; + pixman_format_code_t format = PIXMAN_x8b8g8r8; #endif - } else { - renderer->d.outputBuffer = malloc(240 * 160 * BYTES_PER_PIXEL); - renderer->d.outputBufferStride = 240; + renderer->pix = pixman_image_create_bits(format, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, + renderer->d.outputBuffer, renderer->d.outputBufferStride * BYTES_PER_PIXEL); + renderer->screenpix = pixman_image_create_bits(format, renderer->viewportWidth, renderer->viewportHeight, surface->pixels, surface->pitch); + + pixman_transform_t transform; + pixman_transform_init_identity(&transform); + pixman_transform_scale(0, &transform, pixman_int_to_fixed(renderer->ratio), pixman_int_to_fixed(renderer->ratio)); + pixman_image_set_transform(renderer->pix, &transform); + pixman_image_set_filter(renderer->pix, PIXMAN_FILTER_NEAREST, 0, 0); +#else + return false; +#endif } #endif return true; } -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_Surface* surface = SDL_GetVideoSurface();@@ -62,17 +90,24 @@ #endif
while (context->state < THREAD_EXITING) { while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &renderer->events, &event); + GBASDLHandleEvent(context, &renderer->player, &event); } if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_UnlockTexture(renderer->tex); - SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0); + SDL_UnlockTexture(renderer->sdlTex); + SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0); SDL_RenderPresent(renderer->sdlRenderer); - SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else +#ifdef USE_PIXMAN + if (renderer->ratio > 1) { + pixman_image_composite32(PIXMAN_OP_SRC, renderer->pix, 0, renderer->screenpix, + 0, 0, 0, 0, 0, 0, + renderer->viewportWidth, renderer->viewportHeight); + } +#else switch (renderer->ratio) { #if defined(__ARM_NEON) && COLOR_16_BIT case 2:@@ -87,6 +122,7 @@ break;
default: abort(); } +#endif SDL_UnlockSurface(surface); SDL_Flip(surface); SDL_LockSurface(surface);@@ -96,6 +132,16 @@ GBASyncWaitFrameEnd(&context->sync);
} } -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { - UNUSED(renderer); +void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer) { + if (renderer->ratio > 1) { + free(renderer->d.outputBuffer); + } +#if !SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Surface* surface = SDL_GetVideoSurface(); + SDL_UnlockSurface(surface); +#ifdef USE_PIXMAN + pixman_image_unref(renderer->pix); + pixman_image_unref(renderer->screenpix); +#endif +#endif }
@@ -0,0 +1,34 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef VIDEO_BACKEND_H +#define VIDEO_BACKEND_H + +#include "util/common.h" + +#ifdef _WIN32 +typedef HWND WHandle; +#else +typedef void* WHandle; +#endif + +struct VideoBackend { + void (*init)(struct VideoBackend*, WHandle handle); + void (*deinit)(struct VideoBackend*); + void (*swap)(struct VideoBackend*); + void (*clear)(struct VideoBackend*); + void (*resized)(struct VideoBackend*, int w, int h); + void (*postFrame)(struct VideoBackend*, const void* frame); + void (*drawFrame)(struct VideoBackend*); + void (*setMessage)(struct VideoBackend*, const char* message); + void (*clearMessage)(struct VideoBackend*); + + void* user; + + bool filter; + bool lockAspectRatio; +}; + +#endif
@@ -0,0 +1,84 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef WINDOWS_THREADING_H +#define WINDOWS_THREADING_H + +#include "util/common.h" + +#define _WIN32_WINNT 0x0600 +#include <windows.h> +#define THREAD_ENTRY DWORD WINAPI +typedef THREAD_ENTRY ThreadEntry(LPVOID); + +typedef HANDLE Thread; +typedef CRITICAL_SECTION Mutex; +typedef CONDITION_VARIABLE Condition; + +static inline int MutexInit(Mutex* mutex) { + InitializeCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexDeinit(Mutex* mutex) { + DeleteCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexLock(Mutex* mutex) { + EnterCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexUnlock(Mutex* mutex) { + LeaveCriticalSection(mutex); + return GetLastError(); +} + +static inline int ConditionInit(Condition* cond) { + InitializeConditionVariable(cond); + return GetLastError(); +} + +static inline int ConditionDeinit(Condition* cond) { + // This is a no-op on Windows + UNUSED(cond); + return 0; +} + +static inline int ConditionWait(Condition* cond, Mutex* mutex) { + SleepConditionVariableCS(cond, mutex, INFINITE); + return GetLastError(); +} + +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + SleepConditionVariableCS(cond, mutex, timeoutMs); + return GetLastError(); +} + +static inline int ConditionWake(Condition* cond) { + WakeAllConditionVariable(cond); + return GetLastError(); +} + +static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { + *thread = CreateThread(NULL, 0, entry, context, 0, 0); + return GetLastError(); +} + +static inline int ThreadJoin(Thread thread) { + DWORD error = WaitForSingleObject(thread, INFINITE); + if (error == WAIT_FAILED) { + return GetLastError(); + } + return 0; +} + +static inline int ThreadSetName(const char* name) { + UNUSED(name); + return -1; +} + +#endif
@@ -1,8 +1,9 @@
-# Copyright (c) 2013-2014 Jeffrey Pfau +# Copyright (c) 2013-2015 Jeffrey Pfau # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +#ifdef __ARM_NEON # r0: Destination # r1: Source # r2: Number of words to copy as halfwords@@ -13,28 +14,22 @@ mov r8, r0
mov r9, r1 mov r10, r2 .L0: -tst r10, #7 +tst r10, #15 beq .L1 ldr r0, [r9], #4 strh r0, [r8], #2 sub r10, #1 b .L0 .L1: -ldmia r9!, {r0-r7} -strh r0, [r8], #2 -strh r1, [r8], #2 -strh r2, [r8], #2 -strh r3, [r8], #2 -strh r4, [r8], #2 -strh r5, [r8], #2 -strh r6, [r8], #2 -strh r7, [r8], #2 -subs r10, #8 +vld4.16 {d0, d1, d2, d3}, [r9]! +vld4.16 {d4, d5, d6, d7}, [r9]! +vst2.16 {d0, d2}, [r8]! +vst2.16 {d4, d6}, [r8]! +subs r10, #16 bne .L1 pop {r4-r10} bx lr -#ifdef __ARM_NEON # r0: Destination # r1: Source # r2: Width@@ -97,3 +92,5 @@ bne .n40
pop {r4-r7} bx lr #endif + +.section .note.GNU-stack,"",%progbits
@@ -18,12 +18,106 @@ #include <stdint.h>
#include <stdio.h> #include <stdlib.h> #include <string.h> + +#include "version.h" + +#ifdef _MSC_VER +typedef intptr_t off_t; +typedef intptr_t ssize_t; +#define restrict __restrict +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define ftruncate _chsize +#else +#include <strings.h> #include <unistd.h> +#endif + +#ifndef SSIZE_MAX +#define SSIZE_MAX ((ssize_t) (SIZE_MAX >> 1)) +#endif #define UNUSED(V) (void)(V) -#ifdef _3DS -#define SSIZE_MAX (SIZE_MAX >> 1) +#ifndef M_PI +#define M_PI 3.141592654f #endif + +#if defined(__PPC__) || defined(__POWERPC__) +#define LOAD_32LE(DEST, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + void* _ptr = (ARR); \ + __asm__("lwbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ +} + +#define LOAD_16LE(DEST, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + void* _ptr = (ARR); \ + __asm__("lhbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ +} + +#define STORE_32LE(SRC, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + void* _ptr = (ARR); \ + __asm__("stwbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ +} + +#define STORE_16LE(SRC, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + void* _ptr = (ARR); \ + __asm__("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ +} +#else +#define LOAD_32LE(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2] +#define LOAD_16LE(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1] +#define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC +#define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC +#endif + +#define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START)) +#define CHECK_BITS(SRC, START, END) ((SRC) & MAKE_MASK(START, END)) +#define EXT_BITS(SRC, START, END) (((SRC) >> (START)) & ((1 << ((END) - (START))) - 1)) +#define INS_BITS(SRC, START, END, BITS) (CLEAR_BITS(SRC, START, END) | (((BITS) << (START)) & MAKE_MASK(START, END))) +#define CLEAR_BITS(SRC, START, END) ((SRC) & ~MAKE_MASK(START, END)) +#define FILL_BITS(SRC, START, END) ((SRC) | MAKE_MASK(START, END)) + +#ifdef _MSC_VER +#define ATTRIBUTE_UNUSED +#define ATTRIBUTE_FORMAT(X, Y, Z) +#else +#define ATTRIBUTE_UNUSED __attribute__((unused)) +#define ATTRIBUTE_FORMAT(X, Y, Z) __attribute__((format(X, Y, Z))) +#endif + +#define DECL_BITFIELD(NAME, TYPE) typedef TYPE NAME + +#define DECL_BITS(TYPE, FIELD, START, SIZE) \ + ATTRIBUTE_UNUSED static inline TYPE TYPE ## Is ## FIELD (TYPE src) { \ + return CHECK_BITS(src, (START), (START) + (SIZE)); \ + } \ + ATTRIBUTE_UNUSED static inline TYPE TYPE ## Get ## FIELD (TYPE src) { \ + return EXT_BITS(src, (START), (START) + (SIZE)); \ + } \ + ATTRIBUTE_UNUSED static inline TYPE TYPE ## Clear ## FIELD (TYPE src) { \ + return CLEAR_BITS(src, (START), (START) + (SIZE)); \ + } \ + ATTRIBUTE_UNUSED static inline TYPE TYPE ## Fill ## FIELD (TYPE src) { \ + return FILL_BITS(src, (START), (START) + (SIZE)); \ + } \ + ATTRIBUTE_UNUSED static inline TYPE TYPE ## Set ## FIELD (TYPE src, TYPE bits) { \ + return INS_BITS(src, (START), (START) + (SIZE), bits); \ + } + +#define DECL_BIT(TYPE, FIELD, BIT) DECL_BITS(TYPE, FIELD, BIT, 1) + +#ifndef _MSC_VER +#define LIKELY(X) __builtin_expect(!!(X), 1) +#define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +#define LIKELY(X) (!!(X)) +#define UNLIKELY(X) (!!(X)) +#endif + +#define ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31))) #endif
@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "configuration.h" #include "util/formatting.h" +#include "util/string.h" #include "util/vfs.h" #include "third-party/inih/ini.h"@@ -53,10 +54,14 @@ void ConfigurationSetValue(struct Configuration* configuration, const char* section, const char* key, const char* value) {
struct Table* currentSection = &configuration->root; if (section) { currentSection = HashTableLookup(&configuration->sections, section); - if (!currentSection && value) { - currentSection = malloc(sizeof(*currentSection)); - HashTableInit(currentSection, 0, _sectionDeinit); - HashTableInsert(&configuration->sections, section, currentSection); + if (!currentSection) { + if (value) { + currentSection = malloc(sizeof(*currentSection)); + HashTableInit(currentSection, 0, _sectionDeinit); + HashTableInsert(&configuration->sections, section, currentSection); + } else { + return; + } } } if (value) {
@@ -23,6 +23,7 @@ #include <errno.h>
#include <fcntl.h> #include <netinet/in.h> #include <netinet/tcp.h> +#include <sys/select.h> #include <sys/socket.h> #define INVALID_SOCKET (-1)
@@ -17,6 +17,16 @@ return out;
} #endif +#ifndef HAVE_STRDUP +char* strdup(const char* str) { + size_t len = strlen(str); + char* out = malloc(len + 1); + strncpy(out, str, len); + out[len] = '\0'; + return out; +} +#endif + char* strnrstr(const char* restrict haystack, const char* restrict needle, size_t len) { char* last = 0; const char* next = haystack;@@ -178,13 +188,14 @@ memcpy(utf8, buffer, bytes);
offset = utf8 + bytes; } else if (utf8Length >= utf8TotalBytes) { char* newUTF8 = realloc(utf8, utf8TotalBytes * 2); + offset = offset - utf8 + newUTF8; if (newUTF8 != utf8) { free(utf8); } if (!newUTF8) { return 0; } - offset = offset - utf8 + newUTF8; + utf8 = newUTF8; memcpy(offset, buffer, bytes); offset += bytes; }@@ -197,3 +208,71 @@ }
newUTF8[utf8Length] = '\0'; return newUTF8; } + +int hexDigit(char digit) { + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return digit - '0'; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return digit - 'a' + 10; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return digit - 'A' + 10; + + default: + return -1; + } +} + +const char* hex32(const char* line, uint32_t* out) { + uint32_t value = 0; + int i; + for (i = 0; i < 8; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +const char* hex16(const char* line, uint16_t* out) { + uint16_t value = 0; + *out = 0; + int i; + for (i = 0; i < 4; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +}
@@ -13,9 +13,17 @@ // This is sometimes a macro
char* strndup(const char* start, size_t len); #endif +#ifndef strdup +char* strdup(const char* str); +#endif + char* strnrstr(const char* restrict s1, const char* restrict s2, size_t len); int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t utf8Length); char* utf16to8(const uint16_t* utf16, size_t length); + +int hexDigit(char digit); +const char* hex32(const char* line, uint32_t* out); +const char* hex16(const char* line, uint16_t* out); #endif
@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "table.h" #include "util/hash.h" +#include "util/string.h" #define LIST_INITIAL_SIZE 8 #define TABLE_INITIAL_SIZE 8
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -8,237 +8,16 @@ #define THREADING_H
#include "util/common.h" +#ifndef DISABLE_THREADING #ifdef USE_PTHREADS -#include <pthread.h> -#include <sys/time.h> - -#define THREAD_ENTRY void* -typedef THREAD_ENTRY (*ThreadEntry)(void*); - -typedef pthread_t Thread; -typedef pthread_mutex_t Mutex; -typedef pthread_cond_t Condition; - -static inline int MutexInit(Mutex* mutex) { - return pthread_mutex_init(mutex, 0); -} - -static inline int MutexDeinit(Mutex* mutex) { - return pthread_mutex_destroy(mutex); -} - -static inline int MutexLock(Mutex* mutex) { - return pthread_mutex_lock(mutex); -} - -static inline int MutexUnlock(Mutex* mutex) { - return pthread_mutex_unlock(mutex); -} - -static inline int ConditionInit(Condition* cond) { - return pthread_cond_init(cond, 0); -} - -static inline int ConditionDeinit(Condition* cond) { - return pthread_cond_destroy(cond); -} - -static inline int ConditionWait(Condition* cond, Mutex* mutex) { - return pthread_cond_wait(cond, mutex); -} - -static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { - struct timespec ts; - struct timeval tv; - - gettimeofday(&tv, 0); - ts.tv_sec = tv.tv_sec; - ts.tv_nsec = (tv.tv_usec + timeoutMs * 1000L) * 1000L; - if (ts.tv_nsec >= 1000000000L) { - ts.tv_nsec -= 1000000000L; - ++ts.tv_sec; - } - - return pthread_cond_timedwait(cond, mutex, &ts); -} - -static inline int ConditionWake(Condition* cond) { - return pthread_cond_broadcast(cond); -} - -static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { - return pthread_create(thread, 0, entry, context); -} - -static inline int ThreadJoin(Thread thread) { - return pthread_join(thread, 0); -} - +#include "platform/posix/threading.h" #elif _WIN32 -#define _WIN32_WINNT 0x0600 -#include <windows.h> -#define THREAD_ENTRY DWORD WINAPI -typedef THREAD_ENTRY ThreadEntry(LPVOID); - -typedef HANDLE Thread; -typedef CRITICAL_SECTION Mutex; -typedef CONDITION_VARIABLE Condition; - -static inline int MutexInit(Mutex* mutex) { - InitializeCriticalSection(mutex); - return GetLastError(); -} - -static inline int MutexDeinit(Mutex* mutex) { - DeleteCriticalSection(mutex); - return GetLastError(); -} - -static inline int MutexLock(Mutex* mutex) { - EnterCriticalSection(mutex); - return GetLastError(); -} - -static inline int MutexUnlock(Mutex* mutex) { - LeaveCriticalSection(mutex); - return GetLastError(); -} - -static inline int ConditionInit(Condition* cond) { - InitializeConditionVariable(cond); - return GetLastError(); -} - -static inline int ConditionDeinit(Condition* cond) { - // This is a no-op on Windows - UNUSED(cond); - return 0; -} - -static inline int ConditionWait(Condition* cond, Mutex* mutex) { - SleepConditionVariableCS(cond, mutex, INFINITE); - return GetLastError(); -} - -static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { - SleepConditionVariableCS(cond, mutex, timeoutMs); - return GetLastError(); -} - -static inline int ConditionWake(Condition* cond) { - WakeAllConditionVariable(cond); - return GetLastError(); -} - -static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { - *thread = CreateThread(NULL, 0, entry, context, 0, 0); - return GetLastError(); -} - -static inline int ThreadJoin(Thread thread) { - DWORD error = WaitForSingleObject(thread, INFINITE); - if (error == WAIT_FAILED) { - return GetLastError(); - } - return 0; -} -#elif defined(_3DS) -// Threading primitives are implemented, but untested. Turn them off for now. -#define DISABLE_THREADING - -#include <3ds.h> -#include <malloc.h> - -#define THREAD_ENTRY void -typedef ThreadFunc ThreadEntry; - -typedef struct { - Handle handle; - void* stack; -} Thread; -typedef Handle Mutex; -typedef struct { - Mutex mutex; - Handle semaphore; - u32 waiting; -} Condition; - -static inline int MutexInit(Mutex* mutex) { - return svcCreateMutex(mutex, false); -} - -static inline int MutexDeinit(Mutex* mutex) { - return svcCloseHandle(*mutex); -} - -static inline int MutexLock(Mutex* mutex) { - return svcWaitSynchronization(*mutex, U64_MAX); -} - -static inline int MutexUnlock(Mutex* mutex) { - return svcReleaseMutex(*mutex); -} - -static inline int ConditionInit(Condition* cond) { - Result res = svcCreateMutex(&cond->mutex, false); - if (res) { - return res; - } - res = svcCreateSemaphore(&cond->semaphore, 0, 1); - if (res) { - svcCloseHandle(cond->mutex); - } - cond->waiting = 0; - return res; -} - -static inline int ConditionDeinit(Condition* cond) { - svcCloseHandle(cond->mutex); - return svcCloseHandle(cond->semaphore); -} - -static inline int ConditionWait(Condition* cond, Mutex* mutex) { - MutexLock(&cond->mutex); - ++cond->waiting; - MutexUnlock(mutex); - MutexUnlock(&cond->mutex); - svcWaitSynchronization(cond->semaphore, U64_MAX); - MutexLock(mutex); - return 0; -} - -static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { - MutexLock(&cond->mutex); - ++cond->waiting; - MutexUnlock(mutex); - MutexUnlock(&cond->mutex); - svcWaitSynchronization(cond->semaphore, timeoutMs * 10000000LL); - MutexLock(mutex); - return 0; -} - -static inline int ConditionWake(Condition* cond) { - MutexLock(&cond->mutex); - if (cond->waiting) { - --cond->waiting; - s32 count = 0; - svcReleaseSemaphore(&count, cond->semaphore, 1); - } - MutexUnlock(&cond->mutex); - return 0; -} - -static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { - thread->stack = memalign(8, 0x800000); - return svcCreateThread(&thread->handle, entry, (u32) context, thread->stack, 0x1F, 0); -} - -static inline int ThreadJoin(Thread thread) { - return svcWaitSynchronization(thread.handle, U64_MAX); -} - +#include "platform/windows/threading.h" #else #define DISABLE_THREADING +#endif +#endif +#ifdef DISABLE_THREADING typedef void* Thread; typedef void* Mutex; typedef void* Condition;
@@ -24,6 +24,7 @@ void NAME ## Shift(struct NAME* vector, size_t location, size_t difference); \
void NAME ## Unshift(struct NAME* vector, size_t location, size_t difference); \ void NAME ## EnsureCapacity(struct NAME* vector, size_t capacity); \ size_t NAME ## Size(const struct NAME* vector); \ + size_t NAME ## Index(const struct NAME* vector, const TYPE* member); #define DEFINE_VECTOR(NAME, TYPE) \ void NAME ## Init(struct NAME* vector, size_t capacity) { \@@ -74,6 +75,9 @@ memmove(&vector->vector[location + difference], &vector->vector[location], (vector->size - location - difference) * sizeof(TYPE)); \
} \ size_t NAME ## Size(const struct NAME* vector) { \ return vector->size; \ + } \ + size_t NAME ## Index(const struct NAME* vector, const TYPE* member) { \ + return member - (const TYPE*) vector->vector; \ } \ #endif
@@ -0,0 +1,14 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/version.h" + +const char* const gitCommit = "${GIT_COMMIT}"; +const char* const gitCommitShort = "${GIT_COMMIT_SHORT}"; +const char* const gitBranch = "${GIT_BRANCH}"; +const int gitRevision = ${GIT_REV}; +const char* const binaryName = "${BINARY_NAME}"; +const char* const projectName = "${PROJECT_NAME}"; +const char* const projectVersion = "${VERSION_STRING}";
@@ -0,0 +1,17 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef VERSION_H +#define VERSION_H + +extern const char* const gitCommit; +extern const char* const gitCommitShort; +extern const char* const gitBranch; +extern const int gitRevision; +extern const char* const binaryName; +extern const char* const projectName; +extern const char* const projectVersion; + +#endif
@@ -1,408 +1,52 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "util/vfs.h" - -#include "util/string.h" - -#include <fcntl.h> -#include <dirent.h> -#include <sys/stat.h> - -#ifdef _WIN32 -#include <io.h> -#include <windows.h> -#define PATH_SEP '\\' -#elif defined(_3DS) -#include "util/memory.h" -#define PATH_SEP '/' -#else -#include <sys/mman.h> -#define PATH_SEP '/' -#endif - -struct VFileFD { - struct VFile d; - int fd; -#ifdef _WIN32 - HANDLE hMap; -#endif -}; - -static bool _vfdClose(struct VFile* vf); -static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence); -static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size); -static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size); -static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size); -static void* _vfdMap(struct VFile* vf, size_t size, int flags); -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); -static void _vfdTruncate(struct VFile* vf, size_t size); -static ssize_t _vfdSize(struct VFile* vf); - -static bool _vdClose(struct VDir* vd); -static void _vdRewind(struct VDir* vd); -static struct VDirEntry* _vdListNext(struct VDir* vd); -static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); - -static const char* _vdeName(struct VDirEntry* vde); - -struct VFile* VFileOpen(const char* path, int flags) { - if (!path) { - return 0; - } -#ifdef _WIN32 - flags |= O_BINARY; -#endif - int fd = open(path, flags, 0666); - return VFileFromFD(fd); -} - -struct VFile* VFileFromFD(int fd) { - if (fd < 0) { - return 0; - } - - struct VFileFD* vfd = malloc(sizeof(struct VFileFD)); - if (!vfd) { - return 0; - } - - vfd->fd = fd; - vfd->d.close = _vfdClose; - vfd->d.seek = _vfdSeek; - vfd->d.read = _vfdRead; - vfd->d.readline = _vfdReadline; - vfd->d.write = _vfdWrite; - vfd->d.map = _vfdMap; - vfd->d.unmap = _vfdUnmap; - vfd->d.truncate = _vfdTruncate; - vfd->d.size = _vfdSize; - - return &vfd->d; -} - -bool _vfdClose(struct VFile* vf) { - struct VFileFD* vfd = (struct VFileFD*) vf; - if (close(vfd->fd) < 0) { - return false; - } - free(vfd); - return true; -} - -off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return lseek(vfd->fd, offset, whence); -} +#include "vfs.h" -ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return read(vfd->fd, buffer, size); -} - -ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - size_t bytesRead = 0; +ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { + ssize_t bytesRead = 0; while (bytesRead < size - 1) { - size_t newRead = read(vfd->fd, &buffer[bytesRead], 1); - if (!newRead || buffer[bytesRead] == '\n') { + ssize_t newRead = vf->read(vf, &buffer[bytesRead], 1); + if (newRead <= 0) { break; } bytesRead += newRead; + if (buffer[bytesRead] == '\n') { + break; + } } buffer[bytesRead] = '\0'; return bytesRead; } -ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return write(vfd->fd, buffer, size); -} - -#ifdef _WIN32 -static void* _vfdMap(struct VFile* vf, size_t size, int flags) { - struct VFileFD* vfd = (struct VFileFD*) vf; - int createFlags = PAGE_WRITECOPY; - int mapFiles = FILE_MAP_COPY; - if (flags & MAP_WRITE) { - createFlags = PAGE_READWRITE; - mapFiles = FILE_MAP_WRITE; - } - size_t fileSize; - struct stat stat; - if (fstat(vfd->fd, &stat) < 0) { - return 0; - } - fileSize = stat.st_size; - if (size > fileSize) { - size = fileSize; - } - vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0); - return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size); -} - -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { - UNUSED(size); - struct VFileFD* vfd = (struct VFileFD*) vf; - UnmapViewOfFile(memory); - CloseHandle(vfd->hMap); - vfd->hMap = 0; -} -#elif defined(_3DS) -static void* _vfdMap(struct VFile* vf, size_t size, int flags) { - UNUSED(flags); - void* buffer = anonymousMemoryMap(size); - vf->read(vf, buffer, size); - vf->seek(vf, -(off_t) size, SEEK_CUR); - return buffer; -} - -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { - UNUSED(vf); - mappedMemoryFree(memory, size); -} -#else -static void* _vfdMap(struct VFile* vf, size_t size, int flags) { - struct VFileFD* vfd = (struct VFileFD*) vf; - int mmapFlags = MAP_PRIVATE; - if (flags & MAP_WRITE) { - mmapFlags = MAP_SHARED; - } - return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0); -} - -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { - UNUSED(vf); - munmap(memory, size); -} -#endif - -static void _vfdTruncate(struct VFile* vf, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - ftruncate(vfd->fd, size); -} - -static ssize_t _vfdSize(struct VFile* vf) { - struct VFileFD* vfd = (struct VFileFD*) vf; - struct stat stat; - if (fstat(vfd->fd, &stat) < 0) { - return -1; - } - return stat.st_size; -} - -struct VDirEntryDE { - struct VDirEntry d; - struct dirent* ent; -}; - -struct VDirDE { - struct VDir d; - DIR* de; - struct VDirEntryDE vde; - char* path; -}; - -struct VDir* VDirOpen(const char* path) { - DIR* de = opendir(path); - if (!de) { - return 0; - } - - struct VDirDE* vd = malloc(sizeof(struct VDirDE)); - if (!vd) { - return 0; - } - - vd->d.close = _vdClose; - vd->d.rewind = _vdRewind; - vd->d.listNext = _vdListNext; - vd->d.openFile = _vdOpenFile; - vd->path = strdup(path); - vd->de = de; - - vd->vde.d.name = _vdeName; - - return &vd->d; +ssize_t VFileWrite32LE(struct VFile* vf, int32_t word) { + uint32_t leword; + STORE_32LE(word, 0, &leword); + return vf->write(vf, &leword, 4); } -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - struct VFile* vf; - if (!dir) { - if (!realPath) { - return 0; - } - char* dotPoint = strrchr(realPath, '.'); - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - if (dotPoint > strrchr(realPath, '/')) { - int len = dotPoint - realPath; - strncpy(path, realPath, len); - path[len] = 0; - strncat(path + len, suffix, PATH_MAX - len - 1); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); - } - vf = VFileOpen(path, mode); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); - vf = dir->openFile(dir, path, mode); - } - return vf; +ssize_t VFileWrite16LE(struct VFile* vf, int16_t hword) { + uint16_t lehword; + STORE_16LE(hword, 0, &lehword); + return vf->write(vf, &lehword, 2); } -struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - char realPrefix[PATH_MAX]; - realPrefix[PATH_MAX - 1] = '\0'; - if (!dir) { - if (!realPath) { - return 0; - } - const char* separatorPoint = strrchr(realPath, '/'); - const char* dotPoint; - size_t len; - if (!separatorPoint) { - strcpy(path, "./"); - separatorPoint = realPath; - dotPoint = strrchr(realPath, '.'); - } else { - path[0] = '\0'; - dotPoint = strrchr(separatorPoint, '.'); - - if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - len = separatorPoint - realPath; - strncat(path, realPath, len); - path[len] = '\0'; - ++separatorPoint; - } - - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - if (dotPoint >= separatorPoint) { - len = dotPoint - separatorPoint; - } else { - len = PATH_MAX - 1; - } - - strncpy(realPrefix, separatorPoint, len); - realPrefix[len] = '\0'; - - prefix = realPrefix; - dir = VDirOpen(path); +ssize_t VFileRead32LE(struct VFile* vf, void* word) { + uint32_t leword; + ssize_t r = vf->read(vf, &leword, 4); + if (r == 4) { + STORE_32LE(leword, 0, word); } - if (!dir) { - // This shouldn't be possible - return 0; - } - dir->rewind(dir); - struct VDirEntry* dirent; - size_t prefixLen = strlen(prefix); - size_t infixLen = strlen(infix); - unsigned next = 0; - while ((dirent = dir->listNext(dir))) { - const char* filename = dirent->name(dirent); - char* dotPoint = strrchr(filename, '.'); - size_t len = strlen(filename); - if (dotPoint) { - len = (dotPoint - filename); - } - const char* separator = strnrstr(filename, infix, len); - if (!separator) { - continue; - } - len = separator - filename; - if (len != prefixLen) { - continue; - } - if (strncmp(filename, prefix, prefixLen) == 0) { - int nlen; - separator += infixLen; - snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); - unsigned increment; - if (sscanf(separator, path, &increment, &nlen) < 1) { - continue; - } - len = strlen(separator); - if (nlen < (ssize_t) len) { - continue; - } - if (next <= increment) { - next = increment + 1; - } - } - } - snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); - path[PATH_MAX - 1] = '\0'; - return dir->openFile(dir, path, mode); + return r; } -bool _vdClose(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - if (closedir(vdde->de) < 0) { - return false; +ssize_t VFileRead16LE(struct VFile* vf, void* hword) { + uint16_t lehword; + ssize_t r = vf->read(vf, &lehword, 2); + if (r == 2) { + STORE_16LE(lehword, 0, hword); } - free(vdde->path); - free(vdde); - return true; -} - -void _vdRewind(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - rewinddir(vdde->de); -} - -struct VDirEntry* _vdListNext(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - vdde->vde.ent = readdir(vdde->de); - if (vdde->vde.ent) { - return &vdde->vde.d; - } - - return 0; -} - -struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) { - struct VDirDE* vdde = (struct VDirDE*) vd; - if (!path) { - return 0; - } - const char* dir = vdde->path; - char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s%c%s", dir, PATH_SEP, path); - - struct VFile* file = VFileOpen(combined, mode); - free(combined); - return file; -} - -const char* _vdeName(struct VDirEntry* vde) { - struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; - if (vdede->ent) { - return vdede->ent->d_name; - } - return 0; -} - -ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { - size_t bytesRead = 0; - while (bytesRead < size - 1) { - size_t newRead = vf->read(vf, &buffer[bytesRead], 1); - bytesRead += newRead; - if (!newRead || buffer[bytesRead] == '\n') { - break; - } - } - return buffer[bytesRead] = '\0'; + return r; }
@@ -8,6 +8,22 @@ #define VFS_H
#include "util/common.h" +#ifdef _WIN32 +#include <io.h> +#include <windows.h> +#define PATH_SEP "\\" +#else +#define PATH_SEP "/" +#endif + +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 128 +#endif +#endif + enum { MAP_READ = 1, MAP_WRITE = 2@@ -37,8 +53,10 @@ struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode);
}; struct VFile* VFileOpen(const char* path, int flags); +struct VFile* VFileFOpen(const char* path, const char* mode); struct VFile* VFileFromFD(int fd); struct VFile* VFileFromMemory(void* mem, size_t size); +struct VFile* VFileFromFILE(FILE* file); struct VDir* VDirOpen(const char* path);@@ -54,5 +72,10 @@ struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode);
struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode); ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size); + +ssize_t VFileWrite32LE(struct VFile* vf, int32_t word); +ssize_t VFileWrite16LE(struct VFile* vf, int16_t hword); +ssize_t VFileRead32LE(struct VFile* vf, void* word); +ssize_t VFileRead16LE(struct VFile* vf, void* hword); #endif
@@ -0,0 +1,220 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/vfs.h" + +#include "util/string.h" + +#include <dirent.h> + +static bool _vdClose(struct VDir* vd); +static void _vdRewind(struct VDir* vd); +static struct VDirEntry* _vdListNext(struct VDir* vd); +static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); + +static const char* _vdeName(struct VDirEntry* vde); + +struct VDirEntryDE { + struct VDirEntry d; + struct dirent* ent; +}; + +struct VDirDE { + struct VDir d; + DIR* de; + struct VDirEntryDE vde; + char* path; +}; + +struct VDir* VDirOpen(const char* path) { + DIR* de = opendir(path); + if (!de) { + return 0; + } + + struct VDirDE* vd = malloc(sizeof(struct VDirDE)); + if (!vd) { + closedir(de); + return 0; + } + + vd->d.close = _vdClose; + vd->d.rewind = _vdRewind; + vd->d.listNext = _vdListNext; + vd->d.openFile = _vdOpenFile; + vd->path = strdup(path); + vd->de = de; + + vd->vde.d.name = _vdeName; + + return &vd->d; +} + +struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + char realPrefix[PATH_MAX]; + realPrefix[PATH_MAX - 1] = '\0'; + if (!dir) { + if (!realPath) { + return 0; + } + const char* separatorPoint = strrchr(realPath, '/'); + const char* dotPoint; + size_t len; + if (!separatorPoint) { + strcpy(path, "./"); + separatorPoint = realPath; + dotPoint = strrchr(realPath, '.'); + } else { + path[0] = '\0'; + dotPoint = strrchr(separatorPoint, '.'); + + if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + len = separatorPoint - realPath; + strncat(path, realPath, len); + path[len] = '\0'; + ++separatorPoint; + } + + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + if (dotPoint >= separatorPoint) { + len = dotPoint - separatorPoint; + } else { + len = PATH_MAX - 1; + } + + strncpy(realPrefix, separatorPoint, len); + realPrefix[len] = '\0'; + + prefix = realPrefix; + dir = VDirOpen(path); + } + if (!dir) { + // This shouldn't be possible + return 0; + } + dir->rewind(dir); + struct VDirEntry* dirent; + size_t prefixLen = strlen(prefix); + size_t infixLen = strlen(infix); + unsigned next = 0; + while ((dirent = dir->listNext(dir))) { + const char* filename = dirent->name(dirent); + char* dotPoint = strrchr(filename, '.'); + size_t len = strlen(filename); + if (dotPoint) { + len = (dotPoint - filename); + } + const char* separator = strnrstr(filename, infix, len); + if (!separator) { + continue; + } + len = separator - filename; + if (len != prefixLen) { + continue; + } + if (strncmp(filename, prefix, prefixLen) == 0) { + int nlen; + separator += infixLen; + snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); + unsigned increment; + if (sscanf(separator, path, &increment, &nlen) < 1) { + continue; + } + len = strlen(separator); + if (nlen < (ssize_t) len) { + continue; + } + if (next <= increment) { + next = increment + 1; + } + } + } + snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); + path[PATH_MAX - 1] = '\0'; + return dir->openFile(dir, path, mode); +} + +bool _vdClose(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (closedir(vdde->de) < 0) { + return false; + } + free(vdde->path); + free(vdde); + return true; +} + +void _vdRewind(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + rewinddir(vdde->de); +} + +struct VDirEntry* _vdListNext(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + vdde->vde.ent = readdir(vdde->de); + if (vdde->vde.ent) { + return &vdde->vde.d; + } + + return 0; +} + +struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (!path) { + return 0; + } + const char* dir = vdde->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VFile* file = VFileOpen(combined, mode); + free(combined); + return file; +} + +const char* _vdeName(struct VDirEntry* vde) { + struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; + if (vdede->ent) { + return vdede->ent->d_name; + } + return 0; +} + +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + struct VFile* vf; + if (!dir) { + if (!realPath) { + return 0; + } + char* dotPoint = strrchr(realPath, '.'); + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + if (dotPoint > strrchr(realPath, '/')) { + int len = dotPoint - realPath; + strncpy(path, realPath, len); + path[len] = 0; + strncat(path + len, suffix, PATH_MAX - len - 1); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); + } + vf = VFileOpen(path, mode); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); + vf = dir->openFile(dir, path, mode); + } + return vf; +}
@@ -0,0 +1,162 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/vfs.h" + +#include <fcntl.h> +#include <sys/stat.h> +#ifndef _WIN32 +#include <sys/mman.h> +#endif + +struct VFileFD { + struct VFile d; + int fd; +#ifdef _WIN32 + HANDLE hMap; +#endif +}; + +static bool _vfdClose(struct VFile* vf); +static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size); +static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vfdMap(struct VFile* vf, size_t size, int flags); +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); +static void _vfdTruncate(struct VFile* vf, size_t size); +static ssize_t _vfdSize(struct VFile* vf); + +struct VFile* VFileOpen(const char* path, int flags) { + if (!path) { + return 0; + } +#ifdef _WIN32 + flags |= O_BINARY; +#endif + int fd = open(path, flags, 0666); + return VFileFromFD(fd); +} + +struct VFile* VFileFromFD(int fd) { + if (fd < 0) { + return 0; + } + + struct VFileFD* vfd = malloc(sizeof(struct VFileFD)); + if (!vfd) { + return 0; + } + + vfd->fd = fd; + vfd->d.close = _vfdClose; + vfd->d.seek = _vfdSeek; + vfd->d.read = _vfdRead; + vfd->d.readline = _vfdReadline; + vfd->d.write = _vfdWrite; + vfd->d.map = _vfdMap; + vfd->d.unmap = _vfdUnmap; + vfd->d.truncate = _vfdTruncate; + vfd->d.size = _vfdSize; + + return &vfd->d; +} + +bool _vfdClose(struct VFile* vf) { + struct VFileFD* vfd = (struct VFileFD*) vf; + if (close(vfd->fd) < 0) { + return false; + } + free(vfd); + return true; +} + +off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return lseek(vfd->fd, offset, whence); +} + +ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return read(vfd->fd, buffer, size); +} + +ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + size_t bytesRead = 0; + while (bytesRead < size - 1) { + size_t newRead = read(vfd->fd, &buffer[bytesRead], 1); + if (!newRead || buffer[bytesRead] == '\n') { + break; + } + bytesRead += newRead; + } + buffer[bytesRead] = '\0'; + return bytesRead; +} + +ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return write(vfd->fd, buffer, size); +} + +#ifndef _WIN32 +static void* _vfdMap(struct VFile* vf, size_t size, int flags) { + struct VFileFD* vfd = (struct VFileFD*) vf; + int mmapFlags = MAP_PRIVATE; + if (flags & MAP_WRITE) { + mmapFlags = MAP_SHARED; + } + return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0); +} + +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + munmap(memory, size); +} +#else +static void* _vfdMap(struct VFile* vf, size_t size, int flags) { + struct VFileFD* vfd = (struct VFileFD*) vf; + int createFlags = PAGE_WRITECOPY; + int mapFiles = FILE_MAP_COPY; + if (flags & MAP_WRITE) { + createFlags = PAGE_READWRITE; + mapFiles = FILE_MAP_WRITE; + } + size_t fileSize; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return 0; + } + fileSize = stat.st_size; + if (size > fileSize) { + size = fileSize; + } + vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0); + return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size); +} + +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(size); + struct VFileFD* vfd = (struct VFileFD*) vf; + UnmapViewOfFile(memory); + CloseHandle(vfd->hMap); + vfd->hMap = 0; +} +#endif + +static void _vfdTruncate(struct VFile* vf, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + ftruncate(vfd->fd, size); +} + +static ssize_t _vfdSize(struct VFile* vf) { + struct VFileFD* vfd = (struct VFileFD*) vf; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return -1; + } + return stat.st_size; +}
@@ -0,0 +1,132 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/vfs.h" + +#include "util/memory.h" + +#include <stdio.h> + +struct VFileFILE { + struct VFile d; + FILE* file; +}; + +static bool _vffClose(struct VFile* vf); +static off_t _vffSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vffMap(struct VFile* vf, size_t size, int flags); +static void _vffUnmap(struct VFile* vf, void* memory, size_t size); +static void _vffTruncate(struct VFile* vf, size_t size); +static ssize_t _vffSize(struct VFile* vf); + +struct VFile* VFileFOpen(const char* path, const char* mode) { + if (!path && !mode) { + return 0; + } + FILE* file = fopen(path, mode); + return VFileFromFILE(file); +} + +struct VFile* VFileFromFILE(FILE* file) { + if (!file) { + return 0; + } + + struct VFileFILE* vff = malloc(sizeof(struct VFileFILE)); + if (!vff) { + return 0; + } + + vff->file = file; + vff->d.close = _vffClose; + vff->d.seek = _vffSeek; + vff->d.read = _vffRead; + vff->d.readline = VFileReadline; + vff->d.write = _vffWrite; + vff->d.map = _vffMap; + vff->d.unmap = _vffUnmap; + vff->d.truncate = _vffTruncate; + vff->d.size = _vffSize; + + return &vff->d; +} + +bool _vffClose(struct VFile* vf) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + if (fclose(vff->file) < 0) { + return false; + } + free(vff); + return true; +} + +off_t _vffSeek(struct VFile* vf, off_t offset, int whence) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + return fseek(vff->file, offset, whence); +} + +ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + return fread(buffer, size, 1, vff->file); +} + +ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + return fwrite(buffer, size, 1, vff->file); +} + +static void* _vffMap(struct VFile* vf, size_t size, int flags) { + UNUSED(flags); + struct VFileFILE* vff = (struct VFileFILE*) vf; + void* mem = anonymousMemoryMap(size); + if (!mem) { + return 0; + } + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_SET); + fread(mem, size, 1, vff->file); + fseek(vff->file, pos, SEEK_SET); + return mem; +} + +static void _vffUnmap(struct VFile* vf, void* memory, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_SET); + fwrite(memory, size, 1, vff->file); + fseek(vff->file, pos, SEEK_SET); + mappedMemoryFree(memory, size); +} + +static void _vffTruncate(struct VFile* vf, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_END); + ssize_t realSize = ftell(vff->file); + if (realSize < 0) { + return; + } + while (size > (size_t) realSize) { + static const char zeros[128] = ""; + size_t diff = size - realSize; + if (diff > sizeof(zeros)) { + diff = sizeof(zeros); + } + fwrite(zeros, diff, 1, vff->file); + realSize += diff; + } + fseek(vff->file, pos, SEEK_SET); +} + +static ssize_t _vffSize(struct VFile* vf) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_END); + ssize_t size = ftell(vff->file); + fseek(vff->file, pos, SEEK_SET); + return size; +}
@@ -65,11 +65,11 @@
static const char* _vde7zName(struct VDirEntry* vde); struct VDir* VDirOpen7z(const char* path, int flags) { - struct VDir7z* vd = malloc(sizeof(struct VDir7z)); - if (flags & O_WRONLY || flags & O_CREAT) { return 0; } + + struct VDir7z* vd = malloc(sizeof(struct VDir7z)); // What does any of this mean, Igor? if (InFile_Open(&vd->archiveStream.file, path)) {
@@ -93,7 +93,7 @@ if (size + vfm->offset >= vfm->size) {
size = vfm->size - vfm->offset; } - memcpy(buffer, vfm->mem + vfm->offset, size); + memcpy(buffer, (void*) ((uintptr_t) vfm->mem + vfm->offset), size); vfm->offset += size; return size; }@@ -105,7 +105,7 @@ if (size + vfm->offset >= vfm->size) {
size = vfm->size - vfm->offset; } - memcpy(vfm->mem + vfm->offset, buffer, size); + memcpy((void*) ((uintptr_t) vfm->mem + vfm->offset), buffer, size); vfm->offset += size; return size; }
@@ -44,6 +44,8 @@ split.reverse()
while split: accum.append(split.pop()) newPath = joinPath(accum) + if newPath == '/': + continue try: os.mkdir(newPath) except OSError as e:@@ -67,6 +69,8 @@ if split[0] == '@executable_path':
split[:1] = execPath if split[0] == '/' and not os.access(joinPath(split), os.F_OK): split[:1] = root + oldPath = os.path.realpath(joinPath(split)) + split = splitPath(oldPath) isFramework = False if not split[-1].endswith('.dylib'): isFramework = True@@ -104,16 +108,19 @@ if not qtPath and 'Qt' in oldPath:
qtPath = findQtPath(oldPath) if verbose: print('Found Qt path at {}.'.format(qtPath)) + args = [installNameTool] for path, oldExecPath, newExecPath in toUpdate: if path != bin: updateMachO(path, execPath, root) if verbose: print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath)) - subprocess.check_call([installNameTool, '-change', oldExecPath, newExecPath, bin]) + args.extend(['-change', oldExecPath, newExecPath]) else: if verbose: print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath)) - subprocess.check_call([installNameTool, '-id', newExecPath, bin]) + args.extend(['-id', newExecPath]) + args.append(bin) + subprocess.check_call(args) if __name__ == '__main__': parser = argparse.ArgumentParser()
@@ -56,6 +56,9 @@ sed -i~ "s/,,*/,/g" deb-temp/DEBIAN/control
sed -i~ "s/,$//g" deb-temp/DEBIAN/control sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control rm deb-temp/DEBIAN/control~ + chown -R 0:0 deb-temp + chmod 600 deb-temp/DEBIAN/md5sums dpkg-deb -b deb-temp $DEB + rm -rf deb-temp shift done
@@ -0,0 +1,5 @@
+if(NOT ${BINARY_NAME}_SOURCE_DIR) + set(${BINARY_NAME}_SOURCE_DIR ${CMAKE_SOURCE_DIR}) +endif() + +configure_file("${${BINARY_NAME}_SOURCE_DIR}/src/util/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c")