mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/sio-lockstep
Conflicts: CMakeLists.txt src/gba/gba.c
This commit is contained in:
commit
51b8c862b9
6
CHANGES
6
CHANGES
|
@ -26,6 +26,8 @@ Features:
|
||||||
- Debugger: Add CLI functions for examining memory regions
|
- Debugger: Add CLI functions for examining memory regions
|
||||||
- Runtime configurable audio driver
|
- Runtime configurable audio driver
|
||||||
- Debugger: Add CLI function for writing a register
|
- Debugger: Add CLI function for writing a register
|
||||||
|
- Libretro core for use with RetroArch and other front-ends
|
||||||
|
- Controller profiles for setting different bindings for different controllers
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- ARM7: Extend prefetch by one stage
|
- ARM7: Extend prefetch by one stage
|
||||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||||
|
@ -46,6 +48,9 @@ Bugfixes:
|
||||||
- Qt: Fix crash when starting GDB stub 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 patch loading while a game is running
|
||||||
- Util: Fix sockets on Windows
|
- 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:
|
Misc:
|
||||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
- 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 Memory: Simplify memory API and use fixed bus width
|
||||||
|
@ -68,6 +73,7 @@ Misc:
|
||||||
- Qt: Move frame upload back onto main thread
|
- Qt: Move frame upload back onto main thread
|
||||||
- All: Enable link-time optimization
|
- All: Enable link-time optimization
|
||||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||||
|
- GBA: Move A/V stream interface into core
|
||||||
|
|
||||||
0.1.1: (2015-01-24)
|
0.1.1: (2015-01-24)
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
|
@ -12,11 +12,13 @@ set(USE_BLIP ON CACHE BOOL "Whether or not to enable blip_buf support")
|
||||||
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||||
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
|
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
|
||||||
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
|
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
|
||||||
|
set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
|
||||||
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
||||||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||||
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
|
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
|
||||||
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
|
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.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 GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)
|
||||||
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
||||||
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
||||||
|
@ -26,7 +28,7 @@ file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||||
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||||
source_group("ARM core" FILES ${ARM_SRC})
|
source_group("ARM core" FILES ${ARM_SRC})
|
||||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC})
|
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC})
|
||||||
source_group("GBA supervisor" FILES ${GBA_SV_SRC})
|
source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||||
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||||
|
@ -75,10 +77,11 @@ set(LIB_VERSION_ABI 0.2)
|
||||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||||
|
|
||||||
# Advanced settings
|
# Advanced settings
|
||||||
set(BUILD_PGO CACHE BOOL "Build with profiling-guided optimization")
|
set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization")
|
||||||
|
set(BUILD_PGO OFF CACHE BOOL "Build with profiling-guided optimization")
|
||||||
set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated")
|
set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated")
|
||||||
set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path")
|
set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path")
|
||||||
mark_as_advanced(BUILD_PGO PGO_STAGE_2 PGO_DIR)
|
mark_as_advanced(BUILD_LTO BUILD_PGO PGO_STAGE_2 PGO_DIR)
|
||||||
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR} -fprofile-arcs")
|
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR} -fprofile-arcs")
|
||||||
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")
|
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")
|
||||||
|
|
||||||
|
@ -95,6 +98,7 @@ endif()
|
||||||
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}")
|
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}")
|
||||||
|
|
||||||
# Feature dependencies
|
# Feature dependencies
|
||||||
|
set(FEATURES)
|
||||||
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
|
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
|
||||||
set(LIBEDIT_LIBRARIES -ledit)
|
set(LIBEDIT_LIBRARIES -ledit)
|
||||||
else()
|
else()
|
||||||
|
@ -124,18 +128,21 @@ if(APPLE)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU")
|
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO)
|
||||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(BUILD_BBB OR BUILD_RASPI)
|
if(BUILD_BBB OR BUILD_RASPI)
|
||||||
enable_language(ASM)
|
|
||||||
if(NOT BUILD_EGL)
|
if(NOT BUILD_EGL)
|
||||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*")
|
||||||
|
enable_language(ASM)
|
||||||
|
endif()
|
||||||
|
|
||||||
include(CheckFunctionExists)
|
include(CheckFunctionExists)
|
||||||
check_function_exists(strndup HAVE_STRNDUP)
|
check_function_exists(strndup HAVE_STRNDUP)
|
||||||
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
||||||
|
@ -166,13 +173,14 @@ endif()
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
||||||
|
set(FEATURE_SRC)
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
|
||||||
|
|
||||||
if(USE_CLI_DEBUGGER)
|
if(USE_CLI_DEBUGGER)
|
||||||
add_definitions(-DUSE_CLI_DEBUGGER)
|
list(APPEND FEATURES CLI_DEBUGGER)
|
||||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
||||||
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
||||||
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
||||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||||
|
@ -182,20 +190,20 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_GDB_STUB)
|
if(USE_GDB_STUB)
|
||||||
add_definitions(-DUSE_GDB_STUB)
|
list(APPEND FEATURES GDB_STUB)
|
||||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
||||||
endif()
|
endif()
|
||||||
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
||||||
|
|
||||||
if(USE_FFMPEG)
|
if(USE_FFMPEG)
|
||||||
add_definitions(-DUSE_FFMPEG)
|
list(APPEND FEATURES FFMPEG)
|
||||||
pkg_search_module(LIBSWRESAMPLE QUIET libswresample)
|
pkg_search_module(LIBSWRESAMPLE QUIET libswresample)
|
||||||
if(NOT LIBSWRESAMPLE_FOUND)
|
if(NOT LIBSWRESAMPLE_FOUND)
|
||||||
add_definitions(-DUSE_LIBAV)
|
list(APPEND FEATURES LIBAV)
|
||||||
endif()
|
endif()
|
||||||
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||||
link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
||||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||||
|
@ -207,17 +215,17 @@ if(USE_FFMPEG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_BLIP)
|
if(USE_BLIP)
|
||||||
list(APPEND UTIL_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)
|
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF)
|
||||||
else()
|
else()
|
||||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)
|
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_MAGICK)
|
if(USE_MAGICK)
|
||||||
add_definitions(-DUSE_MAGICK)
|
list(APPEND FEATURES MAGICK)
|
||||||
include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS})
|
include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS})
|
||||||
link_directories(${MAGICKWAND_LIBRARY_DIRS})
|
link_directories(${MAGICKWAND_LIBRARY_DIRS})
|
||||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c")
|
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c")
|
||||||
list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES})
|
list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES})
|
||||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
||||||
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
||||||
|
@ -229,7 +237,7 @@ if(USE_MAGICK)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_PNG)
|
if(USE_PNG)
|
||||||
add_definitions(-DUSE_PNG)
|
list(APPEND FEATURES PNG)
|
||||||
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
||||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0,zlib1g")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0,zlib1g")
|
||||||
|
@ -239,7 +247,7 @@ if(USE_LIBZIP)
|
||||||
include_directories(AFTER ${LIBZIP_INCLUDE_DIRS})
|
include_directories(AFTER ${LIBZIP_INCLUDE_DIRS})
|
||||||
link_directories(${LIBZIP_LIBRARY_DIRS})
|
link_directories(${LIBZIP_LIBRARY_DIRS})
|
||||||
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
|
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
|
||||||
add_definitions(-DENABLE_LIBZIP)
|
list(APPEND FEATURES LIBZIP)
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -247,7 +255,6 @@ if (USE_LZMA)
|
||||||
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
||||||
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
||||||
set(LZMA_SRC
|
set(LZMA_SRC
|
||||||
${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c
|
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf.c
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf.c
|
||||||
|
@ -265,14 +272,20 @@ if (USE_LZMA)
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
||||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
||||||
list(APPEND UTIL_SRC ${LZMA_SRC})
|
list(APPEND FEATURE_SRC ${LZMA_SRC})
|
||||||
add_definitions(-DENABLE_LZMA)
|
list(APPEND FEATURES LZMA)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(FEATURE_DEFINES)
|
||||||
|
foreach(FEATURE IN LISTS FEATURES)
|
||||||
|
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
# Binaries
|
# Binaries
|
||||||
set(SRC
|
set(CORE_SRC
|
||||||
${ARM_SRC}
|
${ARM_SRC}
|
||||||
${GBA_SRC}
|
${GBA_SRC}
|
||||||
|
${GBA_RR_SRC}
|
||||||
${GBA_SV_SRC}
|
${GBA_SV_SRC}
|
||||||
${DEBUGGER_SRC}
|
${DEBUGGER_SRC}
|
||||||
${RENDERER_SRC}
|
${RENDERER_SRC}
|
||||||
|
@ -282,6 +295,10 @@ set(SRC
|
||||||
${OS_SRC}
|
${OS_SRC}
|
||||||
${THIRD_PARTY_SRC})
|
${THIRD_PARTY_SRC})
|
||||||
|
|
||||||
|
set(SRC
|
||||||
|
${CORE_SRC}
|
||||||
|
${FEATURE_SRC})
|
||||||
|
|
||||||
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
||||||
set(BUILD_SHARED ON)
|
set(BUILD_SHARED ON)
|
||||||
endif()
|
endif()
|
||||||
|
@ -290,6 +307,7 @@ if(BUILD_SHARED)
|
||||||
add_library(${BINARY_NAME} SHARED ${SRC})
|
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||||
if(BUILD_STATIC)
|
if(BUILD_STATIC)
|
||||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||||
|
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
|
@ -298,7 +316,14 @@ endif()
|
||||||
|
|
||||||
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
||||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
|
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||||
|
|
||||||
|
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 "COLOR_16_BIT;COLOR_5_6_5")
|
||||||
|
target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB})
|
||||||
|
endif()
|
||||||
|
|
||||||
if(BUILD_SDL)
|
if(BUILD_SDL)
|
||||||
add_definitions(-DBUILD_SDL)
|
add_definitions(-DBUILD_SDL)
|
||||||
|
@ -355,6 +380,7 @@ message(STATUS " Better audio resampling: ${USE_BLIP}")
|
||||||
message(STATUS "Frontend summary:")
|
message(STATUS "Frontend summary:")
|
||||||
message(STATUS " Qt: ${BUILD_QT}")
|
message(STATUS " Qt: ${BUILD_QT}")
|
||||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||||
|
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||||
message(STATUS "Library summary:")
|
message(STATUS "Library summary:")
|
||||||
message(STATUS " Static: ${BUILD_STATIC}")
|
message(STATUS " Static: ${BUILD_STATIC}")
|
||||||
|
|
|
@ -53,6 +53,7 @@ void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||||
debugger->cpu = cpu;
|
debugger->cpu = cpu;
|
||||||
debugger->state = DEBUGGER_RUNNING;
|
debugger->state = DEBUGGER_RUNNING;
|
||||||
debugger->breakpoints = 0;
|
debugger->breakpoints = 0;
|
||||||
|
debugger->swBreakpoints = 0;
|
||||||
debugger->originalMemory = cpu->memory;
|
debugger->originalMemory = cpu->memory;
|
||||||
debugger->watchpoints = 0;
|
debugger->watchpoints = 0;
|
||||||
debugger->currentBreakpoint = 0;
|
debugger->currentBreakpoint = 0;
|
||||||
|
|
|
@ -822,11 +822,15 @@ static void _sample(struct GBAAudio* audio) {
|
||||||
}
|
}
|
||||||
produced = blip_samples_avail(audio->left);
|
produced = blip_samples_avail(audio->left);
|
||||||
#endif
|
#endif
|
||||||
struct GBAThread* thread = GBAThreadGetContext();
|
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||||
if (thread && thread->stream) {
|
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||||
thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight);
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
GBASyncProduceAudio(audio->p->sync, produced >= audio->samples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||||
|
|
|
@ -88,8 +88,8 @@ static void _BgAffineSet(struct GBA* gba) {
|
||||||
// [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ]
|
// [ 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 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 ]
|
// [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
|
||||||
ox = cpu->memory.load32(cpu, offset, 0) / 256.f;
|
ox = (int32_t) cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||||
oy = cpu->memory.load32(cpu, offset + 4, 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);
|
cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0);
|
||||||
cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0);
|
cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0);
|
||||||
sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
||||||
|
@ -233,6 +233,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
case 0x12:
|
case 0x12:
|
||||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source");
|
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||||
default:
|
default:
|
||||||
|
@ -247,6 +248,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
case 0x13:
|
case 0x13:
|
||||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source");
|
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||||
default:
|
default:
|
||||||
|
@ -262,6 +264,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
case 0x15:
|
case 0x15:
|
||||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source");
|
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||||
default:
|
default:
|
||||||
|
@ -278,6 +281,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
||||||
case 0x18:
|
case 0x18:
|
||||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source");
|
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -47,6 +47,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||||
struct GBA* gba = (struct GBA*) component;
|
struct GBA* gba = (struct GBA*) component;
|
||||||
gba->cpu = cpu;
|
gba->cpu = cpu;
|
||||||
gba->debugger = 0;
|
gba->debugger = 0;
|
||||||
|
gba->sync = 0;
|
||||||
|
|
||||||
GBAInterruptHandlerInit(&cpu->irqh);
|
GBAInterruptHandlerInit(&cpu->irqh);
|
||||||
GBAMemoryInit(gba);
|
GBAMemoryInit(gba);
|
||||||
|
@ -77,7 +78,9 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||||
gba->romVf = 0;
|
gba->romVf = 0;
|
||||||
gba->biosVf = 0;
|
gba->biosVf = 0;
|
||||||
|
|
||||||
|
gba->logHandler = 0;
|
||||||
gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||||
|
gba->stream = 0;
|
||||||
|
|
||||||
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||||
|
|
||||||
|
@ -106,7 +109,7 @@ void GBADestroy(struct GBA* gba) {
|
||||||
GBAVideoDeinit(&gba->video);
|
GBAVideoDeinit(&gba->video);
|
||||||
GBAAudioDeinit(&gba->audio);
|
GBAAudioDeinit(&gba->audio);
|
||||||
GBASIODeinit(&gba->sio);
|
GBASIODeinit(&gba->sio);
|
||||||
GBARRContextDestroy(gba);
|
gba->rr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
||||||
|
@ -130,7 +133,7 @@ void GBAReset(struct ARMCore* cpu) {
|
||||||
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
||||||
|
|
||||||
struct GBA* gba = (struct GBA*) cpu->master;
|
struct GBA* gba = (struct GBA*) cpu->master;
|
||||||
if (!GBARRIsPlaying(gba->rr) && !GBARRIsRecording(gba->rr)) {
|
if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) {
|
||||||
GBASavedataUnmask(&gba->memory.savedata);
|
GBASavedataUnmask(&gba->memory.savedata);
|
||||||
}
|
}
|
||||||
GBAMemoryReset(gba);
|
GBAMemoryReset(gba);
|
||||||
|
@ -547,10 +550,10 @@ static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format
|
||||||
threadContext->state = THREAD_CRASHED;
|
threadContext->state = THREAD_CRASHED;
|
||||||
MutexUnlock(&threadContext->stateMutex);
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
}
|
}
|
||||||
if (threadContext->logHandler) {
|
}
|
||||||
threadContext->logHandler(threadContext, level, format, args);
|
if (gba && gba->logHandler) {
|
||||||
return;
|
gba->logHandler(threadContext, level, format, args);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vprintf(format, args);
|
vprintf(format, args);
|
||||||
|
@ -713,10 +716,10 @@ void GBAFrameStarted(struct GBA* gba) {
|
||||||
|
|
||||||
void GBAFrameEnded(struct GBA* gba) {
|
void GBAFrameEnded(struct GBA* gba) {
|
||||||
if (gba->rr) {
|
if (gba->rr) {
|
||||||
GBARRNextFrame(gba->rr);
|
gba->rr->nextFrame(gba->rr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
if (gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||||
struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
|
struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||||
|
@ -733,8 +736,8 @@ void GBAFrameEnded(struct GBA* gba) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread->stream) {
|
if (gba->stream) {
|
||||||
thread->stream->postVideoFrame(thread->stream, thread->renderer);
|
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread->frameCallback) {
|
if (thread->frameCallback) {
|
||||||
|
|
|
@ -90,9 +90,18 @@ enum {
|
||||||
|
|
||||||
struct GBA;
|
struct GBA;
|
||||||
struct GBARotationSource;
|
struct GBARotationSource;
|
||||||
|
struct GBAThread;
|
||||||
struct Patch;
|
struct Patch;
|
||||||
struct VFile;
|
struct VFile;
|
||||||
|
|
||||||
|
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||||
|
|
||||||
|
struct GBAAVStream {
|
||||||
|
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
|
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||||
|
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||||
|
};
|
||||||
|
|
||||||
struct GBATimer {
|
struct GBATimer {
|
||||||
uint16_t reload;
|
uint16_t reload;
|
||||||
uint16_t oldReload;
|
uint16_t oldReload;
|
||||||
|
@ -141,7 +150,9 @@ struct GBA {
|
||||||
|
|
||||||
const char* activeFile;
|
const char* activeFile;
|
||||||
|
|
||||||
int logLevel;
|
GBALogHandler logHandler;
|
||||||
|
enum GBALogLevel logLevel;
|
||||||
|
struct GBAAVStream* stream;
|
||||||
|
|
||||||
enum GBAIdleLoopOptimization idleOptimization;
|
enum GBAIdleLoopOptimization idleOptimization;
|
||||||
uint32_t idleLoop;
|
uint32_t idleLoop;
|
||||||
|
|
|
@ -81,8 +81,8 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
|
||||||
hw->rtc.bitsRead = 0;
|
hw->rtc.bitsRead = 0;
|
||||||
hw->rtc.bits = 0;
|
hw->rtc.bits = 0;
|
||||||
hw->rtc.commandActive = 0;
|
hw->rtc.commandActive = 0;
|
||||||
hw->rtc.command.packed = 0;
|
hw->rtc.command = 0;
|
||||||
hw->rtc.control.packed = 0x40;
|
hw->rtc.control = 0x40;
|
||||||
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,14 +139,14 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (!hw->p0) {
|
if (!(hw->pinState & 1)) {
|
||||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
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 {
|
} else {
|
||||||
if (hw->p2) {
|
if (hw->pinState & 4) {
|
||||||
// GPIO direction should always != reading
|
// GPIO direction should always != reading
|
||||||
if (hw->dir1) {
|
if (hw->direction & 2) {
|
||||||
if (hw->rtc.command.reading) {
|
if (RTCCommandDataIsReading(hw->rtc.command)) {
|
||||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
||||||
}
|
}
|
||||||
++hw->rtc.bitsRead;
|
++hw->rtc.bitsRead;
|
||||||
|
@ -160,7 +160,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||||
--hw->rtc.bytesRemaining;
|
--hw->rtc.bytesRemaining;
|
||||||
if (hw->rtc.bytesRemaining <= 0) {
|
if (hw->rtc.bytesRemaining <= 0) {
|
||||||
hw->rtc.commandActive = 0;
|
hw->rtc.commandActive = 0;
|
||||||
hw->rtc.command.reading = 0;
|
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||||
}
|
}
|
||||||
hw->rtc.bitsRead = 0;
|
hw->rtc.bitsRead = 0;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||||
hw->rtc.bitsRead = 0;
|
hw->rtc.bitsRead = 0;
|
||||||
hw->rtc.bytesRemaining = 0;
|
hw->rtc.bytesRemaining = 0;
|
||||||
hw->rtc.commandActive = 0;
|
hw->rtc.commandActive = 0;
|
||||||
hw->rtc.command.reading = 0;
|
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||||
hw->rtc.transferStep = 0;
|
hw->rtc.transferStep = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,16 +180,16 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||||
--hw->rtc.bytesRemaining;
|
--hw->rtc.bytesRemaining;
|
||||||
if (!hw->rtc.commandActive) {
|
if (!hw->rtc.commandActive) {
|
||||||
union RTCCommandData command;
|
RTCCommandData command;
|
||||||
command.packed = hw->rtc.bits;
|
command = hw->rtc.bits;
|
||||||
if (command.magic == 0x06) {
|
if (RTCCommandDataGetMagic(command) == 0x06) {
|
||||||
hw->rtc.command = command;
|
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;
|
hw->rtc.commandActive = hw->rtc.bytesRemaining > 0;
|
||||||
switch (command.command) {
|
switch (RTCCommandDataGetCommand(command)) {
|
||||||
case RTC_RESET:
|
case RTC_RESET:
|
||||||
hw->rtc.control.packed = 0;
|
hw->rtc.control = 0;
|
||||||
break;
|
break;
|
||||||
case RTC_DATETIME:
|
case RTC_DATETIME:
|
||||||
case RTC_TIME:
|
case RTC_TIME:
|
||||||
|
@ -203,12 +203,12 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||||
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (hw->rtc.command.command) {
|
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||||
case RTC_CONTROL:
|
case RTC_CONTROL:
|
||||||
hw->rtc.control.packed = hw->rtc.bits;
|
hw->rtc.control = hw->rtc.bits;
|
||||||
break;
|
break;
|
||||||
case RTC_FORCE_IRQ:
|
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;
|
break;
|
||||||
case RTC_RESET:
|
case RTC_RESET:
|
||||||
case RTC_DATETIME:
|
case RTC_DATETIME:
|
||||||
|
@ -221,15 +221,15 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||||
hw->rtc.bitsRead = 0;
|
hw->rtc.bitsRead = 0;
|
||||||
if (!hw->rtc.bytesRemaining) {
|
if (!hw->rtc.bytesRemaining) {
|
||||||
hw->rtc.commandActive = 0;
|
hw->rtc.commandActive = 0;
|
||||||
hw->rtc.command.reading = 0;
|
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
||||||
uint8_t outputByte = 0;
|
uint8_t outputByte = 0;
|
||||||
switch (hw->rtc.command.command) {
|
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||||
case RTC_CONTROL:
|
case RTC_CONTROL:
|
||||||
outputByte = hw->rtc.control.packed;
|
outputByte = hw->rtc.control;
|
||||||
break;
|
break;
|
||||||
case RTC_DATETIME:
|
case RTC_DATETIME:
|
||||||
case RTC_TIME:
|
case RTC_TIME:
|
||||||
|
@ -262,7 +262,7 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
|
||||||
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
||||||
hw->rtc.time[2] = _rtcBCD(date.tm_mday);
|
hw->rtc.time[2] = _rtcBCD(date.tm_mday);
|
||||||
hw->rtc.time[3] = _rtcBCD(date.tm_wday);
|
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);
|
hw->rtc.time[4] = _rtcBCD(date.tm_hour);
|
||||||
} else {
|
} else {
|
||||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
||||||
|
@ -292,7 +292,7 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw->p0) {
|
if (hw->pinState & 1) {
|
||||||
if (gyro->sample) {
|
if (gyro->sample) {
|
||||||
gyro->sample(gyro);
|
gyro->sample(gyro);
|
||||||
}
|
}
|
||||||
|
@ -302,14 +302,14 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||||
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
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
|
// Write bit on falling edge
|
||||||
unsigned bit = hw->gyroSample >> 15;
|
unsigned bit = hw->gyroSample >> 15;
|
||||||
hw->gyroSample <<= 1;
|
hw->gyroSample <<= 1;
|
||||||
_outputPins(hw, bit << 2);
|
_outputPins(hw, bit << 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
hw->gyroEdge = hw->p1;
|
hw->gyroEdge = !!(hw->pinState & 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == Rumble
|
// == Rumble
|
||||||
|
@ -324,7 +324,7 @@ void _rumbleReadPins(struct GBACartridgeHardware* hw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
rumble->setRumble(rumble, hw->p3);
|
rumble->setRumble(rumble, !!(hw->pinState & 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// == Light sensor
|
// == Light sensor
|
||||||
|
@ -337,11 +337,11 @@ void GBAHardwareInitLight(struct GBACartridgeHardware* hw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _lightReadPins(struct GBACartridgeHardware* hw) {
|
void _lightReadPins(struct GBACartridgeHardware* hw) {
|
||||||
if (hw->p2) {
|
if (hw->pinState & 4) {
|
||||||
// Boktai chip select
|
// Boktai chip select
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hw->p1) {
|
if (hw->pinState & 2) {
|
||||||
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
||||||
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset");
|
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset");
|
||||||
hw->lightCounter = 0;
|
hw->lightCounter = 0;
|
||||||
|
@ -352,10 +352,10 @@ void _lightReadPins(struct GBACartridgeHardware* hw) {
|
||||||
hw->lightSample = 0xFF;
|
hw->lightSample = 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hw->p0 && hw->lightEdge) {
|
if ((hw->pinState & 1) && hw->lightEdge) {
|
||||||
++hw->lightCounter;
|
++hw->lightCounter;
|
||||||
}
|
}
|
||||||
hw->lightEdge = !hw->p0;
|
hw->lightEdge = !(hw->pinState & 1);
|
||||||
|
|
||||||
bool sendBit = hw->lightCounter >= hw->lightSample;
|
bool sendBit = hw->lightCounter >= hw->lightSample;
|
||||||
_outputPins(hw, sendBit << 3);
|
_outputPins(hw, sendBit << 3);
|
||||||
|
@ -424,7 +424,7 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
|
||||||
|
|
||||||
// == Serialization
|
// == Serialization
|
||||||
|
|
||||||
void GBAHardwareSerialize(struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||||
state->hw.readWrite = hw->readWrite;
|
state->hw.readWrite = hw->readWrite;
|
||||||
state->hw.pinState = hw->pinState;
|
state->hw.pinState = hw->pinState;
|
||||||
state->hw.pinDirection = hw->direction;
|
state->hw.pinDirection = hw->direction;
|
||||||
|
@ -440,7 +440,7 @@ void GBAHardwareSerialize(struct GBACartridgeHardware* hw, struct GBASerializedS
|
||||||
state->hw.lightEdge = hw->lightEdge;
|
state->hw.lightEdge = hw->lightEdge;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||||
hw->readWrite = state->hw.readWrite;
|
hw->readWrite = state->hw.readWrite;
|
||||||
hw->pinState = state->hw.pinState;
|
hw->pinState = state->hw.pinState;
|
||||||
hw->direction = state->hw.pinDirection;
|
hw->direction = state->hw.pinDirection;
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
#include "util/common.h"
|
#include "util/common.h"
|
||||||
|
|
||||||
|
#include "macros.h"
|
||||||
|
|
||||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||||
|
|
||||||
struct GBARotationSource {
|
struct GBARotationSource {
|
||||||
|
@ -52,16 +54,10 @@ enum GPIODirection {
|
||||||
GPIO_READ_WRITE = 1
|
GPIO_READ_WRITE = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
union RTCControl {
|
DECL_BITFIELD(RTCControl, uint8_t);
|
||||||
struct {
|
DECL_BIT(RTCControl, MinIRQ, 3);
|
||||||
unsigned : 3;
|
DECL_BIT(RTCControl, Hour24, 6);
|
||||||
unsigned minIRQ : 1;
|
DECL_BIT(RTCControl, Poweroff, 7);
|
||||||
unsigned : 2;
|
|
||||||
unsigned hour24 : 1;
|
|
||||||
unsigned poweroff : 1;
|
|
||||||
};
|
|
||||||
uint8_t packed;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum RTCCommand {
|
enum RTCCommand {
|
||||||
RTC_RESET = 0,
|
RTC_RESET = 0,
|
||||||
|
@ -71,14 +67,10 @@ enum RTCCommand {
|
||||||
RTC_TIME = 6
|
RTC_TIME = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
union RTCCommandData {
|
DECL_BITFIELD(RTCCommandData, uint8_t);
|
||||||
struct {
|
DECL_BITS(RTCCommandData, Magic, 0, 4);
|
||||||
unsigned magic : 4;
|
DECL_BITS(RTCCommandData, Command, 4, 3);
|
||||||
enum RTCCommand command : 3;
|
DECL_BIT(RTCCommandData, Reading, 7);
|
||||||
unsigned reading : 1;
|
|
||||||
};
|
|
||||||
uint8_t packed;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBARTC {
|
struct GBARTC {
|
||||||
int bytesRemaining;
|
int bytesRemaining;
|
||||||
|
@ -86,8 +78,8 @@ struct GBARTC {
|
||||||
int bitsRead;
|
int bitsRead;
|
||||||
int bits;
|
int bits;
|
||||||
int commandActive;
|
int commandActive;
|
||||||
union RTCCommandData command;
|
RTCCommandData command;
|
||||||
union RTCControl control;
|
RTCControl control;
|
||||||
uint8_t time[7];
|
uint8_t time[7];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
@ -95,31 +87,16 @@ struct GBARumble {
|
||||||
void (*setRumble)(struct GBARumble*, int enable);
|
void (*setRumble)(struct GBARumble*, int enable);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||||
|
|
||||||
struct GBACartridgeHardware {
|
struct GBACartridgeHardware {
|
||||||
struct GBA* p;
|
struct GBA* p;
|
||||||
int devices;
|
int devices;
|
||||||
enum GPIODirection readWrite;
|
enum GPIODirection readWrite;
|
||||||
uint16_t* gpioBase;
|
uint16_t* gpioBase;
|
||||||
|
|
||||||
union {
|
uint16_t pinState;
|
||||||
struct {
|
uint16_t direction;
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBARTC rtc;
|
struct GBARTC rtc;
|
||||||
|
|
||||||
|
@ -149,7 +126,7 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
|
||||||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||||
|
|
||||||
struct GBASerializedState;
|
struct GBASerializedState;
|
||||||
void GBAHardwareSerialize(struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
||||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, const struct GBASerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
166
src/gba/input.c
166
src/gba/input.c
|
@ -24,7 +24,7 @@ struct GBAInputMapImpl {
|
||||||
|
|
||||||
struct GBAAxisSave {
|
struct GBAAxisSave {
|
||||||
struct Configuration* config;
|
struct Configuration* config;
|
||||||
uint32_t type;
|
const char* sectionName;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBAAxisEnumerate {
|
struct GBAAxisEnumerate {
|
||||||
|
@ -45,6 +45,11 @@ const char* GBAKeyNames[] = {
|
||||||
"L"
|
"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) {
|
static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) {
|
||||||
const char* strValue = ConfigurationGetValue(config, section, key);
|
const char* strValue = ConfigurationGetValue(config, section, key);
|
||||||
if (!strValue) {
|
if (!strValue) {
|
||||||
|
@ -122,11 +127,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
||||||
return impl;
|
return impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
static void _loadKey(struct GBAInputMap* map, uint32_t type, const char* sectionName, 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';
|
|
||||||
|
|
||||||
char keyKey[KEY_NAME_MAX];
|
char keyKey[KEY_NAME_MAX];
|
||||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||||
|
@ -138,11 +139,7 @@ static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Config
|
||||||
GBAInputBindKey(map, type, value, key);
|
GBAInputBindKey(map, type, value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const char* sectionName, 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';
|
|
||||||
|
|
||||||
char axisKey[KEY_NAME_MAX];
|
char axisKey[KEY_NAME_MAX];
|
||||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||||
|
@ -179,11 +176,7 @@ static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Confi
|
||||||
GBAInputBindAxis(map, type, axis, &realDescription);
|
GBAInputBindAxis(map, type, axis, &realDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
static void _saveKey(const struct GBAInputMap* map, uint32_t type, const char* sectionName, 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';
|
|
||||||
|
|
||||||
char keyKey[KEY_NAME_MAX];
|
char keyKey[KEY_NAME_MAX];
|
||||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||||
|
@ -195,11 +188,7 @@ static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Config
|
||||||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) {
|
static void _clearAxis(const char* sectionName, 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';
|
|
||||||
|
|
||||||
char axisKey[KEY_NAME_MAX];
|
char axisKey[KEY_NAME_MAX];
|
||||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||||
|
@ -214,10 +203,7 @@ static void _saveAxis(uint32_t axis, void* dp, void* up) {
|
||||||
struct GBAAxisSave* user = up;
|
struct GBAAxisSave* user = up;
|
||||||
const struct GBAAxis* description = dp;
|
const struct GBAAxis* description = dp;
|
||||||
|
|
||||||
uint32_t type = user->type;
|
const char* sectionName = user->sectionName;
|
||||||
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';
|
|
||||||
|
|
||||||
if (description->lowDirection != GBA_KEY_NONE) {
|
if (description->lowDirection != GBA_KEY_NONE) {
|
||||||
const char* keyName = GBAKeyNames[description->lowDirection];
|
const char* keyName = GBAKeyNames[description->lowDirection];
|
||||||
|
@ -271,6 +257,64 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||||
|
_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) {
|
void GBAInputMapInit(struct GBAInputMap* map) {
|
||||||
map->maps = 0;
|
map->maps = 0;
|
||||||
map->numMaps = 0;
|
map->numMaps = 0;
|
||||||
|
@ -414,59 +458,27 @@ void GBAInputEnumerateAxes(const struct GBAInputMap* map, uint32_t type, void (h
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
|
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
|
||||||
_loadKey(map, type, config, GBA_KEY_A, "A");
|
char sectionName[SECTION_NAME_MAX];
|
||||||
_loadKey(map, type, config, GBA_KEY_B, "B");
|
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||||
_loadKey(map, type, config, GBA_KEY_L, "L");
|
_loadAll(map, type, sectionName, config);
|
||||||
_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");
|
|
||||||
|
|
||||||
_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) {
|
void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) {
|
||||||
_saveKey(map, type, config, GBA_KEY_A, "A");
|
char sectionName[SECTION_NAME_MAX];
|
||||||
_saveKey(map, type, config, GBA_KEY_B, "B");
|
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||||
_saveKey(map, type, config, GBA_KEY_L, "L");
|
_saveAll(map, type, sectionName, config);
|
||||||
_saveKey(map, type, config, GBA_KEY_R, "R");
|
}
|
||||||
_saveKey(map, type, config, GBA_KEY_START, "Start");
|
|
||||||
_saveKey(map, type, config, GBA_KEY_SELECT, "Select");
|
void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||||
_saveKey(map, type, config, GBA_KEY_UP, "Up");
|
char sectionName[SECTION_NAME_MAX];
|
||||||
_saveKey(map, type, config, GBA_KEY_DOWN, "Down");
|
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||||
_saveKey(map, type, config, GBA_KEY_LEFT, "Left");
|
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||||
_saveKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
_loadAll(map, type, sectionName, config);
|
||||||
|
}
|
||||||
_clearAxis(type, config, "A");
|
|
||||||
_clearAxis(type, config, "B");
|
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||||
_clearAxis(type, config, "L");
|
char sectionName[SECTION_NAME_MAX];
|
||||||
_clearAxis(type, config, "R");
|
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||||
_clearAxis(type, config, "Start");
|
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||||
_clearAxis(type, config, "Select");
|
_saveAll(map, type, sectionName, config);
|
||||||
_clearAxis(type, config, "Up");
|
|
||||||
_clearAxis(type, config, "Down");
|
|
||||||
_clearAxis(type, config, "Left");
|
|
||||||
_clearAxis(type, config, "Right");
|
|
||||||
|
|
||||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
|
||||||
if (!impl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
struct GBAAxisSave save = {
|
|
||||||
config,
|
|
||||||
type
|
|
||||||
};
|
|
||||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ void GBAInputMapDeinit(struct GBAInputMap*);
|
||||||
|
|
||||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
||||||
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
||||||
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, int key);
|
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||||
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||||
|
|
||||||
enum GBAKey GBAInputMapAxis(const struct GBAInputMap*, uint32_t type, int axis, int value);
|
enum GBAKey GBAInputMapAxis(const struct GBAInputMap*, uint32_t type, int axis, int value);
|
||||||
|
@ -45,4 +45,7 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
|
||||||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||||
|
|
||||||
|
void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||||
|
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
12
src/gba/io.c
12
src/gba/io.c
|
@ -172,7 +172,7 @@ const char* GBAIORegisterNames[] = {
|
||||||
"JOY_RECV_LO",
|
"JOY_RECV_LO",
|
||||||
"JOY_RECV_HI",
|
"JOY_RECV_HI",
|
||||||
"JOY_TRANS_LO",
|
"JOY_TRANS_LO",
|
||||||
"JOY_RECV_HI",
|
"JOY_TRANS_HI",
|
||||||
"JOYSTAT",
|
"JOYSTAT",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -583,12 +583,12 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REG_KEYINPUT:
|
case REG_KEYINPUT:
|
||||||
if (GBARRIsPlaying(gba->rr)) {
|
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||||
return 0x3FF ^ GBARRQueryInput(gba->rr);
|
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||||
} else if (gba->keySource) {
|
} else if (gba->keySource) {
|
||||||
uint16_t input = *gba->keySource;
|
uint16_t input = *gba->keySource;
|
||||||
if (GBARRIsRecording(gba->rr)) {
|
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||||
GBARRLogInput(gba->rr, input);
|
gba->rr->logInput(gba->rr, input);
|
||||||
}
|
}
|
||||||
return 0x3FF ^ input;
|
return 0x3FF ^ input;
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||||
GBAHardwareSerialize(&gba->memory.hw, state);
|
GBAHardwareSerialize(&gba->memory.hw, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < REG_MAX; i += 2) {
|
for (i = 0; i < REG_MAX; i += 2) {
|
||||||
if (_isSpecialRegister[i >> 1]) {
|
if (_isSpecialRegister[i >> 1]) {
|
||||||
|
|
|
@ -161,6 +161,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address);
|
||||||
|
|
||||||
struct GBASerializedState;
|
struct GBASerializedState;
|
||||||
void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state);
|
void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||||
void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state);
|
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1461,12 +1461,12 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
||||||
cpu->cycles += cycles;
|
cpu->cycles += cycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state) {
|
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state) {
|
||||||
memcpy(state->wram, memory->wram, SIZE_WORKING_RAM);
|
memcpy(state->wram, memory->wram, SIZE_WORKING_RAM);
|
||||||
memcpy(state->iwram, memory->iwram, SIZE_WORKING_IRAM);
|
memcpy(state->iwram, memory->iwram, SIZE_WORKING_IRAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state) {
|
void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state) {
|
||||||
memcpy(memory->wram, state->wram, SIZE_WORKING_RAM);
|
memcpy(memory->wram, state->wram, SIZE_WORKING_RAM);
|
||||||
memcpy(memory->iwram, state->iwram, SIZE_WORKING_IRAM);
|
memcpy(memory->iwram, state->iwram, SIZE_WORKING_IRAM);
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,7 +172,7 @@ void GBAMemoryUpdateDMAs(struct GBA* gba, int32_t cycles);
|
||||||
int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles);
|
int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles);
|
||||||
|
|
||||||
struct GBASerializedState;
|
struct GBASerializedState;
|
||||||
void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state);
|
||||||
void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,6 +37,7 @@ static const int _objSizes[32] = {
|
||||||
|
|
||||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
|
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
|
||||||
static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
|
static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||||
|
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
|
||||||
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||||
static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||||
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||||
|
@ -77,7 +78,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
||||||
|
|
||||||
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
||||||
renderer->d.init = GBAVideoSoftwareRendererInit;
|
renderer->d.init = GBAVideoSoftwareRendererInit;
|
||||||
renderer->d.reset = GBAVideoSoftwareRendererInit;
|
renderer->d.reset = GBAVideoSoftwareRendererReset;
|
||||||
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
|
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
|
||||||
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
|
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
|
||||||
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
|
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
|
||||||
|
@ -89,6 +90,21 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
|
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
|
||||||
|
GBAVideoSoftwareRendererReset(renderer);
|
||||||
|
|
||||||
|
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||||
|
|
||||||
|
int y;
|
||||||
|
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||||
|
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||||
|
row[x] = GBA_COLOR_WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
|
||||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -544,7 +560,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COLOR_16_BIT
|
#ifdef COLOR_16_BIT
|
||||||
#ifdef __arm__
|
#ifdef __ARM_NEON
|
||||||
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
||||||
#else
|
#else
|
||||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||||
|
|
|
@ -0,0 +1,578 @@
|
||||||
|
/* 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 "mgm.h"
|
||||||
|
|
||||||
|
#include "gba/gba.h"
|
||||||
|
#include "gba/serialize.h"
|
||||||
|
#include "util/vfs.h"
|
||||||
|
|
||||||
|
#define BINARY_EXT ".mgm"
|
||||||
|
#define BINARY_MAGIC "GBAb"
|
||||||
|
#define METADATA_FILENAME "metadata" BINARY_EXT
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INVALID_INPUT = 0x8000
|
||||||
|
};
|
||||||
|
|
||||||
|
static void GBAMGMContextDestroy(struct GBARRContext*);
|
||||||
|
|
||||||
|
static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||||
|
static void GBAMGMStopPlaying(struct GBARRContext*);
|
||||||
|
static bool GBAMGMStartRecording(struct GBARRContext*);
|
||||||
|
static void GBAMGMStopRecording(struct GBARRContext*);
|
||||||
|
|
||||||
|
static bool GBAMGMIsPlaying(const struct GBARRContext*);
|
||||||
|
static bool GBAMGMIsRecording(const struct GBARRContext*);
|
||||||
|
|
||||||
|
static void GBAMGMNextFrame(struct GBARRContext*);
|
||||||
|
static void GBAMGMLogInput(struct GBARRContext*, uint16_t input);
|
||||||
|
static uint16_t GBAMGMQueryInput(struct GBARRContext*);
|
||||||
|
|
||||||
|
static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||||
|
static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||||
|
|
||||||
|
static bool _loadStream(struct GBAMGMContext*, uint32_t streamId);
|
||||||
|
static bool _incrementStream(struct GBAMGMContext*, bool recursive);
|
||||||
|
static bool _finishSegment(struct GBAMGMContext*);
|
||||||
|
static bool _skipSegment(struct GBAMGMContext*);
|
||||||
|
static bool _markRerecord(struct GBAMGMContext*);
|
||||||
|
|
||||||
|
static bool _emitMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||||
|
static bool _verifyMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||||
|
static enum GBAMGMTag _readTag(struct GBAMGMContext*, struct VFile* vf);
|
||||||
|
static bool _seekTag(struct GBAMGMContext*, struct VFile* vf, enum GBAMGMTag tag);
|
||||||
|
static bool _emitTag(struct GBAMGMContext*, struct VFile* vf, uint8_t tag);
|
||||||
|
static bool _emitEnd(struct GBAMGMContext*, struct VFile* vf);
|
||||||
|
|
||||||
|
static bool _parseMetadata(struct GBAMGMContext*, struct VFile* vf);
|
||||||
|
|
||||||
|
static bool _markStreamNext(struct GBAMGMContext*, uint32_t newStreamId, bool recursive);
|
||||||
|
static void _streamEndReached(struct GBAMGMContext*);
|
||||||
|
|
||||||
|
static struct VFile* GBAMGMOpenSavedata(struct GBARRContext*, int flags);
|
||||||
|
static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags);
|
||||||
|
|
||||||
|
void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
|
||||||
|
memset(mgm, 0, sizeof(*mgm));
|
||||||
|
|
||||||
|
mgm->d.destroy = GBAMGMContextDestroy;
|
||||||
|
|
||||||
|
mgm->d.startPlaying = GBAMGMStartPlaying;
|
||||||
|
mgm->d.stopPlaying = GBAMGMStopPlaying;
|
||||||
|
mgm->d.startRecording = GBAMGMStartRecording;
|
||||||
|
mgm->d.stopRecording = GBAMGMStopRecording;
|
||||||
|
|
||||||
|
mgm->d.isPlaying = GBAMGMIsPlaying;
|
||||||
|
mgm->d.isRecording = GBAMGMIsRecording;
|
||||||
|
|
||||||
|
mgm->d.nextFrame = GBAMGMNextFrame;
|
||||||
|
mgm->d.logInput = GBAMGMLogInput;
|
||||||
|
mgm->d.queryInput = GBAMGMQueryInput;
|
||||||
|
|
||||||
|
mgm->d.stateSaved = GBAMGMStateSaved;
|
||||||
|
mgm->d.stateLoaded = GBAMGMStateLoaded;
|
||||||
|
|
||||||
|
mgm->d.openSavedata = GBAMGMOpenSavedata;
|
||||||
|
mgm->d.openSavestate = GBAMGMOpenSavestate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMContextDestroy(struct GBARRContext* rr) {
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (mgm->metadataFile) {
|
||||||
|
mgm->metadataFile->close(mgm->metadataFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) {
|
||||||
|
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mgm->metadataFile && !mgm->metadataFile->close(mgm->metadataFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mgm->streamDir = stream;
|
||||||
|
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
||||||
|
mgm->currentInput = INVALID_INPUT;
|
||||||
|
if (!_parseMetadata(mgm, mgm->metadataFile)) {
|
||||||
|
mgm->metadataFile->close(mgm->metadataFile);
|
||||||
|
mgm->metadataFile = 0;
|
||||||
|
mgm->maxStreamId = 0;
|
||||||
|
}
|
||||||
|
mgm->streamId = 1;
|
||||||
|
mgm->movieStream = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom) {
|
||||||
|
if (mgm->metadataFile) {
|
||||||
|
mgm->metadataFile->truncate(mgm->metadataFile, 0);
|
||||||
|
} else {
|
||||||
|
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
|
||||||
|
}
|
||||||
|
_emitMagic(mgm, mgm->metadataFile);
|
||||||
|
|
||||||
|
mgm->d.initFrom = initFrom;
|
||||||
|
mgm->initFromOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||||
|
_emitTag(mgm, mgm->metadataFile, TAG_INIT | initFrom);
|
||||||
|
|
||||||
|
mgm->streamId = 0;
|
||||||
|
mgm->maxStreamId = 0;
|
||||||
|
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||||
|
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||||
|
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||||
|
|
||||||
|
mgm->d.rrCount = 0;
|
||||||
|
_emitTag(mgm, mgm->metadataFile, TAG_RR_COUNT);
|
||||||
|
mgm->rrCountOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||||
|
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) {
|
||||||
|
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mgm->movieStream = 0;
|
||||||
|
mgm->streamId = streamId;
|
||||||
|
mgm->currentInput = INVALID_INPUT;
|
||||||
|
char buffer[14];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
|
||||||
|
if (mgm->d.isRecording(&mgm->d)) {
|
||||||
|
int flags = O_CREAT | O_RDWR;
|
||||||
|
if (streamId > mgm->maxStreamId) {
|
||||||
|
flags |= O_TRUNC;
|
||||||
|
}
|
||||||
|
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags);
|
||||||
|
} else if (mgm->d.isPlaying(&mgm->d)) {
|
||||||
|
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY);
|
||||||
|
mgm->peekedTag = TAG_INVALID;
|
||||||
|
if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) {
|
||||||
|
mgm->d.stopPlaying(&mgm->d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
|
||||||
|
mgm->d.frames = 0;
|
||||||
|
mgm->d.lagFrames = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) {
|
||||||
|
uint32_t newStreamId = mgm->maxStreamId + 1;
|
||||||
|
uint32_t oldStreamId = mgm->streamId;
|
||||||
|
if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) {
|
||||||
|
if (!_markStreamNext(mgm, newStreamId, recursive)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_loadStream(mgm, newStreamId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
|
||||||
|
_emitMagic(mgm, mgm->movieStream);
|
||||||
|
mgm->maxStreamId = newStreamId;
|
||||||
|
_emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY);
|
||||||
|
mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId));
|
||||||
|
_emitTag(mgm, mgm->movieStream, TAG_BEGIN);
|
||||||
|
|
||||||
|
mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET);
|
||||||
|
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||||
|
mgm->previously = oldStreamId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||||
|
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
mgm->isPlaying = true;
|
||||||
|
if (!_loadStream(mgm, 1)) {
|
||||||
|
mgm->isPlaying = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mgm->autorecord = autorecord;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMStopPlaying(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isPlaying(rr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
mgm->isPlaying = false;
|
||||||
|
if (mgm->movieStream) {
|
||||||
|
mgm->movieStream->close(mgm->movieStream);
|
||||||
|
mgm->movieStream = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMStartRecording(struct GBARRContext* rr) {
|
||||||
|
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (!mgm->maxStreamIdOffset) {
|
||||||
|
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||||
|
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||||
|
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
mgm->isRecording = true;
|
||||||
|
return _incrementStream(mgm, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMStopRecording(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isRecording(rr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
mgm->isRecording = false;
|
||||||
|
if (mgm->movieStream) {
|
||||||
|
_emitEnd(mgm, mgm->movieStream);
|
||||||
|
mgm->movieStream->close(mgm->movieStream);
|
||||||
|
mgm->movieStream = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMIsPlaying(const struct GBARRContext* rr) {
|
||||||
|
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||||
|
return mgm->isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAMGMIsRecording(const struct GBARRContext* rr) {
|
||||||
|
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||||
|
return mgm->isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMNextFrame(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isRecording(rr) && !rr->isPlaying(rr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (rr->isPlaying(rr)) {
|
||||||
|
while (mgm->peekedTag == TAG_INPUT) {
|
||||||
|
_readTag(mgm, mgm->movieStream);
|
||||||
|
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
|
||||||
|
}
|
||||||
|
if (mgm->peekedTag == TAG_LAG) {
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
|
||||||
|
if (mgm->inputThisFrame) {
|
||||||
|
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++mgm->d.frames;
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", mgm->d.frames);
|
||||||
|
if (!mgm->inputThisFrame) {
|
||||||
|
++mgm->d.lagFrames;
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", mgm->d.lagFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rr->isRecording(rr)) {
|
||||||
|
if (!mgm->inputThisFrame) {
|
||||||
|
_emitTag(mgm, mgm->movieStream, TAG_LAG);
|
||||||
|
}
|
||||||
|
_emitTag(mgm, mgm->movieStream, TAG_FRAME);
|
||||||
|
mgm->inputThisFrame = false;
|
||||||
|
} else {
|
||||||
|
if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) {
|
||||||
|
_streamEndReached(mgm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) {
|
||||||
|
if (!rr->isRecording(rr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (keys != mgm->currentInput) {
|
||||||
|
_emitTag(mgm, mgm->movieStream, TAG_INPUT);
|
||||||
|
mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys));
|
||||||
|
mgm->currentInput = keys;
|
||||||
|
}
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", mgm->currentInput);
|
||||||
|
mgm->inputThisFrame = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t GBAMGMQueryInput(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isPlaying(rr)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (mgm->peekedTag == TAG_INPUT) {
|
||||||
|
_readTag(mgm, mgm->movieStream);
|
||||||
|
}
|
||||||
|
mgm->inputThisFrame = true;
|
||||||
|
if (mgm->currentInput == INVALID_INPUT) {
|
||||||
|
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
|
||||||
|
}
|
||||||
|
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", mgm->currentInput);
|
||||||
|
return mgm->currentInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (rr->isRecording(rr)) {
|
||||||
|
state->associatedStreamId = mgm->streamId;
|
||||||
|
_finishSegment(mgm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
if (rr->isRecording(rr)) {
|
||||||
|
if (state->associatedStreamId != mgm->streamId) {
|
||||||
|
_loadStream(mgm, state->associatedStreamId);
|
||||||
|
_incrementStream(mgm, true);
|
||||||
|
} else {
|
||||||
|
_finishSegment(mgm);
|
||||||
|
}
|
||||||
|
_markRerecord(mgm);
|
||||||
|
} else if (rr->isPlaying(rr)) {
|
||||||
|
_loadStream(mgm, state->associatedStreamId);
|
||||||
|
_skipSegment(mgm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _finishSegment(struct GBAMGMContext* mgm) {
|
||||||
|
if (mgm->movieStream) {
|
||||||
|
if (!_emitEnd(mgm, mgm->movieStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _incrementStream(mgm, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _skipSegment(struct GBAMGMContext* mgm) {
|
||||||
|
mgm->nextTime = 0;
|
||||||
|
while (_readTag(mgm, mgm->movieStream) != TAG_EOF);
|
||||||
|
if (!mgm->nextTime || !_loadStream(mgm, mgm->nextTime)) {
|
||||||
|
_streamEndReached(mgm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _markRerecord(struct GBAMGMContext* mgm) {
|
||||||
|
++mgm->d.rrCount;
|
||||||
|
mgm->metadataFile->seek(mgm->metadataFile, mgm->rrCountOffset, SEEK_SET);
|
||||||
|
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _emitMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||||
|
UNUSED(mgm);
|
||||||
|
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _verifyMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||||
|
UNUSED(mgm);
|
||||||
|
char buffer[4];
|
||||||
|
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||||
|
if (!mgm || !vf) {
|
||||||
|
return TAG_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GBAMGMTag tag = mgm->peekedTag;
|
||||||
|
switch (tag) {
|
||||||
|
case TAG_INPUT:
|
||||||
|
vf->read(vf, &mgm->currentInput, sizeof(uint16_t));
|
||||||
|
break;
|
||||||
|
case TAG_PREVIOUSLY:
|
||||||
|
vf->read(vf, &mgm->previously, sizeof(mgm->previously));
|
||||||
|
break;
|
||||||
|
case TAG_NEXT_TIME:
|
||||||
|
vf->read(vf, &mgm->nextTime, sizeof(mgm->nextTime));
|
||||||
|
break;
|
||||||
|
case TAG_MAX_STREAM:
|
||||||
|
vf->read(vf, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||||
|
break;
|
||||||
|
case TAG_FRAME_COUNT:
|
||||||
|
vf->read(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||||
|
break;
|
||||||
|
case TAG_LAG_COUNT:
|
||||||
|
vf->read(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||||
|
break;
|
||||||
|
case TAG_RR_COUNT:
|
||||||
|
vf->read(vf, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TAG_INIT_EX_NIHILO:
|
||||||
|
mgm->d.initFrom = INIT_EX_NIHILO;
|
||||||
|
break;
|
||||||
|
case TAG_INIT_FROM_SAVEGAME:
|
||||||
|
mgm->d.initFrom = INIT_FROM_SAVEGAME;
|
||||||
|
break;
|
||||||
|
case TAG_INIT_FROM_SAVESTATE:
|
||||||
|
mgm->d.initFrom = INIT_FROM_SAVESTATE;
|
||||||
|
break;
|
||||||
|
case TAG_INIT_FROM_BOTH:
|
||||||
|
mgm->d.initFrom = INIT_FROM_BOTH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// To be spec'd
|
||||||
|
case TAG_AUTHOR:
|
||||||
|
case TAG_COMMENT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Empty markers
|
||||||
|
case TAG_FRAME:
|
||||||
|
case TAG_LAG:
|
||||||
|
case TAG_BEGIN:
|
||||||
|
case TAG_END:
|
||||||
|
case TAG_INVALID:
|
||||||
|
case TAG_EOF:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tagBuffer;
|
||||||
|
if (vf->read(vf, &tagBuffer, 1) != 1) {
|
||||||
|
mgm->peekedTag = TAG_EOF;
|
||||||
|
} else {
|
||||||
|
mgm->peekedTag = tagBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mgm->peekedTag == TAG_END) {
|
||||||
|
_skipSegment(mgm);
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _seekTag(struct GBAMGMContext* mgm, struct VFile* vf, enum GBAMGMTag tag) {
|
||||||
|
enum GBAMGMTag readTag;
|
||||||
|
while ((readTag = _readTag(mgm, vf)) != tag) {
|
||||||
|
if (readTag == TAG_EOF) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _emitTag(struct GBAMGMContext* mgm, struct VFile* vf, uint8_t tag) {
|
||||||
|
UNUSED(mgm);
|
||||||
|
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _parseMetadata(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||||
|
if (!_verifyMagic(mgm, vf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (_readTag(mgm, vf) != TAG_EOF) {
|
||||||
|
switch (mgm->peekedTag) {
|
||||||
|
case TAG_MAX_STREAM:
|
||||||
|
mgm->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
case TAG_INIT_EX_NIHILO:
|
||||||
|
case TAG_INIT_FROM_SAVEGAME:
|
||||||
|
case TAG_INIT_FROM_SAVESTATE:
|
||||||
|
case TAG_INIT_FROM_BOTH:
|
||||||
|
mgm->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
case TAG_RR_COUNT:
|
||||||
|
mgm->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _emitEnd(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||||
|
// TODO: Error check
|
||||||
|
_emitTag(mgm, vf, TAG_END);
|
||||||
|
_emitTag(mgm, vf, TAG_FRAME_COUNT);
|
||||||
|
vf->write(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||||
|
_emitTag(mgm, vf, TAG_LAG_COUNT);
|
||||||
|
vf->write(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||||
|
_emitTag(mgm, vf, TAG_NEXT_TIME);
|
||||||
|
|
||||||
|
uint32_t newStreamId = 0;
|
||||||
|
vf->write(vf, &newStreamId, sizeof(newStreamId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _markStreamNext(struct GBAMGMContext* mgm, uint32_t newStreamId, bool recursive) {
|
||||||
|
if (mgm->movieStream->seek(mgm->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tagBuffer;
|
||||||
|
if (mgm->movieStream->read(mgm->movieStream, &tagBuffer, 1) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tagBuffer != TAG_NEXT_TIME) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mgm->movieStream->write(mgm->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (recursive) {
|
||||||
|
if (mgm->movieStream->seek(mgm->movieStream, 0, SEEK_SET) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!_verifyMagic(mgm, mgm->movieStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_readTag(mgm, mgm->movieStream);
|
||||||
|
if (_readTag(mgm, mgm->movieStream) != TAG_PREVIOUSLY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mgm->previously == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint32_t currentStreamId = mgm->streamId;
|
||||||
|
if (!_loadStream(mgm, mgm->previously)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _markStreamNext(mgm, currentStreamId, mgm->previously);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _streamEndReached(struct GBAMGMContext* mgm) {
|
||||||
|
if (!mgm->d.isPlaying(&mgm->d)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t endStreamId = mgm->streamId;
|
||||||
|
mgm->d.stopPlaying(&mgm->d);
|
||||||
|
if (mgm->autorecord) {
|
||||||
|
mgm->isRecording = true;
|
||||||
|
_loadStream(mgm, endStreamId);
|
||||||
|
_incrementStream(mgm, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VFile* GBAMGMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
return mgm->streamDir->openFile(mgm->streamDir, "movie.sav", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VFile* GBAMGMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||||
|
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||||
|
return mgm->streamDir->openFile(mgm->streamDir, "movie.ssm", flags);
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* 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 RR_MGM_H
|
||||||
|
#define RR_MGM_H
|
||||||
|
|
||||||
|
#include "util/common.h"
|
||||||
|
|
||||||
|
#include "gba/supervisor/rr.h"
|
||||||
|
|
||||||
|
struct GBA;
|
||||||
|
struct VDir;
|
||||||
|
struct VFile;
|
||||||
|
|
||||||
|
enum GBAMGMTag {
|
||||||
|
// Playback tags
|
||||||
|
TAG_INVALID = 0x00,
|
||||||
|
TAG_INPUT = 0x01,
|
||||||
|
TAG_FRAME = 0x02,
|
||||||
|
TAG_LAG = 0x03,
|
||||||
|
|
||||||
|
// Stream chunking tags
|
||||||
|
TAG_BEGIN = 0x10,
|
||||||
|
TAG_END = 0x11,
|
||||||
|
TAG_PREVIOUSLY = 0x12,
|
||||||
|
TAG_NEXT_TIME = 0x13,
|
||||||
|
TAG_MAX_STREAM = 0x14,
|
||||||
|
|
||||||
|
// Recording information tags
|
||||||
|
TAG_FRAME_COUNT = 0x20,
|
||||||
|
TAG_LAG_COUNT = 0x21,
|
||||||
|
TAG_RR_COUNT = 0x22,
|
||||||
|
TAG_INIT = 0x24,
|
||||||
|
TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO,
|
||||||
|
TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME,
|
||||||
|
TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE,
|
||||||
|
TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH,
|
||||||
|
|
||||||
|
// User metadata tags
|
||||||
|
TAG_AUTHOR = 0x30,
|
||||||
|
TAG_COMMENT = 0x31,
|
||||||
|
|
||||||
|
TAG_EOF = INT_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBAMGMContext {
|
||||||
|
struct GBARRContext d;
|
||||||
|
|
||||||
|
// Playback state
|
||||||
|
bool isPlaying;
|
||||||
|
bool autorecord;
|
||||||
|
|
||||||
|
// Recording state
|
||||||
|
bool isRecording;
|
||||||
|
bool inputThisFrame;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
uint32_t streamId;
|
||||||
|
|
||||||
|
uint32_t maxStreamId;
|
||||||
|
off_t maxStreamIdOffset;
|
||||||
|
off_t initFromOffset;
|
||||||
|
off_t rrCountOffset;
|
||||||
|
|
||||||
|
// Streaming state
|
||||||
|
struct VDir* streamDir;
|
||||||
|
struct VFile* metadataFile;
|
||||||
|
struct VFile* movieStream;
|
||||||
|
uint16_t currentInput;
|
||||||
|
enum GBAMGMTag peekedTag;
|
||||||
|
uint32_t nextTime;
|
||||||
|
uint32_t previously;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GBAMGMContextCreate(struct GBAMGMContext*);
|
||||||
|
|
||||||
|
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream);
|
||||||
|
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* 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 "vbm.h"
|
||||||
|
|
||||||
|
#include "gba/gba.h"
|
||||||
|
#include "gba/serialize.h"
|
||||||
|
#include "util/vfs.h"
|
||||||
|
|
||||||
|
static const char VBM_MAGIC[] = "VBM\x1A";
|
||||||
|
|
||||||
|
static void GBAVBMContextDestroy(struct GBARRContext*);
|
||||||
|
|
||||||
|
static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||||
|
static void GBAVBMStopPlaying(struct GBARRContext*);
|
||||||
|
static bool GBAVBMStartRecording(struct GBARRContext*);
|
||||||
|
static void GBAVBMStopRecording(struct GBARRContext*);
|
||||||
|
|
||||||
|
static bool GBAVBMIsPlaying(const struct GBARRContext*);
|
||||||
|
static bool GBAVBMIsRecording(const struct GBARRContext*);
|
||||||
|
|
||||||
|
static void GBAVBMNextFrame(struct GBARRContext*);
|
||||||
|
static uint16_t GBAVBMQueryInput(struct GBARRContext*);
|
||||||
|
|
||||||
|
static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||||
|
static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||||
|
|
||||||
|
static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags);
|
||||||
|
static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags);
|
||||||
|
|
||||||
|
void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
|
||||||
|
memset(vbm, 0, sizeof(*vbm));
|
||||||
|
|
||||||
|
vbm->d.destroy = GBAVBMContextDestroy;
|
||||||
|
|
||||||
|
vbm->d.startPlaying = GBAVBMStartPlaying;
|
||||||
|
vbm->d.stopPlaying = GBAVBMStopPlaying;
|
||||||
|
vbm->d.startRecording = GBAVBMStartRecording;
|
||||||
|
vbm->d.stopRecording = GBAVBMStopRecording;
|
||||||
|
|
||||||
|
vbm->d.isPlaying = GBAVBMIsPlaying;
|
||||||
|
vbm->d.isRecording = GBAVBMIsRecording;
|
||||||
|
|
||||||
|
vbm->d.nextFrame = GBAVBMNextFrame;
|
||||||
|
vbm->d.logInput = 0;
|
||||||
|
vbm->d.queryInput = GBAVBMQueryInput;
|
||||||
|
|
||||||
|
vbm->d.stateSaved = GBAVBMStateSaved;
|
||||||
|
vbm->d.stateLoaded = GBAVBMStateLoaded;
|
||||||
|
|
||||||
|
vbm->d.openSavedata = GBAVBMOpenSavedata;
|
||||||
|
vbm->d.openSavestate = GBAVBMOpenSavestate;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||||
|
if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||||
|
vbm->isPlaying = true;
|
||||||
|
vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMStopPlaying(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isPlaying(rr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||||
|
vbm->isPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAVBMStartRecording(struct GBARRContext* rr) {
|
||||||
|
UNUSED(rr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMStopRecording(struct GBARRContext* rr) {
|
||||||
|
UNUSED(rr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAVBMIsPlaying(const struct GBARRContext* rr) {
|
||||||
|
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||||
|
return vbm->isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAVBMIsRecording(const struct GBARRContext* rr) {
|
||||||
|
UNUSED(rr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMNextFrame(struct GBARRContext* rr) {
|
||||||
|
UNUSED(rr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
||||||
|
if (!rr->isPlaying(rr)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||||
|
uint16_t input;
|
||||||
|
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||||
|
return input & 0x3FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||||
|
UNUSED(rr);
|
||||||
|
UNUSED(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||||
|
UNUSED(rr);
|
||||||
|
UNUSED(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||||
|
UNUSED(rr);
|
||||||
|
UNUSED(flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||||
|
UNUSED(rr);
|
||||||
|
UNUSED(flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAVBMContextDestroy(struct GBARRContext* rr) {
|
||||||
|
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||||
|
if (vbm->vbmFile) {
|
||||||
|
vbm->vbmFile->close(vbm->vbmFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
|
||||||
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
|
char magic[4];
|
||||||
|
vf->read(vf, magic, sizeof(magic));
|
||||||
|
if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t id;
|
||||||
|
vf->read(vf, &id, sizeof(id));
|
||||||
|
if (id != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vf->seek(vf, 4, SEEK_CUR);
|
||||||
|
vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames));
|
||||||
|
vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount));
|
||||||
|
|
||||||
|
uint8_t flags;
|
||||||
|
vf->read(vf, &flags, sizeof(flags));
|
||||||
|
if (flags & 1) {
|
||||||
|
// Incompatible savestate format
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (flags & 2) {
|
||||||
|
// TODO: Implement SRAM loading
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vf->seek(vf, 1, SEEK_CUR);
|
||||||
|
vf->read(vf, &flags, sizeof(flags));
|
||||||
|
if ((flags & 0x7) != 1) {
|
||||||
|
// Non-GBA movie
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: parse more flags
|
||||||
|
|
||||||
|
vf->seek(vf, 0x3C, SEEK_SET);
|
||||||
|
vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset));
|
||||||
|
vf->seek(vf, vbm->inputOffset, SEEK_SET);
|
||||||
|
vbm->vbmFile = vf;
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* 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/common.h"
|
||||||
|
|
||||||
|
#include "gba/supervisor/rr.h"
|
||||||
|
|
||||||
|
struct GBAVBMContext {
|
||||||
|
struct GBARRContext d;
|
||||||
|
|
||||||
|
bool isPlaying;
|
||||||
|
|
||||||
|
struct VFile* vbmFile;
|
||||||
|
int32_t inputOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GBAVBMContextCreate(struct GBAVBMContext*);
|
||||||
|
|
||||||
|
bool GBAVBMSetStream(struct GBAVBMContext*, struct VFile*);
|
|
@ -49,15 +49,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||||
GBAVideoSerialize(&gba->video, state);
|
GBAVideoSerialize(&gba->video, state);
|
||||||
GBAAudioSerialize(&gba->audio, state);
|
GBAAudioSerialize(&gba->audio, state);
|
||||||
|
|
||||||
if (GBARRIsRecording(gba->rr)) {
|
state->associatedStreamId = 0;
|
||||||
state->associatedStreamId = gba->rr->streamId;
|
if (gba->rr) {
|
||||||
GBARRFinishSegment(gba->rr);
|
gba->rr->stateSaved(gba->rr, state);
|
||||||
} else {
|
|
||||||
state->associatedStreamId = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||||
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
||||||
return;
|
return;
|
||||||
|
@ -114,17 +112,8 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||||
GBAVideoDeserialize(&gba->video, state);
|
GBAVideoDeserialize(&gba->video, state);
|
||||||
GBAAudioDeserialize(&gba->audio, state);
|
GBAAudioDeserialize(&gba->audio, state);
|
||||||
|
|
||||||
if (GBARRIsRecording(gba->rr)) {
|
if (gba->rr) {
|
||||||
if (state->associatedStreamId != gba->rr->streamId) {
|
gba->rr->stateLoaded(gba->rr, state);
|
||||||
GBARRLoadStream(gba->rr, state->associatedStreamId);
|
|
||||||
GBARRIncrementStream(gba->rr, true);
|
|
||||||
} else {
|
|
||||||
GBARRFinishSegment(gba->rr);
|
|
||||||
}
|
|
||||||
GBARRMarkRerecord(gba->rr);
|
|
||||||
} else if (GBARRIsPlaying(gba->rr)) {
|
|
||||||
GBARRLoadStream(gba->rr, state->associatedStreamId);
|
|
||||||
GBARRSkipSegment(gba->rr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ struct VDir;
|
||||||
struct GBAThread;
|
struct GBAThread;
|
||||||
|
|
||||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state);
|
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||||
|
|
||||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
|
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
|
||||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||||
|
|
|
@ -36,11 +36,11 @@ static void _switchMode(struct GBASIO* sio) {
|
||||||
} else {
|
} else {
|
||||||
sio->mode = (enum GBASIOMode) (mode & 0xC);
|
sio->mode = (enum GBASIOMode) (mode & 0xC);
|
||||||
}
|
}
|
||||||
if (oldMode != mode) {
|
if (oldMode != sio->mode) {
|
||||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||||
sio->activeDriver->unload(sio->activeDriver);
|
sio->activeDriver->unload(sio->activeDriver);
|
||||||
}
|
}
|
||||||
sio->activeDriver = _lookupDriver(sio, mode);
|
sio->activeDriver = _lookupDriver(sio, sio->mode);
|
||||||
if (sio->activeDriver && sio->activeDriver->load) {
|
if (sio->activeDriver && sio->activeDriver->load) {
|
||||||
sio->activeDriver->load(sio->activeDriver);
|
sio->activeDriver->load(sio->activeDriver);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,13 @@ static const struct GBACartridgeOverride _overrides[] = {
|
||||||
{ "U32E", 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 },
|
{ "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
|
// Drill Dozer
|
||||||
{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||||
{ "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
{ "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||||
|
@ -28,6 +35,9 @@ static const struct GBACartridgeOverride _overrides[] = {
|
||||||
// Final Fantasy Tactics Advance
|
// Final Fantasy Tactics Advance
|
||||||
{ "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 },
|
{ "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 },
|
||||||
|
|
||||||
|
// F-Zero - Climax
|
||||||
|
{ "BFTJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||||
|
|
||||||
// Golden Sun: The Lost Age
|
// Golden Sun: The Lost Age
|
||||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
||||||
|
|
||||||
|
@ -77,15 +87,30 @@ static const struct GBACartridgeOverride _overrides[] = {
|
||||||
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||||
{ "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
{ "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||||
{ "BPRP", 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
|
// Pokemon LeafGreen
|
||||||
{ "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
{ "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||||
{ "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
{ "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||||
{ "BPGP", 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
|
// RockMan EXE 4.5 - Real Operation
|
||||||
{ "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE },
|
{ "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
|
// Shin Bokura no Taiyou: Gyakushuu no Sabata
|
||||||
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||||
|
|
||||||
|
|
|
@ -5,64 +5,9 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "rr.h"
|
#include "rr.h"
|
||||||
|
|
||||||
#include "gba/gba.h"
|
|
||||||
#include "gba/serialize.h"
|
|
||||||
#include "util/vfs.h"
|
#include "util/vfs.h"
|
||||||
|
|
||||||
#define BINARY_EXT ".dat"
|
void GBARRInitRecord(struct GBA* gba) {
|
||||||
#define BINARY_MAGIC "GBAb"
|
|
||||||
#define METADATA_FILENAME "metadata" BINARY_EXT
|
|
||||||
|
|
||||||
enum {
|
|
||||||
INVALID_INPUT = 0x8000
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
|
|
||||||
static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
|
|
||||||
static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
|
|
||||||
static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
|
|
||||||
static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
|
|
||||||
static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf);
|
|
||||||
|
|
||||||
static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
|
|
||||||
|
|
||||||
static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive);
|
|
||||||
static void _streamEndReached(struct GBARRContext* rr);
|
|
||||||
|
|
||||||
static struct VFile* _openSavedata(struct GBARRContext* rr, int flags);
|
|
||||||
static struct VFile* _openSavestate(struct GBARRContext* rr, int flags);
|
|
||||||
|
|
||||||
void GBARRContextCreate(struct GBA* gba) {
|
|
||||||
if (gba->rr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gba->rr = calloc(1, sizeof(*gba->rr));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRContextDestroy(struct GBA* gba) {
|
|
||||||
if (!gba->rr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GBARRIsPlaying(gba->rr)) {
|
|
||||||
GBARRStopPlaying(gba->rr);
|
|
||||||
}
|
|
||||||
if (GBARRIsRecording(gba->rr)) {
|
|
||||||
GBARRStopRecording(gba->rr);
|
|
||||||
}
|
|
||||||
if (gba->rr->metadataFile) {
|
|
||||||
gba->rr->metadataFile->close(gba->rr->metadataFile);
|
|
||||||
}
|
|
||||||
if (gba->rr->savedata) {
|
|
||||||
gba->rr->savedata->close(gba->rr->savedata);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(gba->rr);
|
|
||||||
gba->rr = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRSaveState(struct GBA* gba) {
|
|
||||||
if (!gba || !gba->rr) {
|
if (!gba || !gba->rr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -71,17 +16,17 @@ void GBARRSaveState(struct GBA* gba) {
|
||||||
if (gba->rr->savedata) {
|
if (gba->rr->savedata) {
|
||||||
gba->rr->savedata->close(gba->rr->savedata);
|
gba->rr->savedata->close(gba->rr->savedata);
|
||||||
}
|
}
|
||||||
gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||||
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
||||||
gba->rr->savedata->close(gba->rr->savedata);
|
gba->rr->savedata->close(gba->rr->savedata);
|
||||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||||
} else {
|
} else {
|
||||||
GBASavedataMask(&gba->memory.savedata, 0);
|
GBASavedataMask(&gba->memory.savedata, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||||
struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||||
GBASaveStateNamed(gba, vf, false);
|
GBASaveStateNamed(gba, vf, false);
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
} else {
|
} else {
|
||||||
|
@ -89,7 +34,7 @@ void GBARRSaveState(struct GBA* gba) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBARRLoadState(struct GBA* gba) {
|
void GBARRInitPlay(struct GBA* gba) {
|
||||||
if (!gba || !gba->rr) {
|
if (!gba || !gba->rr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -98,14 +43,14 @@ void GBARRLoadState(struct GBA* gba) {
|
||||||
if (gba->rr->savedata) {
|
if (gba->rr->savedata) {
|
||||||
gba->rr->savedata->close(gba->rr->savedata);
|
gba->rr->savedata->close(gba->rr->savedata);
|
||||||
}
|
}
|
||||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||||
} else {
|
} else {
|
||||||
GBASavedataMask(&gba->memory.savedata, 0);
|
GBASavedataMask(&gba->memory.savedata, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||||
struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
|
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||||
GBALoadStateNamed(gba, vf);
|
GBALoadStateNamed(gba, vf);
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,460 +58,16 @@ void GBARRLoadState(struct GBA* gba) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
|
void GBARRDestroy(struct GBARRContext* rr) {
|
||||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
if (rr->isPlaying(rr)) {
|
||||||
return false;
|
rr->stopPlaying(rr);
|
||||||
}
|
}
|
||||||
|
if (rr->isRecording(rr)) {
|
||||||
if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
|
rr->stopRecording(rr);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (rr->savedata) {
|
||||||
rr->streamDir = stream;
|
rr->savedata->close(rr->savedata);
|
||||||
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
rr->savedata = 0;
|
||||||
rr->currentInput = INVALID_INPUT;
|
|
||||||
if (!_parseMetadata(rr, rr->metadataFile)) {
|
|
||||||
rr->metadataFile->close(rr->metadataFile);
|
|
||||||
rr->metadataFile = 0;
|
|
||||||
rr->maxStreamId = 0;
|
|
||||||
}
|
}
|
||||||
rr->streamId = 1;
|
rr->destroy(rr);
|
||||||
rr->movieStream = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
|
|
||||||
if (!rr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rr->metadataFile) {
|
|
||||||
rr->metadataFile->truncate(rr->metadataFile, 0);
|
|
||||||
} else {
|
|
||||||
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
|
|
||||||
}
|
|
||||||
_emitMagic(rr, rr->metadataFile);
|
|
||||||
|
|
||||||
rr->initFrom = initFrom;
|
|
||||||
rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
|
||||||
_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
|
|
||||||
|
|
||||||
rr->streamId = 0;
|
|
||||||
rr->maxStreamId = 0;
|
|
||||||
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
|
|
||||||
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
|
||||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
|
||||||
|
|
||||||
rr->rrCount = 0;
|
|
||||||
_emitTag(rr, rr->metadataFile, TAG_RR_COUNT);
|
|
||||||
rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
|
||||||
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
|
|
||||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rr->movieStream = 0;
|
|
||||||
rr->streamId = streamId;
|
|
||||||
rr->currentInput = INVALID_INPUT;
|
|
||||||
char buffer[14];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
|
|
||||||
if (GBARRIsRecording(rr)) {
|
|
||||||
int flags = O_CREAT | O_RDWR;
|
|
||||||
if (streamId > rr->maxStreamId) {
|
|
||||||
flags |= O_TRUNC;
|
|
||||||
}
|
|
||||||
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
|
|
||||||
} else if (GBARRIsPlaying(rr)) {
|
|
||||||
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
|
|
||||||
rr->peekedTag = TAG_INVALID;
|
|
||||||
if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
|
|
||||||
GBARRStopPlaying(rr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
|
|
||||||
rr->frames = 0;
|
|
||||||
rr->lagFrames = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) {
|
|
||||||
uint32_t newStreamId = rr->maxStreamId + 1;
|
|
||||||
uint32_t oldStreamId = rr->streamId;
|
|
||||||
if (GBARRIsRecording(rr) && rr->movieStream) {
|
|
||||||
if (!_markStreamNext(rr, newStreamId, recursive)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!GBARRLoadStream(rr, newStreamId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
|
|
||||||
_emitMagic(rr, rr->movieStream);
|
|
||||||
rr->maxStreamId = newStreamId;
|
|
||||||
_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
|
|
||||||
rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
|
|
||||||
_emitTag(rr, rr->movieStream, TAG_BEGIN);
|
|
||||||
|
|
||||||
rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
|
|
||||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
|
||||||
rr->previously = oldStreamId;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
|
||||||
if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rr->isPlaying = true;
|
|
||||||
if (!GBARRLoadStream(rr, 1)) {
|
|
||||||
rr->isPlaying = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rr->autorecord = autorecord;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRStopPlaying(struct GBARRContext* rr) {
|
|
||||||
if (!GBARRIsPlaying(rr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rr->isPlaying = false;
|
|
||||||
if (rr->movieStream) {
|
|
||||||
rr->movieStream->close(rr->movieStream);
|
|
||||||
rr->movieStream = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRStartRecording(struct GBARRContext* rr) {
|
|
||||||
if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rr->maxStreamIdOffset) {
|
|
||||||
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
|
|
||||||
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
|
||||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
|
||||||
}
|
|
||||||
|
|
||||||
rr->isRecording = true;
|
|
||||||
return GBARRIncrementStream(rr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRStopRecording(struct GBARRContext* rr) {
|
|
||||||
if (!GBARRIsRecording(rr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rr->isRecording = false;
|
|
||||||
if (rr->movieStream) {
|
|
||||||
_emitEnd(rr, rr->movieStream);
|
|
||||||
rr->movieStream->close(rr->movieStream);
|
|
||||||
rr->movieStream = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRIsPlaying(struct GBARRContext* rr) {
|
|
||||||
return rr && rr->isPlaying;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRIsRecording(struct GBARRContext* rr) {
|
|
||||||
return rr && rr->isRecording;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRNextFrame(struct GBARRContext* rr) {
|
|
||||||
if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GBARRIsPlaying(rr)) {
|
|
||||||
while (rr->peekedTag == TAG_INPUT) {
|
|
||||||
_readTag(rr, rr->movieStream);
|
|
||||||
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
|
|
||||||
}
|
|
||||||
if (rr->peekedTag == TAG_LAG) {
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
|
|
||||||
if (rr->inputThisFrame) {
|
|
||||||
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++rr->frames;
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
|
|
||||||
if (!rr->inputThisFrame) {
|
|
||||||
++rr->lagFrames;
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GBARRIsRecording(rr)) {
|
|
||||||
if (!rr->inputThisFrame) {
|
|
||||||
_emitTag(rr, rr->movieStream, TAG_LAG);
|
|
||||||
}
|
|
||||||
_emitTag(rr, rr->movieStream, TAG_FRAME);
|
|
||||||
rr->inputThisFrame = false;
|
|
||||||
} else {
|
|
||||||
if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
|
|
||||||
_streamEndReached(rr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
|
|
||||||
if (!GBARRIsRecording(rr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys != rr->currentInput) {
|
|
||||||
_emitTag(rr, rr->movieStream, TAG_INPUT);
|
|
||||||
rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
|
|
||||||
rr->currentInput = keys;
|
|
||||||
}
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput);
|
|
||||||
rr->inputThisFrame = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t GBARRQueryInput(struct GBARRContext* rr) {
|
|
||||||
if (!GBARRIsPlaying(rr)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rr->peekedTag == TAG_INPUT) {
|
|
||||||
_readTag(rr, rr->movieStream);
|
|
||||||
}
|
|
||||||
rr->inputThisFrame = true;
|
|
||||||
if (rr->currentInput == INVALID_INPUT) {
|
|
||||||
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
|
|
||||||
}
|
|
||||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput);
|
|
||||||
return rr->currentInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRFinishSegment(struct GBARRContext* rr) {
|
|
||||||
if (rr->movieStream) {
|
|
||||||
if (!_emitEnd(rr, rr->movieStream)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GBARRIncrementStream(rr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRSkipSegment(struct GBARRContext* rr) {
|
|
||||||
rr->nextTime = 0;
|
|
||||||
while (_readTag(rr, rr->movieStream) != TAG_EOF);
|
|
||||||
if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
|
|
||||||
_streamEndReached(rr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GBARRMarkRerecord(struct GBARRContext* rr) {
|
|
||||||
++rr->rrCount;
|
|
||||||
rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
|
|
||||||
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
|
|
||||||
UNUSED(rr);
|
|
||||||
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
|
|
||||||
UNUSED(rr);
|
|
||||||
char buffer[4];
|
|
||||||
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
|
|
||||||
if (!rr || !vf) {
|
|
||||||
return TAG_EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GBARRTag tag = rr->peekedTag;
|
|
||||||
switch (tag) {
|
|
||||||
case TAG_INPUT:
|
|
||||||
vf->read(vf, &rr->currentInput, sizeof(uint16_t));
|
|
||||||
break;
|
|
||||||
case TAG_PREVIOUSLY:
|
|
||||||
vf->read(vf, &rr->previously, sizeof(rr->previously));
|
|
||||||
break;
|
|
||||||
case TAG_NEXT_TIME:
|
|
||||||
vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
|
|
||||||
break;
|
|
||||||
case TAG_MAX_STREAM:
|
|
||||||
vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
|
||||||
break;
|
|
||||||
case TAG_FRAME_COUNT:
|
|
||||||
vf->read(vf, &rr->frames, sizeof(rr->frames));
|
|
||||||
break;
|
|
||||||
case TAG_LAG_COUNT:
|
|
||||||
vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
|
|
||||||
break;
|
|
||||||
case TAG_RR_COUNT:
|
|
||||||
vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TAG_INIT_EX_NIHILO:
|
|
||||||
rr->initFrom = INIT_EX_NIHILO;
|
|
||||||
break;
|
|
||||||
case TAG_INIT_FROM_SAVEGAME:
|
|
||||||
rr->initFrom = INIT_FROM_SAVEGAME;
|
|
||||||
break;
|
|
||||||
case TAG_INIT_FROM_SAVESTATE:
|
|
||||||
rr->initFrom = INIT_FROM_SAVESTATE;
|
|
||||||
break;
|
|
||||||
case TAG_INIT_FROM_BOTH:
|
|
||||||
rr->initFrom = INIT_FROM_BOTH;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// To be spec'd
|
|
||||||
case TAG_AUTHOR:
|
|
||||||
case TAG_COMMENT:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Empty markers
|
|
||||||
case TAG_FRAME:
|
|
||||||
case TAG_LAG:
|
|
||||||
case TAG_BEGIN:
|
|
||||||
case TAG_END:
|
|
||||||
case TAG_INVALID:
|
|
||||||
case TAG_EOF:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t tagBuffer;
|
|
||||||
if (vf->read(vf, &tagBuffer, 1) != 1) {
|
|
||||||
rr->peekedTag = TAG_EOF;
|
|
||||||
} else {
|
|
||||||
rr->peekedTag = tagBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rr->peekedTag == TAG_END) {
|
|
||||||
GBARRSkipSegment(rr);
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
|
|
||||||
enum GBARRTag readTag;
|
|
||||||
while ((readTag = _readTag(rr, vf)) != tag) {
|
|
||||||
if (readTag == TAG_EOF) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
|
|
||||||
UNUSED(rr);
|
|
||||||
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
|
|
||||||
if (!_verifyMagic(rr, vf)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (_readTag(rr, vf) != TAG_EOF) {
|
|
||||||
switch (rr->peekedTag) {
|
|
||||||
case TAG_MAX_STREAM:
|
|
||||||
rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
|
|
||||||
break;
|
|
||||||
case TAG_INIT_EX_NIHILO:
|
|
||||||
case TAG_INIT_FROM_SAVEGAME:
|
|
||||||
case TAG_INIT_FROM_SAVESTATE:
|
|
||||||
case TAG_INIT_FROM_BOTH:
|
|
||||||
rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
|
|
||||||
break;
|
|
||||||
case TAG_RR_COUNT:
|
|
||||||
rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) {
|
|
||||||
// TODO: Error check
|
|
||||||
_emitTag(rr, vf, TAG_END);
|
|
||||||
_emitTag(rr, vf, TAG_FRAME_COUNT);
|
|
||||||
vf->write(vf, &rr->frames, sizeof(rr->frames));
|
|
||||||
_emitTag(rr, vf, TAG_LAG_COUNT);
|
|
||||||
vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames));
|
|
||||||
_emitTag(rr, vf, TAG_NEXT_TIME);
|
|
||||||
|
|
||||||
uint32_t newStreamId = 0;
|
|
||||||
vf->write(vf, &newStreamId, sizeof(newStreamId));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) {
|
|
||||||
if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t tagBuffer;
|
|
||||||
if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tagBuffer != TAG_NEXT_TIME) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (recursive) {
|
|
||||||
if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!_verifyMagic(rr, rr->movieStream)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_readTag(rr, rr->movieStream);
|
|
||||||
if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rr->previously == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
uint32_t currentStreamId = rr->streamId;
|
|
||||||
if (!GBARRLoadStream(rr, rr->previously)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return _markStreamNext(rr, currentStreamId, rr->previously);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _streamEndReached(struct GBARRContext* rr) {
|
|
||||||
if (!GBARRIsPlaying(rr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t endStreamId = rr->streamId;
|
|
||||||
GBARRStopPlaying(rr);
|
|
||||||
if (rr->autorecord) {
|
|
||||||
rr->isRecording = true;
|
|
||||||
GBARRLoadStream(rr, endStreamId);
|
|
||||||
GBARRIncrementStream(rr, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VFile* _openSavedata(struct GBARRContext* rr, int flags) {
|
|
||||||
return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VFile* _openSavestate(struct GBARRContext* rr, int flags) {
|
|
||||||
return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
#include "util/common.h"
|
#include "util/common.h"
|
||||||
|
|
||||||
struct GBA;
|
#include "gba/serialize.h"
|
||||||
struct VDir;
|
|
||||||
struct VFile;
|
struct VFile;
|
||||||
|
|
||||||
enum GBARRInitFrom {
|
enum GBARRInitFrom {
|
||||||
|
@ -19,95 +19,39 @@ enum GBARRInitFrom {
|
||||||
INIT_FROM_BOTH = 3,
|
INIT_FROM_BOTH = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GBARRTag {
|
|
||||||
// Playback tags
|
|
||||||
TAG_INVALID = 0x00,
|
|
||||||
TAG_INPUT = 0x01,
|
|
||||||
TAG_FRAME = 0x02,
|
|
||||||
TAG_LAG = 0x03,
|
|
||||||
|
|
||||||
// Stream chunking tags
|
|
||||||
TAG_BEGIN = 0x10,
|
|
||||||
TAG_END = 0x11,
|
|
||||||
TAG_PREVIOUSLY = 0x12,
|
|
||||||
TAG_NEXT_TIME = 0x13,
|
|
||||||
TAG_MAX_STREAM = 0x14,
|
|
||||||
|
|
||||||
// Recording information tags
|
|
||||||
TAG_FRAME_COUNT = 0x20,
|
|
||||||
TAG_LAG_COUNT = 0x21,
|
|
||||||
TAG_RR_COUNT = 0x22,
|
|
||||||
TAG_INIT = 0x24,
|
|
||||||
TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO,
|
|
||||||
TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME,
|
|
||||||
TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE,
|
|
||||||
TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH,
|
|
||||||
|
|
||||||
// User metadata tags
|
|
||||||
TAG_AUTHOR = 0x30,
|
|
||||||
TAG_COMMENT = 0x31,
|
|
||||||
|
|
||||||
TAG_EOF = INT_MAX
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBARRContext {
|
struct GBARRContext {
|
||||||
// Playback state
|
void (*destroy)(struct GBARRContext*);
|
||||||
bool isPlaying;
|
|
||||||
bool autorecord;
|
|
||||||
|
|
||||||
// Recording state
|
bool (*startPlaying)(struct GBARRContext*, bool autorecord);
|
||||||
bool isRecording;
|
void (*stopPlaying)(struct GBARRContext*);
|
||||||
bool inputThisFrame;
|
bool (*startRecording)(struct GBARRContext*);
|
||||||
|
void (*stopRecording)(struct GBARRContext*);
|
||||||
|
|
||||||
|
bool (*isPlaying)(const struct GBARRContext*);
|
||||||
|
bool (*isRecording)(const struct GBARRContext*);
|
||||||
|
|
||||||
|
void (*nextFrame)(struct GBARRContext*);
|
||||||
|
void (*logInput)(struct GBARRContext*, uint16_t input);
|
||||||
|
uint16_t (*queryInput)(struct GBARRContext*);
|
||||||
|
|
||||||
|
void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*);
|
||||||
|
void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*);
|
||||||
|
|
||||||
|
struct VFile* (*openSavedata)(struct GBARRContext* mgm, int flags);
|
||||||
|
struct VFile* (*openSavestate)(struct GBARRContext* mgm, int flags);
|
||||||
|
|
||||||
// Metadata
|
|
||||||
uint32_t frames;
|
uint32_t frames;
|
||||||
uint32_t lagFrames;
|
uint32_t lagFrames;
|
||||||
uint32_t streamId;
|
|
||||||
|
|
||||||
uint32_t maxStreamId;
|
|
||||||
off_t maxStreamIdOffset;
|
|
||||||
|
|
||||||
enum GBARRInitFrom initFrom;
|
enum GBARRInitFrom initFrom;
|
||||||
off_t initFromOffset;
|
|
||||||
|
|
||||||
uint32_t rrCount;
|
uint32_t rrCount;
|
||||||
off_t rrCountOffset;
|
|
||||||
|
|
||||||
struct VFile* savedata;
|
struct VFile* savedata;
|
||||||
|
|
||||||
// Streaming state
|
|
||||||
struct VDir* streamDir;
|
|
||||||
struct VFile* metadataFile;
|
|
||||||
struct VFile* movieStream;
|
|
||||||
uint16_t currentInput;
|
|
||||||
enum GBARRTag peekedTag;
|
|
||||||
uint32_t nextTime;
|
|
||||||
uint32_t previously;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBARRContextCreate(struct GBA*);
|
void GBARRDestroy(struct GBARRContext*);
|
||||||
void GBARRContextDestroy(struct GBA*);
|
|
||||||
void GBARRSaveState(struct GBA*);
|
|
||||||
void GBARRLoadState(struct GBA*);
|
|
||||||
|
|
||||||
bool GBARRInitStream(struct GBARRContext*, struct VDir*);
|
void GBARRInitRecord(struct GBA*);
|
||||||
bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom);
|
void GBARRInitPlay(struct GBA*);
|
||||||
bool GBARRLoadStream(struct GBARRContext*, uint32_t streamId);
|
|
||||||
bool GBARRIncrementStream(struct GBARRContext*, bool recursive);
|
|
||||||
bool GBARRFinishSegment(struct GBARRContext*);
|
|
||||||
bool GBARRSkipSegment(struct GBARRContext*);
|
|
||||||
bool GBARRMarkRerecord(struct GBARRContext*);
|
|
||||||
|
|
||||||
bool GBARRStartPlaying(struct GBARRContext*, bool autorecord);
|
|
||||||
void GBARRStopPlaying(struct GBARRContext*);
|
|
||||||
bool GBARRStartRecording(struct GBARRContext*);
|
|
||||||
void GBARRStopRecording(struct GBARRContext*);
|
|
||||||
|
|
||||||
bool GBARRIsPlaying(struct GBARRContext*);
|
|
||||||
bool GBARRIsRecording(struct GBARRContext*);
|
|
||||||
|
|
||||||
void GBARRNextFrame(struct GBARRContext*);
|
|
||||||
void GBARRLogInput(struct GBARRContext*, uint16_t input);
|
|
||||||
uint16_t GBARRQueryInput(struct GBARRContext*);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include "gba/cheats.h"
|
#include "gba/cheats.h"
|
||||||
#include "gba/serialize.h"
|
#include "gba/serialize.h"
|
||||||
#include "gba/supervisor/config.h"
|
#include "gba/supervisor/config.h"
|
||||||
|
#include "gba/rr/mgm.h"
|
||||||
|
#include "gba/rr/vbm.h"
|
||||||
|
|
||||||
#include "debugger/debugger.h"
|
#include "debugger/debugger.h"
|
||||||
|
|
||||||
|
@ -117,6 +119,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
struct GBACheatDevice cheatDevice;
|
struct GBACheatDevice cheatDevice;
|
||||||
struct GBAThread* threadContext = context;
|
struct GBAThread* threadContext = context;
|
||||||
struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
|
struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
|
||||||
|
struct GBARRContext* movie = 0;
|
||||||
int numComponents = GBA_COMPONENT_MAX;
|
int numComponents = GBA_COMPONENT_MAX;
|
||||||
|
|
||||||
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||||
|
@ -131,6 +134,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
gba.sync = &threadContext->sync;
|
gba.sync = &threadContext->sync;
|
||||||
threadContext->gba = &gba;
|
threadContext->gba = &gba;
|
||||||
gba.logLevel = threadContext->logLevel;
|
gba.logLevel = threadContext->logLevel;
|
||||||
|
gba.logHandler = threadContext->logHandler;
|
||||||
|
gba.stream = threadContext->stream;
|
||||||
gba.idleOptimization = threadContext->idleOptimization;
|
gba.idleOptimization = threadContext->idleOptimization;
|
||||||
#ifdef USE_PTHREADS
|
#ifdef USE_PTHREADS
|
||||||
pthread_setspecific(_contextKey, threadContext);
|
pthread_setspecific(_contextKey, threadContext);
|
||||||
|
@ -170,7 +175,43 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threadContext->movie) {
|
||||||
|
struct VDir* movieDir = VDirOpen(threadContext->movie);
|
||||||
|
#ifdef USE_LIBZIP
|
||||||
|
if (!movieDir) {
|
||||||
|
movieDir = VDirOpenZip(threadContext->movie, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (movieDir) {
|
||||||
|
struct GBAMGMContext* mgm = malloc(sizeof(*mgm));
|
||||||
|
GBAMGMContextCreate(mgm);
|
||||||
|
if (!GBAMGMSetStream(mgm, movieDir)) {
|
||||||
|
mgm->d.destroy(&mgm->d);
|
||||||
|
} else {
|
||||||
|
movie = &mgm->d;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct VFile* movieFile = VFileOpen(threadContext->movie, O_RDONLY);
|
||||||
|
if (movieFile) {
|
||||||
|
struct GBAVBMContext* vbm = malloc(sizeof(*vbm));
|
||||||
|
GBAVBMContextCreate(vbm);
|
||||||
|
if (!GBAVBMSetStream(vbm, movieFile)) {
|
||||||
|
vbm->d.destroy(&vbm->d);
|
||||||
|
} else {
|
||||||
|
movie = &vbm->d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ARMReset(&cpu);
|
ARMReset(&cpu);
|
||||||
|
|
||||||
|
if (movie) {
|
||||||
|
gba.rr = movie;
|
||||||
|
movie->startPlaying(movie, false);
|
||||||
|
GBARRInitPlay(&gba);
|
||||||
|
}
|
||||||
|
|
||||||
if (threadContext->skipBios) {
|
if (threadContext->skipBios) {
|
||||||
GBASkipBIOS(&cpu);
|
GBASkipBIOS(&cpu);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +297,11 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||||
GBACheatDeviceDestroy(&cheatDevice);
|
GBACheatDeviceDestroy(&cheatDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (movie) {
|
||||||
|
movie->destroy(movie);
|
||||||
|
free(movie);
|
||||||
|
}
|
||||||
|
|
||||||
threadContext->sync.videoFrameOn = false;
|
threadContext->sync.videoFrameOn = false;
|
||||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||||
|
@ -295,12 +341,12 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
|
||||||
} else {
|
} else {
|
||||||
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
|
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
|
||||||
threadContext->gameDir = 0;
|
threadContext->gameDir = 0;
|
||||||
#if ENABLE_LIBZIP
|
#if USE_LIBZIP
|
||||||
if (!threadContext->gameDir) {
|
if (!threadContext->gameDir) {
|
||||||
threadContext->gameDir = VDirOpenZip(args->fname, 0);
|
threadContext->gameDir = VDirOpenZip(args->fname, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if ENABLE_LZMA
|
#if USE_LZMA
|
||||||
if (!threadContext->gameDir) {
|
if (!threadContext->gameDir) {
|
||||||
threadContext->gameDir = VDirOpen7z(args->fname, 0);
|
threadContext->gameDir = VDirOpen7z(args->fname, 0);
|
||||||
}
|
}
|
||||||
|
@ -309,6 +355,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
|
||||||
threadContext->fname = args->fname;
|
threadContext->fname = args->fname;
|
||||||
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
|
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
|
||||||
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
|
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
|
||||||
|
threadContext->movie = args->movie;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GBAThreadStart(struct GBAThread* threadContext) {
|
bool GBAThreadStart(struct GBAThread* threadContext) {
|
||||||
|
|
|
@ -20,7 +20,6 @@ struct GBACheatSet;
|
||||||
struct GBAOptions;
|
struct GBAOptions;
|
||||||
|
|
||||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||||
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
|
||||||
|
|
||||||
enum ThreadState {
|
enum ThreadState {
|
||||||
THREAD_INITIALIZED = -1,
|
THREAD_INITIALIZED = -1,
|
||||||
|
@ -49,11 +48,6 @@ struct GBASync {
|
||||||
Mutex audioBufferMutex;
|
Mutex audioBufferMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBAAVStream {
|
|
||||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
|
||||||
void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBAThread {
|
struct GBAThread {
|
||||||
// Output
|
// Output
|
||||||
enum ThreadState state;
|
enum ThreadState state;
|
||||||
|
@ -72,6 +66,7 @@ struct GBAThread {
|
||||||
struct VFile* patch;
|
struct VFile* patch;
|
||||||
struct VFile* cheatsFile;
|
struct VFile* cheatsFile;
|
||||||
const char* fname;
|
const char* fname;
|
||||||
|
const char* movie;
|
||||||
int activeKeys;
|
int activeKeys;
|
||||||
struct GBAAVStream* stream;
|
struct GBAAVStream* stream;
|
||||||
struct Configuration* overrides;
|
struct Configuration* overrides;
|
||||||
|
@ -94,7 +89,7 @@ struct GBAThread {
|
||||||
enum ThreadState savedState;
|
enum ThreadState savedState;
|
||||||
int interruptDepth;
|
int interruptDepth;
|
||||||
|
|
||||||
LogHandler logHandler;
|
GBALogHandler logHandler;
|
||||||
int logLevel;
|
int logLevel;
|
||||||
ThreadCallback startCallback;
|
ThreadCallback startCallback;
|
||||||
ThreadCallback cleanCallback;
|
ThreadCallback cleanCallback;
|
||||||
|
|
|
@ -235,7 +235,7 @@ static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, un
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state) {
|
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) {
|
||||||
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
|
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
|
||||||
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
||||||
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
||||||
|
@ -249,7 +249,7 @@ void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state)
|
||||||
state->video.frameCounter = video->frameCounter;
|
state->video.frameCounter = video->frameCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state) {
|
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) {
|
||||||
memcpy(video->renderer->vram, state->vram, SIZE_VRAM);
|
memcpy(video->renderer->vram, state->vram, SIZE_VRAM);
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < SIZE_OAM; i += 2) {
|
for (i = 0; i < SIZE_OAM; i += 2) {
|
||||||
|
|
|
@ -201,7 +201,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles);
|
||||||
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value);
|
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value);
|
||||||
|
|
||||||
struct GBASerializedState;
|
struct GBASerializedState;
|
||||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state);
|
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state);
|
||||||
void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state);
|
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -34,8 +34,8 @@
|
||||||
|
|
||||||
static const struct option _options[] = {
|
static const struct option _options[] = {
|
||||||
{ "bios", required_argument, 0, 'b' },
|
{ "bios", required_argument, 0, 'b' },
|
||||||
{ "cheats", required_argument, 0, 'c' },
|
{ "cheats", required_argument, 0, 'c' },
|
||||||
{ "dirmode", required_argument, 0, 'D' },
|
{ "dirmode", required_argument, 0, 'D' },
|
||||||
{ "frameskip", required_argument, 0, 's' },
|
{ "frameskip", required_argument, 0, 's' },
|
||||||
#ifdef USE_CLI_DEBUGGER
|
#ifdef USE_CLI_DEBUGGER
|
||||||
{ "debug", no_argument, 0, 'd' },
|
{ "debug", no_argument, 0, 'd' },
|
||||||
|
@ -43,6 +43,7 @@ static const struct option _options[] = {
|
||||||
#ifdef USE_GDB_STUB
|
#ifdef USE_GDB_STUB
|
||||||
{ "gdb", no_argument, 0, 'g' },
|
{ "gdb", no_argument, 0, 'g' },
|
||||||
#endif
|
#endif
|
||||||
|
{ "movie", required_argument, 0, 'v' },
|
||||||
{ "patch", required_argument, 0, 'p' },
|
{ "patch", required_argument, 0, 'p' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
@ -52,7 +53,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
||||||
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
|
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
|
||||||
int ch;
|
int ch;
|
||||||
char options[64] =
|
char options[64] =
|
||||||
"b:c:Dl:p:s:"
|
"b:c:Dl:p:s:v:"
|
||||||
#ifdef USE_CLI_DEBUGGER
|
#ifdef USE_CLI_DEBUGGER
|
||||||
"d"
|
"d"
|
||||||
#endif
|
#endif
|
||||||
|
@ -101,6 +102,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
||||||
case 's':
|
case 's':
|
||||||
GBAConfigSetDefaultValue(config, "frameskip", optarg);
|
GBAConfigSetDefaultValue(config, "frameskip", optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'v':
|
||||||
|
opts->movie = strdup(optarg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (subparser) {
|
if (subparser) {
|
||||||
if (!subparser->parse(subparser, config, ch, optarg)) {
|
if (!subparser->parse(subparser, config, ch, optarg)) {
|
||||||
|
@ -125,6 +129,9 @@ void freeArguments(struct GBAArguments* opts) {
|
||||||
|
|
||||||
free(opts->patch);
|
free(opts->patch);
|
||||||
opts->patch = 0;
|
opts->patch = 0;
|
||||||
|
|
||||||
|
free(opts->movie);
|
||||||
|
opts->movie = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
|
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
|
||||||
|
@ -211,6 +218,7 @@ void usage(const char* arg0, const char* extraOptions) {
|
||||||
#ifdef USE_GDB_STUB
|
#ifdef USE_GDB_STUB
|
||||||
puts(" -g, --gdb Start GDB session (default port 2345)");
|
puts(" -g, --gdb Start GDB session (default port 2345)");
|
||||||
#endif
|
#endif
|
||||||
|
puts(" -v, --movie FILE Play back a movie of recorded input");
|
||||||
puts(" -p, --patch FILE Apply a specified patch file when running");
|
puts(" -p, --patch FILE Apply a specified patch file when running");
|
||||||
puts(" -s, --frameskip N Skip every N frames");
|
puts(" -s, --frameskip N Skip every N frames");
|
||||||
if (extraOptions) {
|
if (extraOptions) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ struct GBAArguments {
|
||||||
char* patch;
|
char* patch;
|
||||||
char* cheatsFile;
|
char* cheatsFile;
|
||||||
bool dirmode;
|
bool dirmode;
|
||||||
|
char* movie;
|
||||||
|
|
||||||
enum DebuggerType debuggerType;
|
enum DebuggerType debuggerType;
|
||||||
bool debugAtStart;
|
bool debugAtStart;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include <libswscale/swscale.h>
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right);
|
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PREFERRED_SAMPLE_RATE = 0x8000
|
PREFERRED_SAMPLE_RATE = 0x8000
|
||||||
|
@ -33,6 +33,7 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
||||||
|
|
||||||
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
||||||
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
|
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
|
||||||
|
encoder->d.postAudioBuffer = 0;
|
||||||
|
|
||||||
encoder->audioCodec = 0;
|
encoder->audioCodec = 0;
|
||||||
encoder->videoCodec = 0;
|
encoder->videoCodec = 0;
|
||||||
|
@ -288,10 +289,18 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
encoder->videoFrame->height = encoder->video->height;
|
encoder->videoFrame->height = encoder->video->height;
|
||||||
encoder->videoFrame->pts = 0;
|
encoder->videoFrame->pts = 0;
|
||||||
encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS,
|
encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS,
|
||||||
|
#ifdef COLOR_16_BIT
|
||||||
|
#ifdef COLOR_5_6_5
|
||||||
|
AV_PIX_FMT_RGB565,
|
||||||
|
#else
|
||||||
|
AV_PIX_FMT_BGR555,
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
#ifndef USE_LIBAV
|
#ifndef USE_LIBAV
|
||||||
AV_PIX_FMT_0BGR32,
|
AV_PIX_FMT_0BGR32,
|
||||||
#else
|
#else
|
||||||
AV_PIX_FMT_BGR32,
|
AV_PIX_FMT_BGR32,
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||||
SWS_POINT, 0, 0, 0);
|
SWS_POINT, 0, 0, 0);
|
||||||
|
@ -349,7 +358,7 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
|
||||||
return !!encoder->context;
|
return !!encoder->context;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
|
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||||
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
||||||
if (!encoder->context || !encoder->audioCodec) {
|
if (!encoder->context || !encoder->audioCodec) {
|
||||||
return;
|
return;
|
||||||
|
@ -416,7 +425,7 @@ void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
||||||
uint8_t* pixels;
|
uint8_t* pixels;
|
||||||
unsigned stride;
|
unsigned stride;
|
||||||
renderer->getPixels(renderer, &stride, (void**) &pixels);
|
renderer->getPixels(renderer, &stride, (void**) &pixels);
|
||||||
stride *= 4;
|
stride *= BYTES_PER_PIXEL;
|
||||||
|
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
#include "gba/video.h"
|
#include "gba/video.h"
|
||||||
|
|
||||||
static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
static void _magickPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right);
|
static void _magickPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||||
|
|
||||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
||||||
encoder->wand = 0;
|
encoder->wand = 0;
|
||||||
|
|
||||||
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
||||||
encoder->d.postAudioFrame = _magickPostAudioFrame;
|
encoder->d.postAudioFrame = _magickPostAudioFrame;
|
||||||
|
encoder->d.postAudioBuffer = 0;
|
||||||
|
|
||||||
encoder->frameskip = 2;
|
encoder->frameskip = 2;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +71,7 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
|
||||||
++encoder->currentFrame;
|
++encoder->currentFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _magickPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
|
static void _magickPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||||
UNUSED(stream);
|
UNUSED(stream);
|
||||||
UNUSED(left);
|
UNUSED(left);
|
||||||
UNUSED(right);
|
UNUSED(right);
|
||||||
|
|
|
@ -0,0 +1,353 @@
|
||||||
|
/* 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 "libretro.h"
|
||||||
|
|
||||||
|
#include "gba/gba.h"
|
||||||
|
#include "gba/renderers/video-software.h"
|
||||||
|
#include "gba/serialize.h"
|
||||||
|
#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_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 _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
||||||
|
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||||
|
|
||||||
|
static struct GBA gba;
|
||||||
|
static struct ARMCore cpu;
|
||||||
|
static struct GBAVideoSoftwareRenderer renderer;
|
||||||
|
static struct VFile* rom;
|
||||||
|
static void* data;
|
||||||
|
static struct VFile* save;
|
||||||
|
static void* savedata;
|
||||||
|
static struct GBAAVStream stream;
|
||||||
|
|
||||||
|
unsigned retro_api_version(void) {
|
||||||
|
return RETRO_API_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_environment(retro_environment_t env) {
|
||||||
|
environCallback = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_video_refresh(retro_video_refresh_t video) {
|
||||||
|
videoCallback = video;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_audio_sample(retro_audio_sample_t audio) {
|
||||||
|
UNUSED(audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
|
||||||
|
audioCallback = audioBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_input_poll(retro_input_poll_t inputPoll) {
|
||||||
|
inputPollCallback = inputPoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_input_state(retro_input_state_t input) {
|
||||||
|
inputCallback = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
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->block_extract = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_get_system_av_info(struct retro_system_av_info* info) {
|
||||||
|
info->geometry.base_width = VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
info->geometry.base_height = VIDEO_VERTICAL_PIXELS;
|
||||||
|
info->geometry.max_width = VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
info->geometry.max_height = VIDEO_VERTICAL_PIXELS;
|
||||||
|
info->timing.fps = GBA_ARM7TDMI_FREQUENCY / (float) VIDEO_TOTAL_LENGTH;
|
||||||
|
info->timing.sample_rate = 32768;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_init(void) {
|
||||||
|
enum retro_pixel_format fmt;
|
||||||
|
#ifdef COLOR_16_BIT
|
||||||
|
#ifdef COLOR_5_6_5
|
||||||
|
fmt = RETRO_PIXEL_FORMAT_RGB565;
|
||||||
|
#else
|
||||||
|
#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
|
||||||
|
fmt = RETRO_PIXEL_FORMAT_0RGB1555;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
|
||||||
|
fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||||
|
#endif
|
||||||
|
environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
|
||||||
|
|
||||||
|
struct retro_input_descriptor inputDescriptors[] = {
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
|
||||||
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }
|
||||||
|
};
|
||||||
|
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
|
||||||
|
|
||||||
|
// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
|
||||||
|
// TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE
|
||||||
|
|
||||||
|
struct retro_log_callback log;
|
||||||
|
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
||||||
|
logCallback = log.log;
|
||||||
|
} else {
|
||||||
|
logCallback = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.postAudioFrame = 0;
|
||||||
|
stream.postAudioBuffer = _postAudioBuffer;
|
||||||
|
stream.postVideoFrame = _postVideoFrame;
|
||||||
|
|
||||||
|
GBACreate(&gba);
|
||||||
|
ARMSetComponents(&cpu, &gba.d, 0, 0);
|
||||||
|
ARMInit(&cpu);
|
||||||
|
gba.logLevel = 0; // TODO: Settings
|
||||||
|
gba.logHandler = GBARetroLog;
|
||||||
|
gba.stream = &stream;
|
||||||
|
gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
|
||||||
|
rom = 0;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
GBADestroy(&gba);
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_run(void) {
|
||||||
|
int keys;
|
||||||
|
gba.keySource = &keys;
|
||||||
|
inputPollCallback();
|
||||||
|
|
||||||
|
keys = 0;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
|
||||||
|
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
ARMReset(&cpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retro_load_game(const struct retro_game_info* game) {
|
||||||
|
if (game->data) {
|
||||||
|
data = malloc(game->size);
|
||||||
|
memcpy(data, game->data, game->size);
|
||||||
|
rom = VFileFromMemory(data, game->size);
|
||||||
|
} else {
|
||||||
|
data = 0;
|
||||||
|
rom = VFileOpen(game->path, O_RDONLY);
|
||||||
|
}
|
||||||
|
if (!rom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!GBAIsROM(rom)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
savedata = malloc(SIZE_CART_FLASH1M);
|
||||||
|
save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||||
|
|
||||||
|
GBALoadROM(&gba, rom, save, game->path);
|
||||||
|
|
||||||
|
struct GBACartridgeOverride override;
|
||||||
|
const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
|
||||||
|
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||||
|
if (GBAOverrideFind(0, &override)) {
|
||||||
|
GBAOverrideApply(&gba, &override);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARMReset(&cpu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_unload_game(void) {
|
||||||
|
rom->close(rom);
|
||||||
|
rom = 0;
|
||||||
|
free(data);
|
||||||
|
data = 0;
|
||||||
|
save->close(save);
|
||||||
|
save = 0;
|
||||||
|
free(savedata);
|
||||||
|
savedata = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t retro_serialize_size(void) {
|
||||||
|
return sizeof(struct GBASerializedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retro_serialize(void* data, size_t size) {
|
||||||
|
if (size != retro_serialize_size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GBASerialize(&gba, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retro_unserialize(const void* data, size_t size) {
|
||||||
|
if (size != retro_serialize_size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GBADeserialize(&gba, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_cheat_reset(void) {
|
||||||
|
// TODO: Cheats
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_cheat_set(unsigned index, bool enabled, const char* code) {
|
||||||
|
// TODO: Cheats
|
||||||
|
UNUSED(index);
|
||||||
|
UNUSED(enabled);
|
||||||
|
UNUSED(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned retro_get_region(void) {
|
||||||
|
return RETRO_REGION_NTSC; // TODO: This isn't strictly true
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_set_controller_port_device(unsigned port, unsigned device) {
|
||||||
|
UNUSED(port);
|
||||||
|
UNUSED(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
|
||||||
|
UNUSED(game_type);
|
||||||
|
UNUSED(info);
|
||||||
|
UNUSED(num_info);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* retro_get_memory_data(unsigned id) {
|
||||||
|
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return savedata;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t retro_get_memory_size(unsigned id) {
|
||||||
|
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
switch (gba.memory.savedata.type) {
|
||||||
|
case SAVEDATA_AUTODETECT:
|
||||||
|
case SAVEDATA_FLASH1M:
|
||||||
|
return SIZE_CART_FLASH1M;
|
||||||
|
case SAVEDATA_FLASH512:
|
||||||
|
return SIZE_CART_FLASH512;
|
||||||
|
case SAVEDATA_EEPROM:
|
||||||
|
return SIZE_CART_EEPROM;
|
||||||
|
case SAVEDATA_SRAM:
|
||||||
|
return SIZE_CART_SRAM;
|
||||||
|
case SAVEDATA_FORCE_NONE:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
|
||||||
|
UNUSED(thread);
|
||||||
|
if (!logCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char message[128];
|
||||||
|
vsnprintf(message, sizeof(message), format, args);
|
||||||
|
|
||||||
|
enum retro_log_level retroLevel = RETRO_LOG_INFO;
|
||||||
|
switch (level) {
|
||||||
|
case GBA_LOG_ALL:
|
||||||
|
case GBA_LOG_ERROR:
|
||||||
|
case GBA_LOG_FATAL:
|
||||||
|
retroLevel = RETRO_LOG_ERROR;
|
||||||
|
break;
|
||||||
|
case GBA_LOG_WARN:
|
||||||
|
retroLevel = RETRO_LOG_WARN;
|
||||||
|
break;
|
||||||
|
case GBA_LOG_INFO:
|
||||||
|
case GBA_LOG_GAME_ERROR:
|
||||||
|
case GBA_LOG_SWI:
|
||||||
|
retroLevel = RETRO_LOG_INFO;
|
||||||
|
break;
|
||||||
|
case GBA_LOG_DEBUG:
|
||||||
|
case GBA_LOG_STUB:
|
||||||
|
retroLevel = RETRO_LOG_DEBUG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logCallback(retroLevel, "%s\n", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) {
|
||||||
|
UNUSED(stream);
|
||||||
|
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) {
|
||||||
|
UNUSED(stream);
|
||||||
|
void* pixels;
|
||||||
|
unsigned stride;
|
||||||
|
renderer->getPixels(renderer, &stride, &pixels);
|
||||||
|
videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -47,6 +47,7 @@ void AudioProcessorQt::start() {
|
||||||
format.setSampleType(QAudioFormat::SignedInt);
|
format.setSampleType(QAudioFormat::SignedInt);
|
||||||
|
|
||||||
m_audioOutput = new QAudioOutput(format, this);
|
m_audioOutput = new QAudioOutput(format, this);
|
||||||
|
m_audioOutput->setCategory("game");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_device->setInput(input());
|
m_device->setInput(input());
|
||||||
|
|
|
@ -110,7 +110,7 @@ if(WIN32)
|
||||||
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
||||||
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})
|
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})
|
||||||
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)
|
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||||
|
|
|
@ -271,7 +271,15 @@ void Painter::performDraw() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
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);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
|
||||||
|
#endif
|
||||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||||
if (m_context->sync.videoFrameWait) {
|
if (m_context->sync.videoFrameWait) {
|
||||||
glFlush();
|
glFlush();
|
||||||
|
|
|
@ -20,9 +20,10 @@ const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
|
||||||
const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
|
const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
|
||||||
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
|
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
|
||||||
|
|
||||||
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent)
|
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const char* profile, QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
|
, m_profile(profile)
|
||||||
, m_controller(controller)
|
, m_controller(controller)
|
||||||
{
|
{
|
||||||
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
|
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
|
||||||
|
@ -163,6 +164,10 @@ void GBAKeyEditor::save() {
|
||||||
bindKey(m_keyL, GBA_KEY_L);
|
bindKey(m_keyL, GBA_KEY_L);
|
||||||
bindKey(m_keyR, GBA_KEY_R);
|
bindKey(m_keyR, GBA_KEY_R);
|
||||||
m_controller->saveConfiguration(m_type);
|
m_controller->saveConfiguration(m_type);
|
||||||
|
|
||||||
|
if (m_profile) {
|
||||||
|
m_controller->saveProfile(m_type, m_profile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) {
|
void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class GBAKeyEditor : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr);
|
GBAKeyEditor(InputController* controller, int type, const char* profile = nullptr, QWidget* parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setAll();
|
void setAll();
|
||||||
|
@ -76,6 +76,7 @@ private:
|
||||||
QList<KeyEditor*>::iterator m_currentKey;
|
QList<KeyEditor*>::iterator m_currentKey;
|
||||||
|
|
||||||
uint32_t m_type;
|
uint32_t m_type;
|
||||||
|
const char* m_profile;
|
||||||
InputController* m_controller;
|
InputController* m_controller;
|
||||||
|
|
||||||
QPicture m_background;
|
QPicture m_background;
|
||||||
|
|
|
@ -43,14 +43,16 @@ void GDBController::attach() {
|
||||||
if (m_gameController->isLoaded()) {
|
if (m_gameController->isLoaded()) {
|
||||||
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||||
} else {
|
} else {
|
||||||
connect(m_gameController, &GameController::gameStarted, [this] () {
|
QObject::disconnect(m_autoattach);
|
||||||
disconnect(m_gameController);
|
m_autoattach = connect(m_gameController, &GameController::gameStarted, [this] () {
|
||||||
|
QObject::disconnect(m_autoattach);
|
||||||
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDBController::detach() {
|
void GDBController::detach() {
|
||||||
|
QObject::disconnect(m_autoattach);
|
||||||
if (!isAttached()) {
|
if (!isAttached()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@ private:
|
||||||
|
|
||||||
ushort m_port;
|
ushort m_port;
|
||||||
Address m_bindAddress;
|
Address m_bindAddress;
|
||||||
|
|
||||||
|
QMetaObject::Connection m_autoattach;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,12 +236,12 @@ void GameController::openGame() {
|
||||||
m_threadContext.stateDir = m_threadContext.gameDir;
|
m_threadContext.stateDir = m_threadContext.gameDir;
|
||||||
} else {
|
} else {
|
||||||
m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
|
m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
|
||||||
#if ENABLE_LIBZIP
|
#if USE_LIBZIP
|
||||||
if (!m_threadContext.gameDir) {
|
if (!m_threadContext.gameDir) {
|
||||||
m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
|
m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if ENABLE_LZMA
|
#if USE_LZMA
|
||||||
if (!m_threadContext.gameDir) {
|
if (!m_threadContext.gameDir) {
|
||||||
m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0);
|
m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0);
|
||||||
}
|
}
|
||||||
|
@ -509,6 +509,7 @@ void GameController::setLuminanceValue(uint8_t value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit luminanceValueChanged(m_luxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::setLuminanceLevel(int level) {
|
void GameController::setLuminanceLevel(int level) {
|
||||||
|
|
|
@ -86,6 +86,8 @@ signals:
|
||||||
void gameFailed();
|
void gameFailed();
|
||||||
void stateLoaded(GBAThread*);
|
void stateLoaded(GBAThread*);
|
||||||
|
|
||||||
|
void luminanceValueChanged(int);
|
||||||
|
|
||||||
void postLog(int level, const QString& log);
|
void postLog(int level, const QString& log);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -116,6 +118,7 @@ public slots:
|
||||||
void reloadAudioDriver();
|
void reloadAudioDriver();
|
||||||
|
|
||||||
void setLuminanceValue(uint8_t value);
|
void setLuminanceValue(uint8_t value);
|
||||||
|
uint8_t luminanceValue() const { return m_luxValue; }
|
||||||
void setLuminanceLevel(int level);
|
void setLuminanceLevel(int level);
|
||||||
void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); }
|
void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); }
|
||||||
void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); }
|
void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); }
|
||||||
|
|
|
@ -63,6 +63,7 @@ void InputController::setConfiguration(ConfigController* config) {
|
||||||
loadConfiguration(KEYBOARD);
|
loadConfiguration(KEYBOARD);
|
||||||
#ifdef BUILD_SDL
|
#ifdef BUILD_SDL
|
||||||
loadConfiguration(SDL_BINDING_BUTTON);
|
loadConfiguration(SDL_BINDING_BUTTON);
|
||||||
|
loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +71,34 @@ void InputController::loadConfiguration(uint32_t type) {
|
||||||
GBAInputMapLoad(&m_inputMap, type, m_config->configuration());
|
GBAInputMapLoad(&m_inputMap, type, m_config->configuration());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputController::loadProfile(uint32_t type, const char* profile) {
|
||||||
|
GBAInputProfileLoad(&m_inputMap, type, m_config->configuration(), profile);
|
||||||
|
}
|
||||||
|
|
||||||
void InputController::saveConfiguration(uint32_t type) {
|
void InputController::saveConfiguration(uint32_t type) {
|
||||||
GBAInputMapSave(&m_inputMap, type, m_config->configuration());
|
GBAInputMapSave(&m_inputMap, type, m_config->configuration());
|
||||||
m_config->write();
|
m_config->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputController::saveProfile(uint32_t type, const char* profile) {
|
||||||
|
GBAInputProfileSave(&m_inputMap, type, m_config->configuration(), profile);
|
||||||
|
m_config->write();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* InputController::profileForType(uint32_t type) {
|
||||||
|
UNUSED(type);
|
||||||
|
#ifdef BUILD_SDL
|
||||||
|
if (type == SDL_BINDING_BUTTON) {
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
|
return SDL_JoystickName(m_sdlEvents.joystick);
|
||||||
|
#else
|
||||||
|
return SDL_JoystickName(SDL_JoystickIndex(m_sdlEvents.joystick));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
GBAKey InputController::mapKeyboard(int key) const {
|
GBAKey InputController::mapKeyboard(int key) const {
|
||||||
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
|
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,10 @@ public:
|
||||||
|
|
||||||
void setConfiguration(ConfigController* config);
|
void setConfiguration(ConfigController* config);
|
||||||
void loadConfiguration(uint32_t type);
|
void loadConfiguration(uint32_t type);
|
||||||
|
void loadProfile(uint32_t type, const char* profile);
|
||||||
void saveConfiguration(uint32_t type = KEYBOARD);
|
void saveConfiguration(uint32_t type = KEYBOARD);
|
||||||
|
void saveProfile(uint32_t type, const char* profile);
|
||||||
|
const char* profileForType(uint32_t type);
|
||||||
|
|
||||||
GBAKey mapKeyboard(int key) const;
|
GBAKey mapKeyboard(int key) const;
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,7 @@ void OverrideView::gameStopped() {
|
||||||
m_ui.hwTilt->setEnabled(!m_ui.hwAutodetect->isChecked());
|
m_ui.hwTilt->setEnabled(!m_ui.hwAutodetect->isChecked());
|
||||||
m_ui.hwRumble->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.hwRTC->setChecked(false);
|
||||||
m_ui.hwGyro->setChecked(false);
|
m_ui.hwGyro->setChecked(false);
|
||||||
m_ui.hwLight->setChecked(false);
|
m_ui.hwLight->setChecked(false);
|
||||||
|
@ -150,7 +151,9 @@ void OverrideView::gameStopped() {
|
||||||
m_ui.hwRumble->setChecked(false);
|
m_ui.hwRumble->setChecked(false);
|
||||||
|
|
||||||
m_ui.idleLoop->setEnabled(true);
|
m_ui.idleLoop->setEnabled(true);
|
||||||
|
m_ui.idleLoop->clear();
|
||||||
|
|
||||||
m_ui.clear->setEnabled(false);
|
|
||||||
m_ui.save->setEnabled(false);
|
m_ui.save->setEnabled(false);
|
||||||
|
|
||||||
|
updateOverrides();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,6 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="clear">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Clear</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="save">
|
<widget class="QPushButton" name="save">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
|
|
|
@ -31,12 +31,17 @@ SensorView::SensorView(GameController* controller, QWidget* parent)
|
||||||
connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () {
|
connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () {
|
||||||
m_ui.time->setDateTime(QDateTime::currentDateTime());
|
m_ui.time->setDateTime(QDateTime::currentDateTime());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_controller, SIGNAL(luminanceValueChanged(int)), this, SLOT(luminanceValueChanged(int)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorView::setLuminanceValue(int value) {
|
void SensorView::setLuminanceValue(int value) {
|
||||||
bool oldState;
|
|
||||||
value = std::max(0, std::min(value, 255));
|
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);
|
oldState = m_ui.lightSpin->blockSignals(true);
|
||||||
m_ui.lightSpin->setValue(value);
|
m_ui.lightSpin->setValue(value);
|
||||||
m_ui.lightSpin->blockSignals(oldState);
|
m_ui.lightSpin->blockSignals(oldState);
|
||||||
|
@ -44,6 +49,4 @@ void SensorView::setLuminanceValue(int value) {
|
||||||
oldState = m_ui.lightSlide->blockSignals(true);
|
oldState = m_ui.lightSlide->blockSignals(true);
|
||||||
m_ui.lightSlide->setValue(value);
|
m_ui.lightSlide->setValue(value);
|
||||||
m_ui.lightSlide->blockSignals(oldState);
|
m_ui.lightSlide->blockSignals(oldState);
|
||||||
|
|
||||||
m_controller->setLuminanceValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ public:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setLuminanceValue(int);
|
void setLuminanceValue(int);
|
||||||
|
void luminanceValueChanged(int);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::SensorView m_ui;
|
Ui::SensorView m_ui;
|
||||||
|
|
|
@ -256,7 +256,8 @@ void Window::openCheatsWindow() {
|
||||||
|
|
||||||
#ifdef BUILD_SDL
|
#ifdef BUILD_SDL
|
||||||
void Window::openGamepadWindow() {
|
void Window::openGamepadWindow() {
|
||||||
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON);
|
const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON);
|
||||||
|
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile);
|
||||||
connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
|
connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
|
||||||
keyEditor->setAttribute(Qt::WA_DeleteOnClose);
|
keyEditor->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
keyEditor->show();
|
keyEditor->show();
|
||||||
|
@ -714,22 +715,22 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
|
addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
toolsMenu->addSeparator();
|
QMenu* solarMenu = toolsMenu->addMenu(tr("Solar sensor"));
|
||||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), toolsMenu);
|
QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
|
||||||
connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel()));
|
connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel()));
|
||||||
addControlledAction(toolsMenu, solarIncrease, "increaseLuminanceLevel");
|
addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
|
||||||
|
|
||||||
QAction* solarDecrease = new QAction(tr("Decrease solar level"), toolsMenu);
|
QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu);
|
||||||
connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel()));
|
connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel()));
|
||||||
addControlledAction(toolsMenu, solarDecrease, "decreaseLuminanceLevel");
|
addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel");
|
||||||
|
|
||||||
QAction* maxSolar = new QAction(tr("Brightest solar level"), toolsMenu);
|
QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);
|
||||||
connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); });
|
connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); });
|
||||||
addControlledAction(toolsMenu, maxSolar, "maxLuminanceLevel");
|
addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel");
|
||||||
|
|
||||||
QAction* minSolar = new QAction(tr("Darkest solar level"), toolsMenu);
|
QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu);
|
||||||
connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); });
|
connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); });
|
||||||
addControlledAction(toolsMenu, minSolar, "minLuminanceLevel");
|
addControlledAction(solarMenu, minSolar, "minLuminanceLevel");
|
||||||
|
|
||||||
toolsMenu->addSeparator();
|
toolsMenu->addSeparator();
|
||||||
addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings");
|
addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings");
|
||||||
|
|
|
@ -38,6 +38,7 @@ if(BUILD_RASPI)
|
||||||
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline")
|
||||||
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC})
|
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC})
|
||||||
|
set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||||
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
|
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
|
||||||
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi)
|
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi)
|
||||||
endif()
|
endif()
|
||||||
|
@ -52,6 +53,7 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
|
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
|
||||||
|
set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||||
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
|
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
|
||||||
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
|
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
|
||||||
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
|
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
|
||||||
|
|
|
@ -59,7 +59,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||||
renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS;
|
renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS;
|
||||||
glGenTextures(1, &renderer->tex);
|
glGenTextures(1, &renderer->tex);
|
||||||
glBindTexture(GL_TEXTURE_2D, renderer->tex);
|
glBindTexture(GL_TEXTURE_2D, renderer->tex);
|
||||||
|
|
|
@ -78,7 +78,14 @@ void GBASDLInitBindings(struct GBAInputMap* inputMap) {
|
||||||
|
|
||||||
void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {
|
void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {
|
||||||
GBAInputMapLoad(context->bindings, SDL_BINDING_KEY, 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)
|
||||||
|
GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(context->joystick));
|
||||||
|
#else
|
||||||
|
GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(SDL_JoystickIndex(context->joystick)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
||||||
|
@ -140,14 +147,6 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
||||||
GBARewind(context, 10);
|
GBARewind(context, 10);
|
||||||
GBAThreadContinue(context);
|
GBAThreadContinue(context);
|
||||||
return;
|
return;
|
||||||
case SDLK_ESCAPE:
|
|
||||||
GBAThreadInterrupt(context);
|
|
||||||
if (context->gba->rr) {
|
|
||||||
GBARRStopPlaying(context->gba->rr);
|
|
||||||
GBARRStopRecording(context->gba->rr);
|
|
||||||
}
|
|
||||||
GBAThreadContinue(context);
|
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
|
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
|
||||||
switch (event->keysym.sym) {
|
switch (event->keysym.sym) {
|
||||||
|
@ -169,31 +168,6 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
GBAThreadReset(context);
|
GBAThreadReset(context);
|
||||||
break;
|
break;
|
||||||
case SDLK_t:
|
|
||||||
if (context->stateDir) {
|
|
||||||
GBAThreadInterrupt(context);
|
|
||||||
GBARRContextCreate(context->gba);
|
|
||||||
if (!GBARRIsRecording(context->gba->rr)) {
|
|
||||||
GBARRStopPlaying(context->gba->rr);
|
|
||||||
GBARRInitStream(context->gba->rr, context->stateDir);
|
|
||||||
GBARRReinitStream(context->gba->rr, INIT_EX_NIHILO);
|
|
||||||
GBARRStartRecording(context->gba->rr);
|
|
||||||
GBARRSaveState(context->gba);
|
|
||||||
}
|
|
||||||
GBAThreadContinue(context);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDLK_y:
|
|
||||||
if (context->stateDir) {
|
|
||||||
GBAThreadInterrupt(context);
|
|
||||||
GBARRContextCreate(context->gba);
|
|
||||||
GBARRStopRecording(context->gba->rr);
|
|
||||||
GBARRInitStream(context->gba->rr, context->stateDir);
|
|
||||||
GBARRStartPlaying(context->gba->rr, false);
|
|
||||||
GBARRLoadState(context->gba);
|
|
||||||
GBAThreadContinue(context);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
# 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
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
#ifdef __ARM_NEON
|
||||||
# r0: Destination
|
# r0: Destination
|
||||||
# r1: Source
|
# r1: Source
|
||||||
# r2: Number of words to copy as halfwords
|
# r2: Number of words to copy as halfwords
|
||||||
|
@ -13,28 +14,22 @@ mov r8, r0
|
||||||
mov r9, r1
|
mov r9, r1
|
||||||
mov r10, r2
|
mov r10, r2
|
||||||
.L0:
|
.L0:
|
||||||
tst r10, #7
|
tst r10, #15
|
||||||
beq .L1
|
beq .L1
|
||||||
ldr r0, [r9], #4
|
ldr r0, [r9], #4
|
||||||
strh r0, [r8], #2
|
strh r0, [r8], #2
|
||||||
sub r10, #1
|
sub r10, #1
|
||||||
b .L0
|
b .L0
|
||||||
.L1:
|
.L1:
|
||||||
ldmia r9!, {r0-r7}
|
vld4.16 {d0, d1, d2, d3}, [r9]!
|
||||||
strh r0, [r8], #2
|
vld4.16 {d4, d5, d6, d7}, [r9]!
|
||||||
strh r1, [r8], #2
|
vst2.16 {d0, d2}, [r8]!
|
||||||
strh r2, [r8], #2
|
vst2.16 {d4, d6}, [r8]!
|
||||||
strh r3, [r8], #2
|
subs r10, #16
|
||||||
strh r4, [r8], #2
|
|
||||||
strh r5, [r8], #2
|
|
||||||
strh r6, [r8], #2
|
|
||||||
strh r7, [r8], #2
|
|
||||||
subs r10, #8
|
|
||||||
bne .L1
|
bne .L1
|
||||||
pop {r4-r10}
|
pop {r4-r10}
|
||||||
bx lr
|
bx lr
|
||||||
|
|
||||||
#ifdef __ARM_NEON
|
|
||||||
# r0: Destination
|
# r0: Destination
|
||||||
# r1: Source
|
# r1: Source
|
||||||
# r2: Width
|
# r2: Width
|
||||||
|
@ -97,3 +92,5 @@ bne .n40
|
||||||
pop {r4-r7}
|
pop {r4-r7}
|
||||||
bx lr
|
bx lr
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
.section .note.GNU-stack,"",%progbits
|
||||||
|
|
|
@ -253,7 +253,7 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = timeoutMillis / 1000;
|
tv.tv_sec = timeoutMillis / 1000;
|
||||||
tv.tv_usec = (timeoutMillis % 1000) * 1000;
|
tv.tv_usec = (timeoutMillis % 1000) * 1000;
|
||||||
int result = select(maxFd, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv);
|
int result = select(maxFd + 1, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv);
|
||||||
int r = 0;
|
int r = 0;
|
||||||
int w = 0;
|
int w = 0;
|
||||||
int e = 0;
|
int e = 0;
|
||||||
|
|
|
@ -378,3 +378,15 @@ const char* _vdeName(struct VDirEntry* vde) {
|
||||||
}
|
}
|
||||||
return 0;
|
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';
|
||||||
|
}
|
||||||
|
|
|
@ -38,18 +38,21 @@ struct VDir {
|
||||||
|
|
||||||
struct VFile* VFileOpen(const char* path, int flags);
|
struct VFile* VFileOpen(const char* path, int flags);
|
||||||
struct VFile* VFileFromFD(int fd);
|
struct VFile* VFileFromFD(int fd);
|
||||||
|
struct VFile* VFileFromMemory(void* mem, size_t size);
|
||||||
|
|
||||||
struct VDir* VDirOpen(const char* path);
|
struct VDir* VDirOpen(const char* path);
|
||||||
|
|
||||||
#ifdef ENABLE_LIBZIP
|
#ifdef USE_LIBZIP
|
||||||
struct VDir* VDirOpenZip(const char* path, int flags);
|
struct VDir* VDirOpenZip(const char* path, int flags);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_LZMA
|
#ifdef USE_LZMA
|
||||||
struct VDir* VDirOpen7z(const char* path, int flags);
|
struct VDir* VDirOpen7z(const char* path, int flags);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode);
|
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);
|
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);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "util/vfs.h"
|
#include "util/vfs.h"
|
||||||
|
|
||||||
#ifdef ENABLE_LZMA
|
#ifdef USE_LZMA
|
||||||
|
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ struct VFile7z {
|
||||||
static bool _vf7zClose(struct VFile* vf);
|
static bool _vf7zClose(struct VFile* vf);
|
||||||
static off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence);
|
static off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence);
|
||||||
static ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size);
|
static ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size);
|
||||||
static ssize_t _vf7zReadline(struct VFile* vf, char* buffer, size_t size);
|
|
||||||
static ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size);
|
static ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||||
static void* _vf7zMap(struct VFile* vf, size_t size, int flags);
|
static void* _vf7zMap(struct VFile* vf, size_t size, int flags);
|
||||||
static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size);
|
static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size);
|
||||||
|
@ -142,16 +141,12 @@ off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position <= vf7z->offset) {
|
if (position > vf7z->size) {
|
||||||
vf7z->offset = position;
|
return -1;
|
||||||
return position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position <= vf7z->size) {
|
vf7z->offset = position;
|
||||||
return vf7z->offset;
|
return position;
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) {
|
ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) {
|
||||||
|
@ -162,21 +157,10 @@ ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(buffer, vf7z->outBuffer + vf7z->offset + vf7z->bufferOffset, size);
|
memcpy(buffer, vf7z->outBuffer + vf7z->offset + vf7z->bufferOffset, size);
|
||||||
|
vf7z->offset += size;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t _vf7zReadline(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';
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size) {
|
ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
// TODO
|
// TODO
|
||||||
UNUSED(vf);
|
UNUSED(vf);
|
||||||
|
@ -301,7 +285,7 @@ struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) {
|
||||||
vf->d.close = _vf7zClose;
|
vf->d.close = _vf7zClose;
|
||||||
vf->d.seek = _vf7zSeek;
|
vf->d.seek = _vf7zSeek;
|
||||||
vf->d.read = _vf7zRead;
|
vf->d.read = _vf7zRead;
|
||||||
vf->d.readline = _vf7zReadline;
|
vf->d.readline = VFileReadline;
|
||||||
vf->d.write = _vf7zWrite;
|
vf->d.write = _vf7zWrite;
|
||||||
vf->d.map = _vf7zMap;
|
vf->d.map = _vf7zMap;
|
||||||
vf->d.unmap = _vf7zUnmap;
|
vf->d.unmap = _vf7zUnmap;
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/* 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"
|
||||||
|
|
||||||
|
struct VFileMem {
|
||||||
|
struct VFile d;
|
||||||
|
void* mem;
|
||||||
|
size_t size;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool _vfmClose(struct VFile* vf);
|
||||||
|
static off_t _vfmSeek(struct VFile* vf, off_t offset, int whence);
|
||||||
|
static ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size);
|
||||||
|
static ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||||
|
static void* _vfmMap(struct VFile* vf, size_t size, int flags);
|
||||||
|
static void _vfmUnmap(struct VFile* vf, void* memory, size_t size);
|
||||||
|
static void _vfmTruncate(struct VFile* vf, size_t size);
|
||||||
|
static ssize_t _vfmSize(struct VFile* vf);
|
||||||
|
|
||||||
|
struct VFile* VFileFromMemory(void* mem, size_t size) {
|
||||||
|
if (!mem || !size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VFileMem* vfm = malloc(sizeof(struct VFileMem));
|
||||||
|
if (!vfm) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfm->mem = mem;
|
||||||
|
vfm->size = size;
|
||||||
|
vfm->offset = 0;
|
||||||
|
vfm->d.close = _vfmClose;
|
||||||
|
vfm->d.seek = _vfmSeek;
|
||||||
|
vfm->d.read = _vfmRead;
|
||||||
|
vfm->d.readline = VFileReadline;
|
||||||
|
vfm->d.write = _vfmWrite;
|
||||||
|
vfm->d.map = _vfmMap;
|
||||||
|
vfm->d.unmap = _vfmUnmap;
|
||||||
|
vfm->d.truncate = _vfmTruncate;
|
||||||
|
vfm->d.size = _vfmSize;
|
||||||
|
|
||||||
|
return &vfm->d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _vfmClose(struct VFile* vf) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
vfm->mem = 0;
|
||||||
|
free(vfm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t _vfmSeek(struct VFile* vf, off_t offset, int whence) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
|
||||||
|
size_t position;
|
||||||
|
switch (whence) {
|
||||||
|
case SEEK_SET:
|
||||||
|
position = offset;
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
if (offset < 0 && ((vfm->offset < (size_t) -offset) || (offset == INT_MIN))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
position = vfm->offset + offset;
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
if (offset < 0 && ((vfm->size < (size_t) -offset) || (offset == INT_MIN))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
position = vfm->size + offset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position > vfm->size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfm->offset = position;
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
|
||||||
|
if (size + vfm->offset >= vfm->size) {
|
||||||
|
size = vfm->size - vfm->offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buffer, vfm->mem + vfm->offset, size);
|
||||||
|
vfm->offset += size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
|
||||||
|
if (size + vfm->offset >= vfm->size) {
|
||||||
|
size = vfm->size - vfm->offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(vfm->mem + vfm->offset, buffer, size);
|
||||||
|
vfm->offset += size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* _vfmMap(struct VFile* vf, size_t size, int flags) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
|
||||||
|
UNUSED(flags);
|
||||||
|
if (size > vfm->size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vfm->mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _vfmUnmap(struct VFile* vf, void* memory, size_t size) {
|
||||||
|
UNUSED(vf);
|
||||||
|
UNUSED(memory);
|
||||||
|
UNUSED(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _vfmTruncate(struct VFile* vf, size_t size) {
|
||||||
|
// TODO: Return value?
|
||||||
|
UNUSED(vf);
|
||||||
|
UNUSED(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t _vfmSize(struct VFile* vf) {
|
||||||
|
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||||
|
return vfm->size;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "util/vfs.h"
|
#include "util/vfs.h"
|
||||||
|
|
||||||
#ifdef ENABLE_LIBZIP
|
#ifdef USE_LIBZIP
|
||||||
#include <zip.h>
|
#include <zip.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ struct VFileZip {
|
||||||
static bool _vfzClose(struct VFile* vf);
|
static bool _vfzClose(struct VFile* vf);
|
||||||
static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence);
|
static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence);
|
||||||
static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size);
|
static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size);
|
||||||
static ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size);
|
|
||||||
static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size);
|
static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||||
static void* _vfzMap(struct VFile* vf, size_t size, int flags);
|
static void* _vfzMap(struct VFile* vf, size_t size, int flags);
|
||||||
static void _vfzUnmap(struct VFile* vf, void* memory, size_t size);
|
static void _vfzUnmap(struct VFile* vf, void* memory, size_t size);
|
||||||
|
@ -188,18 +187,6 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t _vfzReadline(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';
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
|
ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
// TODO
|
// TODO
|
||||||
UNUSED(vf);
|
UNUSED(vf);
|
||||||
|
@ -296,7 +283,7 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
|
||||||
vfz->d.close = _vfzClose;
|
vfz->d.close = _vfzClose;
|
||||||
vfz->d.seek = _vfzSeek;
|
vfz->d.seek = _vfzSeek;
|
||||||
vfz->d.read = _vfzRead;
|
vfz->d.read = _vfzRead;
|
||||||
vfz->d.readline = _vfzReadline;
|
vfz->d.readline = VFileReadline;
|
||||||
vfz->d.write = _vfzWrite;
|
vfz->d.write = _vfzWrite;
|
||||||
vfz->d.map = _vfzMap;
|
vfz->d.map = _vfzMap;
|
||||||
vfz->d.unmap = _vfzUnmap;
|
vfz->d.unmap = _vfzUnmap;
|
||||||
|
|
Loading…
Reference in New Issue