mirror of https://github.com/mgba-emu/mgba.git
3DS: Modernize 3DS port
This commit is contained in:
commit
22245617f4
|
@ -0,0 +1,13 @@
|
|||
language: c
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y
|
||||
- sudo apt-add-repository ppa:zoogie/sdl2-snapshots -y
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get purge cmake -qq
|
||||
- sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev
|
||||
|
||||
script: mkdir build && cd build && cmake .. && make
|
|
@ -0,0 +1,127 @@
|
|||
0.2.0: (Future)
|
||||
Features:
|
||||
- Support for gamepad axes, e.g. analog sticks or triggers
|
||||
- Add scale presets for up to 6x
|
||||
- Debugger: Add CLI "frame", frame advance command
|
||||
- Settings window
|
||||
- Bilinear resampling option
|
||||
- Add option to skip BIOS start screen
|
||||
- List of recently opened games
|
||||
- Support for games using the Solar Sensor
|
||||
- Debugger: Add CLI functions for writing to memory
|
||||
- Better audio resampling via blip-buf
|
||||
- Game Pak overrides dialog for setting savetype and sensor values
|
||||
- Support for games using the tilt sensor
|
||||
- Remappable shortcuts for keyboard and gamepad
|
||||
- Rewinding of emulation
|
||||
- Implemented BIOS routines SoftReset, RegisterRamReset, Diff8bitUnFilterWram, Diff8bitUnFilterVram, and Diff16bitUnFilter
|
||||
- Support IPv6
|
||||
- Save directory of last loaded file
|
||||
- Support BPS patches
|
||||
- Automatically detect and optimize out idle loops
|
||||
- Configurable game overrides
|
||||
- Support loading 7-Zip files
|
||||
- Drag and drop game loading
|
||||
- Cheat code support
|
||||
- Debugger: Add CLI functions for examining memory regions
|
||||
- Runtime configurable audio driver
|
||||
- Debugger: Add CLI function for writing a register
|
||||
- Libretro core for use with RetroArch and other front-ends
|
||||
Bugfixes:
|
||||
- ARM7: Extend prefetch by one stage
|
||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||
- GBA Audio: Audio buffer sizes are now correct sizes for both sample rates
|
||||
- GBA BIOS: Fix BIOS prefetch after returning from an IRQ
|
||||
- GBA BIOS: Fix BIOS prefetch after reset
|
||||
- GBA Memory: Fix alignment of open bus 8- and 16-bit loads
|
||||
- GBA Thread: Fix possible hang when loading an archive
|
||||
- Perf: Fix crash when the GBA thread fails to start
|
||||
- SDL: Properly clean up if a game doesn't launch
|
||||
- Debugger: Disassembly now lists PSR bitmasks (fixes #191)
|
||||
- GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184)
|
||||
- GBA RR: Fix fallthrough error when reading tags from a movie
|
||||
- GBA Thread: Fix possible deadlock in video sync
|
||||
- GBA: Fix savestate loading of DISPSTAT and WAITCNT registers
|
||||
- Qt: Fix crash starting a GDB stub if a game isn't loaded
|
||||
- Qt: Fix crash when adjusting settings after closing a game
|
||||
- Qt: Fix crash when starting GDB stub after closing a game
|
||||
- Qt: Fix patch loading while a game is running
|
||||
- Util: Fix sockets on Windows
|
||||
- Qt: Fix crash when loading a game after stopping GDB server
|
||||
- GBA BIOS: Fix BIOS decompression routines with invalid source addresses
|
||||
- GBA: Initialize gba.sync to null
|
||||
Misc:
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
- GBA Video: Start video at the last scanline instead of the first
|
||||
- Debugger: Watchpoints now work on STM/LDM instructions
|
||||
- GBA: Improve accuracy of event timing
|
||||
- Debugger: Clean up GDB stub network interfacing
|
||||
- Debugger: Simplify debugger state machine to play nicer with the GBA thread loop
|
||||
- Debugger: Merge Thumb BL instructions when disassembling
|
||||
- Debugger: Clean up debugger interface, removing obsolete state (fixes #67)
|
||||
- Debugger: Watchpoints now report address watched (fixes #68)
|
||||
- GBA: Add API for getting Configuration structs for overrides and input
|
||||
- GBA: Refactor gba-sensors and gba-gpio into gba-hardware
|
||||
- GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory
|
||||
- Debugger: Add support for soft breakpoints
|
||||
- Util: Use proper locale for reading and writing float values
|
||||
- Debugger: Make I/O register names be addresses instead of values
|
||||
- Debugger: Rename read/write commands
|
||||
- Qt: Optimize logo drawing
|
||||
- Qt: Move frame upload back onto main thread
|
||||
- All: Enable link-time optimization
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA: Move A/V stream interface into core
|
||||
|
||||
0.1.1: (2015-01-24)
|
||||
Bugfixes:
|
||||
- ARM7: Fix LDM writeback to a register already written
|
||||
- GBA: Fix timers 2 and 3 updating incorrectly
|
||||
- GBA Audio: Make larger buffer sizes than 2048 actually work properly
|
||||
- GBA Audio: Fix GB audio channels being too quiet (fixes #159)
|
||||
- GBA Audio: Properly initialize audio FIFO channels
|
||||
- GBA BIOS: Fix HLE Lz77 and RL functions to properly account for width and invalid addresses
|
||||
- GBA BIOS: Fix BIOS prefetch after returning from a SWI
|
||||
- GBA BIOS: Fix LZ77UnCompVram to use 16-bit loads from decompressed memory
|
||||
- GBA BIOS: Fix HuffUnComp to work when games pass an invalid bit length
|
||||
- GBA BIOS: Fix GetBiosChecksum to return the value of a real GBA, regardless of used BIOS
|
||||
- GBA BIOS: Fix HuffUnComp boundary conditions
|
||||
- GBA Memory: Don't call into GPIO write calls if GPIO devices are absent
|
||||
- GBA Memory: Properly initialize 1 Mb flash, and add debug logging
|
||||
- GBA Memory: Filter out top nybble of DMA addresses
|
||||
- GBA Memory: Properly bounds-check VRAM accesses
|
||||
- GBA Memory: Fix initial DMA state
|
||||
- GBA Thread: Allow halted games to exit cleanly
|
||||
- GBA Video: Fix blend issues with obscured middle layers
|
||||
- GBA Video: Fix windows not disabling target 1 appropriately (fixes #161)
|
||||
- GBA Video: Fix sprite mis-ordering behavior in some cases (fixes #168)
|
||||
- GBA Video: Fix window interactions with 16-color mode 0 mosaic
|
||||
- GBA Video: Fix sprite boundary conditions with mosaic
|
||||
- GBA Video: Fix mode 0 being able to read tiles above appropriate tile range
|
||||
- Qt: Fix issue with set frame sizes being the wrong height
|
||||
- Qt: Fix emulator crashing when full screen if a game is not running
|
||||
- Qt: Fix window focus issues
|
||||
- Qt: Properly set default video recording settings
|
||||
- Qt: Fix a race condition when a game crashes immediately
|
||||
- Qt: Fix some cases where key mapping can break if focus is adjusted
|
||||
- Qt: Fix crash if a game pauses before any frames are shown
|
||||
- Debugger: Negative PC-relative loads now properly subtract the offset
|
||||
- Debugger: Align PC-relative loads in Thumb
|
||||
- Debugger: Fix watchpoints triggering too late
|
||||
- Debugger: Fix binary print putting spaces between digits
|
||||
- Video: Ensure FFmpeg encoder has audio frames
|
||||
- Video: Fix uncompressed PCM audio recording
|
||||
- Video: Fix FFmpeg crashing when the file extension is wrong
|
||||
- Util: Fix SOCKET_FAILED macro
|
||||
Misc:
|
||||
- GBA: Exit cleanly on FATAL if the port supports it
|
||||
- GBA Memory: Implement 16- and 32-bit loads from SRAM
|
||||
- Qt: Disable sync to video by default
|
||||
- Qt: Handle a game crash without crashing
|
||||
- Qt: Set default log level to FATAL, ERROR and WARN
|
||||
- Qt: Clarify some phrasing in the menus
|
||||
- Qt: Clear active buttons when focus is lost
|
||||
|
||||
0.1.0: (2014-12-13)
|
||||
- Initial release
|
222
CMakeLists.txt
222
CMakeLists.txt
|
@ -8,11 +8,18 @@ set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
|
|||
set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support")
|
||||
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable ZIP support")
|
||||
set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support")
|
||||
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(BUILD_QT ON CACHE BOOL "Build Qt 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_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.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 UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
||||
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
||||
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
|
||||
|
@ -20,15 +27,17 @@ file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
|
|||
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||
source_group("ARM core" FILES ${ARM_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC})
|
||||
source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/gba)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Function definitions
|
||||
|
@ -61,16 +70,17 @@ endfunction()
|
|||
|
||||
# Version information
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 1)
|
||||
set(LIB_VERSION_MINOR 2)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_ABI 0.1)
|
||||
set(LIB_VERSION_ABI 0.2)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
# 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_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_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")
|
||||
|
||||
|
@ -87,21 +97,18 @@ endif()
|
|||
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}")
|
||||
|
||||
# Feature dependencies
|
||||
find_feature(USE_CLI_DEBUGGER "libedit")
|
||||
set(FEATURES)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
|
||||
set(LIBEDIT_LIBRARIES -ledit)
|
||||
else()
|
||||
find_feature(USE_CLI_DEBUGGER "libedit")
|
||||
endif()
|
||||
find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale")
|
||||
find_feature(USE_PNG "ZLIB;PNG")
|
||||
find_feature(USE_LIBZIP "libzip")
|
||||
find_feature(USE_MAGICK "MagickWand")
|
||||
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(strndup HAVE_STRNDUP)
|
||||
|
||||
if(HAVE_STRNDUP)
|
||||
add_definitions(-DHAVE_STRNDUP)
|
||||
endif()
|
||||
|
||||
# Platform support
|
||||
set(BINARY_TYPE SHARED)
|
||||
if(WIN32)
|
||||
add_definitions(-D_WIN32_WINNT=0x0600)
|
||||
list(APPEND OS_LIB ws2_32)
|
||||
|
@ -109,94 +116,220 @@ if(WIN32)
|
|||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
elseif(UNIX)
|
||||
add_definitions(-DUSE_PTHREADS)
|
||||
list(APPEND OS_LIB pthread)
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||
endif()
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
elseif(3DS)
|
||||
enable_language(ASM)
|
||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||
list(APPEND OS_LIB ctru)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/3ds/3ds-*.c)
|
||||
source_group("3DS-specific code" FILES ${OS_SRC})
|
||||
set(BINARY_TYPE STATIC)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
||||
endif()
|
||||
|
||||
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
||||
endif()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI)
|
||||
enable_language(ASM)
|
||||
if(NOT BUILD_EGL)
|
||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm")
|
||||
enable_language(ASM)
|
||||
endif()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(strndup HAVE_STRNDUP)
|
||||
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# The strtof_l on Linux not actually exposed nor actually strtof_l
|
||||
set(HAVE_STRTOF_L OFF)
|
||||
else()
|
||||
check_function_exists(strtof_l HAVE_STRTOF_L)
|
||||
endif()
|
||||
check_function_exists(newlocale HAVE_NEWLOCALE)
|
||||
check_function_exists(freelocale HAVE_FREELOCALE)
|
||||
check_function_exists(uselocale HAVE_USELOCALE)
|
||||
|
||||
if(HAVE_STRNDUP)
|
||||
add_definitions(-DHAVE_STRNDUP)
|
||||
endif()
|
||||
|
||||
if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE)
|
||||
add_definitions(-DHAVE_LOCALE)
|
||||
if (HAVE_STRTOF_L)
|
||||
add_definitions(-DHAVE_STRTOF_L)
|
||||
endif()
|
||||
if (HAVE_SNPRINTF_L)
|
||||
add_definitions(-DHAVE_SNPRINTF_L)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Features
|
||||
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")
|
||||
|
||||
if(USE_CLI_DEBUGGER)
|
||||
add_definitions(-DUSE_CLI_DEBUGGER)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/gba/gba-cli.c)
|
||||
list(APPEND FEATURES CLI_DEBUGGER)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
||||
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
||||
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libedit2")
|
||||
else()
|
||||
set(DEBUGGER_LIB "")
|
||||
endif()
|
||||
|
||||
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)
|
||||
endif()
|
||||
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
||||
|
||||
if(USE_FFMPEG)
|
||||
add_definitions(-DUSE_FFMPEG)
|
||||
list(APPEND FEATURES FFMPEG)
|
||||
pkg_search_module(LIBSWRESAMPLE QUIET libswresample)
|
||||
if(NOT LIBSWRESAMPLE_FOUND)
|
||||
list(APPEND FEATURES LIBAV)
|
||||
endif()
|
||||
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})
|
||||
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]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra")
|
||||
endif()
|
||||
|
||||
if(USE_BLIP)
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF)
|
||||
else()
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)
|
||||
endif()
|
||||
|
||||
if(USE_MAGICK)
|
||||
add_definitions(-DUSE_MAGICK)
|
||||
list(APPEND FEATURES MAGICK)
|
||||
include_directories(AFTER ${MAGICKWAND_INCLUDE_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})
|
||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
||||
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
||||
set(MAGICKWAND_DEB_VERSION "5")
|
||||
else()
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-2")
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libmagickwand${MAGICKWAND_DEB_VERSION}")
|
||||
endif()
|
||||
|
||||
if(USE_PNG)
|
||||
add_definitions(-DUSE_PNG)
|
||||
list(APPEND FEATURES PNG)
|
||||
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0,zlib1g")
|
||||
endif()
|
||||
|
||||
if(USE_LIBZIP)
|
||||
include_directories(AFTER ${LIBZIP_INCLUDE_DIRS})
|
||||
link_directories(${LIBZIP_LIBRARY_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
|
||||
add_definitions(-DENABLE_LIBZIP)
|
||||
list(APPEND FEATURES LIBZIP)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
||||
endif()
|
||||
|
||||
if (USE_LZMA)
|
||||
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
||||
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
||||
set(LZMA_SRC
|
||||
${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/7zBuf.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf2.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrc.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrcOpt.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zDec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/CpuArch.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/LzmaDec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Lzma2Dec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra86.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bcj2.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7.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/7zStream.c)
|
||||
list(APPEND FEATURE_SRC ${LZMA_SRC})
|
||||
list(APPEND FEATURES LZMA)
|
||||
endif()
|
||||
|
||||
set(FEATURE_DEFINES)
|
||||
foreach(FEATURE IN LISTS FEATURES)
|
||||
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||
endforeach()
|
||||
|
||||
# Binaries
|
||||
add_library(${BINARY_NAME} ${BINARY_TYPE}
|
||||
set(CORE_SRC
|
||||
${ARM_SRC}
|
||||
${GBA_SRC}
|
||||
${GBA_RR_SRC}
|
||||
${GBA_SV_SRC}
|
||||
${DEBUGGER_SRC}
|
||||
${RENDERER_SRC}
|
||||
${UTIL_SRC}
|
||||
${VFS_SRC}
|
||||
${OS_SRC}
|
||||
${THIRD_PARTY_SRC})
|
||||
|
||||
set(SRC
|
||||
${CORE_SRC}
|
||||
${FEATURE_SRC})
|
||||
|
||||
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
||||
set(BUILD_SHARED ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED)
|
||||
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
target_compile_definitions(${BINARY_NAME}-static PRIVATE ${FEATURE_DEFINES})
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
endif()
|
||||
else()
|
||||
add_library(${BINARY_NAME} STATIC ${SRC})
|
||||
endif()
|
||||
|
||||
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib)
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE ${FEATURE_DEFINES})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
|
||||
|
||||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "")
|
||||
target_compile_definitions(${BINARY_NAME}_libretro PRIVATE COLOR_16_BIT;COLOR_5_6_5 PUBLIC "" INTERFACE "")
|
||||
target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB})
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_definitions(-DBUILD_SDL)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl)
|
||||
|
@ -214,8 +347,8 @@ if(BUILD_PERF)
|
|||
|
||||
add_executable(${BINARY_NAME}-perf ${PERF_SRC})
|
||||
target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB})
|
||||
install(TARGETS ${BINARY_NAME}-perf DESTINATION bin)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}")
|
||||
install(TARGETS ${BINARY_NAME}-perf DESTINATION bin COMPONENT ${BINARY_NAME}-perf)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf)
|
||||
endif()
|
||||
|
||||
if(3DS)
|
||||
|
@ -229,9 +362,20 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
|
|||
set(CPACK_PACKAGE_VERSION_MINOR ${LIB_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${LIB_VERSION_PATCH})
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_SOURCE_DIR}/README.md)
|
||||
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "mGBA Game Boy Advance Emulator")
|
||||
set(CPACK_PACKAGE_VENDOR "Jeffrey Pfau")
|
||||
set(CPACK_PACKAGE_CONTACT "Jeffrey Pfau <jeffrey@endrift.com>")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
|
||||
SET(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
|
||||
set(CPACK_STRIP_FILES ${BINARY_NAME})
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/README.md ${CMAKE_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME})
|
||||
|
||||
include(CPack)
|
||||
|
||||
# Summaries
|
||||
|
@ -242,7 +386,13 @@ message(STATUS " Video recording: ${USE_FFMPEG}")
|
|||
message(STATUS " GIF recording: ${USE_MAGICK}")
|
||||
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
||||
message(STATUS " ZIP support: ${USE_LIBZIP}")
|
||||
message(STATUS " 7-Zip support: ${USE_LZMA}")
|
||||
message(STATUS " Better audio resampling: ${USE_BLIP}")
|
||||
message(STATUS "Frontend summary:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||
message(STATUS "Library summary:")
|
||||
message(STATUS " Static: ${BUILD_STATIC}")
|
||||
message(STATUS " Shared: ${BUILD_SHARED}")
|
||||
|
|
50
README.md
50
README.md
|
@ -1,7 +1,11 @@
|
|||
mGBA
|
||||
====
|
||||
|
||||
mGBA is a new emulator for running Game Boy Advance games.
|
||||
mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack.
|
||||
|
||||
Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/).
|
||||
|
||||
![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -15,18 +19,19 @@ Features
|
|||
- Turbo/fast-forward support by holding Tab.
|
||||
- Frameskip, configurable up to 9.
|
||||
- Screenshot support.
|
||||
- Cheat code support.
|
||||
- 9 savestate slots. Savestates are also viewable as screenshots.
|
||||
- Video and GIF recording.
|
||||
- Remappable controls for both keyboards and gamepads.
|
||||
- Loading from ZIP files.
|
||||
- IPS and UPS patch support.
|
||||
- Loading from ZIP and 7z files.
|
||||
- IPS, UPS and BPS patch support.
|
||||
- Game debugging via a command-line interface (not available with Qt port) and GDB remote support.
|
||||
- Configurable emulation rewinding.
|
||||
|
||||
### Planned features
|
||||
|
||||
- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)).
|
||||
- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)).
|
||||
- Cheat codes ([Bug #58](https://endrift.com/mgba/bugs/show_bug.cgi?id=58)).
|
||||
- Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker))
|
||||
- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)).
|
||||
- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)).
|
||||
|
@ -43,11 +48,27 @@ Supported Platforms
|
|||
|
||||
Other Unix-like platforms work as well, but are untested.
|
||||
|
||||
### System requirements
|
||||
|
||||
Requirements are minimal. Any computer that can run Windows Vista or newer should be able to handle emulation. Support for OpenGL 1.1 or newer is also required.
|
||||
|
||||
Downloads
|
||||
---------
|
||||
|
||||
Downloads can be found on the official website, in the [Downloads][downloads] section. The source code can be found on [GitHub][source].
|
||||
|
||||
Controls
|
||||
--------
|
||||
|
||||
Controls are configurable in the menu. The default gamepad controls are mapped so as to work with a DualShock 3. The default keyboard controls are as follows:
|
||||
|
||||
- **A**: X
|
||||
- **B**: Z
|
||||
- **L**: A
|
||||
- **R**: S
|
||||
- **Start**: Enter
|
||||
- **Select**: Backspace
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
|
@ -55,11 +76,11 @@ Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-
|
|||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr ..
|
||||
make
|
||||
make install
|
||||
sudo make install
|
||||
|
||||
Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown at the end of the `cmake` command.
|
||||
This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them.
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
@ -69,7 +90,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are
|
|||
- SDL: for a more basic frontend and gamepad support in the Qt frontend. SDL 2 is recommended, but 1.2 is supported.
|
||||
- zlib and libpng: for screenshot support and savestate-in-PNG support.
|
||||
- libedit: for command-line debugger support.
|
||||
- ffmpeg or libav: for video recording. Libav is untested.
|
||||
- ffmpeg or libav: for video recording.
|
||||
- libzip: for loading ROMs stored in zip files.
|
||||
- ImageMagick: for GIF recording.
|
||||
|
||||
|
@ -80,7 +101,10 @@ Footnotes
|
|||
|
||||
- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9))
|
||||
- Cartridges with light sensors (Boktai: The Sun is in Your Hand and Boktai 2: Solar Boy Django) ([Bug #46](https://endrift.com/mgba/bugs/show_bug.cgi?id=46))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141))
|
||||
- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142))
|
||||
- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199))
|
||||
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered.
|
||||
|
||||
|
@ -92,4 +116,10 @@ Footnotes
|
|||
Copyright
|
||||
---------
|
||||
|
||||
mGBA is Copyright © 2013 – 2014 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
mGBA is Copyright © 2013 – 2015 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
|
||||
mGBA contains the following third-party libraries:
|
||||
|
||||
- [inih](https://code.google.com/p/inih/), which is copyright © 2009 Brush Technology and used under a BSD 3-clause license.
|
||||
- [blip-buf](https://code.google.com/p/blip-buf/), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License.
|
||||
- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public doman.
|
||||
|
|
|
@ -22,12 +22,16 @@
|
|||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
@ -71,9 +71,11 @@ static inline enum RegisterBank _ARMSelectBank(enum PrivilegeMode mode) {
|
|||
|
||||
void ARMInit(struct ARMCore* cpu) {
|
||||
cpu->master->init(cpu, cpu->master);
|
||||
int i;
|
||||
size_t i;
|
||||
for (i = 0; i < cpu->numComponents; ++i) {
|
||||
cpu->components[i]->init(cpu, cpu->components[i]);
|
||||
if (cpu->components[i] && cpu->components[i]->init) {
|
||||
cpu->components[i]->init(cpu, cpu->components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,9 +83,9 @@ void ARMDeinit(struct ARMCore* cpu) {
|
|||
if (cpu->master->deinit) {
|
||||
cpu->master->deinit(cpu->master);
|
||||
}
|
||||
int i;
|
||||
size_t i;
|
||||
for (i = 0; i < cpu->numComponents; ++i) {
|
||||
if (cpu->components[i]->deinit) {
|
||||
if (cpu->components[i] && cpu->components[i]->deinit) {
|
||||
cpu->components[i]->deinit(cpu->components[i]);
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +97,19 @@ void ARMSetComponents(struct ARMCore* cpu, struct ARMComponent* master, int extr
|
|||
cpu->components = extras;
|
||||
}
|
||||
|
||||
void ARMHotplugAttach(struct ARMCore* cpu, size_t slot) {
|
||||
if (slot >= cpu->numComponents) {
|
||||
return;
|
||||
}
|
||||
cpu->components[slot]->init(cpu, cpu->components[slot]);
|
||||
}
|
||||
|
||||
void ARMHotplugDetach(struct ARMCore* cpu, size_t slot) {
|
||||
if (slot >= cpu->numComponents) {
|
||||
return;
|
||||
}
|
||||
cpu->components[slot]->init(cpu, cpu->components[slot]);
|
||||
}
|
||||
|
||||
void ARMReset(struct ARMCore* cpu) {
|
||||
int i;
|
||||
|
@ -176,9 +191,10 @@ void ARMRaiseSWI(struct ARMCore* cpu) {
|
|||
}
|
||||
|
||||
static inline void ARMStep(struct ARMCore* cpu) {
|
||||
uint32_t opcode = cpu->prefetch;
|
||||
LOAD_32(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
uint32_t opcode = cpu->prefetch[0];
|
||||
cpu->prefetch[0] = cpu->prefetch[1];
|
||||
cpu->gprs[ARM_PC] += WORD_SIZE_ARM;
|
||||
LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
|
||||
unsigned condition = opcode >> 28;
|
||||
if (condition != 0xE) {
|
||||
|
@ -239,9 +255,10 @@ static inline void ARMStep(struct ARMCore* cpu) {
|
|||
}
|
||||
|
||||
static inline void ThumbStep(struct ARMCore* cpu) {
|
||||
uint32_t opcode = cpu->prefetch;
|
||||
LOAD_16(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
uint32_t opcode = cpu->prefetch[0];
|
||||
cpu->prefetch[0] = cpu->prefetch[1];
|
||||
cpu->gprs[ARM_PC] += WORD_SIZE_THUMB;
|
||||
LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
ThumbInstruction instruction = _thumbTable[opcode >> 6];
|
||||
instruction(cpu, opcode);
|
||||
}
|
||||
|
@ -269,3 +286,13 @@ void ARMRunLoop(struct ARMCore* cpu) {
|
|||
}
|
||||
cpu->irqh.processEvents(cpu);
|
||||
}
|
||||
|
||||
void ARMRunFake(struct ARMCore* cpu, uint32_t opcode) {
|
||||
if (cpu->executionMode== MODE_ARM) {
|
||||
cpu->gprs[ARM_PC] -= WORD_SIZE_ARM;
|
||||
} else {
|
||||
cpu->gprs[ARM_PC] -= WORD_SIZE_THUMB;
|
||||
}
|
||||
cpu->prefetch[1] = cpu->prefetch[0];
|
||||
cpu->prefetch[0] = opcode;
|
||||
}
|
||||
|
|
|
@ -93,11 +93,9 @@ union PSR {
|
|||
};
|
||||
|
||||
struct ARMMemory {
|
||||
int32_t (*load32)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
int16_t (*load16)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
uint16_t (*loadU16)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
int8_t (*load8)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
uint8_t (*loadU8)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
uint32_t (*load32)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
uint32_t (*load16)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
uint32_t (*load8)(struct ARMCore*, uint32_t address, int* cycleCounter);
|
||||
|
||||
void (*store32)(struct ARMCore*, uint32_t address, int32_t value, int* cycleCounter);
|
||||
void (*store16)(struct ARMCore*, uint32_t address, int16_t value, int* cycleCounter);
|
||||
|
@ -123,6 +121,8 @@ struct ARMInterruptHandler {
|
|||
void (*swi16)(struct ARMCore* cpu, int immediate);
|
||||
void (*swi32)(struct ARMCore* cpu, int immediate);
|
||||
void (*hitIllegal)(struct ARMCore* cpu, uint32_t opcode);
|
||||
void (*bkpt16)(struct ARMCore* cpu, int immediate);
|
||||
void (*bkpt32)(struct ARMCore* cpu, int immediate);
|
||||
void (*readCPSR)(struct ARMCore* cpu);
|
||||
|
||||
void (*hitStub)(struct ARMCore* cpu, uint32_t opcode);
|
||||
|
@ -149,7 +149,7 @@ struct ARMCore {
|
|||
int32_t shifterOperand;
|
||||
int32_t shifterCarryOut;
|
||||
|
||||
uint32_t prefetch;
|
||||
uint32_t prefetch[2];
|
||||
enum ExecutionMode executionMode;
|
||||
enum PrivilegeMode privilegeMode;
|
||||
|
||||
|
@ -158,13 +158,15 @@ struct ARMCore {
|
|||
|
||||
struct ARMComponent* master;
|
||||
|
||||
int numComponents;
|
||||
size_t numComponents;
|
||||
struct ARMComponent** components;
|
||||
};
|
||||
|
||||
void ARMInit(struct ARMCore* cpu);
|
||||
void ARMDeinit(struct ARMCore* cpu);
|
||||
void ARMSetComponents(struct ARMCore* cpu, struct ARMComponent* master, int extra, struct ARMComponent** extras);
|
||||
void ARMHotplugAttach(struct ARMCore* cpu, size_t slot);
|
||||
void ARMHotplugDetach(struct ARMCore* cpu, size_t slot);
|
||||
|
||||
void ARMReset(struct ARMCore* cpu);
|
||||
void ARMSetPrivilegeMode(struct ARMCore*, enum PrivilegeMode);
|
||||
|
@ -173,5 +175,6 @@ void ARMRaiseSWI(struct ARMCore*);
|
|||
|
||||
void ARMRun(struct ARMCore* cpu);
|
||||
void ARMRunLoop(struct ARMCore* cpu);
|
||||
void ARMRunFake(struct ARMCore* cpu, uint32_t opcode);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
#define ADDR_MODE_1_IMM \
|
||||
int rotate = (opcode & 0x00000F00) >> 7; \
|
||||
int immediate = opcode & 0x000000FF; \
|
||||
info->op3.immediate = ARM_ROR(immediate, rotate); \
|
||||
info->op3.immediate = ROR(immediate, rotate); \
|
||||
info->operandFormat |= ARM_OPERAND_IMMEDIATE_3;
|
||||
|
||||
#define ADDR_MODE_2_SHIFT(OP) \
|
||||
|
@ -213,7 +213,6 @@
|
|||
ARM_MEMORY_WRITEBACK, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## P, MNEMONIC, \
|
||||
ARM_MEMORY_PRE_INCREMENT | \
|
||||
ARM_MEMORY_OFFSET_SUBTRACT, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PW, MNEMONIC, \
|
||||
|
@ -222,10 +221,9 @@
|
|||
ARM_MEMORY_OFFSET_SUBTRACT, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PU, MNEMONIC, \
|
||||
ARM_MEMORY_PRE_INCREMENT, \
|
||||
0, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PUW, MNEMONIC, \
|
||||
ARM_MEMORY_PRE_INCREMENT | \
|
||||
ARM_MEMORY_WRITEBACK, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE)
|
||||
|
||||
|
@ -243,10 +241,12 @@
|
|||
#define DEFINE_LOAD_STORE_T_DECODER_SET_ARM(NAME, MNEMONIC, ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME, MNEMONIC, \
|
||||
ARM_MEMORY_POST_INCREMENT | \
|
||||
ARM_MEMORY_WRITEBACK | \
|
||||
ARM_MEMORY_OFFSET_SUBTRACT, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE) \
|
||||
DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## U, MNEMONIC, \
|
||||
ARM_MEMORY_POST_INCREMENT, \
|
||||
ARM_MEMORY_POST_INCREMENT | \
|
||||
ARM_MEMORY_WRITEBACK, \
|
||||
ADDRESSING_MODE, CYCLES, TYPE)
|
||||
|
||||
#define DEFINE_LOAD_STORE_T_DECODER_ARM(NAME, MNEMONIC, CYCLES, TYPE) \
|
||||
|
@ -386,6 +386,7 @@ DEFINE_DECODER_ARM(ILL, ILL, info->operandFormat = ARM_OPERAND_NONE;) // Illegal
|
|||
DEFINE_DECODER_ARM(MSR, MSR,
|
||||
info->affectsCPSR = 1;
|
||||
info->op1.reg = ARM_CPSR;
|
||||
info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK;
|
||||
info->op2.reg = opcode & 0x0000000F;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
|
@ -393,42 +394,45 @@ DEFINE_DECODER_ARM(MSR, MSR,
|
|||
|
||||
DEFINE_DECODER_ARM(MSRR, MSR,
|
||||
info->op1.reg = ARM_SPSR;
|
||||
info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK;
|
||||
info->op2.reg = opcode & 0x0000000F;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_REGISTER_2;)
|
||||
|
||||
DEFINE_DECODER_ARM(MRS, MRS, info->affectsCPSR = 1;
|
||||
DEFINE_DECODER_ARM(MRS, MRS,
|
||||
info->affectsCPSR = 1;
|
||||
info->op1.reg = (opcode >> 12) & 0xF;
|
||||
info->op2.reg = ARM_CPSR;
|
||||
info->op2.psrBits = 0;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_REGISTER_2;)
|
||||
|
||||
DEFINE_DECODER_ARM(MRSR, MRS, info->affectsCPSR = 1;
|
||||
info->affectsCPSR = 1;
|
||||
DEFINE_DECODER_ARM(MRSR, MRS,
|
||||
info->op1.reg = (opcode >> 12) & 0xF;
|
||||
info->op2.reg = ARM_SPSR;
|
||||
info->op2.psrBits = 0;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_REGISTER_2;)
|
||||
|
||||
DEFINE_DECODER_ARM(MSRI, MSR, info->affectsCPSR = 1;
|
||||
DEFINE_DECODER_ARM(MSRI, MSR,
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
info->affectsCPSR = 1;
|
||||
info->op1.reg = ARM_CPSR;
|
||||
info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK;
|
||||
info->op2.immediate = operand;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_IMMEDIATE_2;)
|
||||
|
||||
DEFINE_DECODER_ARM(MSRRI, MSR, info->affectsCPSR = 1;
|
||||
DEFINE_DECODER_ARM(MSRRI, MSR,
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate);
|
||||
info->affectsCPSR = 1;
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
info->op1.reg = ARM_SPSR;
|
||||
info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK;
|
||||
info->op2.immediate = operand;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 |
|
||||
ARM_OPERAND_AFFECTED_1 |
|
||||
|
|
|
@ -290,14 +290,21 @@ DEFINE_THUMB_DECODER(B, B,
|
|||
info->operandFormat = ARM_OPERAND_IMMEDIATE_1;
|
||||
info->branchType = ARM_BRANCH;)
|
||||
|
||||
DEFINE_THUMB_DECODER(BL1, BLH,
|
||||
DEFINE_THUMB_DECODER(BL1, BL,
|
||||
int16_t immediate = (opcode & 0x07FF) << 5;
|
||||
info->op1.immediate = (((int32_t) immediate) << 7);
|
||||
info->operandFormat = ARM_OPERAND_IMMEDIATE_1;)
|
||||
info->op1.reg = ARM_LR;
|
||||
info->op2.reg = ARM_PC;
|
||||
info->op3.immediate = (((int32_t) immediate) << 7);
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_REGISTER_2 | ARM_OPERAND_AFFECTED_2 |
|
||||
ARM_OPERAND_IMMEDIATE_3;)
|
||||
|
||||
DEFINE_THUMB_DECODER(BL2, BL,
|
||||
info->op1.immediate = (opcode & 0x07FF) << 1;
|
||||
info->operandFormat = ARM_OPERAND_IMMEDIATE_1;
|
||||
info->op1.reg = ARM_PC;
|
||||
info->op2.reg = ARM_LR;
|
||||
info->op3.immediate = (opcode & 0x07FF) << 1;
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 |
|
||||
ARM_OPERAND_REGISTER_2 | ARM_OPERAND_IMMEDIATE_3;
|
||||
info->branchType = ARM_BRANCH_LINKED;)
|
||||
|
||||
DEFINE_THUMB_DECODER(BX, BX,
|
||||
|
@ -332,3 +339,33 @@ void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info) {
|
|||
ThumbDecoder decoder = _thumbDecoderTable[opcode >> 6];
|
||||
decoder(opcode, info);
|
||||
}
|
||||
|
||||
bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out) {
|
||||
if (info1->execMode != MODE_THUMB || info1->mnemonic != ARM_MN_BL) {
|
||||
return false;
|
||||
}
|
||||
if (info2->execMode != MODE_THUMB || info2->mnemonic != ARM_MN_BL) {
|
||||
return false;
|
||||
}
|
||||
if (info1->op1.reg != ARM_LR || info1->op2.reg != ARM_PC) {
|
||||
return false;
|
||||
}
|
||||
if (info2->op1.reg != ARM_PC || info2->op2.reg != ARM_LR) {
|
||||
return false;
|
||||
}
|
||||
out->op1.immediate = info1->op3.immediate | info2->op3.immediate;
|
||||
out->operandFormat = ARM_OPERAND_IMMEDIATE_1;
|
||||
out->execMode = MODE_THUMB;
|
||||
out->mnemonic = ARM_MN_BL;
|
||||
out->branchType = ARM_BRANCH_LINKED;
|
||||
out->traps = 0;
|
||||
out->affectsCPSR = 0;
|
||||
out->condition = ARM_CONDITION_AL;
|
||||
out->sDataCycles = 0;
|
||||
out->nDataCycles = 0;
|
||||
out->sInstructionCycles = 2;
|
||||
out->nInstructionCycles = 0;
|
||||
out->iCycles = 0;
|
||||
out->cCycles = 0;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
static int _decodeRegister(int reg, char* buffer, int blen);
|
||||
static int _decodeRegisterList(int list, char* buffer, int blen);
|
||||
static int _decodePSR(int bits, char* buffer, int blen);
|
||||
static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen);
|
||||
static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, int blen);
|
||||
static int _decodeShift(union ARMOperand operand, bool reg, char* buffer, int blen);
|
||||
|
@ -113,6 +114,32 @@ static int _decodeRegisterList(int list, char* buffer, int blen) {
|
|||
return total;
|
||||
}
|
||||
|
||||
static int _decodePSR(int psrBits, char* buffer, int blen) {
|
||||
if (!psrBits) {
|
||||
return 0;
|
||||
}
|
||||
int total = 0;
|
||||
strncpy(buffer, "_", blen - 1);
|
||||
ADVANCE(1);
|
||||
if (psrBits & ARM_PSR_C) {
|
||||
strncpy(buffer, "c", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
if (psrBits & ARM_PSR_X) {
|
||||
strncpy(buffer, "x", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
if (psrBits & ARM_PSR_S) {
|
||||
strncpy(buffer, "s", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
if (psrBits & ARM_PSR_F) {
|
||||
strncpy(buffer, "f", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen) {
|
||||
return snprintf(buffer, blen - 1, "$%08X", address + pc);
|
||||
}
|
||||
|
@ -127,7 +154,7 @@ static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, in
|
|||
int written;
|
||||
if (memory.format & ARM_MEMORY_REGISTER_BASE) {
|
||||
if (memory.baseReg == ARM_PC && memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) {
|
||||
written = _decodePCRelative(memory.offset.immediate, pc, buffer, blen);
|
||||
written = _decodePCRelative(memory.format & ARM_MEMORY_OFFSET_SUBTRACT ? -memory.offset.immediate : memory.offset.immediate, pc & 0xFFFFFFFC, buffer, blen);
|
||||
ADVANCE(written);
|
||||
} else {
|
||||
written = _decodeRegister(memory.baseReg, buffer, blen);
|
||||
|
@ -223,7 +250,6 @@ static const char* _armMnemonicStrings[] = {
|
|||
"bic",
|
||||
"bkpt",
|
||||
"bl",
|
||||
"blh",
|
||||
"bx",
|
||||
"cmn",
|
||||
"cmp",
|
||||
|
@ -355,8 +381,11 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i
|
|||
}
|
||||
break;
|
||||
case ARM_MN_B:
|
||||
written = _decodePCRelative(info->op1.immediate, pc, buffer, blen);
|
||||
ADVANCE(written);
|
||||
case ARM_MN_BL:
|
||||
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_1) {
|
||||
written = _decodePCRelative(info->op1.immediate, pc, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_1) {
|
||||
|
@ -368,6 +397,10 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i
|
|||
} else if (info->operandFormat & ARM_OPERAND_REGISTER_1) {
|
||||
written = _decodeRegister(info->op1.reg, buffer, blen);
|
||||
ADVANCE(written);
|
||||
if (info->op1.reg > ARM_PC) {
|
||||
written = _decodePSR(info->op1.psrBits, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_SHIFT_REGISTER_1) {
|
||||
written = _decodeShift(info->op1, true, buffer, blen);
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
#define ARM_MEMORY_INCREMENT_BEFORE 0x0300
|
||||
#define ARM_MEMORY_SPSR_SWAP 0x0400
|
||||
|
||||
#define ARM_PSR_C 1
|
||||
#define ARM_PSR_X 2
|
||||
#define ARM_PSR_S 4
|
||||
#define ARM_PSR_F 8
|
||||
#define ARM_PSR_MASK 0xF
|
||||
|
||||
#define MEMORY_FORMAT_TO_DIRECTION(F) (((F) >> 8) & 0x3)
|
||||
|
||||
enum ARMCondition {
|
||||
|
@ -99,6 +105,7 @@ union ARMOperand {
|
|||
union {
|
||||
uint8_t shifterReg;
|
||||
uint8_t shifterImm;
|
||||
uint8_t psrBits;
|
||||
};
|
||||
};
|
||||
int32_t immediate;
|
||||
|
@ -138,7 +145,6 @@ enum ARMMnemonic {
|
|||
ARM_MN_BIC,
|
||||
ARM_MN_BKPT,
|
||||
ARM_MN_BL,
|
||||
ARM_MN_BLH,
|
||||
ARM_MN_BX,
|
||||
ARM_MN_CMN,
|
||||
ARM_MN_CMP,
|
||||
|
@ -203,6 +209,7 @@ struct ARMInstructionInfo {
|
|||
|
||||
void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info);
|
||||
void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info);
|
||||
bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out);
|
||||
int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -138,7 +138,7 @@ static inline void _shiftROR(struct ARMCore* cpu, uint32_t opcode) {
|
|||
int rm = opcode & 0x0000000F;
|
||||
int immediate = (opcode & 0x00000F80) >> 7;
|
||||
if (immediate) {
|
||||
cpu->shifterOperand = ARM_ROR(cpu->gprs[rm], immediate);
|
||||
cpu->shifterOperand = ROR(cpu->gprs[rm], immediate);
|
||||
cpu->shifterCarryOut = (cpu->gprs[rm] >> (immediate - 1)) & 1;
|
||||
} else {
|
||||
// RRX
|
||||
|
@ -165,7 +165,7 @@ static inline void _shiftRORR(struct ARMCore* cpu, uint32_t opcode) {
|
|||
cpu->shifterOperand = shiftVal;
|
||||
cpu->shifterCarryOut = cpu->cpsr.c;
|
||||
} else if (rotate) {
|
||||
cpu->shifterOperand = ARM_ROR(shiftVal, rotate);
|
||||
cpu->shifterOperand = ROR(shiftVal, rotate);
|
||||
cpu->shifterCarryOut = (shiftVal >> (rotate - 1)) & 1;
|
||||
} else {
|
||||
cpu->shifterOperand = shiftVal;
|
||||
|
@ -180,7 +180,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
cpu->shifterOperand = immediate;
|
||||
cpu->shifterCarryOut = cpu->cpsr.c;
|
||||
} else {
|
||||
cpu->shifterOperand = ARM_ROR(immediate, rotate);
|
||||
cpu->shifterOperand = ROR(immediate, rotate);
|
||||
cpu->shifterCarryOut = ARM_SIGN(cpu->shifterOperand);
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
#define ADDR_MODE_2_LSL (cpu->gprs[rm] << ADDR_MODE_2_I)
|
||||
#define ADDR_MODE_2_LSR (ADDR_MODE_2_I_TEST ? ((uint32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : 0)
|
||||
#define ADDR_MODE_2_ASR (ADDR_MODE_2_I_TEST ? ((int32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : ((int32_t) cpu->gprs[rm]) >> 31)
|
||||
#define ADDR_MODE_2_ROR (ADDR_MODE_2_I_TEST ? ARM_ROR(cpu->gprs[rm], ADDR_MODE_2_I) : (cpu->cpsr.c << 31) | (((uint32_t) cpu->gprs[rm]) >> 1))
|
||||
#define ADDR_MODE_2_ROR (ADDR_MODE_2_I_TEST ? ROR(cpu->gprs[rm], ADDR_MODE_2_I) : (cpu->cpsr.c << 31) | (((uint32_t) cpu->gprs[rm]) >> 1))
|
||||
|
||||
#define ADDR_MODE_3_ADDRESS ADDR_MODE_2_ADDRESS
|
||||
#define ADDR_MODE_3_RN ADDR_MODE_2_RN
|
||||
|
@ -246,7 +246,12 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
#define ADDR_MODE_3_INDEX(U_OP, M) ADDR_MODE_2_INDEX(U_OP, M)
|
||||
#define ADDR_MODE_3_WRITEBACK(ADDR) ADDR_MODE_2_WRITEBACK(ADDR)
|
||||
|
||||
#define ADDR_MODE_4_WRITEBACK cpu->gprs[rn] = address
|
||||
#define ADDR_MODE_4_WRITEBACK_LDM \
|
||||
if (!((1 << rn) & rs)) { \
|
||||
cpu->gprs[rn] = address; \
|
||||
}
|
||||
|
||||
#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address;
|
||||
|
||||
#define ARM_LOAD_POST_BODY \
|
||||
++currentCycles; \
|
||||
|
@ -405,22 +410,22 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
|
||||
|
||||
#define DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(NAME, LS, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DA, LS, , , , DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DAW, LS, ADDR_MODE_4_WRITEBACK, , , DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DB, LS, , , , DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DBW, LS, ADDR_MODE_4_WRITEBACK, , , DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IA, LS, , , , IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IAW, LS, ADDR_MODE_4_WRITEBACK, , , IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IB, LS, , , , IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IBW, LS, ADDR_MODE_4_WRITEBACK, , , IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDA, LS, , ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDAW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDB, LS, , ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDBW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIA, LS, , ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIAW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIB, LS, , ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIBW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY)
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DA, LS, , , , DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DB, LS, , , , DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IA, LS, , , , IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IB, LS, , , , IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDA, LS, , ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDB, LS, , ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIA, LS, , ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIB, LS, , ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) \
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY)
|
||||
|
||||
// Begin ALU definitions
|
||||
|
||||
|
@ -519,10 +524,10 @@ DEFINE_MULTIPLY_INSTRUCTION_ARM(UMULL,
|
|||
// Begin load/store definitions
|
||||
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDR, cpu->gprs[rd] = cpu->memory.load32(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDRB, cpu->gprs[rd] = cpu->memory.loadU8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.loadU16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDRB, cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STR, cpu->memory.store32(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STRB, cpu->memory.store8(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
|
@ -530,7 +535,7 @@ DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address,
|
|||
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(LDRBT,
|
||||
enum PrivilegeMode priv = cpu->privilegeMode;
|
||||
ARMSetPrivilegeMode(cpu, MODE_USER);
|
||||
cpu->gprs[rd] = cpu->memory.loadU8(cpu, address, ¤tCycles);
|
||||
cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles);
|
||||
ARMSetPrivilegeMode(cpu, priv);
|
||||
ARM_LOAD_POST_BODY;)
|
||||
|
||||
|
@ -578,7 +583,7 @@ DEFINE_INSTRUCTION_ARM(SWPB,
|
|||
int rm = opcode & 0xF;
|
||||
int rd = (opcode >> 12) & 0xF;
|
||||
int rn = (opcode >> 16) & 0xF;
|
||||
int32_t d = cpu->memory.loadU8(cpu, cpu->gprs[rn], ¤tCycles);
|
||||
int32_t d = cpu->memory.load8(cpu, cpu->gprs[rn], ¤tCycles);
|
||||
cpu->memory.store8(cpu, cpu->gprs[rn], cpu->gprs[rm], ¤tCycles);
|
||||
cpu->gprs[rd] = d;)
|
||||
|
||||
|
@ -620,7 +625,7 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB)
|
|||
|
||||
// Begin miscellaneous definitions
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(BKPT, ARM_STUB) // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_INSTRUCTION_ARM(BKPT, cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF))); // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_INSTRUCTION_ARM(ILL, ARM_ILL) // Illegal opcode
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MSR,
|
||||
|
@ -657,7 +662,7 @@ DEFINE_INSTRUCTION_ARM(MSRI,
|
|||
int c = opcode & 0x00010000;
|
||||
int f = opcode & 0x00080000;
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
if (mask & PSR_USER_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK);
|
||||
|
@ -672,7 +677,7 @@ DEFINE_INSTRUCTION_ARM(MSRRI,
|
|||
int c = opcode & 0x00010000;
|
||||
int f = opcode & 0x00080000;
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK;
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);)
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
#define ARM_COND_AL 1
|
||||
|
||||
#define ARM_SIGN(I) ((I) >> 31)
|
||||
#define ARM_ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31)))
|
||||
|
||||
#define ARM_SXT_8(I) (((int8_t) (I) << 24) >> 24)
|
||||
#define ARM_SXT_16(I) (((int16_t) (I) << 16) >> 16)
|
||||
|
||||
#define ARM_CARRY_FROM(M, N, D) (((uint32_t) (M) >> 31) + ((uint32_t) (N) >> 31) > ((uint32_t) (D) >> 31))
|
||||
#define ARM_BORROW_FROM(M, N, D) (((uint32_t) (M)) >= ((uint32_t) (N)))
|
||||
|
@ -52,15 +52,17 @@
|
|||
#define ARM_WRITE_PC \
|
||||
cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_ARM); \
|
||||
cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \
|
||||
LOAD_32(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
LOAD_32(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
cpu->gprs[ARM_PC] += WORD_SIZE_ARM; \
|
||||
LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
currentCycles += 2 + cpu->memory.activeUncachedCycles32 + cpu->memory.activeSeqCycles32;
|
||||
|
||||
#define THUMB_WRITE_PC \
|
||||
cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_THUMB); \
|
||||
cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \
|
||||
LOAD_16(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
LOAD_16(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
cpu->gprs[ARM_PC] += WORD_SIZE_THUMB; \
|
||||
LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \
|
||||
currentCycles += 2 + cpu->memory.activeUncachedCycles16 + cpu->memory.activeSeqCycles16;
|
||||
|
||||
static inline int _ARMModeHasSPSR(enum PrivilegeMode mode) {
|
||||
|
@ -89,4 +91,15 @@ static inline void _ARMReadCPSR(struct ARMCore* cpu) {
|
|||
cpu->irqh.readCPSR(cpu);
|
||||
}
|
||||
|
||||
static inline uint32_t _ARMPCAddress(struct ARMCore* cpu) {
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
instructionLength = WORD_SIZE_ARM;
|
||||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
return cpu->gprs[ARM_PC] - instructionLength * 2;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -97,8 +97,8 @@ DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(ASR1,
|
|||
THUMB_NEUTRAL_S( , , cpu->gprs[rd]);)
|
||||
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDR1, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[rm] + immediate * 4, ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRB1, cpu->gprs[rd] = cpu->memory.loadU8(cpu, cpu->gprs[rm] + immediate, ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRH1, cpu->gprs[rd] = cpu->memory.loadU16(cpu, cpu->gprs[rm] + immediate * 2, ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRB1, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rm] + immediate, ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRH1, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rm] + immediate * 2, ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STR1, cpu->memory.store32(cpu, cpu->gprs[rm] + immediate * 4, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRB1, cpu->memory.store8(cpu, cpu->gprs[rm] + immediate, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRH1, cpu->memory.store16(cpu, cpu->gprs[rm] + immediate * 2, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
|
@ -219,7 +219,7 @@ DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(ROR,
|
|||
int r4 = rs & 0x1F;
|
||||
if (r4 > 0) {
|
||||
cpu->cpsr.c = (cpu->gprs[rd] >> (r4 - 1)) & 1;
|
||||
cpu->gprs[rd] = ARM_ROR(cpu->gprs[rd], r4);
|
||||
cpu->gprs[rd] = ROR(cpu->gprs[rd], r4);
|
||||
} else {
|
||||
cpu->cpsr.c = ARM_SIGN(cpu->gprs[rd]);
|
||||
}
|
||||
|
@ -286,10 +286,10 @@ DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD6, cpu->gprs[rd] = cpu->gprs[ARM_SP] + i
|
|||
COUNT_CALL_3(DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB, NAME ## _R, BODY)
|
||||
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.loadU8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.loadU16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, cpu->memory.store32(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, cpu->memory.store8(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
|
@ -381,7 +381,7 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSHR,
|
|||
cpu->gprs[ARM_SP] = address)
|
||||
|
||||
DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL)
|
||||
DEFINE_INSTRUCTION_THUMB(BKPT, ARM_STUB)
|
||||
DEFINE_INSTRUCTION_THUMB(BKPT, cpu->irqh.bkpt16(cpu, opcode & 0xFF);)
|
||||
DEFINE_INSTRUCTION_THUMB(B,
|
||||
int16_t immediate = (opcode & 0x07FF) << 5;
|
||||
cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4);
|
||||
|
|
|
@ -69,4 +69,7 @@
|
|||
|
||||
#define LIKELY(X) __builtin_expect(!!(X), 1)
|
||||
#define UNLIKELY(X) __builtin_expect(!!(X), 0)
|
||||
|
||||
#define ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31)))
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#endif
|
||||
|
||||
static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share
|
||||
static const char* ERROR_OVERFLOW = "Arguments overflow";
|
||||
|
||||
static struct CLIDebugger* _activeDebugger;
|
||||
|
||||
|
@ -34,15 +35,26 @@ static void _reset(struct CLIDebugger*, struct CLIDebugVector*);
|
|||
static void _readHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _readWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _clearBreakpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
||||
static void _breakIntoDefault(int signal);
|
||||
static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode);
|
||||
static void _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
|
||||
static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
||||
{ "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
|
||||
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
|
||||
{ "c", _continue, 0, "Continue execution" },
|
||||
{ "continue", _continue, 0, "Continue execution" },
|
||||
|
@ -71,14 +83,21 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
|||
{ "print/x", _printHex, CLIDVParse, "Print a value as hexadecimal" },
|
||||
{ "q", _quit, 0, "Quit the emulator" },
|
||||
{ "quit", _quit, 0, "Quit the emulator" },
|
||||
{ "rb", _readByte, CLIDVParse, "Read a byte from a specified offset" },
|
||||
{ "reset", _reset, 0, "Reset the emulation" },
|
||||
{ "rh", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" },
|
||||
{ "rw", _readWord, CLIDVParse, "Read a word from a specified offset" },
|
||||
{ "r/1", _readByte, CLIDVParse, "Read a byte from a specified offset" },
|
||||
{ "r/2", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" },
|
||||
{ "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" },
|
||||
{ "status", _printStatus, 0, "Print the current status" },
|
||||
{ "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" },
|
||||
{ "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" },
|
||||
{ "x", _breakInto, 0, "Break into attached debugger (for developers)" },
|
||||
{ "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" },
|
||||
{ "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" },
|
||||
{ "w/4", _writeWord, CLIDVParse, "Write a word at a specified offset" },
|
||||
{ "w/r", _writeRegister, CLIDVParse, "Write a register" },
|
||||
{ "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" },
|
||||
{ "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" },
|
||||
{ "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" },
|
||||
{ "!", _breakInto, 0, "Break into attached debugger (for developers)" },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
|
@ -122,6 +141,12 @@ static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
if (debugger->d.currentBreakpoint) {
|
||||
if (debugger->d.currentBreakpoint->isSw && debugger->d.setSoftwareBreakpoint) {
|
||||
debugger->d.setSoftwareBreakpoint(&debugger->d, debugger->d.currentBreakpoint->address, debugger->d.currentBreakpoint->sw.mode, &debugger->d.currentBreakpoint->sw.opcode);
|
||||
}
|
||||
debugger->d.currentBreakpoint = 0;
|
||||
}
|
||||
ARMRun(debugger->d.cpu);
|
||||
_printStatus(debugger, 0);
|
||||
}
|
||||
|
@ -165,8 +190,7 @@ static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector
|
|||
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
_printLine(debugger, address, mode);
|
||||
address += wordSize;
|
||||
address += _printLine(debugger, address, mode);;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +208,7 @@ static void _printBin(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
printf(" 0b");
|
||||
int i = 32;
|
||||
while (i--) {
|
||||
printf(" %u", (dv->intValue >> i) & 1);
|
||||
printf("%u", (dv->intValue >> i) & 1);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -231,7 +255,7 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
}
|
||||
}
|
||||
|
||||
static inline void _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
char disassembly[48];
|
||||
struct ARMInstructionInfo info;
|
||||
printf("%08X: ", address);
|
||||
|
@ -240,11 +264,23 @@ static inline void _printLine(struct CLIDebugger* debugger, uint32_t address, en
|
|||
ARMDecodeARM(instruction, &info);
|
||||
ARMDisassemble(&info, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly));
|
||||
printf("%08X\t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_ARM;
|
||||
} else {
|
||||
uint16_t instruction = debugger->d.cpu->memory.loadU16(debugger->d.cpu, address, 0);
|
||||
struct ARMInstructionInfo info2;
|
||||
struct ARMInstructionInfo combined;
|
||||
uint16_t instruction = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0);
|
||||
uint16_t instruction2 = debugger->d.cpu->memory.load16(debugger->d.cpu, address + WORD_SIZE_THUMB, 0);
|
||||
ARMDecodeThumb(instruction, &info);
|
||||
ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X\t%s\n", instruction, disassembly);
|
||||
ARMDecodeThumb(instruction2, &info2);
|
||||
if (ARMDecodeThumbCombine(&info, &info2, &combined)) {
|
||||
ARMDisassemble(&combined, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X %04X\t%s\n", instruction, instruction2, disassembly);
|
||||
return WORD_SIZE_THUMB * 2;
|
||||
} else {
|
||||
ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X \t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_THUMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +316,7 @@ static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint8_t value = debugger->d.cpu->memory.loadU8(debugger->d.cpu, address, 0);
|
||||
uint8_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0);
|
||||
printf(" 0x%02X\n", value);
|
||||
}
|
||||
|
||||
|
@ -296,7 +332,7 @@ static void _readHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint16_t value = debugger->d.cpu->memory.loadU16(debugger->d.cpu, address, 0);
|
||||
uint16_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address & ~1, 0);
|
||||
printf(" 0x%04X\n", value);
|
||||
}
|
||||
|
||||
|
@ -306,10 +342,149 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0);
|
||||
uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address & ~3, 0);
|
||||
printf(" 0x%08X\n", value);
|
||||
}
|
||||
|
||||
static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
if (value > 0xFF) {
|
||||
printf("%s\n", ERROR_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->memory.store8(debugger->d.cpu, address, value, 0);
|
||||
}
|
||||
|
||||
static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
if (value > 0xFFFF) {
|
||||
printf("%s\n", ERROR_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->memory.store16(debugger->d.cpu, address, value, 0);
|
||||
}
|
||||
|
||||
static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
debugger->d.cpu->memory.store32(debugger->d.cpu, address, value, 0);
|
||||
}
|
||||
|
||||
static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t regid = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
if (regid >= ARM_PC) {
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->gprs[regid] = value;
|
||||
}
|
||||
|
||||
static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t words = 16;
|
||||
if (dv->next && dv->next->type == CLIDV_INT_TYPE) {
|
||||
words = dv->next->intValue;
|
||||
}
|
||||
while (words) {
|
||||
uint32_t line = 16;
|
||||
if (line > words) {
|
||||
line = words;
|
||||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, ++address, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0);
|
||||
printf(" %02X", value);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t words = 8;
|
||||
if (dv->next && dv->next->type == CLIDV_INT_TYPE) {
|
||||
words = dv->next->intValue;
|
||||
}
|
||||
while (words) {
|
||||
uint32_t line = 8;
|
||||
if (line > words) {
|
||||
line = words;
|
||||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, address += 2, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0);
|
||||
printf(" %04X", value);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t words = 4;
|
||||
if (dv->next && dv->next->type == CLIDV_INT_TYPE) {
|
||||
words = dv->next->intValue;
|
||||
}
|
||||
while (words) {
|
||||
uint32_t line = 4;
|
||||
if (line > words) {
|
||||
line = words;
|
||||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, address += 4, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0);
|
||||
printf(" %08X", value);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
|
@ -319,6 +494,24 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
ARMDebuggerSetBreakpoint(&debugger->d, address);
|
||||
}
|
||||
|
||||
static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_ARM);
|
||||
}
|
||||
|
||||
static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_THUMB);
|
||||
}
|
||||
|
||||
static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
|
@ -339,7 +532,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
|
||||
static void _breakIntoDefault(int signal) {
|
||||
UNUSED(signal);
|
||||
ARMDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL);
|
||||
ARMDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
}
|
||||
|
||||
static uint32_t _performOperation(enum Operation operation, uint32_t current, uint32_t next, struct CLIDebugVector* dv) {
|
||||
|
@ -572,7 +765,6 @@ static void _commandLine(struct ARMDebugger* debugger) {
|
|||
while (debugger->state == DEBUGGER_PAUSED) {
|
||||
line = el_gets(cliDebugger->elstate, &count);
|
||||
if (!line) {
|
||||
debugger->state = DEBUGGER_EXITING;
|
||||
return;
|
||||
}
|
||||
if (line[0] == '\n') {
|
||||
|
@ -586,20 +778,32 @@ static void _commandLine(struct ARMDebugger* debugger) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) {
|
||||
static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
UNUSED(debugger);
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_MANUAL:
|
||||
case DEBUGGER_ENTER_ATTACHED:
|
||||
break;
|
||||
case DEBUGGER_ENTER_BREAKPOINT:
|
||||
printf("Hit breakpoint\n");
|
||||
if (info) {
|
||||
printf("Hit breakpoint at 0x%08X\n", info->address);
|
||||
} else {
|
||||
printf("Hit breakpoint\n");
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
printf("Hit watchpoint\n");
|
||||
if (info) {
|
||||
printf("Hit watchpoint at 0x%08X: (old value = 0x%08X)\n", info->address, info->oldValue);
|
||||
} else {
|
||||
printf("Hit watchpoint\n");
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_ENTER_ILLEGAL_OP:
|
||||
printf("Hit illegal opcode\n");
|
||||
if (info) {
|
||||
printf("Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->opcode);
|
||||
} else {
|
||||
printf("Hit illegal opcode\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -684,10 +888,22 @@ static void _cliDebuggerDeinit(struct ARMDebugger* debugger) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _cliDebuggerCustom(struct ARMDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
bool retain = false;
|
||||
if (cliDebugger->system) {
|
||||
retain = cliDebugger->system->custom(cliDebugger->system);
|
||||
}
|
||||
if (!retain && debugger->state == DEBUGGER_CUSTOM) {
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
void CLIDebuggerCreate(struct CLIDebugger* debugger) {
|
||||
ARMDebuggerCreate(&debugger->d);
|
||||
debugger->d.init = _cliDebuggerInit;
|
||||
debugger->d.deinit = _cliDebuggerDeinit;
|
||||
debugger->d.custom = _cliDebuggerCustom;
|
||||
debugger->d.paused = _commandLine;
|
||||
debugger->d.entered = _reportEntry;
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ struct CLIDebuggerSystem {
|
|||
|
||||
void (*init)(struct CLIDebuggerSystem*);
|
||||
void (*deinit)(struct CLIDebuggerSystem*);
|
||||
bool (*custom)(struct CLIDebuggerSystem*);
|
||||
|
||||
uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||
|
||||
|
|
|
@ -6,13 +6,22 @@
|
|||
#include "debugger.h"
|
||||
|
||||
#include "arm.h"
|
||||
#include "isa-inlines.h"
|
||||
|
||||
#include "memory-debugger.h"
|
||||
|
||||
const uint32_t ARM_DEBUGGER_ID = 0xDEADBEEF;
|
||||
|
||||
static struct DebugBreakpoint* _lookupBreakpoint(struct DebugBreakpoint* breakpoints, uint32_t address) {
|
||||
for (; breakpoints; breakpoints = breakpoints->next) {
|
||||
if (breakpoints->address == address) {
|
||||
return breakpoints;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _checkBreakpoints(struct ARMDebugger* debugger) {
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = debugger->cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
|
@ -20,12 +29,14 @@ static void _checkBreakpoints(struct ARMDebugger* debugger) {
|
|||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
for (breakpoint = debugger->breakpoints; breakpoint; breakpoint = breakpoint->next) {
|
||||
if (breakpoint->address + instructionLength == (uint32_t) debugger->cpu->gprs[ARM_PC]) {
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_BREAKPOINT);
|
||||
break;
|
||||
}
|
||||
struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength);
|
||||
if (!breakpoint) {
|
||||
return;
|
||||
}
|
||||
struct DebuggerEntryInfo info = {
|
||||
.address = breakpoint->address
|
||||
};
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_BREAKPOINT, &info);
|
||||
}
|
||||
|
||||
static void ARMDebuggerInit(struct ARMCore*, struct ARMComponent*);
|
||||
|
@ -42,8 +53,10 @@ void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
debugger->cpu = cpu;
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
debugger->breakpoints = 0;
|
||||
debugger->swBreakpoints = 0;
|
||||
debugger->originalMemory = cpu->memory;
|
||||
debugger->watchpoints = 0;
|
||||
debugger->currentBreakpoint = 0;
|
||||
if (debugger->init) {
|
||||
debugger->init(debugger);
|
||||
}
|
||||
|
@ -55,41 +68,56 @@ void ARMDebuggerDeinit(struct ARMComponent* component) {
|
|||
}
|
||||
|
||||
void ARMDebuggerRun(struct ARMDebugger* debugger) {
|
||||
if (debugger->state == DEBUGGER_EXITING) {
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
}
|
||||
while (debugger->state < DEBUGGER_EXITING) {
|
||||
if (!debugger->breakpoints) {
|
||||
while (debugger->state == DEBUGGER_RUNNING) {
|
||||
ARMRun(debugger->cpu);
|
||||
}
|
||||
switch (debugger->state) {
|
||||
case DEBUGGER_RUNNING:
|
||||
if (!debugger->breakpoints && !debugger->watchpoints) {
|
||||
ARMRunLoop(debugger->cpu);
|
||||
} else {
|
||||
while (debugger->state == DEBUGGER_RUNNING) {
|
||||
ARMRun(debugger->cpu);
|
||||
_checkBreakpoints(debugger);
|
||||
}
|
||||
ARMRun(debugger->cpu);
|
||||
_checkBreakpoints(debugger);
|
||||
}
|
||||
switch (debugger->state) {
|
||||
case DEBUGGER_RUNNING:
|
||||
break;
|
||||
case DEBUGGER_PAUSED:
|
||||
if (debugger->paused) {
|
||||
debugger->paused(debugger);
|
||||
} else {
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_EXITING:
|
||||
case DEBUGGER_SHUTDOWN:
|
||||
return;
|
||||
break;
|
||||
case DEBUGGER_CUSTOM:
|
||||
ARMRun(debugger->cpu);
|
||||
_checkBreakpoints(debugger);
|
||||
debugger->custom(debugger);
|
||||
break;
|
||||
case DEBUGGER_PAUSED:
|
||||
if (debugger->paused) {
|
||||
debugger->paused(debugger);
|
||||
} else {
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
}
|
||||
if (debugger->state != DEBUGGER_PAUSED && debugger->currentBreakpoint) {
|
||||
if (debugger->currentBreakpoint->isSw && debugger->setSoftwareBreakpoint) {
|
||||
debugger->setSoftwareBreakpoint(debugger, debugger->currentBreakpoint->address, debugger->currentBreakpoint->sw.mode, &debugger->currentBreakpoint->sw.opcode);
|
||||
}
|
||||
debugger->currentBreakpoint = 0;
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_SHUTDOWN:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) {
|
||||
void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
debugger->state = DEBUGGER_PAUSED;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
cpu->nextEvent = 0;
|
||||
if (reason == DEBUGGER_ENTER_BREAKPOINT) {
|
||||
struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu));
|
||||
debugger->currentBreakpoint = breakpoint;
|
||||
if (breakpoint && breakpoint->isSw) {
|
||||
info->address = breakpoint->address;
|
||||
if (debugger->clearSoftwareBreakpoint) {
|
||||
debugger->clearSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, breakpoint->sw.opcode);
|
||||
}
|
||||
|
||||
ARMRunFake(cpu, breakpoint->sw.opcode);
|
||||
}
|
||||
}
|
||||
if (debugger->entered) {
|
||||
debugger->entered(debugger, reason);
|
||||
debugger->entered(debugger, reason, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,9 +125,27 @@ void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
|||
struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint));
|
||||
breakpoint->address = address;
|
||||
breakpoint->next = debugger->breakpoints;
|
||||
breakpoint->isSw = false;
|
||||
debugger->breakpoints = breakpoint;
|
||||
}
|
||||
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
uint32_t opcode;
|
||||
if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint));
|
||||
breakpoint->address = address;
|
||||
breakpoint->next = debugger->swBreakpoints;
|
||||
breakpoint->isSw = true;
|
||||
breakpoint->sw.opcode = opcode;
|
||||
breakpoint->sw.mode = mode;
|
||||
debugger->swBreakpoints = breakpoint;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint** previous = &debugger->breakpoints;
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
|
@ -115,15 +161,15 @@ void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
|||
if (!debugger->watchpoints) {
|
||||
ARMDebuggerInstallMemoryShim(debugger);
|
||||
}
|
||||
struct DebugBreakpoint* watchpoint = malloc(sizeof(struct DebugBreakpoint));
|
||||
struct DebugWatchpoint* watchpoint = malloc(sizeof(struct DebugWatchpoint));
|
||||
watchpoint->address = address;
|
||||
watchpoint->next = debugger->watchpoints;
|
||||
debugger->watchpoints = watchpoint;
|
||||
}
|
||||
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint** previous = &debugger->watchpoints;
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
struct DebugWatchpoint** previous = &debugger->watchpoints;
|
||||
struct DebugWatchpoint* breakpoint;
|
||||
for (; (breakpoint = *previous); previous = &breakpoint->next) {
|
||||
if (breakpoint->address == address) {
|
||||
*previous = breakpoint->next;
|
||||
|
|
|
@ -15,13 +15,30 @@ extern const uint32_t ARM_DEBUGGER_ID;
|
|||
enum DebuggerState {
|
||||
DEBUGGER_PAUSED,
|
||||
DEBUGGER_RUNNING,
|
||||
DEBUGGER_EXITING,
|
||||
DEBUGGER_CUSTOM,
|
||||
DEBUGGER_SHUTDOWN
|
||||
};
|
||||
|
||||
struct DebugBreakpoint {
|
||||
struct DebugBreakpoint* next;
|
||||
uint32_t address;
|
||||
bool isSw;
|
||||
struct {
|
||||
uint32_t opcode;
|
||||
enum ExecutionMode mode;
|
||||
} sw;
|
||||
};
|
||||
|
||||
enum WatchpointType {
|
||||
WATCHPOINT_WRITE = 1,
|
||||
WATCHPOINT_READ = 2,
|
||||
WATCHPOINT_RW = 3
|
||||
};
|
||||
|
||||
struct DebugWatchpoint {
|
||||
struct DebugWatchpoint* next;
|
||||
uint32_t address;
|
||||
enum WatchpointType type;
|
||||
};
|
||||
|
||||
enum DebuggerEntryReason {
|
||||
|
@ -32,6 +49,20 @@ enum DebuggerEntryReason {
|
|||
DEBUGGER_ENTER_ILLEGAL_OP
|
||||
};
|
||||
|
||||
struct DebuggerEntryInfo {
|
||||
uint32_t address;
|
||||
union {
|
||||
struct {
|
||||
uint32_t oldValue;
|
||||
enum WatchpointType watchType;
|
||||
};
|
||||
|
||||
struct {
|
||||
uint32_t opcode;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
enum DebuggerLogLevel {
|
||||
DEBUGGER_LOG_DEBUG = 0x01,
|
||||
DEBUGGER_LOG_INFO = 0x02,
|
||||
|
@ -45,13 +76,20 @@ struct ARMDebugger {
|
|||
struct ARMCore* cpu;
|
||||
|
||||
struct DebugBreakpoint* breakpoints;
|
||||
struct DebugBreakpoint* watchpoints;
|
||||
struct DebugBreakpoint* swBreakpoints;
|
||||
struct DebugWatchpoint* watchpoints;
|
||||
struct ARMMemory originalMemory;
|
||||
|
||||
struct DebugBreakpoint* currentBreakpoint;
|
||||
|
||||
void (*init)(struct ARMDebugger*);
|
||||
void (*deinit)(struct ARMDebugger*);
|
||||
void (*paused)(struct ARMDebugger*);
|
||||
void (*entered)(struct ARMDebugger*, enum DebuggerEntryReason);
|
||||
void (*entered)(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*);
|
||||
void (*custom)(struct ARMDebugger*);
|
||||
|
||||
bool (*setSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);
|
||||
bool (*clearSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
|
||||
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
void (*log)(struct ARMDebugger*, enum DebuggerLogLevel, const char* format, ...);
|
||||
|
@ -59,8 +97,9 @@ struct ARMDebugger {
|
|||
|
||||
void ARMDebuggerCreate(struct ARMDebugger*);
|
||||
void ARMDebuggerRun(struct ARMDebugger*);
|
||||
void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason);
|
||||
void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*);
|
||||
void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gdb-stub.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifndef SIGTRAP
|
||||
#define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */
|
||||
#endif
|
||||
|
||||
#define SOCKET_TIMEOUT 50
|
||||
|
||||
enum GDBError {
|
||||
GDB_NO_ERROR = 0x00,
|
||||
GDB_BAD_ARGUMENTS = 0x06,
|
||||
|
@ -32,16 +33,34 @@ static void _gdbStubDeinit(struct ARMDebugger* debugger) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) {
|
||||
static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_MANUAL:
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
|
||||
break;
|
||||
case DEBUGGER_ENTER_BREAKPOINT:
|
||||
case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
|
||||
break;
|
||||
case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address
|
||||
if (info) {
|
||||
const char* type = 0;
|
||||
switch (info->watchType) {
|
||||
case WATCHPOINT_WRITE:
|
||||
type = "watch";
|
||||
break;
|
||||
case WATCHPOINT_READ:
|
||||
type = "rwatch";
|
||||
break;
|
||||
case WATCHPOINT_RW:
|
||||
type = "awatch";
|
||||
break;
|
||||
}
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08X", SIGTRAP, type, info->address);
|
||||
} else {
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_ENTER_ILLEGAL_OP:
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);
|
||||
break;
|
||||
|
@ -53,15 +72,19 @@ static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReas
|
|||
|
||||
static void _gdbStubPoll(struct ARMDebugger* debugger) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
while (stub->d.state == DEBUGGER_PAUSED) {
|
||||
if (!SOCKET_FAILED(stub->connection)) {
|
||||
if (!SocketSetBlocking(stub->connection, 1)) {
|
||||
GDBStubHangup(stub);
|
||||
return;
|
||||
}
|
||||
}
|
||||
GDBStubUpdate(stub);
|
||||
--stub->untilPoll;
|
||||
if (stub->untilPoll > 0) {
|
||||
return;
|
||||
}
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
stub->shouldBlock = false;
|
||||
GDBStubUpdate(stub);
|
||||
}
|
||||
|
||||
static void _gdbStubWait(struct ARMDebugger* debugger) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
stub->shouldBlock = true;
|
||||
GDBStubUpdate(stub);
|
||||
}
|
||||
|
||||
static void _ack(struct GDBStub* stub) {
|
||||
|
@ -169,13 +192,8 @@ static void _writeHostInfo(struct GDBStub* stub) {
|
|||
}
|
||||
|
||||
static void _continue(struct GDBStub* stub, const char* message) {
|
||||
stub->d.state = DEBUGGER_RUNNING;
|
||||
if (!SOCKET_FAILED(stub->connection)) {
|
||||
if (!SocketSetBlocking(stub->connection, 0)) {
|
||||
GDBStubHangup(stub);
|
||||
return;
|
||||
}
|
||||
}
|
||||
stub->d.state = DEBUGGER_CUSTOM;
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
// TODO: parse message
|
||||
UNUSED(message);
|
||||
}
|
||||
|
@ -278,7 +296,7 @@ static void _processVReadCommand(struct GDBStub* stub, const char* message) {
|
|||
stub->outgoing[0] = '\0';
|
||||
if (!strncmp("Attach", message, 6)) {
|
||||
strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
}
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
@ -347,7 +365,7 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
++message;
|
||||
break;
|
||||
case '\x03':
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL);
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
return parsed;
|
||||
default:
|
||||
_nak(stub);
|
||||
|
@ -441,46 +459,47 @@ void GDBStubCreate(struct GDBStub* stub) {
|
|||
stub->connection = INVALID_SOCKET;
|
||||
stub->d.init = 0;
|
||||
stub->d.deinit = _gdbStubDeinit;
|
||||
stub->d.paused = _gdbStubPoll;
|
||||
stub->d.paused = _gdbStubWait;
|
||||
stub->d.entered = _gdbStubEntered;
|
||||
stub->d.custom = _gdbStubPoll;
|
||||
stub->d.log = 0;
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
}
|
||||
|
||||
int GDBStubListen(struct GDBStub* stub, int port, uint32_t bindAddress) {
|
||||
bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
|
||||
if (!SOCKET_FAILED(stub->socket)) {
|
||||
GDBStubShutdown(stub);
|
||||
}
|
||||
// TODO: support IPv6
|
||||
stub->socket = SocketOpenTCP(port, bindAddress);
|
||||
if (SOCKET_FAILED(stub->socket)) {
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket");
|
||||
}
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
if (!SocketSetBlocking(stub->socket, false)) {
|
||||
goto cleanup;
|
||||
}
|
||||
int err = SocketListen(stub->socket, 1);
|
||||
if (err) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (!SocketSetBlocking(stub->socket, 0)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
|
||||
cleanup:
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port");
|
||||
}
|
||||
SocketClose(stub->socket);
|
||||
stub->socket = -1;
|
||||
return 0;
|
||||
stub->socket = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GDBStubHangup(struct GDBStub* stub) {
|
||||
if (!SOCKET_FAILED(stub->connection)) {
|
||||
SocketClose(stub->connection);
|
||||
stub->connection = -1;
|
||||
stub->connection = INVALID_SOCKET;
|
||||
}
|
||||
if (stub->d.state == DEBUGGER_PAUSED) {
|
||||
stub->d.state = DEBUGGER_RUNNING;
|
||||
|
@ -491,34 +510,45 @@ void GDBStubShutdown(struct GDBStub* stub) {
|
|||
GDBStubHangup(stub);
|
||||
if (!SOCKET_FAILED(stub->socket)) {
|
||||
SocketClose(stub->socket);
|
||||
stub->socket = -1;
|
||||
stub->socket = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void GDBStubUpdate(struct GDBStub* stub) {
|
||||
if (stub->socket == INVALID_SOCKET) {
|
||||
if (stub->d.state == DEBUGGER_PAUSED) {
|
||||
stub->d.state = DEBUGGER_RUNNING;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (stub->connection == INVALID_SOCKET) {
|
||||
stub->connection = SocketAccept(stub->socket, 0, 0);
|
||||
if (stub->shouldBlock) {
|
||||
Socket reads = stub->socket;
|
||||
SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT);
|
||||
}
|
||||
stub->connection = SocketAccept(stub->socket, 0);
|
||||
if (!SOCKET_FAILED(stub->connection)) {
|
||||
if (!SocketSetBlocking(stub->connection, 0)) {
|
||||
if (!SocketSetBlocking(stub->connection, false)) {
|
||||
goto connectionLost;
|
||||
}
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED);
|
||||
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
} else if (SocketWouldBlock()) {
|
||||
return;
|
||||
} else {
|
||||
goto connectionLost;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (stub->shouldBlock) {
|
||||
Socket reads = stub->connection;
|
||||
SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT);
|
||||
}
|
||||
ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1);
|
||||
if (messageLen == 0) {
|
||||
goto connectionLost;
|
||||
}
|
||||
if (messageLen == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
if (SocketWouldBlock()) {
|
||||
return;
|
||||
}
|
||||
goto connectionLost;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "util/socket.h"
|
||||
|
||||
#define GDB_STUB_MAX_LINE 1200
|
||||
#define GDB_STUB_INTERVAL 32
|
||||
|
||||
enum GDBStubAckState {
|
||||
GDB_ACK_PENDING = 0,
|
||||
|
@ -30,10 +31,13 @@ struct GDBStub {
|
|||
|
||||
Socket socket;
|
||||
Socket connection;
|
||||
|
||||
bool shouldBlock;
|
||||
int untilPoll;
|
||||
};
|
||||
|
||||
void GDBStubCreate(struct GDBStub*);
|
||||
int GDBStubListen(struct GDBStub*, int port, uint32_t bindAddress);
|
||||
bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress);
|
||||
|
||||
void GDBStubHangup(struct GDBStub*);
|
||||
void GDBStubShutdown(struct GDBStub*);
|
||||
|
|
|
@ -9,12 +9,18 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
static bool _checkWatchpoints(struct DebugBreakpoint* watchpoints, uint32_t address, int width);
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width);
|
||||
|
||||
static uint32_t _popcount32(unsigned bits) {
|
||||
bits = bits - ((bits >> 1) & 0x55555555);
|
||||
bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333);
|
||||
return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||
}
|
||||
|
||||
#define FIND_DEBUGGER(DEBUGGER, CPU) \
|
||||
{ \
|
||||
DEBUGGER = 0; \
|
||||
int i; \
|
||||
size_t i; \
|
||||
for (i = 0; i < CPU->numComponents; ++i) { \
|
||||
if (CPU->components[i]->id == ARM_DEBUGGER_ID) { \
|
||||
DEBUGGER = (struct ARMDebugger*) cpu->components[i]; \
|
||||
|
@ -34,26 +40,65 @@ static bool _checkWatchpoints(struct DebugBreakpoint* watchpoints, uint32_t addr
|
|||
static RETURN ARMDebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
if (_checkWatchpoints(debugger->watchpoints, address, WIDTH)) { \
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT); \
|
||||
struct DebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, address, &info, WIDTH)) { \
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, ARGS); \
|
||||
}
|
||||
|
||||
CREATE_WATCHPOINT_SHIM(load32, 4, int32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load16, 2, int16_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(loadU16, 2, uint16_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load8, 1, int8_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(loadU8, 1, uint8_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME) \
|
||||
static uint32_t ARMDebuggerShim_ ## NAME (struct ARMCore* cpu, uint32_t address, int mask, enum LSMDirection direction, int* cycleCounter) { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
uint32_t popcount = _popcount32(mask); \
|
||||
int offset = 4; \
|
||||
int base = address; \
|
||||
if (direction & LSM_D) { \
|
||||
offset = -4; \
|
||||
base -= (popcount << 2) - 4; \
|
||||
} \
|
||||
if (direction & LSM_B) { \
|
||||
base += offset; \
|
||||
} \
|
||||
unsigned i; \
|
||||
for (i = 0; i < popcount; ++i) { \
|
||||
struct DebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, base + 4 * i, &info, 4)) { \
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \
|
||||
}
|
||||
|
||||
CREATE_WATCHPOINT_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple)
|
||||
CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address)
|
||||
|
||||
static bool _checkWatchpoints(struct DebugBreakpoint* watchpoints, uint32_t address, int width) {
|
||||
width -= 1;
|
||||
for (; watchpoints; watchpoints = watchpoints->next) {
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width) {
|
||||
--width;
|
||||
struct DebugWatchpoint* watchpoints;
|
||||
for (watchpoints = debugger->watchpoints; watchpoints; watchpoints = watchpoints->next) {
|
||||
if (!((watchpoints->address ^ address) & ~width)) {
|
||||
switch (width + 1) {
|
||||
case 1:
|
||||
info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 2:
|
||||
info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 4:
|
||||
info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0);
|
||||
break;
|
||||
}
|
||||
info->address = address;
|
||||
info->watchType = watchpoints->type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +112,9 @@ void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger) {
|
|||
debugger->cpu->memory.store8 = ARMDebuggerShim_store8;
|
||||
debugger->cpu->memory.load32 = ARMDebuggerShim_load32;
|
||||
debugger->cpu->memory.load16 = ARMDebuggerShim_load16;
|
||||
debugger->cpu->memory.loadU16 = ARMDebuggerShim_loadU16;
|
||||
debugger->cpu->memory.load8 = ARMDebuggerShim_load8;
|
||||
debugger->cpu->memory.loadU8 = ARMDebuggerShim_loadU8;
|
||||
debugger->cpu->memory.storeMultiple = ARMDebuggerShim_storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = ARMDebuggerShim_loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = ARMDebuggerShim_setActiveRegion;
|
||||
}
|
||||
|
||||
|
@ -79,8 +124,8 @@ void ARMDebuggerRemoveMemoryShim(struct ARMDebugger* debugger) {
|
|||
debugger->cpu->memory.store8 = debugger->originalMemory.store8;
|
||||
debugger->cpu->memory.load32 = debugger->originalMemory.load32;
|
||||
debugger->cpu->memory.load16 = debugger->originalMemory.load16;
|
||||
debugger->cpu->memory.loadU16 = debugger->originalMemory.loadU16;
|
||||
debugger->cpu->memory.load8 = debugger->originalMemory.load8;
|
||||
debugger->cpu->memory.loadU8 = debugger->originalMemory.loadU8;
|
||||
debugger->cpu->memory.storeMultiple = debugger->originalMemory.storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = debugger->originalMemory.loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = debugger->originalMemory.setActiveRegion;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-audio.h"
|
||||
#include "audio.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba-video.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
const unsigned GBA_AUDIO_SAMPLES = 2048;
|
||||
const unsigned BLIP_BUFFER_SIZE = 0x4000;
|
||||
const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t);
|
||||
#define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128)
|
||||
|
||||
|
@ -27,8 +28,17 @@ static int _applyBias(struct GBAAudio* audio, int sample);
|
|||
static void _sample(struct GBAAudio* audio);
|
||||
|
||||
void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
|
||||
CircleBufferInit(&audio->left, samples * sizeof(int32_t));
|
||||
CircleBufferInit(&audio->right, samples * sizeof(int32_t));
|
||||
audio->samples = samples;
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
CircleBufferInit(&audio->left, samples * sizeof(int16_t));
|
||||
CircleBufferInit(&audio->right, samples * sizeof(int16_t));
|
||||
#else
|
||||
audio->left = blip_new(BLIP_BUFFER_SIZE);
|
||||
audio->right = blip_new(BLIP_BUFFER_SIZE);
|
||||
// Guess too large; we hang producing extra samples if we guess too low
|
||||
blip_set_rates(audio->left, GBA_ARM7TDMI_FREQUENCY, 96000);
|
||||
blip_set_rates(audio->right, GBA_ARM7TDMI_FREQUENCY, 96000);
|
||||
#endif
|
||||
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
}
|
||||
|
@ -43,8 +53,8 @@ void GBAAudioReset(struct GBAAudio* audio) {
|
|||
audio->ch2 = (struct GBAAudioChannel2) { .envelope = { .nextStep = INT_MAX } };
|
||||
audio->ch3 = (struct GBAAudioChannel3) { .bank = { .bank = 0 } };
|
||||
audio->ch4 = (struct GBAAudioChannel4) { .envelope = { .nextStep = INT_MAX } };
|
||||
audio->chA.dmaSource = 0;
|
||||
audio->chB.dmaSource = 0;
|
||||
audio->chA.dmaSource = 1;
|
||||
audio->chB.dmaSource = 2;
|
||||
audio->chA.sample = 0;
|
||||
audio->chB.sample = 0;
|
||||
audio->eventDiff = 0;
|
||||
|
@ -77,50 +87,67 @@ void GBAAudioReset(struct GBAAudio* audio) {
|
|||
audio->enable = false;
|
||||
audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / audio->sampleRate;
|
||||
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
CircleBufferClear(&audio->left);
|
||||
CircleBufferClear(&audio->right);
|
||||
#else
|
||||
blip_clear(audio->left);
|
||||
blip_clear(audio->right);
|
||||
audio->clock = 0;
|
||||
#endif
|
||||
CircleBufferClear(&audio->chA.fifo);
|
||||
CircleBufferClear(&audio->chB.fifo);
|
||||
}
|
||||
|
||||
void GBAAudioDeinit(struct GBAAudio* audio) {
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
CircleBufferDeinit(&audio->left);
|
||||
CircleBufferDeinit(&audio->right);
|
||||
#else
|
||||
blip_delete(audio->left);
|
||||
blip_delete(audio->right);
|
||||
#endif
|
||||
CircleBufferDeinit(&audio->chA.fifo);
|
||||
CircleBufferDeinit(&audio->chB.fifo);
|
||||
}
|
||||
|
||||
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
|
||||
if (samples > GBA_AUDIO_SAMPLES) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBASyncLockAudio(audio->p->sync);
|
||||
int32_t buffer[GBA_AUDIO_SAMPLES];
|
||||
int32_t dummy;
|
||||
audio->samples = samples;
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
size_t oldCapacity = audio->left.capacity;
|
||||
int16_t* buffer = malloc(oldCapacity);
|
||||
int16_t dummy;
|
||||
size_t read;
|
||||
size_t i;
|
||||
|
||||
read = CircleBufferDump(&audio->left, buffer, sizeof(buffer));
|
||||
read = CircleBufferDump(&audio->left, buffer, oldCapacity);
|
||||
CircleBufferDeinit(&audio->left);
|
||||
CircleBufferInit(&audio->left, samples * sizeof(int32_t));
|
||||
for (i = 0; i * sizeof(int32_t) < read; ++i) {
|
||||
if (!CircleBufferWrite32(&audio->left, buffer[i])) {
|
||||
CircleBufferRead32(&audio->left, &dummy);
|
||||
CircleBufferWrite32(&audio->left, buffer[i]);
|
||||
CircleBufferInit(&audio->left, samples * sizeof(int16_t));
|
||||
for (i = 0; i * sizeof(int16_t) < read; ++i) {
|
||||
if (!CircleBufferWrite16(&audio->left, buffer[i])) {
|
||||
CircleBufferRead16(&audio->left, &dummy);
|
||||
CircleBufferWrite16(&audio->left, buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
read = CircleBufferDump(&audio->right, buffer, sizeof(buffer));
|
||||
read = CircleBufferDump(&audio->right, buffer, oldCapacity);
|
||||
CircleBufferDeinit(&audio->right);
|
||||
CircleBufferInit(&audio->right, samples * sizeof(int32_t));
|
||||
for (i = 0; i * sizeof(int32_t) < read; ++i) {
|
||||
if (!CircleBufferWrite32(&audio->right, buffer[i])) {
|
||||
CircleBufferRead32(&audio->right, &dummy);
|
||||
CircleBufferWrite32(&audio->right, buffer[i]);
|
||||
CircleBufferInit(&audio->right, samples * sizeof(int16_t));
|
||||
for (i = 0; i * sizeof(int16_t) < read; ++i) {
|
||||
if (!CircleBufferWrite16(&audio->right, buffer[i])) {
|
||||
CircleBufferRead16(&audio->right, &dummy);
|
||||
CircleBufferWrite16(&audio->right, buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
#else
|
||||
blip_clear(audio->left);
|
||||
blip_clear(audio->right);
|
||||
audio->clock = 0;
|
||||
#endif
|
||||
|
||||
GBASyncConsumeAudio(audio->p->sync);
|
||||
}
|
||||
|
||||
|
@ -453,6 +480,30 @@ void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value) {
|
||||
struct CircleBuffer* fifo;
|
||||
switch (address) {
|
||||
case REG_FIFO_A_LO:
|
||||
case REG_FIFO_A_HI:
|
||||
fifo = &audio->chA.fifo;
|
||||
break;
|
||||
case REG_FIFO_B_LO:
|
||||
case REG_FIFO_B_HI:
|
||||
fifo = &audio->chB.fifo;
|
||||
break;
|
||||
default:
|
||||
GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", address);
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < 2; ++i) {
|
||||
while (!CircleBufferWrite8(fifo, value >> (8 * i))) {
|
||||
int8_t dummy;
|
||||
CircleBufferRead8(fifo, &dummy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
||||
struct GBAAudioFIFO* channel;
|
||||
if (fifoId == 0) {
|
||||
|
@ -472,20 +523,21 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
|||
CircleBufferRead8(&channel->fifo, &channel->sample);
|
||||
}
|
||||
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples) {
|
||||
GBASyncLockAudio(audio->p->sync);
|
||||
unsigned read = 0;
|
||||
if (left) {
|
||||
unsigned readL = CircleBufferRead(&audio->left, left, nSamples * sizeof(int32_t)) >> 2;
|
||||
unsigned readL = CircleBufferRead(&audio->left, left, nSamples * sizeof(int16_t)) >> 1;
|
||||
if (readL < nSamples) {
|
||||
memset((int32_t*) left + readL, 0, nSamples - readL);
|
||||
memset((int16_t*) left + readL, 0, nSamples - readL);
|
||||
}
|
||||
read = readL;
|
||||
}
|
||||
if (right) {
|
||||
unsigned readR = CircleBufferRead(&audio->right, right, nSamples * sizeof(int32_t)) >> 2;
|
||||
unsigned readR = CircleBufferRead(&audio->right, right, nSamples * sizeof(int16_t)) >> 1;
|
||||
if (readR < nSamples) {
|
||||
memset((int32_t*) right + readR, 0, nSamples - readR);
|
||||
memset((int16_t*) right + readR, 0, nSamples - readR);
|
||||
}
|
||||
read = read >= readR ? read : readR;
|
||||
}
|
||||
|
@ -494,8 +546,8 @@ unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned
|
|||
}
|
||||
|
||||
unsigned GBAAudioResampleNN(struct GBAAudio* audio, float ratio, float* drift, struct GBAStereoSample* output, unsigned nSamples) {
|
||||
int32_t left[GBA_AUDIO_SAMPLES];
|
||||
int32_t right[GBA_AUDIO_SAMPLES];
|
||||
int16_t left[GBA_AUDIO_SAMPLES];
|
||||
int16_t right[GBA_AUDIO_SAMPLES];
|
||||
|
||||
// toRead is in GBA samples
|
||||
// TODO: Do this with fixed-point math
|
||||
|
@ -530,6 +582,7 @@ unsigned GBAAudioResampleNN(struct GBAAudio* audio, float ratio, float* drift, s
|
|||
}
|
||||
return totalRead;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value) {
|
||||
envelope->length = GBAAudioRegisterEnvelopeGetLength(value);
|
||||
|
@ -685,13 +738,13 @@ static int _applyBias(struct GBAAudio* audio, int sample) {
|
|||
} else if (sample < 0) {
|
||||
sample = 0;
|
||||
}
|
||||
return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 6;
|
||||
return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5;
|
||||
}
|
||||
|
||||
static void _sample(struct GBAAudio* audio) {
|
||||
int32_t sampleLeft = 0;
|
||||
int32_t sampleRight = 0;
|
||||
int psgShift = 6 - audio->volume;
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
int psgShift = 5 - audio->volume;
|
||||
|
||||
if (audio->ch1Left) {
|
||||
sampleLeft += audio->ch1.sample;
|
||||
|
@ -748,14 +801,31 @@ static void _sample(struct GBAAudio* audio) {
|
|||
sampleRight = _applyBias(audio, sampleRight);
|
||||
|
||||
GBASyncLockAudio(audio->p->sync);
|
||||
CircleBufferWrite32(&audio->left, sampleLeft);
|
||||
CircleBufferWrite32(&audio->right, sampleRight);
|
||||
unsigned produced = CircleBufferSize(&audio->left);
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (thread && thread->stream) {
|
||||
thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight);
|
||||
unsigned produced;
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
CircleBufferWrite16(&audio->left, sampleLeft);
|
||||
CircleBufferWrite16(&audio->right, sampleRight);
|
||||
produced = CircleBufferSize(&audio->left) / 2;
|
||||
#else
|
||||
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
|
||||
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
int clockNeeded = blip_clocks_needed(audio->left, audio->samples / 32);
|
||||
if (audio->clock >= clockNeeded) {
|
||||
blip_end_frame(audio->left, audio->clock);
|
||||
blip_end_frame(audio->right, audio->clock);
|
||||
audio->clock -= clockNeeded;
|
||||
}
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= CircleBufferCapacity(&audio->left) / sizeof(int32_t) * 3);
|
||||
produced = blip_samples_avail(audio->left);
|
||||
#endif
|
||||
if (audio->p->stream) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= audio->samples);
|
||||
}
|
||||
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||
|
@ -838,6 +908,6 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
|
|||
audio->nextSample = state->audio.nextSample;
|
||||
}
|
||||
|
||||
float GBAAudioCalculateRatio(struct GBAAudio* audio, float desiredFPS, float desiredSampleRate) {
|
||||
return desiredSampleRate * GBA_ARM7TDMI_FREQUENCY / (VIDEO_TOTAL_LENGTH * desiredFPS * audio->sampleRate);
|
||||
float GBAAudioCalculateRatio(float inputSampleRate, float desiredFPS, float desiredSampleRate) {
|
||||
return desiredSampleRate * GBA_ARM7TDMI_FREQUENCY / (VIDEO_TOTAL_LENGTH * desiredFPS * inputSampleRate);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -11,10 +11,17 @@
|
|||
|
||||
#include "util/circle-buffer.h"
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
#include "third-party/blip_buf/blip_buf.h"
|
||||
#endif
|
||||
|
||||
struct GBADMA;
|
||||
|
||||
extern const unsigned GBA_AUDIO_SAMPLES;
|
||||
|
||||
#define RESAMPLE_NN 0
|
||||
#define RESAMPLE_BLIP_BUF 2
|
||||
|
||||
DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t);
|
||||
DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6);
|
||||
DECL_BITS(GBAAudioRegisterEnvelope, Duty, 6, 2);
|
||||
|
@ -177,8 +184,16 @@ struct GBAAudio {
|
|||
struct GBAAudioFIFO chA;
|
||||
struct GBAAudioFIFO chB;
|
||||
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
struct CircleBuffer left;
|
||||
struct CircleBuffer right;
|
||||
#else
|
||||
blip_t* left;
|
||||
blip_t* right;
|
||||
int16_t lastLeft;
|
||||
int16_t lastRight;
|
||||
int clock;
|
||||
#endif
|
||||
|
||||
uint8_t volumeRight;
|
||||
uint8_t volumeLeft;
|
||||
|
@ -207,6 +222,7 @@ struct GBAAudio {
|
|||
bool playingCh4;
|
||||
bool enable;
|
||||
|
||||
size_t samples;
|
||||
unsigned sampleRate;
|
||||
|
||||
GBARegisterSOUNDBIAS soundbias;
|
||||
|
@ -252,16 +268,19 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
|
|||
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value);
|
||||
|
||||
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value);
|
||||
void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value);
|
||||
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
|
||||
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
|
||||
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples);
|
||||
unsigned GBAAudioResampleNN(struct GBAAudio*, float ratio, float* drift, struct GBAStereoSample* output, unsigned nSamples);
|
||||
#endif
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state);
|
||||
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state);
|
||||
|
||||
float GBAAudioCalculateRatio(struct GBAAudio* audio, float desiredFPS, float desiredSampleRatio);
|
||||
float GBAAudioCalculateRatio(float inputSampleRate, float desiredFPS, float desiredSampleRatio);
|
||||
|
||||
#endif
|
|
@ -1,25 +1,76 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-bios.h"
|
||||
#include "bios.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-memory.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/memory.h"
|
||||
#include "isa-inlines.h"
|
||||
|
||||
const uint32_t GBA_BIOS_CHECKSUM = 0xBAAE187F;
|
||||
const uint32_t GBA_DS_BIOS_CHECKSUM = 0xBAAE1880;
|
||||
|
||||
static void _unLz77(struct GBA* gba, uint32_t source, uint8_t* dest);
|
||||
static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest);
|
||||
static void _unRl(struct GBA* gba, uint32_t source, uint8_t* dest);
|
||||
static void _unLz77(struct GBA* gba, int width);
|
||||
static void _unHuffman(struct GBA* gba);
|
||||
static void _unRl(struct GBA* gba, int width);
|
||||
static void _unFilter(struct GBA* gba, int inwidth, int outwidth);
|
||||
|
||||
static void _SoftReset(struct GBA* gba) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
ARMSetPrivilegeMode(cpu, MODE_IRQ);
|
||||
cpu->spsr.packed = 0;
|
||||
cpu->gprs[ARM_LR] = 0;
|
||||
cpu->gprs[ARM_SP] = SP_BASE_IRQ;
|
||||
ARMSetPrivilegeMode(cpu, MODE_SUPERVISOR);
|
||||
cpu->spsr.packed = 0;
|
||||
cpu->gprs[ARM_LR] = 0;
|
||||
cpu->gprs[ARM_SP] = SP_BASE_SUPERVISOR;
|
||||
ARMSetPrivilegeMode(cpu, MODE_SYSTEM);
|
||||
cpu->gprs[ARM_LR] = 0;
|
||||
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
||||
int8_t flag = ((int8_t*) gba->memory.iwram)[0x7FFA];
|
||||
memset(((int8_t*) gba->memory.iwram) + SIZE_WORKING_IRAM - 0x200, 0, 0x200);
|
||||
if (flag) {
|
||||
cpu->gprs[ARM_PC] = BASE_WORKING_RAM;
|
||||
} else {
|
||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||
}
|
||||
_ARMSetMode(cpu, MODE_ARM);
|
||||
int currentCycles = 0;
|
||||
ARM_WRITE_PC;
|
||||
}
|
||||
|
||||
static void _RegisterRamReset(struct GBA* gba) {
|
||||
uint32_t registers = gba->cpu->gprs[0];
|
||||
UNUSED(registers);
|
||||
GBALog(gba, GBA_LOG_STUB, "RegisterRamReset unimplemented");
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
cpu->memory.store16(cpu, BASE_IO | REG_DISPCNT, 0x0080, 0);
|
||||
if (registers & 0x01) {
|
||||
memset(gba->memory.wram, 0, SIZE_WORKING_RAM);
|
||||
}
|
||||
if (registers & 0x02) {
|
||||
memset(gba->memory.iwram, 0, SIZE_WORKING_IRAM - 0x200);
|
||||
}
|
||||
if (registers & 0x04) {
|
||||
memset(gba->video.palette, 0, SIZE_PALETTE_RAM);
|
||||
}
|
||||
if (registers & 0x08) {
|
||||
memset(gba->video.renderer->vram, 0, SIZE_VRAM);
|
||||
}
|
||||
if (registers & 0x10) {
|
||||
memset(gba->video.oam.raw, 0, SIZE_OAM);
|
||||
}
|
||||
if (registers & 0x20) {
|
||||
GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on SIO unimplemented");
|
||||
}
|
||||
if (registers & 0x40) {
|
||||
GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on Audio unimplemented");
|
||||
}
|
||||
if (registers & 0x80) {
|
||||
GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on IO unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
static void _BgAffineSet(struct GBA* gba) {
|
||||
|
@ -39,11 +90,11 @@ static void _BgAffineSet(struct GBA* gba) {
|
|||
// [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
|
||||
ox = cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||
oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f;
|
||||
cx = cpu->memory.load16(cpu, offset + 8, 0);
|
||||
cy = cpu->memory.load16(cpu, offset + 10, 0);
|
||||
sx = cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
||||
sy = cpu->memory.load16(cpu, offset + 14, 0) / 256.f;
|
||||
theta = (cpu->memory.loadU16(cpu, offset + 16, 0) >> 8) / 128.f * M_PI;
|
||||
cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0);
|
||||
cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0);
|
||||
sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
||||
sy = (int16_t) cpu->memory.load16(cpu, offset + 14, 0) / 256.f;
|
||||
theta = (cpu->memory.load16(cpu, offset + 16, 0) >> 8) / 128.f * M_PI;
|
||||
offset += 20;
|
||||
// Rotation
|
||||
a = d = cosf(theta);
|
||||
|
@ -78,9 +129,9 @@ static void _ObjAffineSet(struct GBA* gba) {
|
|||
while (i--) {
|
||||
// [ sx 0 ] [ cos(theta) -sin(theta) ] [ A B ]
|
||||
// [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ]
|
||||
sx = cpu->memory.load16(cpu, offset, 0) / 256.f;
|
||||
sy = cpu->memory.load16(cpu, offset + 2, 0) / 256.f;
|
||||
theta = (cpu->memory.loadU16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI;
|
||||
sx = (int16_t) cpu->memory.load16(cpu, offset, 0) / 256.f;
|
||||
sy = (int16_t) cpu->memory.load16(cpu, offset + 2, 0) / 256.f;
|
||||
theta = (cpu->memory.load16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI;
|
||||
offset += 8;
|
||||
// Rotation
|
||||
a = d = cosf(theta);
|
||||
|
@ -131,6 +182,9 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
return;
|
||||
}
|
||||
switch (immediate) {
|
||||
case 0x0:
|
||||
_SoftReset(gba);
|
||||
break;
|
||||
case 0x1:
|
||||
_RegisterRamReset(gba);
|
||||
break;
|
||||
|
@ -158,10 +212,17 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
break;
|
||||
case 0xB:
|
||||
case 0xC:
|
||||
if (cpu->gprs[0] >> BASE_OFFSET == REGION_BIOS) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot CpuSet from BIOS");
|
||||
return;
|
||||
}
|
||||
ARMRaiseSWI(cpu);
|
||||
break;
|
||||
case 0xD:
|
||||
cpu->gprs[0] = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||
cpu->gprs[0] = GBA_BIOS_CHECKSUM;
|
||||
cpu->gprs[1] = 1;
|
||||
cpu->gprs[3] = SIZE_BIOS;
|
||||
break;
|
||||
case 0xE:
|
||||
_BgAffineSet(gba);
|
||||
break;
|
||||
|
@ -175,17 +236,12 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
_unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 1))]);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
_unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 1))]);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
_unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFF)]);
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination");
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
_unLz77(gba, immediate == 0x11 ? 1 : 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -195,17 +251,12 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
_unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 3)) >> 2]);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
_unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 3)) >> 2]);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
_unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFC) >> 2]);
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination");
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
_unHuffman(gba);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -216,17 +267,29 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
_unRl(gba, cpu->gprs[0], &((uint8_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 1))]);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
_unRl(gba, cpu->gprs[0], &((uint8_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 1))]);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
_unRl(gba, cpu->gprs[0], &((uint8_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFF)]);
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination");
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
_unRl(gba, immediate == 0x14 ? 1 : 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source");
|
||||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination");
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
_unFilter(gba, immediate == 0x18 ? 2 : 1, immediate == 0x16 ? 1 : 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -236,6 +299,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: %02X", immediate);
|
||||
}
|
||||
gba->memory.biosPrefetch = 0xE3A02004;
|
||||
}
|
||||
|
||||
void GBASwi32(struct ARMCore* cpu, int immediate) {
|
||||
|
@ -251,44 +315,74 @@ uint32_t GBAChecksum(uint32_t* memory, size_t size) {
|
|||
return sum;
|
||||
}
|
||||
|
||||
static void _unLz77(struct GBA* gba, uint32_t source, uint8_t* dest) {
|
||||
static void _unLz77(struct GBA* gba, int width) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
uint32_t source = cpu->gprs[0];
|
||||
uint32_t dest = cpu->gprs[1];
|
||||
int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8;
|
||||
// We assume the signature byte (0x10) is correct
|
||||
int blockheader;
|
||||
uint32_t sPointer = source + 4;
|
||||
uint8_t* dPointer = dest;
|
||||
int blockheader = 0; // Some compilers warn if this isn't set, even though it's trivially provably always set
|
||||
source += 4;
|
||||
int blocksRemaining = 0;
|
||||
int block;
|
||||
uint8_t* disp;
|
||||
uint32_t disp;
|
||||
int bytes;
|
||||
int byte;
|
||||
int halfword;
|
||||
while (remaining > 0) {
|
||||
if (blocksRemaining) {
|
||||
if (blockheader & 0x80) {
|
||||
// Compressed
|
||||
block = cpu->memory.loadU8(cpu, sPointer, 0) | (cpu->memory.loadU8(cpu, sPointer + 1, 0) << 8);
|
||||
sPointer += 2;
|
||||
disp = dPointer - (((block & 0x000F) << 8) | ((block & 0xFF00) >> 8)) - 1;
|
||||
bytes = ((block & 0x00F0) >> 4) + 3;
|
||||
int block = cpu->memory.load8(cpu, source + 1, 0) | (cpu->memory.load8(cpu, source, 0) << 8);
|
||||
source += 2;
|
||||
disp = dest - (block & 0x0FFF) - 1;
|
||||
bytes = (block >> 12) + 3;
|
||||
while (bytes-- && remaining) {
|
||||
--remaining;
|
||||
*dPointer = *disp;
|
||||
if (width == 2) {
|
||||
byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0);
|
||||
if (dest & 1) {
|
||||
byte >>= (disp & 1) * 8;
|
||||
halfword |= byte << 8;
|
||||
cpu->memory.store16(cpu, dest ^ 1, halfword, 0);
|
||||
} else {
|
||||
byte >>= (disp & 1) * 8;
|
||||
halfword = byte & 0xFF;
|
||||
}
|
||||
} else {
|
||||
byte = cpu->memory.load8(cpu, disp, 0);
|
||||
cpu->memory.store8(cpu, dest, byte, 0);
|
||||
}
|
||||
++disp;
|
||||
++dPointer;
|
||||
++dest;
|
||||
}
|
||||
} else {
|
||||
// Uncompressed
|
||||
*dPointer = cpu->memory.loadU8(cpu, sPointer++, 0);
|
||||
++dPointer;
|
||||
byte = cpu->memory.load8(cpu, source, 0);
|
||||
++source;
|
||||
if (width == 2) {
|
||||
if (dest & 1) {
|
||||
halfword |= byte << 8;
|
||||
cpu->memory.store16(cpu, dest ^ 1, halfword, 0);
|
||||
} else {
|
||||
halfword = byte;
|
||||
}
|
||||
} else {
|
||||
cpu->memory.store8(cpu, dest, byte, 0);
|
||||
}
|
||||
++dest;
|
||||
--remaining;
|
||||
}
|
||||
blockheader <<= 1;
|
||||
--blocksRemaining;
|
||||
} else {
|
||||
blockheader = cpu->memory.loadU8(cpu, sPointer++, 0);
|
||||
blockheader = cpu->memory.load8(cpu, source, 0);
|
||||
++source;
|
||||
blocksRemaining = 8;
|
||||
}
|
||||
}
|
||||
cpu->gprs[0] = source;
|
||||
cpu->gprs[1] = dest;
|
||||
cpu->gprs[3] = 0;
|
||||
}
|
||||
|
||||
DECL_BITFIELD(HuffmanNode, uint8_t);
|
||||
|
@ -296,24 +390,26 @@ DECL_BITS(HuffmanNode, Offset, 0, 6);
|
|||
DECL_BIT(HuffmanNode, RTerm, 6);
|
||||
DECL_BIT(HuffmanNode, LTerm, 7);
|
||||
|
||||
static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest) {
|
||||
static void _unHuffman(struct GBA* gba) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
source = source & 0xFFFFFFFC;
|
||||
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
|
||||
uint32_t dest = cpu->gprs[1];
|
||||
uint32_t header = cpu->memory.load32(cpu, source, 0);
|
||||
int remaining = header >> 8;
|
||||
int bits = header & 0xF;
|
||||
if (32 % bits) {
|
||||
if (bits == 0) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Invalid Huffman bits");
|
||||
bits = 8;
|
||||
}
|
||||
if (32 % bits || bits == 1) {
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented unaligned Huffman");
|
||||
return;
|
||||
}
|
||||
int padding = (4 - remaining) & 0x3;
|
||||
remaining &= 0xFFFFFFFC;
|
||||
// We assume the signature byte (0x20) is correct
|
||||
int treesize = (cpu->memory.loadU8(cpu, source + 4, 0) << 1) + 1;
|
||||
int treesize = (cpu->memory.load8(cpu, source + 4, 0) << 1) + 1;
|
||||
int block = 0;
|
||||
uint32_t treeBase = source + 5;
|
||||
uint32_t sPointer = source + 5 + treesize;
|
||||
uint32_t* dPointer = dest;
|
||||
source += 5 + treesize;
|
||||
uint32_t nPointer = treeBase;
|
||||
HuffmanNode node;
|
||||
int bitsRemaining;
|
||||
|
@ -321,8 +417,8 @@ static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest) {
|
|||
int bitsSeen = 0;
|
||||
node = cpu->memory.load8(cpu, nPointer, 0);
|
||||
while (remaining > 0) {
|
||||
uint32_t bitstream = cpu->memory.load32(cpu, sPointer, 0);
|
||||
sPointer += 4;
|
||||
uint32_t bitstream = cpu->memory.load32(cpu, source, 0);
|
||||
source += 4;
|
||||
for (bitsRemaining = 32; bitsRemaining > 0 && remaining > 0; --bitsRemaining, bitstream <<= 1) {
|
||||
uint32_t next = (nPointer & ~1) + HuffmanNodeGetOffset(node) * 2 + 2;
|
||||
if (bitstream & 0x80000000) {
|
||||
|
@ -351,53 +447,129 @@ static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest) {
|
|||
node = cpu->memory.load8(cpu, nPointer, 0);
|
||||
if (bitsSeen == 32) {
|
||||
bitsSeen = 0;
|
||||
*dPointer = block;
|
||||
++dPointer;
|
||||
cpu->memory.store32(cpu, dest, block, 0);
|
||||
dest += 4;
|
||||
remaining -= 4;
|
||||
block = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (padding) {
|
||||
*dPointer = block;
|
||||
}
|
||||
cpu->gprs[0] = source;
|
||||
cpu->gprs[1] = dest;
|
||||
}
|
||||
|
||||
static void _unRl(struct GBA* gba, uint32_t source, uint8_t* dest) {
|
||||
static void _unRl(struct GBA* gba, int width) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
source = source & 0xFFFFFFFC;
|
||||
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
|
||||
int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8;
|
||||
int padding = (4 - remaining) & 0x3;
|
||||
// We assume the signature byte (0x30) is correct
|
||||
int blockheader;
|
||||
int block;
|
||||
uint32_t sPointer = source + 4;
|
||||
uint8_t* dPointer = dest;
|
||||
source += 4;
|
||||
uint32_t dest = cpu->gprs[1];
|
||||
int halfword = 0;
|
||||
while (remaining > 0) {
|
||||
blockheader = cpu->memory.loadU8(cpu, sPointer++, 0);
|
||||
blockheader = cpu->memory.load8(cpu, source, 0);
|
||||
++source;
|
||||
if (blockheader & 0x80) {
|
||||
// Compressed
|
||||
blockheader &= 0x7F;
|
||||
blockheader += 3;
|
||||
block = cpu->memory.loadU8(cpu, sPointer++, 0);
|
||||
block = cpu->memory.load8(cpu, source, 0);
|
||||
++source;
|
||||
while (blockheader-- && remaining) {
|
||||
--remaining;
|
||||
*dPointer = block;
|
||||
++dPointer;
|
||||
if (width == 2) {
|
||||
if (dest & 1) {
|
||||
halfword |= block << 8;
|
||||
cpu->memory.store16(cpu, dest ^ 1, halfword, 0);
|
||||
} else {
|
||||
halfword = block;
|
||||
}
|
||||
} else {
|
||||
cpu->memory.store8(cpu, dest, block, 0);
|
||||
}
|
||||
++dest;
|
||||
}
|
||||
} else {
|
||||
// Uncompressed
|
||||
blockheader++;
|
||||
while (blockheader-- && remaining) {
|
||||
--remaining;
|
||||
*dPointer = cpu->memory.loadU8(cpu, sPointer++, 0);
|
||||
++dPointer;
|
||||
int byte = cpu->memory.load8(cpu, source, 0);
|
||||
++source;
|
||||
if (width == 2) {
|
||||
if (dest & 1) {
|
||||
halfword |= byte << 8;
|
||||
cpu->memory.store16(cpu, dest ^ 1, halfword, 0);
|
||||
} else {
|
||||
halfword = byte;
|
||||
}
|
||||
} else {
|
||||
cpu->memory.store8(cpu, dest, byte, 0);
|
||||
}
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (padding--) {
|
||||
*dPointer = 0;
|
||||
++dPointer;
|
||||
if (width == 2) {
|
||||
if (dest & 1) {
|
||||
--padding;
|
||||
++dest;
|
||||
}
|
||||
for (; padding > 0; padding -= 2, dest += 2) {
|
||||
cpu->memory.store16(cpu, dest, 0, 0);
|
||||
}
|
||||
} else {
|
||||
while (padding--) {
|
||||
cpu->memory.store8(cpu, dest, 0, 0);
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
cpu->gprs[0] = source;
|
||||
cpu->gprs[1] = dest;
|
||||
}
|
||||
|
||||
static void _unFilter(struct GBA* gba, int inwidth, int outwidth) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
|
||||
uint32_t dest = cpu->gprs[1];
|
||||
uint32_t header = cpu->memory.load32(cpu, source, 0);
|
||||
int remaining = header >> 8;
|
||||
// We assume the signature nybble (0x8) is correct
|
||||
uint16_t halfword = 0;
|
||||
uint16_t old = 0;
|
||||
source += 4;
|
||||
while (remaining > 0) {
|
||||
uint16_t new;
|
||||
if (inwidth == 1) {
|
||||
new = cpu->memory.load8(cpu, source, 0);
|
||||
} else {
|
||||
new = cpu->memory.load16(cpu, source, 0);
|
||||
}
|
||||
new += old;
|
||||
if (outwidth > inwidth) {
|
||||
halfword >>= 8;
|
||||
halfword |= (new << 8);
|
||||
if (source & 1) {
|
||||
cpu->memory.store16(cpu, dest, halfword, 0);
|
||||
dest += outwidth;
|
||||
remaining -= outwidth;
|
||||
}
|
||||
} else if (outwidth == 1) {
|
||||
cpu->memory.store8(cpu, dest, new, 0);
|
||||
dest += outwidth;
|
||||
remaining -= outwidth;
|
||||
} else {
|
||||
cpu->memory.store16(cpu, dest, new, 0);
|
||||
dest += outwidth;
|
||||
remaining -= outwidth;
|
||||
}
|
||||
old = new;
|
||||
source += inwidth;
|
||||
}
|
||||
cpu->gprs[0] = source;
|
||||
cpu->gprs[1] = dest;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@ -0,0 +1,967 @@
|
|||
/* 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 "cheats.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define MAX_LINE_LENGTH 128
|
||||
|
||||
const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE;
|
||||
|
||||
DEFINE_VECTOR(GBACheatList, struct GBACheat);
|
||||
DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*);
|
||||
DEFINE_VECTOR(StringList, char*);
|
||||
|
||||
static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 };
|
||||
static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 };
|
||||
|
||||
static const uint8_t _gsa1T1[256] = {
|
||||
0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
|
||||
0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
|
||||
0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
|
||||
0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
|
||||
0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
|
||||
0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
|
||||
0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
|
||||
0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
|
||||
0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
|
||||
0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
|
||||
0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
|
||||
0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
|
||||
0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
|
||||
0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
|
||||
0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
|
||||
0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
|
||||
};
|
||||
|
||||
static const uint8_t _gsa1T2[256] = {
|
||||
0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
|
||||
0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
|
||||
0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
|
||||
0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
|
||||
0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
|
||||
0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
|
||||
0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
|
||||
0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
|
||||
0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
|
||||
0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
|
||||
0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
|
||||
0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
|
||||
0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
|
||||
0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
|
||||
0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
|
||||
0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
|
||||
};
|
||||
|
||||
static const uint8_t _par3T1[256] = {
|
||||
0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
|
||||
0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
|
||||
0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
|
||||
0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
|
||||
0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
|
||||
0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
|
||||
0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
|
||||
0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
|
||||
0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
|
||||
0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
|
||||
0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
|
||||
0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
|
||||
0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
|
||||
0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
|
||||
0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
|
||||
0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
|
||||
};
|
||||
|
||||
static const uint8_t _par3T2[256] = {
|
||||
0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
|
||||
0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
|
||||
0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
|
||||
0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
|
||||
0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
|
||||
0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
|
||||
0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
|
||||
0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
|
||||
0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
|
||||
0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
|
||||
0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
|
||||
0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
|
||||
0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
|
||||
0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
|
||||
0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
|
||||
0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
|
||||
};
|
||||
|
||||
static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return cpu->memory.load8(cpu, address, 0);
|
||||
case 2:
|
||||
return cpu->memory.load16(cpu, address, 0);
|
||||
case 4:
|
||||
return cpu->memory.load32(cpu, address, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
cpu->memory.store8(cpu, address, value, 0);
|
||||
break;
|
||||
case 2:
|
||||
cpu->memory.store16(cpu, address, value, 0);
|
||||
break;
|
||||
case 4:
|
||||
cpu->memory.store32(cpu, address, value, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int _hexDigit(char digit) {
|
||||
switch (digit) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return digit - '0';
|
||||
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return digit - 'a' + 10;
|
||||
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
return digit - 'A' + 10;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* _hex32(const char* line, uint32_t* out) {
|
||||
uint32_t value = 0;
|
||||
int i;
|
||||
for (i = 0; i < 8; ++i, ++line) {
|
||||
char digit = *line;
|
||||
value <<= 4;
|
||||
int nybble = _hexDigit(digit);
|
||||
if (nybble < 0) {
|
||||
return 0;
|
||||
}
|
||||
value |= nybble;
|
||||
}
|
||||
*out = value;
|
||||
return line;
|
||||
}
|
||||
|
||||
static const char* _hex16(const char* line, uint16_t* out) {
|
||||
uint16_t value = 0;
|
||||
*out = 0;
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i, ++line) {
|
||||
char digit = *line;
|
||||
value <<= 4;
|
||||
int nybble = _hexDigit(digit);
|
||||
if (nybble < 0) {
|
||||
return 0;
|
||||
}
|
||||
value |= nybble;
|
||||
}
|
||||
*out = value;
|
||||
return line;
|
||||
}
|
||||
|
||||
static void _registerLine(struct GBACheatSet* cheats, const char* line) {
|
||||
*StringListAppend(&cheats->lines) = strdup(line);
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
|
||||
static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) {
|
||||
uint32_t sum = 0xC6EF3720;
|
||||
int i;
|
||||
for (i = 0; i < 32; ++i) {
|
||||
*op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]);
|
||||
*op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]);
|
||||
sum -= 0x9E3779B9;
|
||||
}
|
||||
}
|
||||
|
||||
static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) {
|
||||
int x, y;
|
||||
int s0 = params >> 8;
|
||||
int s1 = params & 0xFF;
|
||||
for (y = 0; y < 4; ++y) {
|
||||
for (x = 0; x < 4; ++x) {
|
||||
uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF];
|
||||
seeds[y] <<= 8;
|
||||
seeds[y] |= z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) {
|
||||
cheats->gsaVersion = 1;
|
||||
switch (version) {
|
||||
case 1:
|
||||
memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
case 3:
|
||||
memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
enum GBAGameSharkType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses == 0) {
|
||||
cheats->incompleteCheat = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case GSA_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_4:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_LIST:
|
||||
cheats->remainingAddresses = (op1 & 0xFFFF) - 1;
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case GSA_PATCH:
|
||||
cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1;
|
||||
cheats->romPatches[0].newValue = op2;
|
||||
cheats->romPatches[0].applied = false;
|
||||
cheats->romPatches[0].exists = true;
|
||||
return true;
|
||||
case GSA_BUTTON:
|
||||
// TODO: Implement button
|
||||
return false;
|
||||
case GSA_IF_EQ:
|
||||
if (op1 == 0xDEADFACE) {
|
||||
_reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);
|
||||
return true;
|
||||
}
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_IF_EQ_RANGE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op2 & 0x0FFFFFFF;
|
||||
cheat->operand = op1 & 0xFFFF;
|
||||
cheat->repeat = (op1 >> 16) & 0xFF;
|
||||
return true;
|
||||
case GSA_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
// TODO
|
||||
UNUSED(cheats);
|
||||
UNUSED(op1);
|
||||
UNUSED(op2);
|
||||
UNUSED(_par3T1);
|
||||
UNUSED(_par3T2);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p || !cheats->hook) {
|
||||
return;
|
||||
}
|
||||
++cheats->hook->reentries;
|
||||
if (cheats->hook->reentries > 1) {
|
||||
return;
|
||||
}
|
||||
GBASetBreakpoint(device->p, &device->d, cheats->hook->address, cheats->hook->mode, &cheats->hook->patchedOpcode);
|
||||
}
|
||||
|
||||
static void _removeBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p || !cheats->hook) {
|
||||
return;
|
||||
}
|
||||
--cheats->hook->reentries;
|
||||
if (cheats->hook->reentries > 0) {
|
||||
return;
|
||||
}
|
||||
GBAClearBreakpoint(device->p, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode);
|
||||
}
|
||||
|
||||
static void _patchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
if (!cheats->romPatches[i].exists || cheats->romPatches[i].applied) {
|
||||
continue;
|
||||
}
|
||||
GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].newValue, &cheats->romPatches[i].oldValue);
|
||||
cheats->romPatches[i].applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void _unpatchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
if (!cheats->romPatches[i].exists || !cheats->romPatches[i].applied) {
|
||||
continue;
|
||||
}
|
||||
GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].oldValue, 0);
|
||||
cheats->romPatches[i].applied = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*);
|
||||
static void GBACheatDeviceDeinit(struct ARMComponent*);
|
||||
|
||||
void GBACheatDeviceCreate(struct GBACheatDevice* device) {
|
||||
device->d.id = GBA_CHEAT_DEVICE_ID;
|
||||
device->d.init = GBACheatDeviceInit;
|
||||
device->d.deinit = GBACheatDeviceDeinit;
|
||||
GBACheatSetsInit(&device->cheats, 4);
|
||||
}
|
||||
|
||||
void GBACheatDeviceDestroy(struct GBACheatDevice* device) {
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
GBACheatSetDeinit(set);
|
||||
free(set);
|
||||
}
|
||||
GBACheatSetsDeinit(&device->cheats);
|
||||
}
|
||||
|
||||
void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
|
||||
GBACheatListInit(&set->list, 4);
|
||||
StringListInit(&set->lines, 4);
|
||||
set->incompleteCheat = 0;
|
||||
set->gsaVersion = 0;
|
||||
set->remainingAddresses = 0;
|
||||
set->hook = 0;
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
set->romPatches[i].exists = false;
|
||||
}
|
||||
if (name) {
|
||||
set->name = strdup(name);
|
||||
} else {
|
||||
set->name = 0;
|
||||
}
|
||||
set->enabled = true;
|
||||
}
|
||||
|
||||
void GBACheatSetDeinit(struct GBACheatSet* set) {
|
||||
GBACheatListDeinit(&set->list);
|
||||
size_t i;
|
||||
for (i = 0; i < StringListSize(&set->lines); ++i) {
|
||||
free(*StringListGetPointer(&set->lines, i));
|
||||
}
|
||||
if (set->name) {
|
||||
free(set->name);
|
||||
}
|
||||
if (set->hook) {
|
||||
--set->hook->refs;
|
||||
if (set->hook->refs == 0) {
|
||||
free(set->hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) {
|
||||
if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||
ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
|
||||
}
|
||||
gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d;
|
||||
ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
|
||||
}
|
||||
|
||||
void GBACheatAddSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
*GBACheatSetsAppend(&device->cheats) = cheats;
|
||||
_addBreakpoint(device, cheats);
|
||||
_patchROM(device, cheats);
|
||||
}
|
||||
|
||||
void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
if (*GBACheatSetsGetPointer(&device->cheats, i) == cheats) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == GBACheatSetsSize(&device->cheats)) {
|
||||
return;
|
||||
}
|
||||
GBACheatSetsShift(&device->cheats, i, 1);
|
||||
_unpatchROM(device, cheats);
|
||||
_removeBreakpoint(device, cheats);
|
||||
}
|
||||
|
||||
bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
|
||||
char line[14] = "XXXXXXXX XXXX";
|
||||
snprintf(line, sizeof(line), "%08X %04X", op1, op2);
|
||||
_registerLine(cheats, line);
|
||||
|
||||
enum GBACodeBreakerType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
cheats->incompleteCheat->repeat = op1 & 0xFFFF;
|
||||
cheats->incompleteCheat->addressOffset = op2;
|
||||
cheats->incompleteCheat->operandOffset = 0;
|
||||
cheats->incompleteCheat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case CB_GAME_ID:
|
||||
// TODO: Run checksum
|
||||
return true;
|
||||
case CB_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
case CB_OR_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_OR;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
break;
|
||||
case CB_FILL:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case CB_FILL_8:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
case CB_AND_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_EQ:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ENCRYPT:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
|
||||
return false;
|
||||
case CB_IF_NE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_NE;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_GT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_GT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_LT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_LT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_SPECIAL:
|
||||
switch (op1 & 0x0FFFFFFF) {
|
||||
case 0x20:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
cheat->address = BASE_IO | REG_JOYSTAT;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
default:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
}
|
||||
case CB_ADD_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ADD;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_AND:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex16(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
||||
|
||||
bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
_registerLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
_setGameSharkVersion(set, 1);
|
||||
// Fall through
|
||||
case 1:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addGSA1(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddGameShark(cheats, op1, op2);
|
||||
}
|
||||
|
||||
bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
_registerLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
// Try to detect GameShark version
|
||||
_decryptGameShark(&o1, &o2, _gsa1S);
|
||||
if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
|
||||
_setGameSharkVersion(set, 1);
|
||||
return _addGSA1(set, o1, o2);
|
||||
}
|
||||
o1 = op1;
|
||||
o2 = op2;
|
||||
_decryptGameShark(&o1, &o2, _par3S);
|
||||
if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
|
||||
_setGameSharkVersion(set, 3);
|
||||
return _addPAR3(set, o1, o2);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addGSA1(set, o1, o2);
|
||||
case 3:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addPAR3(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddAutodetect(cheats, op1, op2);
|
||||
}
|
||||
|
||||
bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
|
||||
char cheat[MAX_LINE_LENGTH];
|
||||
struct GBACheatSet* set = 0;
|
||||
struct GBACheatSet* newSet;
|
||||
int gsaVersion = 0;
|
||||
bool nextDisabled = false;
|
||||
bool reset = false;
|
||||
while (true) {
|
||||
size_t i = 0;
|
||||
ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
||||
if (bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
if (bytesRead < 0) {
|
||||
return false;
|
||||
}
|
||||
while (isspace(cheat[i])) {
|
||||
++i;
|
||||
}
|
||||
switch (cheat[i]) {
|
||||
case '#':
|
||||
do {
|
||||
++i;
|
||||
} while (isspace(cheat[i]));
|
||||
newSet = malloc(sizeof(*set));
|
||||
GBACheatSetInit(newSet, &cheat[i]);
|
||||
newSet->enabled = !nextDisabled;
|
||||
nextDisabled = false;
|
||||
if (set) {
|
||||
GBACheatAddSet(device, set);
|
||||
}
|
||||
if (set && !reset) {
|
||||
GBACheatSetCopyProperties(newSet, set);
|
||||
} else {
|
||||
_setGameSharkVersion(newSet, gsaVersion);
|
||||
}
|
||||
reset = false;
|
||||
set = newSet;
|
||||
break;
|
||||
case '!':
|
||||
do {
|
||||
++i;
|
||||
} while (isspace(cheat[i]));
|
||||
if (strncasecmp(&cheat[i], "GSAv", 4) == 0 || strncasecmp(&cheat[i], "PARv", 4) == 0) {
|
||||
i += 4;
|
||||
gsaVersion = atoi(&cheat[i]);
|
||||
break;
|
||||
}
|
||||
if (strcasecmp(&cheat[i], "disabled") == 0) {
|
||||
nextDisabled = true;
|
||||
break;
|
||||
}
|
||||
if (strcasecmp(&cheat[i], "reset") == 0) {
|
||||
reset = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!set) {
|
||||
set = malloc(sizeof(*set));
|
||||
GBACheatSetInit(set, 0);
|
||||
set->enabled = !nextDisabled;
|
||||
nextDisabled = false;
|
||||
_setGameSharkVersion(set, gsaVersion);
|
||||
}
|
||||
GBACheatAddLine(set, cheat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (set) {
|
||||
GBACheatAddSet(device, set);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) {
|
||||
static const char lineStart[3] = "# ";
|
||||
static const char lineEnd = '\n';
|
||||
|
||||
struct GBACheatHook* lastHook = 0;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
if (lastHook && set->hook != lastHook) {
|
||||
static const char* resetDirective = "!reset\n";
|
||||
vf->write(vf, resetDirective, strlen(resetDirective));
|
||||
}
|
||||
switch (set->gsaVersion) {
|
||||
case 1: {
|
||||
static const char* versionDirective = "!GSAv1\n";
|
||||
vf->write(vf, versionDirective, strlen(versionDirective));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
static const char* versionDirective = "!PARv3\n";
|
||||
vf->write(vf, versionDirective, strlen(versionDirective));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
lastHook = set->hook;
|
||||
if (!set->enabled) {
|
||||
static const char* disabledDirective = "!disabled\n";
|
||||
vf->write(vf, disabledDirective, strlen(disabledDirective));
|
||||
}
|
||||
|
||||
vf->write(vf, lineStart, 2);
|
||||
if (set->name) {
|
||||
vf->write(vf, set->name, strlen(set->name));
|
||||
}
|
||||
vf->write(vf, &lineEnd, 1);
|
||||
size_t c;
|
||||
for (c = 0; c < StringListSize(&set->lines); ++c) {
|
||||
const char* line = *StringListGetPointer(&set->lines, c);
|
||||
vf->write(vf, line, strlen(line));
|
||||
vf->write(vf, &lineEnd, 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
uint16_t op3;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (isspace(line[0])) {
|
||||
++line;
|
||||
}
|
||||
line = _hex16(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
if (!line[0] || isspace(line[0])) {
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
||||
line = _hex16(line, &op3);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
uint32_t realOp2 = op2;
|
||||
realOp2 <<= 16;
|
||||
realOp2 |= op3;
|
||||
return GBACheatAddAutodetect(cheats, op1, realOp2);
|
||||
}
|
||||
|
||||
void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!cheats->enabled) {
|
||||
return;
|
||||
}
|
||||
bool condition = true;
|
||||
int conditionRemaining = 0;
|
||||
_patchROM(device, cheats);
|
||||
|
||||
size_t nCodes = GBACheatListSize(&cheats->list);
|
||||
size_t i;
|
||||
for (i = 0; i < nCodes; ++i) {
|
||||
if (conditionRemaining > 0) {
|
||||
--conditionRemaining;
|
||||
if (!condition) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
condition = true;
|
||||
}
|
||||
struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i);
|
||||
int32_t value = 0;
|
||||
int32_t operand = cheat->operand;
|
||||
uint32_t operationsRemaining = cheat->repeat;
|
||||
uint32_t address = cheat->address;
|
||||
bool performAssignment = false;
|
||||
for (; operationsRemaining; --operationsRemaining) {
|
||||
switch (cheat->type) {
|
||||
case CHEAT_ASSIGN:
|
||||
value = operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_AND:
|
||||
value = _readMem(device->p->cpu, address, cheat->width) & operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_ADD:
|
||||
value = _readMem(device->p->cpu, address, cheat->width) + operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_OR:
|
||||
value = _readMem(device->p->cpu, address, cheat->width) | operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_IF_EQ:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) == operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_NE:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) != operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_LT:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) < operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_GT:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) > operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_ULT:
|
||||
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_UGT:
|
||||
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_AND:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) & operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
case CHEAT_IF_LAND:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) && operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
break;
|
||||
}
|
||||
|
||||
if (performAssignment) {
|
||||
_writeMem(device->p->cpu, address, cheat->width, value);
|
||||
}
|
||||
|
||||
address += cheat->addressOffset;
|
||||
operand += cheat->operandOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) {
|
||||
newSet->gsaVersion = set->gsaVersion;
|
||||
memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds));
|
||||
if (set->hook) {
|
||||
if (newSet->hook) {
|
||||
--newSet->hook->refs;
|
||||
if (newSet->hook->refs == 0) {
|
||||
free(newSet->hook);
|
||||
}
|
||||
}
|
||||
newSet->hook = set->hook;
|
||||
++newSet->hook->refs;
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||
struct GBACheatDevice* device = (struct GBACheatDevice*) component;
|
||||
device->p = (struct GBA*) cpu->master;
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
_addBreakpoint(device, cheats);
|
||||
_patchROM(device, cheats);
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatDeviceDeinit(struct ARMComponent* component) {
|
||||
struct GBACheatDevice* device = (struct GBACheatDevice*) component;
|
||||
size_t i;
|
||||
for (i = GBACheatSetsSize(&device->cheats); i--;) {
|
||||
struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
_unpatchROM(device, cheats);
|
||||
_removeBreakpoint(device, cheats);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_CHEATS_H
|
||||
#define GBA_CHEATS_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "arm.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
#define MAX_ROM_PATCHES 4
|
||||
|
||||
enum GBACheatType {
|
||||
CHEAT_ASSIGN,
|
||||
CHEAT_AND,
|
||||
CHEAT_ADD,
|
||||
CHEAT_OR,
|
||||
CHEAT_IF_EQ,
|
||||
CHEAT_IF_NE,
|
||||
CHEAT_IF_LT,
|
||||
CHEAT_IF_GT,
|
||||
CHEAT_IF_ULT,
|
||||
CHEAT_IF_UGT,
|
||||
CHEAT_IF_AND,
|
||||
CHEAT_IF_LAND
|
||||
};
|
||||
|
||||
enum GBACodeBreakerType {
|
||||
CB_GAME_ID = 0x0,
|
||||
CB_HOOK = 0x1,
|
||||
CB_OR_2 = 0x2,
|
||||
CB_ASSIGN_1 = 0x3,
|
||||
CB_FILL = 0x4,
|
||||
CB_FILL_8 = 0x5,
|
||||
CB_AND_2 = 0x6,
|
||||
CB_IF_EQ = 0x7,
|
||||
CB_ASSIGN_2 = 0x8,
|
||||
CB_ENCRYPT = 0x9,
|
||||
CB_IF_NE = 0xA,
|
||||
CB_IF_GT = 0xB,
|
||||
CB_IF_LT = 0xC,
|
||||
CB_IF_SPECIAL = 0xD,
|
||||
CB_ADD_2 = 0xE,
|
||||
CB_IF_AND = 0xF,
|
||||
};
|
||||
|
||||
enum GBAGameSharkType {
|
||||
GSA_ASSIGN_1 = 0x0,
|
||||
GSA_ASSIGN_2 = 0x1,
|
||||
GSA_ASSIGN_4 = 0x2,
|
||||
GSA_ASSIGN_LIST = 0x3,
|
||||
GSA_PATCH = 0x6,
|
||||
GSA_BUTTON = 0x8,
|
||||
GSA_IF_EQ = 0xD,
|
||||
GSA_IF_EQ_RANGE = 0xE,
|
||||
GSA_HOOK = 0xF
|
||||
};
|
||||
|
||||
enum GBAActionReplay3Condition {
|
||||
PAR3_COND_OTHER = 0x00000000,
|
||||
PAR3_COND_EQ = 0x08000000,
|
||||
PAR3_COND_NE = 0x10000000,
|
||||
PAR3_COND_LT = 0x18000000,
|
||||
PAR3_COND_GT = 0x20000000,
|
||||
PAR3_COND_ULT = 0x28000000,
|
||||
PAR3_COND_UGT = 0x30000000,
|
||||
PAR3_COND_LAND = 0x38000000,
|
||||
};
|
||||
|
||||
enum GBAActionReplay3Width {
|
||||
PAR3_WIDTH_1 = 0x00000000,
|
||||
PAR3_WIDTH_2 = 0x02000000,
|
||||
PAR3_WIDTH_4 = 0x04000000,
|
||||
PAR3_WIDTH_FALSE = 0x06000000,
|
||||
};
|
||||
|
||||
enum GBAActionReplay3Action {
|
||||
PAR3_ACTION_NEXT = 0x00000000,
|
||||
PAR3_ACTION_NEXT_TWO = 0x40000000,
|
||||
PAR3_ACTION_BLOCK = 0x80000000,
|
||||
PAR3_ACTION_DISABLE = 0xC0000000,
|
||||
};
|
||||
|
||||
enum GBAActionReplay3Base {
|
||||
PAR3_BASE_ASSIGN_1 = 0x00000000,
|
||||
PAR3_BASE_ASSIGN_2 = 0x02000000,
|
||||
PAR3_BASE_ASSIGN_4 = 0x04000000,
|
||||
PAR3_BASE_INDIRECT_1 = 0x40000000,
|
||||
PAR3_BASE_INDIRECT_2 = 0x42000000,
|
||||
PAR3_BASE_INDIRECT_4 = 0x44000000,
|
||||
PAR3_BASE_ADD_1 = 0x80000000,
|
||||
PAR3_BASE_ADD_2 = 0x82000000,
|
||||
PAR3_BASE_ADD_4 = 0x84000000,
|
||||
PAR3_BASE_HOOK = 0xC4000000,
|
||||
PAR3_BASE_IO_2 = 0xC6000000,
|
||||
PAR3_BASE_IO_3 = 0xC7000000,
|
||||
};
|
||||
|
||||
enum GBAActionReplay3Other {
|
||||
PAR3_OTHER_END = 0x00000000,
|
||||
PAR3_OTHER_SLOWDOWN = 0x08000000,
|
||||
PAR3_OTHER_BUTTON_1 = 0x10000000,
|
||||
PAR3_OTHER_BUTTON_2 = 0x12000000,
|
||||
PAR3_OTHER_BUTTON_4 = 0x14000000,
|
||||
PAR3_OTHER_PATCH_1 = 0x18000000,
|
||||
PAR3_OTHER_PATCH_2 = 0x1A000000,
|
||||
PAR3_OTHER_PATCH_3 = 0x1C000000,
|
||||
PAR3_OTHER_PATCH_4 = 0x1E000000,
|
||||
PAR3_OTHER_ENDIF = 0x40000000,
|
||||
PAR3_OTHER_ELSE = 0x60000000,
|
||||
PAR3_OTHER_FILL_1 = 0x80000000,
|
||||
PAR3_OTHER_FILL_2 = 0x82000000,
|
||||
PAR3_OTHER_FILL_4 = 0x84000000,
|
||||
};
|
||||
|
||||
enum {
|
||||
PAR3_COND = 0x38000000,
|
||||
PAR3_WIDTH = 0x06000000,
|
||||
PAR3_ACTION = 0xC0000000
|
||||
};
|
||||
|
||||
struct GBACheat {
|
||||
enum GBACheatType type;
|
||||
int width;
|
||||
uint32_t address;
|
||||
uint32_t operand;
|
||||
uint32_t repeat;
|
||||
|
||||
int32_t addressOffset;
|
||||
int32_t operandOffset;
|
||||
};
|
||||
|
||||
struct GBACheatHook {
|
||||
uint32_t address;
|
||||
enum ExecutionMode mode;
|
||||
uint32_t patchedOpcode;
|
||||
size_t refs;
|
||||
size_t reentries;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(GBACheatList, struct GBACheat);
|
||||
DECLARE_VECTOR(StringList, char*);
|
||||
|
||||
struct GBACheatSet {
|
||||
struct GBACheatHook* hook;
|
||||
struct GBACheatList list;
|
||||
|
||||
struct GBACheat* incompleteCheat;
|
||||
|
||||
struct GBACheatPatch {
|
||||
uint32_t address;
|
||||
int16_t newValue;
|
||||
int16_t oldValue;
|
||||
bool applied;
|
||||
bool exists;
|
||||
} romPatches[MAX_ROM_PATCHES];
|
||||
|
||||
int gsaVersion;
|
||||
uint32_t gsaSeeds[4];
|
||||
int remainingAddresses;
|
||||
|
||||
char* name;
|
||||
bool enabled;
|
||||
struct StringList lines;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(GBACheatSets, struct GBACheatSet*);
|
||||
|
||||
struct GBACheatDevice {
|
||||
struct ARMComponent d;
|
||||
struct GBA* p;
|
||||
|
||||
struct GBACheatSets cheats;
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
|
||||
void GBACheatDeviceCreate(struct GBACheatDevice*);
|
||||
void GBACheatDeviceDestroy(struct GBACheatDevice*);
|
||||
|
||||
void GBACheatSetInit(struct GBACheatSet*, const char* name);
|
||||
void GBACheatSetDeinit(struct GBACheatSet*);
|
||||
|
||||
void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice*);
|
||||
|
||||
void GBACheatAddSet(struct GBACheatDevice*, struct GBACheatSet*);
|
||||
void GBACheatRemoveSet(struct GBACheatDevice*, struct GBACheatSet*);
|
||||
void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set);
|
||||
|
||||
bool GBACheatAddCodeBreaker(struct GBACheatSet*, uint32_t op1, uint16_t op2);
|
||||
bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatParseFile(struct GBACheatDevice*, struct VFile*);
|
||||
bool GBACheatSaveFile(struct GBACheatDevice*, struct VFile*);
|
||||
|
||||
bool GBACheatAddLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*);
|
||||
|
||||
#endif
|
|
@ -1,332 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba.h"
|
||||
|
||||
#include "gba-gpio.h"
|
||||
#include "gba-sensors.h"
|
||||
#include "gba-serialize.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static void _readPins(struct GBACartridgeGPIO* gpio);
|
||||
static void _outputPins(struct GBACartridgeGPIO* gpio, unsigned pins);
|
||||
|
||||
static void _rtcReadPins(struct GBACartridgeGPIO* gpio);
|
||||
static unsigned _rtcOutput(struct GBACartridgeGPIO* gpio);
|
||||
static void _rtcProcessByte(struct GBACartridgeGPIO* gpio);
|
||||
static void _rtcUpdateClock(struct GBACartridgeGPIO* gpio);
|
||||
static unsigned _rtcBCD(unsigned value);
|
||||
|
||||
static void _gyroReadPins(struct GBACartridgeGPIO* gpio);
|
||||
|
||||
static void _rumbleReadPins(struct GBACartridgeGPIO* gpio);
|
||||
|
||||
static const int RTC_BYTES[8] = {
|
||||
0, // Force reset
|
||||
0, // Empty
|
||||
7, // Date/Time
|
||||
0, // Force IRQ
|
||||
1, // Control register
|
||||
0, // Empty
|
||||
3, // Time
|
||||
0 // Empty
|
||||
};
|
||||
|
||||
void GBAGPIOInit(struct GBACartridgeGPIO* gpio, uint16_t* base) {
|
||||
gpio->gpioDevices = GPIO_NONE;
|
||||
gpio->direction = GPIO_WRITE_ONLY;
|
||||
gpio->gpioBase = base;
|
||||
gpio->pinState = 0;
|
||||
gpio->direction = 0;
|
||||
}
|
||||
|
||||
void GBAGPIOWrite(struct GBACartridgeGPIO* gpio, uint32_t address, uint16_t value) {
|
||||
switch (address) {
|
||||
case GPIO_REG_DATA:
|
||||
gpio->pinState = value;
|
||||
_readPins(gpio);
|
||||
break;
|
||||
case GPIO_REG_DIRECTION:
|
||||
gpio->direction = value;
|
||||
break;
|
||||
case GPIO_REG_CONTROL:
|
||||
gpio->readWrite = value;
|
||||
break;
|
||||
default:
|
||||
GBALog(gpio->p, GBA_LOG_WARN, "Invalid GPIO address");
|
||||
}
|
||||
|
||||
if (gpio->readWrite) {
|
||||
uint16_t old = gpio->gpioBase[0];
|
||||
old &= ~gpio->direction;
|
||||
gpio->gpioBase[0] = old | (value & gpio->direction);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAGPIOInitRTC(struct GBACartridgeGPIO* gpio) {
|
||||
gpio->gpioDevices |= GPIO_RTC;
|
||||
gpio->rtc.bytesRemaining = 0;
|
||||
|
||||
gpio->rtc.transferStep = 0;
|
||||
|
||||
gpio->rtc.bitsRead = 0;
|
||||
gpio->rtc.bits = 0;
|
||||
gpio->rtc.commandActive = 0;
|
||||
gpio->rtc.command.packed = 0;
|
||||
gpio->rtc.control.packed = 0x40;
|
||||
memset(gpio->rtc.time, 0, sizeof(gpio->rtc.time));
|
||||
}
|
||||
|
||||
void _readPins(struct GBACartridgeGPIO* gpio) {
|
||||
if (gpio->gpioDevices & GPIO_RTC) {
|
||||
_rtcReadPins(gpio);
|
||||
}
|
||||
|
||||
if (gpio->gpioDevices & GPIO_GYRO) {
|
||||
_gyroReadPins(gpio);
|
||||
}
|
||||
|
||||
if (gpio->gpioDevices & GPIO_RUMBLE) {
|
||||
_rumbleReadPins(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
void _outputPins(struct GBACartridgeGPIO* gpio, unsigned pins) {
|
||||
if (gpio->readWrite) {
|
||||
uint16_t old = gpio->gpioBase[0];
|
||||
old &= gpio->direction;
|
||||
gpio->gpioBase[0] = old | (pins & ~gpio->direction & 0xF);
|
||||
}
|
||||
}
|
||||
|
||||
// == RTC
|
||||
|
||||
void _rtcReadPins(struct GBACartridgeGPIO* gpio) {
|
||||
// Transfer sequence:
|
||||
// P: 0 | 1 | 2 | 3
|
||||
// == Initiate
|
||||
// > HI | - | LO | -
|
||||
// > HI | - | HI | -
|
||||
// == Transfer bit (x8)
|
||||
// > LO | x | HI | -
|
||||
// > HI | - | HI | -
|
||||
// < ?? | x | ?? | -
|
||||
// == Terminate
|
||||
// > - | - | LO | -
|
||||
switch (gpio->rtc.transferStep) {
|
||||
case 0:
|
||||
if ((gpio->pinState & 5) == 1) {
|
||||
gpio->rtc.transferStep = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if ((gpio->pinState & 5) == 5) {
|
||||
gpio->rtc.transferStep = 2;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!gpio->p0) {
|
||||
gpio->rtc.bits &= ~(1 << gpio->rtc.bitsRead);
|
||||
gpio->rtc.bits |= gpio->p1 << gpio->rtc.bitsRead;
|
||||
} else {
|
||||
if (gpio->p2) {
|
||||
// GPIO direction should always != reading
|
||||
if (gpio->dir1) {
|
||||
if (gpio->rtc.command.reading) {
|
||||
GBALog(gpio->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
||||
}
|
||||
++gpio->rtc.bitsRead;
|
||||
if (gpio->rtc.bitsRead == 8) {
|
||||
_rtcProcessByte(gpio);
|
||||
}
|
||||
} else {
|
||||
_outputPins(gpio, 5 | (_rtcOutput(gpio) << 1));
|
||||
++gpio->rtc.bitsRead;
|
||||
if (gpio->rtc.bitsRead == 8) {
|
||||
--gpio->rtc.bytesRemaining;
|
||||
if (gpio->rtc.bytesRemaining <= 0) {
|
||||
gpio->rtc.commandActive = 0;
|
||||
gpio->rtc.command.reading = 0;
|
||||
}
|
||||
gpio->rtc.bitsRead = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gpio->rtc.bitsRead = 0;
|
||||
gpio->rtc.bytesRemaining = 0;
|
||||
gpio->rtc.commandActive = 0;
|
||||
gpio->rtc.command.reading = 0;
|
||||
gpio->rtc.transferStep = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _rtcProcessByte(struct GBACartridgeGPIO* gpio) {
|
||||
--gpio->rtc.bytesRemaining;
|
||||
if (!gpio->rtc.commandActive) {
|
||||
union RTCCommandData command;
|
||||
command.packed = gpio->rtc.bits;
|
||||
if (command.magic == 0x06) {
|
||||
gpio->rtc.command = command;
|
||||
|
||||
gpio->rtc.bytesRemaining = RTC_BYTES[gpio->rtc.command.command];
|
||||
gpio->rtc.commandActive = gpio->rtc.bytesRemaining > 0;
|
||||
switch (command.command) {
|
||||
case RTC_RESET:
|
||||
gpio->rtc.control.packed = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
_rtcUpdateClock(gpio);
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_CONTROL:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
GBALog(gpio->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", gpio->rtc.bits);
|
||||
}
|
||||
} else {
|
||||
switch (gpio->rtc.command.command) {
|
||||
case RTC_CONTROL:
|
||||
gpio->rtc.control.packed = gpio->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
GBALog(gpio->p, GBA_LOG_STUB, "Unimplemented RTC command %u", gpio->rtc.command.command);
|
||||
break;
|
||||
case RTC_RESET:
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gpio->rtc.bits = 0;
|
||||
gpio->rtc.bitsRead = 0;
|
||||
if (!gpio->rtc.bytesRemaining) {
|
||||
gpio->rtc.commandActive = 0;
|
||||
gpio->rtc.command.reading = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned _rtcOutput(struct GBACartridgeGPIO* gpio) {
|
||||
uint8_t outputByte = 0;
|
||||
switch (gpio->rtc.command.command) {
|
||||
case RTC_CONTROL:
|
||||
outputByte = gpio->rtc.control.packed;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
outputByte = gpio->rtc.time[7 - gpio->rtc.bytesRemaining];
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_RESET:
|
||||
break;
|
||||
}
|
||||
unsigned output = (outputByte >> gpio->rtc.bitsRead) & 1;
|
||||
return output;
|
||||
}
|
||||
|
||||
void _rtcUpdateClock(struct GBACartridgeGPIO* gpio) {
|
||||
time_t t = time(0);
|
||||
struct tm date;
|
||||
#ifdef _WIN32
|
||||
date = *localtime(&t);
|
||||
#else
|
||||
localtime_r(&t, &date);
|
||||
#endif
|
||||
gpio->rtc.time[0] = _rtcBCD(date.tm_year - 100);
|
||||
gpio->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
||||
gpio->rtc.time[2] = _rtcBCD(date.tm_mday);
|
||||
gpio->rtc.time[3] = _rtcBCD(date.tm_wday);
|
||||
if (gpio->rtc.control.hour24) {
|
||||
gpio->rtc.time[4] = _rtcBCD(date.tm_hour);
|
||||
} else {
|
||||
gpio->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
||||
}
|
||||
gpio->rtc.time[5] = _rtcBCD(date.tm_min);
|
||||
gpio->rtc.time[6] = _rtcBCD(date.tm_sec);
|
||||
}
|
||||
|
||||
unsigned _rtcBCD(unsigned value) {
|
||||
int counter = value % 10;
|
||||
value /= 10;
|
||||
counter += (value % 10) << 4;
|
||||
return counter;
|
||||
}
|
||||
|
||||
// == Gyro
|
||||
|
||||
void GBAGPIOInitGyro(struct GBACartridgeGPIO* gpio) {
|
||||
gpio->gpioDevices |= GPIO_GYRO;
|
||||
gpio->gyroSample = 0;
|
||||
gpio->gyroEdge = 0;
|
||||
}
|
||||
|
||||
void _gyroReadPins(struct GBACartridgeGPIO* gpio) {
|
||||
struct GBARotationSource* gyro = gpio->p->rotationSource;
|
||||
if (!gyro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gpio->p0) {
|
||||
if (gyro->sample) {
|
||||
gyro->sample(gyro);
|
||||
}
|
||||
int32_t sample = gyro->readGyroZ(gyro);
|
||||
|
||||
// Normalize to ~12 bits, focused on 0x6C0
|
||||
gpio->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
||||
}
|
||||
|
||||
if (gpio->gyroEdge && !gpio->p1) {
|
||||
// Write bit on falling edge
|
||||
unsigned bit = gpio->gyroSample >> 15;
|
||||
gpio->gyroSample <<= 1;
|
||||
_outputPins(gpio, bit << 2);
|
||||
}
|
||||
|
||||
gpio->gyroEdge = gpio->p1;
|
||||
}
|
||||
|
||||
// == Rumble
|
||||
|
||||
void GBAGPIOInitRumble(struct GBACartridgeGPIO* gpio) {
|
||||
gpio->gpioDevices |= GPIO_RUMBLE;
|
||||
}
|
||||
|
||||
void _rumbleReadPins(struct GBACartridgeGPIO* gpio) {
|
||||
struct GBARumble* rumble = gpio->p->rumble;
|
||||
if (!rumble) {
|
||||
return;
|
||||
}
|
||||
|
||||
rumble->setRumble(rumble, gpio->p3);
|
||||
}
|
||||
|
||||
// == Serialization
|
||||
|
||||
void GBAGPIOSerialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state) {
|
||||
state->gpio.readWrite = gpio->readWrite;
|
||||
state->gpio.pinState = gpio->pinState;
|
||||
state->gpio.pinDirection = gpio->direction;
|
||||
state->gpio.devices = gpio->gpioDevices;
|
||||
state->gpio.rtc = gpio->rtc;
|
||||
state->gpio.gyroSample = gpio->gyroSample;
|
||||
state->gpio.gyroEdge = gpio->gyroEdge;
|
||||
}
|
||||
|
||||
void GBAGPIODeserialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state) {
|
||||
gpio->readWrite = state->gpio.readWrite;
|
||||
gpio->pinState = state->gpio.pinState;
|
||||
gpio->direction = state->gpio.pinDirection;
|
||||
// TODO: Deterministic RTC
|
||||
gpio->rtc = state->gpio.rtc;
|
||||
gpio->gyroSample = state->gpio.gyroSample;
|
||||
gpio->gyroEdge = state->gpio.gyroEdge;
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_GPIO_H
|
||||
#define GBA_GPIO_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
enum GPIODevice {
|
||||
GPIO_NONE = 0,
|
||||
GPIO_RTC = 1,
|
||||
GPIO_RUMBLE = 2,
|
||||
GPIO_LIGHT_SENSOR = 4,
|
||||
GPIO_GYRO = 8,
|
||||
GPIO_TILT = 16
|
||||
};
|
||||
|
||||
enum GPIORegister {
|
||||
GPIO_REG_DATA = 0xC4,
|
||||
GPIO_REG_DIRECTION = 0xC6,
|
||||
GPIO_REG_CONTROL = 0xC8
|
||||
};
|
||||
|
||||
enum GPIODirection {
|
||||
GPIO_WRITE_ONLY = 0,
|
||||
GPIO_READ_WRITE = 1
|
||||
};
|
||||
|
||||
union RTCControl {
|
||||
struct {
|
||||
unsigned : 3;
|
||||
unsigned minIRQ : 1;
|
||||
unsigned : 2;
|
||||
unsigned hour24 : 1;
|
||||
unsigned poweroff : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
|
||||
enum RTCCommand {
|
||||
RTC_RESET = 0,
|
||||
RTC_DATETIME = 2,
|
||||
RTC_FORCE_IRQ = 3,
|
||||
RTC_CONTROL = 4,
|
||||
RTC_TIME = 6
|
||||
};
|
||||
|
||||
union RTCCommandData {
|
||||
struct {
|
||||
unsigned magic : 4;
|
||||
enum RTCCommand command : 3;
|
||||
unsigned reading : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
|
||||
struct GBARTC {
|
||||
int bytesRemaining;
|
||||
int transferStep;
|
||||
int bitsRead;
|
||||
int bits;
|
||||
int commandActive;
|
||||
union RTCCommandData command;
|
||||
union RTCControl control;
|
||||
uint8_t time[7];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct GBARumble {
|
||||
void (*setRumble)(struct GBARumble*, int enable);
|
||||
};
|
||||
|
||||
struct GBACartridgeGPIO {
|
||||
struct GBA* p;
|
||||
int gpioDevices;
|
||||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned p0 : 1;
|
||||
unsigned p1 : 1;
|
||||
unsigned p2 : 1;
|
||||
unsigned p3 : 1;
|
||||
};
|
||||
uint16_t pinState;
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned dir0 : 1;
|
||||
unsigned dir1 : 1;
|
||||
unsigned dir2 : 1;
|
||||
unsigned dir3 : 1;
|
||||
};
|
||||
uint16_t direction;
|
||||
};
|
||||
|
||||
struct GBARTC rtc;
|
||||
|
||||
uint16_t gyroSample;
|
||||
bool gyroEdge;
|
||||
};
|
||||
|
||||
void GBAGPIOInit(struct GBACartridgeGPIO* gpio, uint16_t* gpioBase);
|
||||
void GBAGPIOWrite(struct GBACartridgeGPIO* gpio, uint32_t address, uint16_t value);
|
||||
|
||||
void GBAGPIOInitRTC(struct GBACartridgeGPIO* gpio);
|
||||
|
||||
void GBAGPIOInitGyro(struct GBACartridgeGPIO* gpio);
|
||||
|
||||
void GBAGPIOInitRumble(struct GBACartridgeGPIO* gpio);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAGPIOSerialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state);
|
||||
void GBAGPIODeserialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state);
|
||||
|
||||
#endif
|
|
@ -1,181 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-input.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define SECTION_NAME_MAX 128
|
||||
#define KEY_NAME_MAX 32
|
||||
#define KEY_VALUE_MAX 16
|
||||
|
||||
struct GBAInputMapImpl {
|
||||
int* map;
|
||||
uint32_t type;
|
||||
};
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
const char* value = ConfigurationGetValue(config, sectionName, keyKey);
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
char* end;
|
||||
long intValue = strtol(value, &end, 10);
|
||||
if (*end) {
|
||||
return;
|
||||
}
|
||||
GBAInputBindKey(map, type, intValue, key);
|
||||
}
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
int value = GBAInputQueryBinding(map, type, key);
|
||||
char keyValue[KEY_VALUE_MAX];
|
||||
snprintf(keyValue, KEY_VALUE_MAX, "%" PRIi32, value);
|
||||
|
||||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||
}
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap* map) {
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
}
|
||||
|
||||
void GBAInputMapDeinit(struct GBAInputMap* map) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
free(map->maps[m].map);
|
||||
}
|
||||
free(map->maps);
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
}
|
||||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key) {
|
||||
size_t m;
|
||||
const struct GBAInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!impl || !impl->map) {
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
for (m = 0; m < GBA_KEY_MAX; ++m) {
|
||||
if (impl->map[m] == key) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) {
|
||||
struct GBAInputMapImpl* impl = 0;
|
||||
if (map->numMaps == 0) {
|
||||
map->maps = malloc(sizeof(*map->maps));
|
||||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
} else {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!impl) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (!map->maps[m].type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
map->maps[m].type = 0;
|
||||
map->maps[m].map = 0;
|
||||
}
|
||||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
}
|
||||
}
|
||||
impl->map[input] = key;
|
||||
}
|
||||
|
||||
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||
if (input >= GBA_KEY_MAX) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t m;
|
||||
const struct GBAInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!impl || !impl->map) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return impl->map[input];
|
||||
}
|
||||
|
||||
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
|
||||
_loadKey(map, type, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, config, GBA_KEY_L, "L");
|
||||
_loadKey(map, type, config, GBA_KEY_R, "R");
|
||||
_loadKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_loadKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_loadKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_loadKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_loadKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_loadKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
}
|
||||
|
||||
void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) {
|
||||
_saveKey(map, type, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_INPUT_H
|
||||
#define GBA_INPUT_H
|
||||
|
||||
#include "gba.h"
|
||||
|
||||
struct Configuration;
|
||||
|
||||
struct GBAInputMap {
|
||||
struct GBAInputMapImpl* maps;
|
||||
size_t numMaps;
|
||||
};
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap*);
|
||||
void GBAInputMapDeinit(struct GBAInputMap*);
|
||||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
||||
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
||||
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
|
||||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
#endif
|
571
src/gba/gba-rr.c
571
src/gba/gba-rr.c
|
@ -1,571 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-rr.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define BINARY_EXT ".dat"
|
||||
#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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, false);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRLoadState(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
|
||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rr->streamDir = stream;
|
||||
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
||||
rr->currentInput = INVALID_INPUT;
|
||||
if (!_parseMetadata(rr, rr->metadataFile)) {
|
||||
rr->metadataFile->close(rr->metadataFile);
|
||||
rr->metadataFile = 0;
|
||||
rr->maxStreamId = 0;
|
||||
}
|
||||
rr->streamId = 1;
|
||||
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;
|
||||
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);
|
||||
}
|
113
src/gba/gba-rr.h
113
src/gba/gba-rr.h
|
@ -1,113 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_RR_H
|
||||
#define GBA_RR_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GBA;
|
||||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
enum GBARRInitFrom {
|
||||
INIT_EX_NIHILO = 0,
|
||||
INIT_FROM_SAVEGAME = 1,
|
||||
INIT_FROM_SAVESTATE = 2,
|
||||
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 {
|
||||
// Playback state
|
||||
bool isPlaying;
|
||||
bool autorecord;
|
||||
|
||||
// Recording state
|
||||
bool isRecording;
|
||||
bool inputThisFrame;
|
||||
|
||||
// Metadata
|
||||
uint32_t frames;
|
||||
uint32_t lagFrames;
|
||||
uint32_t streamId;
|
||||
|
||||
uint32_t maxStreamId;
|
||||
off_t maxStreamIdOffset;
|
||||
|
||||
enum GBARRInitFrom initFrom;
|
||||
off_t initFromOffset;
|
||||
|
||||
uint32_t rrCount;
|
||||
off_t rrCountOffset;
|
||||
|
||||
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 GBARRContextDestroy(struct GBA*);
|
||||
void GBARRSaveState(struct GBA*);
|
||||
void GBARRLoadState(struct GBA*);
|
||||
|
||||
bool GBARRInitStream(struct GBARRContext*, struct VDir*);
|
||||
bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom);
|
||||
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
|
|
@ -1,20 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_SENSORS_H
|
||||
#define GBA_SENSORS_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GBARotationSource {
|
||||
void (*sample)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readTiltX)(struct GBARotationSource*);
|
||||
int32_t (*readTiltY)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readGyroZ)(struct GBARotationSource*);
|
||||
};
|
||||
|
||||
#endif
|
390
src/gba/gba.c
390
src/gba/gba.c
|
@ -1,15 +1,19 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba.h"
|
||||
|
||||
#include "gba-bios.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-rr.h"
|
||||
#include "gba-sio.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba/bios.h"
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/supervisor/rr.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/sio.h"
|
||||
|
||||
#include "isa-inlines.h"
|
||||
|
||||
#include "util/crc32.h"
|
||||
#include "util/memory.h"
|
||||
|
@ -19,99 +23,8 @@
|
|||
const uint32_t GBA_ARM7TDMI_FREQUENCY = 0x1000000;
|
||||
const uint32_t GBA_COMPONENT_MAGIC = 0x1000000;
|
||||
|
||||
static const size_t GBA_ROM_MAGIC_OFFSET = 2;
|
||||
static const uint8_t GBA_ROM_MAGIC[] = { 0x00, 0xEA };
|
||||
|
||||
enum {
|
||||
SP_BASE_SYSTEM = 0x03FFFF00,
|
||||
SP_BASE_IRQ = 0x03FFFFA0,
|
||||
SP_BASE_SUPERVISOR = 0x03FFFFE0
|
||||
};
|
||||
|
||||
struct GBACartridgeOverride {
|
||||
const char id[4];
|
||||
enum SavedataType type;
|
||||
int gpio;
|
||||
uint32_t busyLoop;
|
||||
};
|
||||
|
||||
static const struct GBACartridgeOverride _overrides[] = {
|
||||
// Boktai: The Sun is in Your Hand
|
||||
{ "U3IE", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
|
||||
{ "U3IP", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
|
||||
|
||||
// Boktai 2: Solar Boy Django
|
||||
{ "U32E", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
|
||||
{ "U32P", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
|
||||
|
||||
// Drill Dozer
|
||||
{ "V49J", SAVEDATA_SRAM, GPIO_RUMBLE, -1 },
|
||||
{ "V49E", SAVEDATA_SRAM, GPIO_RUMBLE, -1 },
|
||||
|
||||
// Final Fantasy Tactics Advance
|
||||
{ "AFXE", SAVEDATA_FLASH512, GPIO_NONE, 0x8000418 },
|
||||
|
||||
// Mega Man Battle Network
|
||||
{ "AREE", SAVEDATA_SRAM, GPIO_NONE, 0x800032E },
|
||||
|
||||
// Pokemon Ruby
|
||||
{ "AXVJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVS", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVD", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXVF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
|
||||
// Pokemon Sapphire
|
||||
{ "AXPJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPS", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPD", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "AXPF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
|
||||
// Pokemon Emerald
|
||||
{ "BPEJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPEE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPEP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPEI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPES", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPED", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
{ "BPEF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
|
||||
|
||||
// Pokemon Mystery Dungeon
|
||||
{ "B24J", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "B24E", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "B24P", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "B24U", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
|
||||
// Pokemon FireRed
|
||||
{ "BPRJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "BPRE", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "BPRP", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
|
||||
// Pokemon LeafGreen
|
||||
{ "BPGJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "BPGE", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "BPGP", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
|
||||
// RockMan EXE 4.5 - Real Operation
|
||||
{ "BR4J", SAVEDATA_FLASH512, GPIO_RTC, -1 },
|
||||
|
||||
// Super Mario Advance 4
|
||||
{ "AX4J", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "AX4E", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
{ "AX4P", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
|
||||
|
||||
// Wario Ware Twisted
|
||||
{ "RZWJ", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
|
||||
{ "RZWE", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
|
||||
{ "RZWP", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
|
||||
|
||||
{ { 0, 0, 0, 0 }, 0, 0, -1 }
|
||||
};
|
||||
static const size_t GBA_ROM_MAGIC_OFFSET = 3;
|
||||
static const uint8_t GBA_ROM_MAGIC[] = { 0xEA };
|
||||
|
||||
static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component);
|
||||
static void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh);
|
||||
|
@ -119,8 +32,10 @@ static void GBAProcessEvents(struct ARMCore* cpu);
|
|||
static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles);
|
||||
static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode);
|
||||
static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode);
|
||||
static void GBABreakpoint(struct ARMCore* cpu, int immediate);
|
||||
|
||||
static void _checkOverrides(struct GBA* gba, uint32_t code);
|
||||
static bool _setSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);
|
||||
static bool _clearSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
|
||||
|
||||
void GBACreate(struct GBA* gba) {
|
||||
gba->d.id = GBA_COMPONENT_MAGIC;
|
||||
|
@ -132,6 +47,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
struct GBA* gba = (struct GBA*) component;
|
||||
gba->cpu = cpu;
|
||||
gba->debugger = 0;
|
||||
gba->sync = 0;
|
||||
|
||||
GBAInterruptHandlerInit(&cpu->irqh);
|
||||
GBAMemoryInit(gba);
|
||||
|
@ -154,17 +70,26 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->springIRQ = 0;
|
||||
gba->keySource = 0;
|
||||
gba->rotationSource = 0;
|
||||
gba->luminanceSource = 0;
|
||||
gba->rtcSource = 0;
|
||||
gba->rumble = 0;
|
||||
gba->rr = 0;
|
||||
|
||||
gba->romVf = 0;
|
||||
gba->biosVf = 0;
|
||||
|
||||
gba->logHandler = 0;
|
||||
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->busyLoop = -1;
|
||||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
gba->idleLoop = IDLE_LOOP_NONE;
|
||||
gba->lastJump = 0;
|
||||
gba->idleDetectionStep = 0;
|
||||
gba->idleDetectionFailures = 0;
|
||||
gba->performingDMA = false;
|
||||
}
|
||||
|
||||
void GBADestroy(struct GBA* gba) {
|
||||
|
@ -183,7 +108,8 @@ void GBADestroy(struct GBA* gba) {
|
|||
GBAMemoryDeinit(gba);
|
||||
GBAVideoDeinit(&gba->video);
|
||||
GBAAudioDeinit(&gba->audio);
|
||||
GBARRContextDestroy(gba);
|
||||
GBASIODeinit(&gba->sio);
|
||||
gba->rr = 0;
|
||||
}
|
||||
|
||||
void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
||||
|
@ -194,6 +120,8 @@ void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
|||
irqh->hitIllegal = GBAIllegal;
|
||||
irqh->readCPSR = GBATestIRQ;
|
||||
irqh->hitStub = GBAHitStub;
|
||||
irqh->bkpt16 = GBABreakpoint;
|
||||
irqh->bkpt32 = GBABreakpoint;
|
||||
}
|
||||
|
||||
void GBAReset(struct ARMCore* cpu) {
|
||||
|
@ -205,7 +133,7 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
||||
|
||||
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);
|
||||
}
|
||||
GBAMemoryReset(gba);
|
||||
|
@ -220,16 +148,24 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
memset(gba->timers, 0, sizeof(gba->timers));
|
||||
}
|
||||
|
||||
void GBASkipBIOS(struct ARMCore* cpu) {
|
||||
if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
|
||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||
int currentCycles = 0;
|
||||
ARM_WRITE_PC;
|
||||
}
|
||||
}
|
||||
|
||||
static void GBAProcessEvents(struct ARMCore* cpu) {
|
||||
do {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
int32_t cycles = cpu->cycles;
|
||||
int32_t cycles = cpu->nextEvent;
|
||||
int32_t nextEvent = INT_MAX;
|
||||
int32_t testEvent;
|
||||
|
||||
gba->bus = cpu->prefetch;
|
||||
gba->bus = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
gba->bus |= cpu->prefetch << 16;
|
||||
gba->bus |= cpu->prefetch[1] << 16;
|
||||
}
|
||||
|
||||
if (gba->springIRQ) {
|
||||
|
@ -357,7 +293,6 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
if (timer->enable) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
nextEvent = timer->nextEvent;
|
||||
if (timer->nextEvent <= 0) {
|
||||
timer->lastEvent = timer->nextEvent;
|
||||
timer->nextEvent += timer->overflowInterval;
|
||||
|
@ -389,7 +324,6 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
if (timer->enable) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
nextEvent = timer->nextEvent;
|
||||
if (timer->nextEvent <= 0) {
|
||||
timer->lastEvent = timer->nextEvent;
|
||||
timer->nextEvent += timer->overflowInterval;
|
||||
|
@ -413,34 +347,48 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
|
||||
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger) {
|
||||
debugger->setSoftwareBreakpoint = _setSoftwareBreakpoint;
|
||||
debugger->clearSoftwareBreakpoint = _clearSoftwareBreakpoint;
|
||||
gba->debugger = debugger;
|
||||
gba->cpu->components[GBA_COMPONENT_DEBUGGER] = &debugger->d;
|
||||
ARMHotplugAttach(gba->cpu, GBA_COMPONENT_DEBUGGER);
|
||||
}
|
||||
|
||||
void GBADetachDebugger(struct GBA* gba) {
|
||||
gba->debugger = 0;
|
||||
ARMHotplugDetach(gba->cpu, GBA_COMPONENT_DEBUGGER);
|
||||
gba->cpu->components[GBA_COMPONENT_DEBUGGER] = 0;
|
||||
}
|
||||
|
||||
void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) {
|
||||
gba->romVf = vf;
|
||||
gba->pristineRomSize = vf->seek(vf, 0, SEEK_END);
|
||||
gba->pristineRomSize = vf->size(vf);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (gba->pristineRomSize > SIZE_CART0) {
|
||||
gba->pristineRomSize = SIZE_CART0;
|
||||
}
|
||||
gba->pristineRom = vf->map(vf, gba->pristineRomSize, MAP_READ);
|
||||
if (!gba->pristineRom) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM");
|
||||
return;
|
||||
}
|
||||
gba->memory.rom = gba->pristineRom;
|
||||
gba->activeFile = fname;
|
||||
gba->memory.romSize = gba->pristineRomSize;
|
||||
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
|
||||
GBASavedataInit(&gba->memory.savedata, sav);
|
||||
GBAGPIOInit(&gba->memory.gpio, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);
|
||||
_checkOverrides(gba, ((struct GBACartridge*) gba->memory.rom)->id);
|
||||
GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);
|
||||
// TODO: error check
|
||||
}
|
||||
|
||||
void GBALoadBIOS(struct GBA* gba, struct VFile* vf) {
|
||||
gba->biosVf = vf;
|
||||
gba->memory.bios = vf->map(vf, SIZE_BIOS, MAP_READ);
|
||||
uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ);
|
||||
if (!bios) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Couldn't map BIOS");
|
||||
return;
|
||||
}
|
||||
gba->memory.bios = bios;
|
||||
gba->memory.fullBios = 1;
|
||||
uint32_t checksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||
GBALog(gba, GBA_LOG_DEBUG, "BIOS Checksum: 0x%X", checksum);
|
||||
|
@ -464,8 +412,7 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
|
|||
return;
|
||||
}
|
||||
gba->memory.rom = anonymousMemoryMap(patchedSize);
|
||||
memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize > patchedSize ? patchedSize : gba->memory.romSize);
|
||||
if (!patch->applyPatch(patch, gba->memory.rom, patchedSize)) {
|
||||
if (!patch->applyPatch(patch, gba->pristineRom, gba->pristineRomSize, gba->memory.rom, patchedSize)) {
|
||||
mappedMemoryFree(gba->memory.rom, patchedSize);
|
||||
gba->memory.rom = gba->pristineRom;
|
||||
return;
|
||||
|
@ -578,25 +525,41 @@ void GBAHalt(struct GBA* gba) {
|
|||
|
||||
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||
if (threadContext) {
|
||||
if (!gba) {
|
||||
gba = threadContext->gba;
|
||||
}
|
||||
enum GBALogLevel logLevel = -1;
|
||||
|
||||
if (gba) {
|
||||
logLevel = gba->logLevel;
|
||||
}
|
||||
|
||||
if (gba && !(level & gba->logLevel) && level != GBA_LOG_FATAL) {
|
||||
if (threadContext) {
|
||||
logLevel = threadContext->logLevel;
|
||||
gba = threadContext->gba;
|
||||
}
|
||||
|
||||
if (!(level & logLevel) && level != GBA_LOG_FATAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadContext && threadContext->logHandler) {
|
||||
threadContext->logHandler(threadContext, level, format, args);
|
||||
if (level == GBA_LOG_FATAL && gba) {
|
||||
gba->cpu->nextEvent = 0;
|
||||
}
|
||||
|
||||
if (threadContext) {
|
||||
if (level == GBA_LOG_FATAL) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
threadContext->state = THREAD_CRASHED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
}
|
||||
if (gba && gba->logHandler) {
|
||||
gba->logHandler(threadContext, level, format, args);
|
||||
return;
|
||||
}
|
||||
|
||||
vprintf(format, args);
|
||||
printf("\n");
|
||||
|
||||
if (level == GBA_LOG_FATAL) {
|
||||
if (level == GBA_LOG_FATAL && !threadContext) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
@ -647,6 +610,23 @@ bool GBAIsROM(struct VFile* vf) {
|
|||
return memcmp(signature, GBA_ROM_MAGIC, sizeof(signature)) == 0;
|
||||
}
|
||||
|
||||
bool GBAIsBIOS(struct VFile* vf) {
|
||||
if (vf->seek(vf, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t interruptTable[7];
|
||||
if (vf->read(vf, &interruptTable, sizeof(interruptTable)) != sizeof(interruptTable)) {
|
||||
return false;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < 7; ++i) {
|
||||
if ((interruptTable[i] & 0xFFFF0000) != 0xEA000000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAGetGameCode(struct GBA* gba, char* out) {
|
||||
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->id, 4);
|
||||
}
|
||||
|
@ -660,7 +640,11 @@ void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {
|
|||
enum GBALogLevel level = GBA_LOG_FATAL;
|
||||
if (gba->debugger) {
|
||||
level = GBA_LOG_STUB;
|
||||
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP);
|
||||
struct DebuggerEntryInfo info = {
|
||||
.address = _ARMPCAddress(cpu),
|
||||
.opcode = opcode
|
||||
};
|
||||
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info);
|
||||
}
|
||||
GBALog(gba, level, "Stub opcode: %08x", opcode);
|
||||
}
|
||||
|
@ -669,52 +653,140 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) {
|
|||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode);
|
||||
if (gba->debugger) {
|
||||
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP);
|
||||
struct DebuggerEntryInfo info = {
|
||||
.address = _ARMPCAddress(cpu),
|
||||
.opcode = opcode
|
||||
};
|
||||
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info);
|
||||
}
|
||||
}
|
||||
|
||||
void _checkOverrides(struct GBA* gba, uint32_t id) {
|
||||
int i;
|
||||
gba->busyLoop = -1;
|
||||
if ((id & 0xFF) == 'F') {
|
||||
GBALog(gba, GBA_LOG_DEBUG, "Found Classic NES Series game, using EEPROM saves");
|
||||
GBASavedataInitEEPROM(&gba->memory.savedata);
|
||||
void GBABreakpoint(struct ARMCore* cpu, int immediate) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
if (immediate >= GBA_COMPONENT_MAX) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; _overrides[i].id[0]; ++i) {
|
||||
const uint32_t* overrideId = (const uint32_t*) _overrides[i].id;
|
||||
if (*overrideId == id) {
|
||||
GBALog(gba, GBA_LOG_DEBUG, "Found override for game %s!", _overrides[i].id);
|
||||
switch (_overrides[i].type) {
|
||||
case SAVEDATA_FLASH512:
|
||||
case SAVEDATA_FLASH1M:
|
||||
gba->memory.savedata.type = _overrides[i].type;
|
||||
GBASavedataInitFlash(&gba->memory.savedata);
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
GBASavedataInitEEPROM(&gba->memory.savedata);
|
||||
break;
|
||||
case SAVEDATA_SRAM:
|
||||
GBASavedataInitSRAM(&gba->memory.savedata);
|
||||
break;
|
||||
case SAVEDATA_NONE:
|
||||
break;
|
||||
switch (immediate) {
|
||||
case GBA_COMPONENT_DEBUGGER:
|
||||
if (gba->debugger) {
|
||||
struct DebuggerEntryInfo info = {
|
||||
.address = _ARMPCAddress(cpu)
|
||||
};
|
||||
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_BREAKPOINT, &info);
|
||||
}
|
||||
break;
|
||||
case GBA_COMPONENT_CHEAT_DEVICE:
|
||||
if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
|
||||
struct GBACheatHook* hook = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
if (cheats->hook && cheats->hook->address == _ARMPCAddress(cpu)) {
|
||||
GBACheatRefresh(device, cheats);
|
||||
hook = cheats->hook;
|
||||
}
|
||||
}
|
||||
|
||||
if (_overrides[i].gpio & GPIO_RTC) {
|
||||
GBAGPIOInitRTC(&gba->memory.gpio);
|
||||
if (hook) {
|
||||
ARMRunFake(cpu, hook->patchedOpcode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_overrides[i].gpio & GPIO_GYRO) {
|
||||
GBAGPIOInitGyro(&gba->memory.gpio);
|
||||
}
|
||||
void GBAFrameStarted(struct GBA* gba) {
|
||||
UNUSED(gba);
|
||||
|
||||
if (_overrides[i].gpio & GPIO_RUMBLE) {
|
||||
GBAGPIOInitRumble(&gba->memory.gpio);
|
||||
}
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
gba->busyLoop = _overrides[i].busyLoop;
|
||||
return;
|
||||
if (thread->rewindBuffer) {
|
||||
--thread->rewindBufferNext;
|
||||
if (thread->rewindBufferNext <= 0) {
|
||||
thread->rewindBufferNext = thread->rewindBufferInterval;
|
||||
GBARecordFrame(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAFrameEnded(struct GBA* gba) {
|
||||
if (gba->rr) {
|
||||
gba->rr->nextFrame(gba->rr);
|
||||
}
|
||||
|
||||
if (gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
|
||||
if (!cheats->hook) {
|
||||
GBACheatRefresh(device, cheats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->stream) {
|
||||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||
}
|
||||
|
||||
if (thread->frameCallback) {
|
||||
thread->frameCallback(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) {
|
||||
size_t immediate;
|
||||
for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) {
|
||||
if (gba->cpu->components[immediate] == component) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (immediate == gba->cpu->numComponents) {
|
||||
return;
|
||||
}
|
||||
if (mode == MODE_ARM) {
|
||||
int32_t value;
|
||||
int32_t old;
|
||||
value = 0xE1200070;
|
||||
value |= immediate & 0xF;
|
||||
value |= (immediate & 0xFFF0) << 4;
|
||||
GBAPatch32(gba->cpu, address, value, &old);
|
||||
*opcode = old;
|
||||
} else {
|
||||
int16_t value;
|
||||
int16_t old;
|
||||
value = 0xBE00;
|
||||
value |= immediate & 0xFF;
|
||||
GBAPatch16(gba->cpu, address, value, &old);
|
||||
*opcode = (uint16_t) old;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode) {
|
||||
if (mode == MODE_ARM) {
|
||||
GBAPatch32(gba->cpu, address, opcode, 0);
|
||||
} else {
|
||||
GBAPatch16(gba->cpu, address, opcode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool _setSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) {
|
||||
GBASetBreakpoint((struct GBA*) debugger->cpu->master, &debugger->d, address, mode, opcode);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _clearSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t opcode) {
|
||||
GBAClearBreakpoint((struct GBA*) debugger->cpu->master, address, mode, opcode);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -11,10 +11,10 @@
|
|||
#include "arm.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
#include "gba-memory.h"
|
||||
#include "gba-video.h"
|
||||
#include "gba-audio.h"
|
||||
#include "gba-sio.h"
|
||||
#include "gba/memory.h"
|
||||
#include "gba/video.h"
|
||||
#include "gba/audio.h"
|
||||
#include "gba/sio.h"
|
||||
|
||||
extern const uint32_t GBA_ARM7TDMI_FREQUENCY;
|
||||
|
||||
|
@ -35,11 +35,6 @@ enum GBAIRQ {
|
|||
IRQ_GAMEPAK = 0xD
|
||||
};
|
||||
|
||||
enum GBAError {
|
||||
GBA_NO_ERROR = 0,
|
||||
GBA_OUT_OF_MEMORY = -1
|
||||
};
|
||||
|
||||
enum GBALogLevel {
|
||||
GBA_LOG_FATAL = 0x01,
|
||||
GBA_LOG_ERROR = 0x02,
|
||||
|
@ -75,11 +70,37 @@ enum GBAKey {
|
|||
GBA_KEY_NONE = -1
|
||||
};
|
||||
|
||||
enum GBAComponent {
|
||||
GBA_COMPONENT_DEBUGGER,
|
||||
GBA_COMPONENT_CHEAT_DEVICE,
|
||||
GBA_COMPONENT_MAX
|
||||
};
|
||||
|
||||
enum GBAIdleLoopOptimization {
|
||||
IDLE_LOOP_IGNORE = -1,
|
||||
IDLE_LOOP_REMOVE = 0,
|
||||
IDLE_LOOP_DETECT
|
||||
};
|
||||
|
||||
enum {
|
||||
SP_BASE_SYSTEM = 0x03007F00,
|
||||
SP_BASE_IRQ = 0x03007FA0,
|
||||
SP_BASE_SUPERVISOR = 0x03007FE0
|
||||
};
|
||||
|
||||
struct GBA;
|
||||
struct GBARotationSource;
|
||||
struct GBAThread;
|
||||
struct Patch;
|
||||
struct VFile;
|
||||
|
||||
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
};
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
|
@ -106,6 +127,7 @@ struct GBA {
|
|||
struct ARMDebugger* debugger;
|
||||
|
||||
uint32_t bus;
|
||||
bool performingDMA;
|
||||
|
||||
int timersEnabled;
|
||||
struct GBATimer timers[4];
|
||||
|
@ -113,9 +135,11 @@ struct GBA {
|
|||
int springIRQ;
|
||||
uint32_t biosChecksum;
|
||||
int* keySource;
|
||||
uint32_t busyLoop;
|
||||
struct GBARotationSource* rotationSource;
|
||||
struct GBALuminanceSource* luminanceSource;
|
||||
struct GBARTCSource* rtcSource;
|
||||
struct GBARumble* rumble;
|
||||
|
||||
struct GBARRContext* rr;
|
||||
void* pristineRom;
|
||||
size_t pristineRomSize;
|
||||
|
@ -125,7 +149,17 @@ struct GBA {
|
|||
|
||||
const char* activeFile;
|
||||
|
||||
int logLevel;
|
||||
GBALogHandler logHandler;
|
||||
enum GBALogLevel logLevel;
|
||||
struct GBAAVStream* stream;
|
||||
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
uint32_t idleLoop;
|
||||
uint32_t lastJump;
|
||||
int idleDetectionStep;
|
||||
int idleDetectionFailures;
|
||||
int32_t cachedRegisters[16];
|
||||
bool taintedRegisters[16];
|
||||
};
|
||||
|
||||
struct GBACartridge {
|
||||
|
@ -147,6 +181,7 @@ void GBACreate(struct GBA* gba);
|
|||
void GBADestroy(struct GBA* gba);
|
||||
|
||||
void GBAReset(struct ARMCore* cpu);
|
||||
void GBASkipBIOS(struct ARMCore* cpu);
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer);
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t value);
|
||||
|
@ -161,14 +196,21 @@ void GBAHalt(struct GBA* gba);
|
|||
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger);
|
||||
void GBADetachDebugger(struct GBA* gba);
|
||||
|
||||
void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);
|
||||
void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
|
||||
|
||||
void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname);
|
||||
void GBALoadBIOS(struct GBA* gba, struct VFile* vf);
|
||||
void GBAApplyPatch(struct GBA* gba, struct Patch* patch);
|
||||
|
||||
bool GBAIsROM(struct VFile* vf);
|
||||
bool GBAIsBIOS(struct VFile* vf);
|
||||
void GBAGetGameCode(struct GBA* gba, char* out);
|
||||
void GBAGetGameTitle(struct GBA* gba, char* out);
|
||||
|
||||
void GBAFrameStarted(struct GBA* gba);
|
||||
void GBAFrameEnded(struct GBA* gba);
|
||||
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...);
|
||||
|
||||
|
|
|
@ -0,0 +1,457 @@
|
|||
/* 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 "hardware.h"
|
||||
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static void _readPins(struct GBACartridgeHardware* hw);
|
||||
static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
|
||||
|
||||
static void _rtcReadPins(struct GBACartridgeHardware* hw);
|
||||
static unsigned _rtcOutput(struct GBACartridgeHardware* hw);
|
||||
static void _rtcProcessByte(struct GBACartridgeHardware* hw);
|
||||
static void _rtcUpdateClock(struct GBACartridgeHardware* hw);
|
||||
static unsigned _rtcBCD(unsigned value);
|
||||
|
||||
static void _gyroReadPins(struct GBACartridgeHardware* hw);
|
||||
|
||||
static void _rumbleReadPins(struct GBACartridgeHardware* hw);
|
||||
|
||||
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
||||
|
||||
static const int RTC_BYTES[8] = {
|
||||
0, // Force reset
|
||||
0, // Empty
|
||||
7, // Date/Time
|
||||
0, // Force IRQ
|
||||
1, // Control register
|
||||
0, // Empty
|
||||
3, // Time
|
||||
0 // Empty
|
||||
};
|
||||
|
||||
void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
||||
hw->gpioBase = base;
|
||||
GBAHardwareClear(hw);
|
||||
}
|
||||
|
||||
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||
hw->devices = HW_NONE;
|
||||
hw->direction = GPIO_WRITE_ONLY;
|
||||
hw->pinState = 0;
|
||||
hw->direction = 0;
|
||||
}
|
||||
|
||||
void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) {
|
||||
switch (address) {
|
||||
case GPIO_REG_DATA:
|
||||
hw->pinState &= ~hw->direction;
|
||||
hw->pinState |= value;
|
||||
_readPins(hw);
|
||||
break;
|
||||
case GPIO_REG_DIRECTION:
|
||||
hw->direction = value;
|
||||
break;
|
||||
case GPIO_REG_CONTROL:
|
||||
hw->readWrite = value;
|
||||
break;
|
||||
default:
|
||||
GBALog(hw->p, GBA_LOG_WARN, "Invalid GPIO address");
|
||||
}
|
||||
if (hw->readWrite) {
|
||||
uint16_t old = hw->gpioBase[0];
|
||||
old &= ~hw->direction;
|
||||
hw->gpioBase[0] = old | hw->pinState;
|
||||
} else {
|
||||
hw->gpioBase[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
|
||||
hw->devices |= HW_RTC;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
|
||||
hw->rtc.transferStep = 0;
|
||||
|
||||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.packed = 0;
|
||||
hw->rtc.control.packed = 0x40;
|
||||
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
||||
}
|
||||
|
||||
void _readPins(struct GBACartridgeHardware* hw) {
|
||||
if (hw->devices & HW_RTC) {
|
||||
_rtcReadPins(hw);
|
||||
}
|
||||
|
||||
if (hw->devices & HW_GYRO) {
|
||||
_gyroReadPins(hw);
|
||||
}
|
||||
|
||||
if (hw->devices & HW_RUMBLE) {
|
||||
_rumbleReadPins(hw);
|
||||
}
|
||||
|
||||
if (hw->devices & HW_LIGHT_SENSOR) {
|
||||
_lightReadPins(hw);
|
||||
}
|
||||
}
|
||||
|
||||
void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) {
|
||||
if (hw->readWrite) {
|
||||
uint16_t old = hw->gpioBase[0];
|
||||
old &= hw->direction;
|
||||
hw->pinState = old | (pins & ~hw->direction & 0xF);
|
||||
hw->gpioBase[0] = hw->pinState;
|
||||
}
|
||||
}
|
||||
|
||||
// == RTC
|
||||
|
||||
void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||
// Transfer sequence:
|
||||
// P: 0 | 1 | 2 | 3
|
||||
// == Initiate
|
||||
// > HI | - | LO | -
|
||||
// > HI | - | HI | -
|
||||
// == Transfer bit (x8)
|
||||
// > LO | x | HI | -
|
||||
// > HI | - | HI | -
|
||||
// < ?? | x | ?? | -
|
||||
// == Terminate
|
||||
// > - | - | LO | -
|
||||
switch (hw->rtc.transferStep) {
|
||||
case 0:
|
||||
if ((hw->pinState & 5) == 1) {
|
||||
hw->rtc.transferStep = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if ((hw->pinState & 5) == 5) {
|
||||
hw->rtc.transferStep = 2;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!hw->p0) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead;
|
||||
} else {
|
||||
if (hw->p2) {
|
||||
// GPIO direction should always != reading
|
||||
if (hw->dir1) {
|
||||
if (hw->rtc.command.reading) {
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
||||
}
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
_rtcProcessByte(hw);
|
||||
}
|
||||
} else {
|
||||
_outputPins(hw, 5 | (_rtcOutput(hw) << 1));
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
}
|
||||
hw->rtc.bitsRead = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.transferStep = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (!hw->rtc.commandActive) {
|
||||
union RTCCommandData command;
|
||||
command.packed = hw->rtc.bits;
|
||||
if (command.magic == 0x06) {
|
||||
hw->rtc.command = command;
|
||||
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command];
|
||||
hw->rtc.commandActive = hw->rtc.bytesRemaining > 0;
|
||||
switch (command.command) {
|
||||
case RTC_RESET:
|
||||
hw->rtc.control.packed = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
_rtcUpdateClock(hw);
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_CONTROL:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||
}
|
||||
} else {
|
||||
switch (hw->rtc.command.command) {
|
||||
case RTC_CONTROL:
|
||||
hw->rtc.control.packed = hw->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command);
|
||||
break;
|
||||
case RTC_RESET:
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.bitsRead = 0;
|
||||
if (!hw->rtc.bytesRemaining) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
||||
uint8_t outputByte = 0;
|
||||
switch (hw->rtc.command.command) {
|
||||
case RTC_CONTROL:
|
||||
outputByte = hw->rtc.control.packed;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
outputByte = hw->rtc.time[7 - hw->rtc.bytesRemaining];
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_RESET:
|
||||
break;
|
||||
}
|
||||
unsigned output = (outputByte >> hw->rtc.bitsRead) & 1;
|
||||
return output;
|
||||
}
|
||||
|
||||
void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
|
||||
time_t t;
|
||||
struct GBARTCSource* rtc = hw->p->rtcSource;
|
||||
if (rtc) {
|
||||
rtc->sample(rtc);
|
||||
t = rtc->unixTime(rtc);
|
||||
} else {
|
||||
t = time(0);
|
||||
}
|
||||
struct tm date;
|
||||
#ifdef _WIN32
|
||||
date = *localtime(&t);
|
||||
#else
|
||||
localtime_r(&t, &date);
|
||||
#endif
|
||||
hw->rtc.time[0] = _rtcBCD(date.tm_year - 100);
|
||||
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
||||
hw->rtc.time[2] = _rtcBCD(date.tm_mday);
|
||||
hw->rtc.time[3] = _rtcBCD(date.tm_wday);
|
||||
if (hw->rtc.control.hour24) {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour);
|
||||
} else {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
||||
}
|
||||
hw->rtc.time[5] = _rtcBCD(date.tm_min);
|
||||
hw->rtc.time[6] = _rtcBCD(date.tm_sec);
|
||||
}
|
||||
|
||||
unsigned _rtcBCD(unsigned value) {
|
||||
int counter = value % 10;
|
||||
value /= 10;
|
||||
counter += (value % 10) << 4;
|
||||
return counter;
|
||||
}
|
||||
|
||||
// == Gyro
|
||||
|
||||
void GBAHardwareInitGyro(struct GBACartridgeHardware* hw) {
|
||||
hw->devices |= HW_GYRO;
|
||||
hw->gyroSample = 0;
|
||||
hw->gyroEdge = 0;
|
||||
}
|
||||
|
||||
void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||
struct GBARotationSource* gyro = hw->p->rotationSource;
|
||||
if (!gyro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hw->p0) {
|
||||
if (gyro->sample) {
|
||||
gyro->sample(gyro);
|
||||
}
|
||||
int32_t sample = gyro->readGyroZ(gyro);
|
||||
|
||||
// Normalize to ~12 bits, focused on 0x6C0
|
||||
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
||||
}
|
||||
|
||||
if (hw->gyroEdge && !hw->p1) {
|
||||
// Write bit on falling edge
|
||||
unsigned bit = hw->gyroSample >> 15;
|
||||
hw->gyroSample <<= 1;
|
||||
_outputPins(hw, bit << 2);
|
||||
}
|
||||
|
||||
hw->gyroEdge = hw->p1;
|
||||
}
|
||||
|
||||
// == Rumble
|
||||
|
||||
void GBAHardwareInitRumble(struct GBACartridgeHardware* hw) {
|
||||
hw->devices |= HW_RUMBLE;
|
||||
}
|
||||
|
||||
void _rumbleReadPins(struct GBACartridgeHardware* hw) {
|
||||
struct GBARumble* rumble = hw->p->rumble;
|
||||
if (!rumble) {
|
||||
return;
|
||||
}
|
||||
|
||||
rumble->setRumble(rumble, hw->p3);
|
||||
}
|
||||
|
||||
// == Light sensor
|
||||
|
||||
void GBAHardwareInitLight(struct GBACartridgeHardware* hw) {
|
||||
hw->devices |= HW_LIGHT_SENSOR;
|
||||
hw->lightCounter = 0;
|
||||
hw->lightEdge = false;
|
||||
hw->lightSample = 0xFF;
|
||||
}
|
||||
|
||||
void _lightReadPins(struct GBACartridgeHardware* hw) {
|
||||
if (hw->p2) {
|
||||
// Boktai chip select
|
||||
return;
|
||||
}
|
||||
if (hw->p1) {
|
||||
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
||||
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset");
|
||||
hw->lightCounter = 0;
|
||||
if (lux) {
|
||||
lux->sample(lux);
|
||||
hw->lightSample = lux->readLuminance(lux);
|
||||
} else {
|
||||
hw->lightSample = 0xFF;
|
||||
}
|
||||
}
|
||||
if (hw->p0 && hw->lightEdge) {
|
||||
++hw->lightCounter;
|
||||
}
|
||||
hw->lightEdge = !hw->p0;
|
||||
|
||||
bool sendBit = hw->lightCounter >= hw->lightSample;
|
||||
_outputPins(hw, sendBit << 3);
|
||||
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Output %u with pins %u", hw->lightCounter, hw->pinState);
|
||||
}
|
||||
|
||||
// == Tilt
|
||||
|
||||
void GBAHardwareInitTilt(struct GBACartridgeHardware* hw) {
|
||||
hw->devices |= HW_TILT;
|
||||
hw->tiltX = 0xFFF;
|
||||
hw->tiltY = 0xFFF;
|
||||
hw->tiltState = 0;
|
||||
}
|
||||
|
||||
void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uint8_t value) {
|
||||
switch (address) {
|
||||
case 0x8000:
|
||||
if (value == 0x55) {
|
||||
hw->tiltState = 1;
|
||||
} else {
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value);
|
||||
}
|
||||
break;
|
||||
case 0x8100:
|
||||
if (value == 0xAA && hw->tiltState == 1) {
|
||||
hw->tiltState = 0;
|
||||
struct GBARotationSource* rotationSource = hw->p->rotationSource;
|
||||
if (!rotationSource || !rotationSource->readTiltX || !rotationSource->readTiltY) {
|
||||
return;
|
||||
}
|
||||
if (rotationSource->sample) {
|
||||
rotationSource->sample(rotationSource);
|
||||
}
|
||||
int32_t x = rotationSource->readTiltX(rotationSource);
|
||||
int32_t y = rotationSource->readTiltY(rotationSource);
|
||||
// Normalize to ~12 bits, focused on 0x3A0
|
||||
hw->tiltX = (x >> 21) + 0x3A0; // Crop off an extra bit so that we can't go negative
|
||||
hw->tiltY = (y >> 21) + 0x3A0;
|
||||
} else {
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Invalid tilt sensor write to %04x: %02x", address, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
|
||||
switch (address) {
|
||||
case 0x8200:
|
||||
return hw->tiltX & 0xFF;
|
||||
case 0x8300:
|
||||
return ((hw->tiltX >> 8) & 0xF) | 0x80;
|
||||
case 0x8400:
|
||||
return hw->tiltY & 0xFF;
|
||||
case 0x8500:
|
||||
return (hw->tiltY >> 8) & 0xF;
|
||||
default:
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Invalid tilt sensor read from %04x", address);
|
||||
break;
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// == Serialization
|
||||
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
state->hw.readWrite = hw->readWrite;
|
||||
state->hw.pinState = hw->pinState;
|
||||
state->hw.pinDirection = hw->direction;
|
||||
state->hw.devices = hw->devices;
|
||||
state->hw.rtc = hw->rtc;
|
||||
state->hw.gyroSample = hw->gyroSample;
|
||||
state->hw.gyroEdge = hw->gyroEdge;
|
||||
state->hw.tiltSampleX = hw->tiltX;
|
||||
state->hw.tiltSampleY = hw->tiltY;
|
||||
state->hw.tiltState = hw->tiltState;
|
||||
state->hw.lightCounter = hw->lightCounter;
|
||||
state->hw.lightSample = hw->lightSample;
|
||||
state->hw.lightEdge = hw->lightEdge;
|
||||
}
|
||||
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||
hw->readWrite = state->hw.readWrite;
|
||||
hw->pinState = state->hw.pinState;
|
||||
hw->direction = state->hw.pinDirection;
|
||||
// TODO: Deterministic RTC
|
||||
hw->rtc = state->hw.rtc;
|
||||
hw->gyroSample = state->hw.gyroSample;
|
||||
hw->gyroEdge = state->hw.gyroEdge;
|
||||
hw->tiltX = state->hw.tiltSampleX;
|
||||
hw->tiltY = state->hw.tiltSampleY;
|
||||
hw->tiltState = state->hw.tiltState;
|
||||
hw->lightCounter = state->hw.lightCounter;
|
||||
hw->lightSample = state->hw.lightSample;
|
||||
hw->lightEdge = state->hw.lightEdge;
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_HARDWARE_H
|
||||
#define GBA_HARDWARE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
struct GBARotationSource {
|
||||
void (*sample)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readTiltX)(struct GBARotationSource*);
|
||||
int32_t (*readTiltY)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readGyroZ)(struct GBARotationSource*);
|
||||
};
|
||||
|
||||
struct GBALuminanceSource {
|
||||
void (*sample)(struct GBALuminanceSource*);
|
||||
|
||||
uint8_t (*readLuminance)(struct GBALuminanceSource*);
|
||||
};
|
||||
|
||||
struct GBARTCSource {
|
||||
void (*sample)(struct GBARTCSource*);
|
||||
|
||||
time_t (*unixTime)(struct GBARTCSource*);
|
||||
};
|
||||
|
||||
enum GBAHardwareDevice {
|
||||
HW_NO_OVERRIDE = 0x8000,
|
||||
HW_NONE = 0,
|
||||
HW_RTC = 1,
|
||||
HW_RUMBLE = 2,
|
||||
HW_LIGHT_SENSOR = 4,
|
||||
HW_GYRO = 8,
|
||||
HW_TILT = 16
|
||||
};
|
||||
|
||||
enum GPIORegister {
|
||||
GPIO_REG_DATA = 0xC4,
|
||||
GPIO_REG_DIRECTION = 0xC6,
|
||||
GPIO_REG_CONTROL = 0xC8
|
||||
};
|
||||
|
||||
enum GPIODirection {
|
||||
GPIO_WRITE_ONLY = 0,
|
||||
GPIO_READ_WRITE = 1
|
||||
};
|
||||
|
||||
union RTCControl {
|
||||
struct {
|
||||
unsigned : 3;
|
||||
unsigned minIRQ : 1;
|
||||
unsigned : 2;
|
||||
unsigned hour24 : 1;
|
||||
unsigned poweroff : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
|
||||
enum RTCCommand {
|
||||
RTC_RESET = 0,
|
||||
RTC_DATETIME = 2,
|
||||
RTC_FORCE_IRQ = 3,
|
||||
RTC_CONTROL = 4,
|
||||
RTC_TIME = 6
|
||||
};
|
||||
|
||||
union RTCCommandData {
|
||||
struct {
|
||||
unsigned magic : 4;
|
||||
enum RTCCommand command : 3;
|
||||
unsigned reading : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
|
||||
struct GBARTC {
|
||||
int bytesRemaining;
|
||||
int transferStep;
|
||||
int bitsRead;
|
||||
int bits;
|
||||
int commandActive;
|
||||
union RTCCommandData command;
|
||||
union RTCControl control;
|
||||
uint8_t time[7];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct GBARumble {
|
||||
void (*setRumble)(struct GBARumble*, int enable);
|
||||
};
|
||||
|
||||
struct GBACartridgeHardware {
|
||||
struct GBA* p;
|
||||
int devices;
|
||||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned p0 : 1;
|
||||
unsigned p1 : 1;
|
||||
unsigned p2 : 1;
|
||||
unsigned p3 : 1;
|
||||
};
|
||||
uint16_t pinState;
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned dir0 : 1;
|
||||
unsigned dir1 : 1;
|
||||
unsigned dir2 : 1;
|
||||
unsigned dir3 : 1;
|
||||
};
|
||||
uint16_t direction;
|
||||
};
|
||||
|
||||
struct GBARTC rtc;
|
||||
|
||||
uint16_t gyroSample;
|
||||
bool gyroEdge;
|
||||
|
||||
unsigned lightCounter : 12;
|
||||
uint8_t lightSample;
|
||||
bool lightEdge;
|
||||
|
||||
uint16_t tiltX;
|
||||
uint16_t tiltY;
|
||||
int tiltState;
|
||||
};
|
||||
|
||||
void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);
|
||||
void GBAHardwareClear(struct GBACartridgeHardware* gpio);
|
||||
|
||||
void GBAHardwareInitRTC(struct GBACartridgeHardware* gpio);
|
||||
void GBAHardwareInitGyro(struct GBACartridgeHardware* gpio);
|
||||
void GBAHardwareInitRumble(struct GBACartridgeHardware* gpio);
|
||||
void GBAHardwareInitLight(struct GBACartridgeHardware* gpio);
|
||||
void GBAHardwareInitTilt(struct GBACartridgeHardware* gpio);
|
||||
|
||||
void GBAHardwareGPIOWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint16_t value);
|
||||
void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint8_t value);
|
||||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
|
@ -1,30 +1,27 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "hle-bios.h"
|
||||
|
||||
#include "gba-memory.h"
|
||||
#include "gba/memory.h"
|
||||
|
||||
const uint8_t hleBios[SIZE_BIOS] = {
|
||||
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x05, 0x00, 0x00, 0xea,
|
||||
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x07, 0x00, 0x00, 0xea,
|
||||
0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1,
|
||||
0x24, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02,
|
||||
0x00, 0x58, 0x2d, 0xe9, 0x02, 0xb0, 0x5e, 0xe5, 0x7c, 0xc0, 0xa0, 0xe3,
|
||||
0x0b, 0xb1, 0x9c, 0xe7, 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1,
|
||||
0x00, 0x10, 0x2d, 0xe9, 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3,
|
||||
0x0c, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0x1b, 0xff, 0x2f, 0x11, 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3,
|
||||
0x00, 0x10, 0xbd, 0xe8, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8,
|
||||
0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00,
|
||||
0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0x00, 0x00, 0x5d, 0xe3,
|
||||
0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9,
|
||||
0x02, 0xb0, 0x5e, 0xe5, 0x8c, 0xc0, 0xa0, 0xe3, 0x0b, 0xb1, 0x9c, 0xe7,
|
||||
0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0x10, 0x2d, 0xe9,
|
||||
0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, 0x0c, 0xf0, 0x29, 0xe1,
|
||||
0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, 0x1b, 0xff, 0x2f, 0x11,
|
||||
0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x10, 0xbd, 0xe8,
|
||||
0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x01, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9,
|
||||
0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5,
|
||||
0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3,
|
||||
0xe8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
|
||||
0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2,
|
||||
0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x01, 0x00, 0xa0, 0xe3,
|
||||
0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3,
|
||||
0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
|
|
@ -13,6 +13,6 @@ hle-bios.bin: hle-bios.o
|
|||
hle-bios.c: hle-bios.bin
|
||||
echo '#include "hle-bios.h"' > $@
|
||||
echo >> $@
|
||||
echo '#include "gba-memory.h"' >> $@
|
||||
echo '#include "gba/memory.h"' >> $@
|
||||
echo >> $@
|
||||
xxd -i $< | sed -e 's/unsigned char hle_bios_bin\[\]/const uint8_t hleBios[SIZE_BIOS]/' | grep -v hle_bios_bin_len >> $@
|
||||
|
|
|
@ -18,6 +18,8 @@ b fiqBase
|
|||
|
||||
resetBase:
|
||||
mov pc, #0x8000000
|
||||
.word 0
|
||||
.word 0xE129F000
|
||||
|
||||
swiBase:
|
||||
cmp sp, #0
|
||||
|
@ -42,6 +44,8 @@ ldmfd sp!, {r12}
|
|||
msr spsr, r12
|
||||
ldmfd sp!, {r11-r12, lr}
|
||||
movs pc, lr
|
||||
.word 0
|
||||
.word 0xE3A02004
|
||||
|
||||
swiTable:
|
||||
.word SoftReset
|
||||
|
@ -66,6 +70,8 @@ add lr, pc, #0
|
|||
ldr pc, [r0, #-4]
|
||||
ldmfd sp!, {r0-r3, r12, lr}
|
||||
subs pc, lr, #4
|
||||
.word 0
|
||||
.word 0xE55EC002
|
||||
|
||||
VBlankIntrWait:
|
||||
mov r0, #1
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
/* 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 "input.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
#include "util/table.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define SECTION_NAME_MAX 128
|
||||
#define KEY_NAME_MAX 32
|
||||
#define KEY_VALUE_MAX 16
|
||||
#define AXIS_INFO_MAX 12
|
||||
|
||||
struct GBAInputMapImpl {
|
||||
int* map;
|
||||
uint32_t type;
|
||||
|
||||
struct Table axes;
|
||||
};
|
||||
|
||||
struct GBAAxisSave {
|
||||
struct Configuration* config;
|
||||
uint32_t type;
|
||||
};
|
||||
|
||||
struct GBAAxisEnumerate {
|
||||
void (*handler)(int axis, const struct GBAAxis* description, void* user);
|
||||
void* user;
|
||||
};
|
||||
|
||||
const char* GBAKeyNames[] = {
|
||||
"A",
|
||||
"B",
|
||||
"Select",
|
||||
"Start",
|
||||
"Right",
|
||||
"Left",
|
||||
"Up",
|
||||
"Down",
|
||||
"R",
|
||||
"L"
|
||||
};
|
||||
|
||||
static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) {
|
||||
const char* strValue = ConfigurationGetValue(config, section, key);
|
||||
if (!strValue) {
|
||||
return false;
|
||||
}
|
||||
char* end;
|
||||
long intValue = strtol(strValue, &end, 10);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
*value = intValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct GBAInputMapImpl* _lookupMap(struct GBAInputMap* map, uint32_t type) {
|
||||
size_t m;
|
||||
struct GBAInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static const struct GBAInputMapImpl* _lookupMapConst(const struct GBAInputMap* map, uint32_t type) {
|
||||
size_t m;
|
||||
const struct GBAInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t type) {
|
||||
struct GBAInputMapImpl* impl = 0;
|
||||
if (map->numMaps == 0) {
|
||||
map->maps = malloc(sizeof(*map->maps));
|
||||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
TableInit(&impl->axes, 2, free);
|
||||
} else {
|
||||
impl = _lookupMap(map, type);
|
||||
}
|
||||
if (!impl) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (!map->maps[m].type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
map->maps[m].type = 0;
|
||||
map->maps[m].map = 0;
|
||||
}
|
||||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
int value;
|
||||
if (!_getIntValue(config, sectionName, keyKey, &value)) {
|
||||
return;
|
||||
}
|
||||
GBAInputBindKey(map, type, value, key);
|
||||
}
|
||||
|
||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
int value;
|
||||
if (!_getIntValue(config, sectionName, axisKey, &value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
int axis;
|
||||
const char* strValue = ConfigurationGetValue(config, sectionName, axisKey);
|
||||
if (!strValue || !strValue[0]) {
|
||||
return;
|
||||
}
|
||||
char* end;
|
||||
axis = strtoul(&strValue[1], &end, 10);
|
||||
if (*end) {
|
||||
return;
|
||||
}
|
||||
|
||||
const struct GBAAxis* description = GBAInputQueryAxis(map, type, axis);
|
||||
struct GBAAxis realDescription = { GBA_KEY_NONE, GBA_KEY_NONE, 0, 0 };
|
||||
if (description) {
|
||||
realDescription = *description;
|
||||
}
|
||||
if (strValue[0] == '+') {
|
||||
realDescription.deadHigh = value;
|
||||
realDescription.highDirection = direction;
|
||||
} else if (strValue[0] == '-') {
|
||||
realDescription.deadLow = value;
|
||||
realDescription.lowDirection = direction;
|
||||
}
|
||||
GBAInputBindAxis(map, type, axis, &realDescription);
|
||||
}
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
int value = GBAInputQueryBinding(map, type, key);
|
||||
char keyValue[KEY_VALUE_MAX];
|
||||
snprintf(keyValue, KEY_VALUE_MAX, "%" PRIi32, value);
|
||||
|
||||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||
}
|
||||
|
||||
static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationClearValue(config, sectionName, axisKey);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationClearValue(config, sectionName, axisKey);
|
||||
}
|
||||
|
||||
static void _saveAxis(uint32_t axis, void* dp, void* up) {
|
||||
struct GBAAxisSave* user = up;
|
||||
const struct GBAAxis* description = dp;
|
||||
|
||||
uint32_t type = user->type;
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
if (description->lowDirection != GBA_KEY_NONE) {
|
||||
const char* keyName = GBAKeyNames[description->lowDirection];
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadLow);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisInfo[AXIS_INFO_MAX];
|
||||
snprintf(axisInfo, AXIS_INFO_MAX, "-%u", axis);
|
||||
axisInfo[AXIS_INFO_MAX - 1] = '\0';
|
||||
ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo);
|
||||
}
|
||||
if (description->highDirection != GBA_KEY_NONE) {
|
||||
const char* keyName = GBAKeyNames[description->highDirection];
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadHigh);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisInfo[AXIS_INFO_MAX];
|
||||
snprintf(axisInfo, AXIS_INFO_MAX, "+%u", axis);
|
||||
axisInfo[AXIS_INFO_MAX - 1] = '\0';
|
||||
ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void _enumerateAxis(uint32_t axis, void* dp, void* ep) {
|
||||
struct GBAAxisEnumerate* enumUser = ep;
|
||||
const struct GBAAxis* description = dp;
|
||||
enumUser->handler(axis, description, enumUser->user);
|
||||
}
|
||||
|
||||
void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
||||
UNUSED(axis);
|
||||
enum GBAKey* key = user;
|
||||
struct GBAAxis* description = dp;
|
||||
if (description->highDirection == *key) {
|
||||
description->highDirection = GBA_KEY_NONE;
|
||||
}
|
||||
if (description->lowDirection == *key) {
|
||||
description->lowDirection = GBA_KEY_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap* map) {
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
}
|
||||
|
||||
void GBAInputMapDeinit(struct GBAInputMap* map) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type) {
|
||||
free(map->maps[m].map);
|
||||
TableDeinit(&map->maps[m].axes);
|
||||
}
|
||||
}
|
||||
free(map->maps);
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
}
|
||||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key) {
|
||||
size_t m;
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl || !impl->map) {
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
for (m = 0; m < GBA_KEY_MAX; ++m) {
|
||||
if (impl->map[m] == key) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) {
|
||||
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
GBAInputUnbindKey(map, type, input);
|
||||
impl->map[input] = key;
|
||||
}
|
||||
|
||||
void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||
struct GBAInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (input < 0 || input >= GBA_KEY_MAX) {
|
||||
return;
|
||||
}
|
||||
if (impl) {
|
||||
impl->map[input] = GBA_NO_MAPPING;
|
||||
}
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &input);
|
||||
}
|
||||
|
||||
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||
if (input >= GBA_KEY_MAX) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl || !impl->map) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return impl->map[input];
|
||||
}
|
||||
|
||||
enum GBAKey GBAInputMapAxis(const struct GBAInputMap* map, uint32_t type, int axis, int value) {
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
struct GBAAxis* description = TableLookup(&impl->axes, axis);
|
||||
if (!description) {
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
int state = 0;
|
||||
if (value < description->deadLow) {
|
||||
state = -1;
|
||||
} else if (value > description->deadHigh) {
|
||||
state = 1;
|
||||
}
|
||||
if (state > 0) {
|
||||
return description->highDirection;
|
||||
}
|
||||
if (state < 0) {
|
||||
return description->lowDirection;
|
||||
}
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, int keys) {
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return keys;
|
||||
}
|
||||
struct GBAAxis* description = TableLookup(&impl->axes, axis);
|
||||
if (!description) {
|
||||
return keys;
|
||||
}
|
||||
return keys &= ~((1 << description->highDirection) | (1 << description->lowDirection));
|
||||
}
|
||||
|
||||
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
|
||||
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
struct GBAAxis* dup = malloc(sizeof(struct GBAAxis));
|
||||
GBAInputUnbindKey(map, type, description->lowDirection);
|
||||
GBAInputUnbindKey(map, type, description->highDirection);
|
||||
*dup = *description;
|
||||
TableInsert(&impl->axes, axis, dup);
|
||||
}
|
||||
|
||||
void GBAInputUnbindAxis(struct GBAInputMap* map, uint32_t type, int axis) {
|
||||
struct GBAInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (impl) {
|
||||
TableRemove(&impl->axes, axis);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAInputUnbindAllAxes(struct GBAInputMap* map, uint32_t type) {
|
||||
struct GBAInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (impl) {
|
||||
TableClear(&impl->axes);
|
||||
}
|
||||
}
|
||||
|
||||
const struct GBAAxis* GBAInputQueryAxis(const struct GBAInputMap* map, uint32_t type, int axis) {
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return 0;
|
||||
}
|
||||
return TableLookup(&impl->axes, axis);
|
||||
}
|
||||
|
||||
void GBAInputEnumerateAxes(const struct GBAInputMap* map, uint32_t type, void (handler(int axis, const struct GBAAxis* description, void* user)), void* user) {
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct GBAAxisEnumerate enumUser = {
|
||||
handler,
|
||||
user
|
||||
};
|
||||
TableEnumerate(&impl->axes, _enumerateAxis, &enumUser);
|
||||
}
|
||||
|
||||
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
|
||||
_loadKey(map, type, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, config, GBA_KEY_L, "L");
|
||||
_loadKey(map, type, config, GBA_KEY_R, "R");
|
||||
_loadKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_loadKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_loadKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_loadKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_loadKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_loadKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_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) {
|
||||
_saveKey(map, type, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_clearAxis(type, config, "A");
|
||||
_clearAxis(type, config, "B");
|
||||
_clearAxis(type, config, "L");
|
||||
_clearAxis(type, config, "R");
|
||||
_clearAxis(type, config, "Start");
|
||||
_clearAxis(type, config, "Select");
|
||||
_clearAxis(type, config, "Up");
|
||||
_clearAxis(type, config, "Down");
|
||||
_clearAxis(type, config, "Left");
|
||||
_clearAxis(type, config, "Right");
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct GBAAxisSave save = {
|
||||
config,
|
||||
type
|
||||
};
|
||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_INPUT_H
|
||||
#define GBA_INPUT_H
|
||||
|
||||
#include "gba/gba.h"
|
||||
|
||||
struct Configuration;
|
||||
|
||||
struct GBAInputMap {
|
||||
struct GBAInputMapImpl* maps;
|
||||
size_t numMaps;
|
||||
};
|
||||
|
||||
struct GBAAxis {
|
||||
enum GBAKey highDirection;
|
||||
enum GBAKey lowDirection;
|
||||
int32_t deadHigh;
|
||||
int32_t deadLow;
|
||||
};
|
||||
|
||||
#define GBA_NO_MAPPING -1
|
||||
|
||||
extern const char* GBAKeyNames[];
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap*);
|
||||
void GBAInputMapDeinit(struct GBAInputMap*);
|
||||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
||||
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
||||
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, int key);
|
||||
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
|
||||
enum GBAKey GBAInputMapAxis(const struct GBAInputMap*, uint32_t type, int axis, int value);
|
||||
int GBAInputClearAxis(const struct GBAInputMap*, uint32_t type, int axis, int keys);
|
||||
void GBAInputBindAxis(struct GBAInputMap*, uint32_t type, int axis, const struct GBAAxis* description);
|
||||
void GBAInputUnbindAxis(struct GBAInputMap*, uint32_t type, int axis);
|
||||
void GBAInputUnbindAllAxes(struct GBAInputMap*, uint32_t type);
|
||||
const struct GBAAxis* GBAInputQueryAxis(const struct GBAInputMap*, uint32_t type, int axis);
|
||||
void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handler(int axis, const struct GBAAxis* description, void* user)), void* user);
|
||||
|
||||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
#endif
|
|
@ -1,14 +1,14 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-io.h"
|
||||
#include "io.h"
|
||||
|
||||
#include "gba-rr.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba-sio.h"
|
||||
#include "gba-video.h"
|
||||
#include "gba/supervisor/rr.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/sio.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
const char* GBAIORegisterNames[] = {
|
||||
// Video
|
||||
|
@ -86,7 +86,7 @@ const char* GBAIORegisterNames[] = {
|
|||
0,
|
||||
0,
|
||||
0,
|
||||
"WAVE_RAM0_LO"
|
||||
"WAVE_RAM0_LO",
|
||||
"WAVE_RAM0_HI",
|
||||
"WAVE_RAM1_LO",
|
||||
"WAVE_RAM1_HI",
|
||||
|
@ -141,7 +141,6 @@ const char* GBAIORegisterNames[] = {
|
|||
"TM2CNT_HI",
|
||||
"TM3CNT_LO",
|
||||
"TM3CNT_HI",
|
||||
0,
|
||||
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
|
@ -173,7 +172,7 @@ const char* GBAIORegisterNames[] = {
|
|||
"JOY_RECV_LO",
|
||||
"JOY_RECV_HI",
|
||||
"JOY_TRANS_LO",
|
||||
"JOY_RECV_HI",
|
||||
"JOY_TRANS_HI",
|
||||
"JOYSTAT",
|
||||
0,
|
||||
0,
|
||||
|
@ -242,7 +241,7 @@ static const int _isValidRegister[REG_MAX >> 1] = {
|
|||
|
||||
static const int _isSpecialRegister[REG_MAX >> 1] = {
|
||||
// Video
|
||||
0, 0, 0, 1, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
@ -279,7 +278,7 @@ static const int _isSpecialRegister[REG_MAX >> 1] = {
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Interrupts
|
||||
1, 1, 1, 0, 1
|
||||
1, 1, 0, 0, 1
|
||||
};
|
||||
|
||||
void GBAIOInit(struct GBA* gba) {
|
||||
|
@ -298,7 +297,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
case REG_DISPSTAT:
|
||||
value &= 0xFFF8;
|
||||
GBAVideoWriteDISPSTAT(&gba->video, value);
|
||||
break;
|
||||
return;
|
||||
|
||||
// Audio
|
||||
case REG_SOUND1CNT_LO:
|
||||
|
@ -357,8 +356,6 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
case REG_WAVE_RAM1_LO:
|
||||
case REG_WAVE_RAM2_LO:
|
||||
case REG_WAVE_RAM3_LO:
|
||||
case REG_FIFO_A_LO:
|
||||
case REG_FIFO_B_LO:
|
||||
GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
|
||||
break;
|
||||
|
||||
|
@ -366,9 +363,26 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
case REG_WAVE_RAM1_HI:
|
||||
case REG_WAVE_RAM2_HI:
|
||||
case REG_WAVE_RAM3_HI:
|
||||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
break;
|
||||
|
||||
// TODO: Confirm this behavior on real hardware
|
||||
case REG_FIFO_A_LO:
|
||||
case REG_FIFO_B_LO:
|
||||
if (gba->performingDMA) {
|
||||
GBAAudioWriteFIFO16(&gba->audio, address, value);
|
||||
} else {
|
||||
GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
|
||||
}
|
||||
break;
|
||||
|
||||
case REG_FIFO_A_HI:
|
||||
case REG_FIFO_B_HI:
|
||||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
if (gba->performingDMA) {
|
||||
GBAAudioWriteFIFO16(&gba->audio, address, value);
|
||||
} else {
|
||||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
}
|
||||
break;
|
||||
|
||||
// DMA
|
||||
|
@ -553,6 +567,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
|
|||
}
|
||||
|
||||
uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||
gba->lastJump = -1; // IO reads need to invalidate detected idle loops
|
||||
switch (address) {
|
||||
case REG_TM0CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 0);
|
||||
|
@ -568,12 +583,12 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
break;
|
||||
|
||||
case REG_KEYINPUT:
|
||||
if (GBARRIsPlaying(gba->rr)) {
|
||||
return 0x3FF ^ GBARRQueryInput(gba->rr);
|
||||
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||
} else if (gba->keySource) {
|
||||
uint16_t input = *gba->keySource;
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
GBARRLogInput(gba->rr, input);
|
||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||
gba->rr->logInput(gba->rr, input);
|
||||
}
|
||||
return 0x3FF ^ input;
|
||||
}
|
||||
|
@ -657,10 +672,10 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
}
|
||||
|
||||
memcpy(state->timers, gba->timers, sizeof(state->timers));
|
||||
GBAGPIOSerialize(&gba->memory.gpio, 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;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
if (_isSpecialRegister[i >> 1]) {
|
||||
|
@ -686,5 +701,5 @@ void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
gba->timersEnabled |= 1 << i;
|
||||
}
|
||||
}
|
||||
GBAGPIODeserialize(&gba->memory.gpio, state);
|
||||
GBAHardwareDeserialize(&gba->memory.hw, state);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba/gba.h"
|
||||
|
||||
enum GBAIORegisters {
|
||||
// Video
|
||||
|
@ -161,6 +161,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address);
|
|||
|
||||
struct GBASerializedState;
|
||||
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
|
|
@ -1,19 +1,23 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-memory.h"
|
||||
#include "memory.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#include "gba-gpio.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "hle-bios.h"
|
||||
#include "decoder.h"
|
||||
#include "gba/hardware.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/hle-bios.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
#define IDLE_LOOP_THRESHOLD 10000
|
||||
|
||||
static uint32_t _popcount32(unsigned bits);
|
||||
static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE };
|
||||
|
||||
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region);
|
||||
static void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info);
|
||||
|
@ -30,9 +34,7 @@ void GBAMemoryInit(struct GBA* gba) {
|
|||
struct ARMCore* cpu = gba->cpu;
|
||||
cpu->memory.load32 = GBALoad32;
|
||||
cpu->memory.load16 = GBALoad16;
|
||||
cpu->memory.loadU16 = GBALoadU16;
|
||||
cpu->memory.load8 = GBALoad8;
|
||||
cpu->memory.loadU8 = GBALoadU8;
|
||||
cpu->memory.loadMultiple = GBALoadMultiple;
|
||||
cpu->memory.store32 = GBAStore32;
|
||||
cpu->memory.store16 = GBAStore16;
|
||||
|
@ -44,7 +46,7 @@ void GBAMemoryInit(struct GBA* gba) {
|
|||
gba->memory.wram = 0;
|
||||
gba->memory.iwram = 0;
|
||||
gba->memory.rom = 0;
|
||||
gba->memory.gpio.p = gba;
|
||||
gba->memory.hw.p = gba;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 16; ++i) {
|
||||
|
@ -101,9 +103,10 @@ void GBAMemoryReset(struct GBA* gba) {
|
|||
memset(gba->memory.dma, 0, sizeof(gba->memory.dma));
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
gba->memory.dma[i].count = 0x10000;
|
||||
gba->memory.dma[i].count = 0x4000;
|
||||
gba->memory.dma[i].nextEvent = INT_MAX;
|
||||
}
|
||||
gba->memory.dma[3].count = 0x10000;
|
||||
gba->memory.activeDMA = -1;
|
||||
gba->memory.nextDMA = INT_MAX;
|
||||
gba->memory.eventDiff = 0;
|
||||
|
@ -114,20 +117,120 @@ void GBAMemoryReset(struct GBA* gba) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) {
|
||||
struct ARMInstructionInfo info;
|
||||
uint32_t nextAddress = address;
|
||||
memset(gba->taintedRegisters, 0, sizeof(gba->taintedRegisters));
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
while (true) {
|
||||
uint16_t opcode;
|
||||
LOAD_16(opcode, nextAddress & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
ARMDecodeThumb(opcode, &info);
|
||||
switch (info.branchType) {
|
||||
case ARM_BRANCH_NONE:
|
||||
if (info.operandFormat & ARM_OPERAND_MEMORY_2) {
|
||||
if (info.mnemonic == ARM_MN_STR || gba->taintedRegisters[info.memory.baseReg]) {
|
||||
gba->idleDetectionStep = -1;
|
||||
return;
|
||||
}
|
||||
uint32_t loadAddress = gba->cachedRegisters[info.memory.baseReg];
|
||||
uint32_t offset = 0;
|
||||
if (info.memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) {
|
||||
offset = info.memory.offset.immediate;
|
||||
} else if (info.memory.format & ARM_MEMORY_REGISTER_OFFSET) {
|
||||
int reg = info.memory.offset.reg;
|
||||
if (gba->cachedRegisters[reg]) {
|
||||
gba->idleDetectionStep = -1;
|
||||
return;
|
||||
}
|
||||
offset = gba->cachedRegisters[reg];
|
||||
}
|
||||
if (info.memory.format & ARM_MEMORY_OFFSET_SUBTRACT) {
|
||||
loadAddress -= offset;
|
||||
} else {
|
||||
loadAddress += offset;
|
||||
}
|
||||
if ((loadAddress >> BASE_OFFSET) == REGION_IO) {
|
||||
gba->idleDetectionStep = -1;
|
||||
return;
|
||||
}
|
||||
if ((loadAddress >> BASE_OFFSET) < REGION_CART0 || (loadAddress >> BASE_OFFSET) > REGION_CART2_EX) {
|
||||
gba->taintedRegisters[info.op1.reg] = true;
|
||||
} else {
|
||||
switch (info.memory.width) {
|
||||
case 1:
|
||||
gba->cachedRegisters[info.op1.reg] = GBALoad8(cpu, loadAddress, 0);
|
||||
break;
|
||||
case 2:
|
||||
gba->cachedRegisters[info.op1.reg] = GBALoad16(cpu, loadAddress, 0);
|
||||
break;
|
||||
case 4:
|
||||
gba->cachedRegisters[info.op1.reg] = GBALoad32(cpu, loadAddress, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (info.operandFormat & ARM_OPERAND_AFFECTED_1) {
|
||||
gba->taintedRegisters[info.op1.reg] = true;
|
||||
}
|
||||
nextAddress += WORD_SIZE_THUMB;
|
||||
break;
|
||||
case ARM_BRANCH:
|
||||
if ((uint32_t) info.op1.immediate + nextAddress + WORD_SIZE_THUMB * 2 == address) {
|
||||
gba->idleLoop = address;
|
||||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
}
|
||||
gba->idleDetectionStep = -1;
|
||||
return;
|
||||
default:
|
||||
gba->idleDetectionStep = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gba->idleDetectionStep = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
|
||||
if (address == gba->busyLoop && memory->activeRegion != REGION_BIOS) {
|
||||
GBAHalt(gba);
|
||||
int newRegion = address >> BASE_OFFSET;
|
||||
if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) {
|
||||
if (address == gba->lastJump && address == gba->idleLoop) {
|
||||
GBAHalt(gba);
|
||||
} else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) {
|
||||
if (address == gba->lastJump) {
|
||||
switch (gba->idleDetectionStep) {
|
||||
case 0:
|
||||
memcpy(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters));
|
||||
++gba->idleDetectionStep;
|
||||
break;
|
||||
case 1:
|
||||
if (memcmp(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters))) {
|
||||
gba->idleDetectionStep = -1;
|
||||
++gba->idleDetectionFailures;
|
||||
if (gba->idleDetectionFailures > IDLE_LOOP_THRESHOLD) {
|
||||
gba->idleOptimization = IDLE_LOOP_IGNORE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
_analyzeForIdleLoop(gba, cpu, address);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
gba->idleDetectionStep = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int newRegion = address >> BASE_OFFSET;
|
||||
gba->lastJump = address;
|
||||
if (newRegion == memory->activeRegion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memory->activeRegion == REGION_BIOS) {
|
||||
memory->biosPrefetch = cpu->prefetch;
|
||||
memory->biosPrefetch = cpu->prefetch[1];
|
||||
}
|
||||
memory->activeRegion = newRegion;
|
||||
switch (address & ~OFFSET_MASK) {
|
||||
|
@ -157,7 +260,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
cpu->memory.activeMask = SIZE_CART0 - 1;
|
||||
break;
|
||||
default:
|
||||
cpu->memory.activeRegion = 0;
|
||||
cpu->memory.activeRegion = _deadbeef;
|
||||
cpu->memory.activeMask = 0;
|
||||
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
|
||||
break;
|
||||
|
@ -172,10 +275,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
|
||||
#define LOAD_BAD \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \
|
||||
if (cpu->cycles >= cpu->nextEvent) { \
|
||||
if (gba->performingDMA) { \
|
||||
value = gba->bus; \
|
||||
} else { \
|
||||
value = cpu->prefetch; \
|
||||
value = cpu->prefetch[1]; \
|
||||
if (cpu->executionMode == MODE_THUMB) { \
|
||||
value |= value << 16; \
|
||||
} \
|
||||
|
@ -205,7 +308,11 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
++wait;
|
||||
|
||||
#define LOAD_VRAM \
|
||||
LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
|
||||
LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \
|
||||
} else { \
|
||||
LOAD_32(value, address & 0x00017FFF, gba->video.renderer->vram); \
|
||||
} \
|
||||
++wait;
|
||||
|
||||
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
|
@ -221,10 +328,12 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
}
|
||||
|
||||
#define LOAD_SRAM \
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load32: 0x%08X", address); \
|
||||
value = 0xDEADBEEF;
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; \
|
||||
value = GBALoad8(cpu, address, 0); \
|
||||
value |= value << 8; \
|
||||
value |= value << 16;
|
||||
|
||||
int32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
uint32_t value = 0;
|
||||
|
@ -275,17 +384,13 @@ int32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
// Unaligned 32-bit loads are "rotated" so they make some semblance of sense
|
||||
int rotate = (address & 3) << 3;
|
||||
return (value >> rotate) | (value << (32 - rotate));
|
||||
return ROR(value, rotate);
|
||||
}
|
||||
|
||||
uint16_t GBALoadU16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
return GBALoad16(cpu, address, cycleCounter);
|
||||
}
|
||||
|
||||
int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
uint16_t value = 0;
|
||||
uint32_t value = 0;
|
||||
int wait = 0;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
|
@ -295,14 +400,18 @@ int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
LOAD_16(value, address, memory->bios);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load16: 0x%08X", address);
|
||||
value = memory->biosPrefetch;
|
||||
LOAD_16(value, address & 2, &memory->biosPrefetch);
|
||||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
|
||||
if (cpu->cycles >= cpu->nextEvent) {
|
||||
value = gba->bus;
|
||||
if (gba->performingDMA) {
|
||||
LOAD_16(value, address & 2, &gba->bus);
|
||||
} else {
|
||||
value = cpu->prefetch;
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
LOAD_16(value, address & 2, &prefetch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -320,7 +429,11 @@ int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
LOAD_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
|
@ -351,14 +464,20 @@ int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load16: 0x%08X", address);
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
|
||||
value = GBALoad8(cpu, address, 0);
|
||||
value |= value << 8;
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
|
||||
if (cpu->cycles >= cpu->nextEvent) {
|
||||
value = gba->bus;
|
||||
if (gba->performingDMA) {
|
||||
LOAD_16(value, address & 2, &gba->bus);
|
||||
} else {
|
||||
value = cpu->prefetch;
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
LOAD_16(value, address & 2, &prefetch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -368,17 +487,13 @@ int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
// Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too.
|
||||
int rotate = (address & 1) << 3;
|
||||
return (value >> rotate) | (value << (16 - rotate));
|
||||
return ROR(value, rotate);
|
||||
}
|
||||
|
||||
uint8_t GBALoadU8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
return GBALoad8(cpu, address, cycleCounter);
|
||||
}
|
||||
|
||||
int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
int8_t value = 0;
|
||||
uint8_t value = 0;
|
||||
int wait = 0;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
|
@ -388,14 +503,18 @@ int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
value = ((int8_t*) memory->bios)[address];
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address);
|
||||
value = memory->biosPrefetch;
|
||||
value = ((uint8_t*) &memory->biosPrefetch)[address & 3];
|
||||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
if (cpu->cycles >= cpu->nextEvent) {
|
||||
value = gba->bus;
|
||||
if (gba->performingDMA) {
|
||||
value = ((uint8_t*) &gba->bus)[address & 3];
|
||||
} else {
|
||||
value = cpu->prefetch;
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
value = ((uint8_t*) &prefetch)[address & 3];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -413,7 +532,11 @@ int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
|
||||
} else {
|
||||
value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF];
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load8: 0x%08X", address);
|
||||
|
@ -435,7 +558,7 @@ int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
|
||||
if (memory->savedata.type == SAVEDATA_NONE) {
|
||||
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame");
|
||||
GBASavedataInitSRAM(&memory->savedata);
|
||||
}
|
||||
|
@ -443,17 +566,23 @@ int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
value = memory->savedata.data[address & (SIZE_CART_SRAM - 1)];
|
||||
} else if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) {
|
||||
value = GBASavedataReadFlash(&memory->savedata, address);
|
||||
} else if (memory->hw.devices & HW_TILT) {
|
||||
value = GBAHardwareTiltRead(&memory->hw, address & OFFSET_MASK);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address);
|
||||
value = 7;
|
||||
value = 0xFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
if (cpu->cycles >= cpu->nextEvent) {
|
||||
value = gba->bus;
|
||||
if (gba->performingDMA) {
|
||||
value = ((uint8_t*) &gba->bus)[address & 3];
|
||||
} else {
|
||||
value = cpu->prefetch;
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
value = ((uint8_t*) &prefetch)[address & 3];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -481,9 +610,9 @@ int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
|
||||
#define STORE_VRAM \
|
||||
if ((address & OFFSET_MASK) < SIZE_VRAM) { \
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
|
||||
STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \
|
||||
} else if ((address & OFFSET_MASK) < 0x00020000) { \
|
||||
} else { \
|
||||
STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); \
|
||||
} \
|
||||
++wait;
|
||||
|
@ -570,9 +699,9 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
|||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & OFFSET_MASK) < SIZE_VRAM) {
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
} else if ((address & OFFSET_MASK) < 0x00020000) {
|
||||
} else {
|
||||
STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
|
@ -581,15 +710,15 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
|||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
if (IS_GPIO_REGISTER(address & 0xFFFFFF)) {
|
||||
if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFF)) {
|
||||
uint32_t reg = address & 0xFFFFFF;
|
||||
GBAGPIOWrite(&memory->gpio, reg, value);
|
||||
GBAHardwareGPIOWrite(&memory->hw, reg, value);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad cartridge Store16: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
case REGION_CART2_EX:
|
||||
if (memory->savedata.type == SAVEDATA_NONE) {
|
||||
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected EEPROM savegame");
|
||||
GBASavedataInitEEPROM(&memory->savedata);
|
||||
}
|
||||
|
@ -645,7 +774,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_NONE) {
|
||||
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
|
||||
if (address == SAVEDATA_FLASH_BASE) {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame");
|
||||
GBASavedataInitFlash(&memory->savedata);
|
||||
|
@ -658,6 +787,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
GBASavedataWriteFlash(&memory->savedata, address, value);
|
||||
} else if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
|
||||
} else if (memory->hw.devices & HW_TILT) {
|
||||
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
|
@ -673,6 +804,142 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
}
|
||||
}
|
||||
|
||||
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
int32_t oldValue = -1;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch32: 0x%08X", address);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
LOAD_32(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_32(oldValue, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_32(oldValue, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_32(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 1)) + 2) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
|
||||
LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
LOAD_32(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
STORE_32(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address);
|
||||
break;
|
||||
}
|
||||
if (old) {
|
||||
*old = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
int16_t oldValue = -1;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch16: 0x%08X", address);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_16(oldValue, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_16(oldValue, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_16(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
|
||||
LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
LOAD_16(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
STORE_16(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address);
|
||||
break;
|
||||
}
|
||||
if (old) {
|
||||
*old = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
#define LDM_LOOP(LDM) \
|
||||
for (i = 0; i < 16; i += 4) { \
|
||||
if (UNLIKELY(mask & (1 << i))) { \
|
||||
|
@ -967,12 +1234,12 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
|
|||
|
||||
void GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
memory->dma[dma].source = address & 0xFFFFFFFE;
|
||||
memory->dma[dma].source = address & 0x0FFFFFFE;
|
||||
}
|
||||
|
||||
void GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
memory->dma[dma].dest = address & 0xFFFFFFFE;
|
||||
memory->dma[dma].dest = address & 0x0FFFFFFE;
|
||||
}
|
||||
|
||||
void GBAMemoryWriteDMACNT_LO(struct GBA* gba, int dma, uint16_t count) {
|
||||
|
@ -1106,7 +1373,7 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
uint32_t dest = info->nextDest;
|
||||
uint32_t sourceRegion = source >> BASE_OFFSET;
|
||||
uint32_t destRegion = dest >> BASE_OFFSET;
|
||||
int32_t cycles = 0;
|
||||
int32_t cycles = 2;
|
||||
|
||||
if (source == info->source) {
|
||||
// TODO: support 4 cycles for ROM access
|
||||
|
@ -1126,6 +1393,7 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
}
|
||||
}
|
||||
|
||||
gba->performingDMA = true;
|
||||
int32_t word;
|
||||
if (width == 4) {
|
||||
word = cpu->memory.load32(cpu, source, 0);
|
||||
|
@ -1143,7 +1411,7 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
dest += destOffset;
|
||||
--wordsRemaining;
|
||||
} else if (destRegion == REGION_CART2_EX) {
|
||||
if (memory->savedata.type == SAVEDATA_NONE) {
|
||||
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected EEPROM savegame");
|
||||
GBASavedataInitEEPROM(&memory->savedata);
|
||||
}
|
||||
|
@ -1162,9 +1430,10 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
--wordsRemaining;
|
||||
}
|
||||
}
|
||||
gba->performingDMA = false;
|
||||
|
||||
if (!wordsRemaining) {
|
||||
if (!GBADMARegisterIsRepeat(info->reg)) {
|
||||
if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) {
|
||||
info->reg = GBADMARegisterClearEnable(info->reg);
|
||||
info->nextEvent = INT_MAX;
|
||||
|
||||
|
@ -1192,12 +1461,12 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
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->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->iwram, state->iwram, SIZE_WORKING_IRAM);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -11,8 +11,8 @@
|
|||
#include "arm.h"
|
||||
#include "macros.h"
|
||||
|
||||
#include "gba-gpio.h"
|
||||
#include "gba-savedata.h"
|
||||
#include "gba/hardware.h"
|
||||
#include "gba/savedata.h"
|
||||
|
||||
enum GBAMemoryRegion {
|
||||
REGION_BIOS = 0x0,
|
||||
|
@ -116,7 +116,7 @@ struct GBAMemory {
|
|||
uint32_t* rom;
|
||||
uint16_t io[SIZE_IO >> 1];
|
||||
|
||||
struct GBACartridgeGPIO gpio;
|
||||
struct GBACartridgeHardware hw;
|
||||
struct GBASavedata savedata;
|
||||
size_t romSize;
|
||||
uint16_t romID;
|
||||
|
@ -144,16 +144,17 @@ void GBAMemoryDeinit(struct GBA* gba);
|
|||
|
||||
void GBAMemoryReset(struct GBA* gba);
|
||||
|
||||
int32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint16_t GBALoadU16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint8_t GBALoadU8(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
|
||||
void GBAStore32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter);
|
||||
void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter);
|
||||
void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter);
|
||||
|
||||
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old);
|
||||
void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old);
|
||||
|
||||
uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);
|
||||
uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);
|
||||
|
||||
|
@ -171,7 +172,7 @@ void GBAMemoryUpdateDMAs(struct GBA* gba, int32_t cycles);
|
|||
int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
|
@ -1,280 +0,0 @@
|
|||
#include "video-glsl.h"
|
||||
|
||||
#include "gba-io.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define UNIFORM_LOCATION(UNIFORM) (glGetUniformLocation(glslRenderer->program, UNIFORM))
|
||||
|
||||
static const GLfloat _vertices[4] = {
|
||||
-1, 0,
|
||||
1, 0
|
||||
};
|
||||
|
||||
static const GLchar* _fragmentShader =
|
||||
"varying float x;\n"
|
||||
"uniform float y;\n"
|
||||
"uniform sampler2D vram;\n"
|
||||
"uniform float dispcnt;\n"
|
||||
"uniform float bg0cnt;\n"
|
||||
"uniform float bg1cnt;\n"
|
||||
"uniform float bg2cnt;\n"
|
||||
"uniform float bg3cnt;\n"
|
||||
"uniform float bg0hofs;\n"
|
||||
"uniform float bg0vofs;\n"
|
||||
"uniform float bg1hofs;\n"
|
||||
"uniform float bg1vofs;\n"
|
||||
"uniform float bg2hofs;\n"
|
||||
"uniform float bg2vofs;\n"
|
||||
"uniform float bg3hofs;\n"
|
||||
"uniform float bg3vofs;\n"
|
||||
"#define PALETTE_INDEX(i) texture2D(vram, (vec2(mod(i + 1.0, 512.0) / 512.0 - 1.0 / 1024.0, (floor(i / 512.0) + 1.0) / 256.0 - 1.0 / 1024.0)))\n"
|
||||
"#define VRAM_INDEX(i) texture2D(vram, (vec2(mod(i + 1.0, 512.0) / 512.0 - 1.0 / 1024.0, (floor(i / 512.0) + 161.0) / 256.0 - 1.0 / 1024.0)))\n"
|
||||
"#define DESERIALIZE(vec) dot(vec4(63488.0, 1984.0, 62.0, 1.0), vec)\n"
|
||||
"#define IMOD(a, b) mod(floor(a), b)\n"
|
||||
"#define BIT_CHECK(a, b) (IMOD(a / b, 2.0) > 0.0)\n"
|
||||
"#define DEBUG(fl) return vec4(fract(fl / 256.0), fract(floor(fl / 256.0) / 256.0), fract(floor(fl / 65536.0) / 256.0), 1.0)\n"
|
||||
|
||||
"vec4 backgroundMode0(float bgcnt, float hofs, float vofs) {\n"
|
||||
" float charBase = IMOD(bgcnt / 4.0, 4.0) * 8192.0;\n"
|
||||
" float screenBase = IMOD(bgcnt / 256.0, 32.0) * 1024.0;\n"
|
||||
" float size = IMOD(bgcnt / 16384.0, 4.0);\n"
|
||||
" vec2 local = vec2(hofs + x, vofs + y);\n"
|
||||
" vec2 base = IMOD(local, 256.0);\n"
|
||||
" base -= IMOD(local, 8.0);\n"
|
||||
" if (size == 1.0) {\n"
|
||||
" base.x += IMOD(local.x / 256.0, 2.0) * 8192.0;\n"
|
||||
" } else if (size == 2.0) {\n"
|
||||
" base.y += IMOD(local.y / 256.0, 2.0) * 256.0;\n"
|
||||
" } else if (size == 3.0) {\n"
|
||||
" base += IMOD(local / 256.0, 2.0) * vec2(8192.0, 512.0);\n"
|
||||
" }\n"
|
||||
" screenBase += dot(base, vec2(1.0 / 8.0, 4.0));\n"
|
||||
" float mapData = DESERIALIZE(VRAM_INDEX(screenBase));\n"
|
||||
" charBase += IMOD(mapData, 1024.0) * 16.0 + dot(IMOD(local * vec2(1.0 / 4.0, 1.0), vec2(2.0, 8.0)), vec2(1.0, 2.0));\n"
|
||||
" float tileData = DESERIALIZE(VRAM_INDEX(charBase));\n"
|
||||
" tileData *= pow(0.5, IMOD(local.x, 4.0) * 4.0);\n"
|
||||
" tileData = IMOD(tileData, 16.0);\n"
|
||||
" if (tileData == 0.0) {\n"
|
||||
" return vec4(0, 0, 0, 0);\n"
|
||||
" }\n"
|
||||
" return PALETTE_INDEX(tileData + floor(mapData / 4096.0) * 16.0);\n"
|
||||
"}\n"
|
||||
|
||||
"void runPriority(float priority, inout vec4 color) {\n"
|
||||
" if (color.a > 0.0) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (BIT_CHECK(dispcnt, 256.0) && IMOD(bg0cnt, 4.0) == priority) {\n"
|
||||
" color = backgroundMode0(bg0cnt, bg0hofs, bg0vofs);\n"
|
||||
" }\n"
|
||||
" if (color.a > 0.0) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (BIT_CHECK(dispcnt, 512.0) && IMOD(bg1cnt, 4.0) == priority) {\n"
|
||||
" color = backgroundMode0(bg1cnt, bg1hofs, bg1vofs);\n"
|
||||
" }\n"
|
||||
" if (color.a > 0.0) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (BIT_CHECK(dispcnt, 1024.0) && IMOD(bg2cnt, 4.0) == priority) {\n"
|
||||
" color = backgroundMode0(bg2cnt, bg2hofs, bg2vofs);\n"
|
||||
" }\n"
|
||||
" if (color.a > 0.0) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" if (BIT_CHECK(dispcnt, 2048.0) && IMOD(bg3cnt, 4.0) == priority) {\n"
|
||||
" color = backgroundMode0(bg3cnt, bg3hofs, bg3vofs);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n"
|
||||
" runPriority(0.0, color);\n"
|
||||
" runPriority(1.0, color);\n"
|
||||
" runPriority(2.0, color);\n"
|
||||
" runPriority(3.0, color);\n"
|
||||
" if (color.a == 0.0) {\n"
|
||||
" color = texture2D(vram, vec2(0.0, y / 256.0));\n"
|
||||
" }\n"
|
||||
" gl_FragColor = color;\n"
|
||||
"}\n";
|
||||
|
||||
static const GLchar* _vertexShader[] = {
|
||||
"varying float x;",
|
||||
"attribute vec2 vert;",
|
||||
"uniform float y;",
|
||||
|
||||
"void main() {",
|
||||
" x = vert.x * 120.0 + 120.0;",
|
||||
" gl_Position = vec4(vert.x, 1.0 - (y + 1.0) / 80.0, 0, 1.0);",
|
||||
"}"
|
||||
};
|
||||
|
||||
static void GBAVideoGLSLRendererInit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoGLSLRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoGLSLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static uint16_t GBAVideoGLSLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoGLSLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
|
||||
static void GBAVideoGLSLRendererFinishFrame(struct GBAVideoRenderer* renderer);
|
||||
|
||||
void GBAVideoGLSLRendererCreate(struct GBAVideoGLSLRenderer* glslRenderer) {
|
||||
glslRenderer->d.init = GBAVideoGLSLRendererInit;
|
||||
glslRenderer->d.deinit = GBAVideoGLSLRendererDeinit;
|
||||
glslRenderer->d.writeVideoRegister = GBAVideoGLSLRendererWriteVideoRegister;
|
||||
glslRenderer->d.writePalette = GBAVideoGLSLRendererWritePalette;
|
||||
glslRenderer->d.drawScanline = GBAVideoGLSLRendererDrawScanline;
|
||||
glslRenderer->d.finishFrame = GBAVideoGLSLRendererFinishFrame;
|
||||
|
||||
glslRenderer->d.turbo = 0;
|
||||
glslRenderer->d.framesPending = 0;
|
||||
glslRenderer->d.frameskip = 0;
|
||||
|
||||
glslRenderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glslRenderer->vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glslRenderer->program = glCreateProgram();
|
||||
|
||||
glShaderSource(glslRenderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0);
|
||||
glShaderSource(glslRenderer->vertexShader, 7, _vertexShader, 0);
|
||||
|
||||
glAttachShader(glslRenderer->program, glslRenderer->vertexShader);
|
||||
glAttachShader(glslRenderer->program, glslRenderer->fragmentShader);
|
||||
char log[1024];
|
||||
glCompileShader(glslRenderer->fragmentShader);
|
||||
glCompileShader(glslRenderer->vertexShader);
|
||||
glGetShaderInfoLog(glslRenderer->fragmentShader, 1024, 0, log);
|
||||
glGetShaderInfoLog(glslRenderer->vertexShader, 1024, 0, log);
|
||||
glLinkProgram(glslRenderer->program);
|
||||
|
||||
glGenTextures(1, &glslRenderer->vramTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, glslRenderer->vramTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
memset(glslRenderer->vram, 0, sizeof (glslRenderer->vram));
|
||||
|
||||
{
|
||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
glslRenderer->mutex = mutex;
|
||||
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
||||
glslRenderer->upCond = cond;
|
||||
glslRenderer->downCond = cond;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAVideoGLSLRendererProcessEvents(struct GBAVideoGLSLRenderer* glslRenderer) {
|
||||
glUseProgram(glslRenderer->program);
|
||||
glUniform1i(UNIFORM_LOCATION("vram"), 0);
|
||||
glUniform1f(UNIFORM_LOCATION("dispcnt"), glslRenderer->io[0][REG_DISPCNT >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg0cnt"), glslRenderer->io[0][REG_BG0CNT >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg1cnt"), glslRenderer->io[0][REG_BG1CNT >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg2cnt"), glslRenderer->io[0][REG_BG2CNT >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg3cnt"), glslRenderer->io[0][REG_BG3CNT >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg0hofs"), glslRenderer->io[0][REG_BG0HOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg0vofs"), glslRenderer->io[0][REG_BG0VOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg1hofs"), glslRenderer->io[0][REG_BG1HOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg1vofs"), glslRenderer->io[0][REG_BG1VOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg2hofs"), glslRenderer->io[0][REG_BG2HOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg2vofs"), glslRenderer->io[0][REG_BG2VOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg3hofs"), glslRenderer->io[0][REG_BG3HOFS >> 1]);
|
||||
glUniform1f(UNIFORM_LOCATION("bg3vofs"), glslRenderer->io[0][REG_BG3VOFS >> 1]);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, glslRenderer->vramTexture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, glslRenderer->vram);
|
||||
|
||||
GLuint location = glGetAttribLocation(glslRenderer->program, "vert");
|
||||
glEnableVertexAttribArray(location);
|
||||
glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
|
||||
int y;
|
||||
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
glUniform1f(UNIFORM_LOCATION("y"), y);
|
||||
glDrawArrays(GL_LINES, 0, 2);
|
||||
}
|
||||
glDisableVertexAttribArray(location);
|
||||
glFlush();
|
||||
}
|
||||
|
||||
static void GBAVideoGLSLRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
|
||||
glslRenderer->state = GLSL_NONE;
|
||||
glslRenderer->y = 0;
|
||||
|
||||
glslRenderer->oldVram = renderer->vram;
|
||||
renderer->vram = &glslRenderer->vram[512 * 160];
|
||||
|
||||
pthread_mutex_init(&glslRenderer->mutex, 0);
|
||||
pthread_cond_init(&glslRenderer->upCond, 0);
|
||||
pthread_cond_init(&glslRenderer->downCond, 0);
|
||||
}
|
||||
|
||||
static void GBAVideoGLSLRendererDeinit(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
|
||||
/*glDeleteShader(glslRenderer->fragmentShader);
|
||||
glDeleteShader(glslRenderer->vertexShader);
|
||||
glDeleteProgram(glslRenderer->program);
|
||||
|
||||
glDeleteTextures(1, &glslRenderer->paletteTexture);*/
|
||||
|
||||
renderer->vram = glslRenderer->oldVram;
|
||||
|
||||
pthread_mutex_lock(&glslRenderer->mutex);
|
||||
pthread_cond_broadcast(&glslRenderer->upCond);
|
||||
pthread_mutex_unlock(&glslRenderer->mutex);
|
||||
|
||||
pthread_mutex_destroy(&glslRenderer->mutex);
|
||||
pthread_cond_destroy(&glslRenderer->upCond);
|
||||
pthread_cond_destroy(&glslRenderer->downCond);
|
||||
}
|
||||
|
||||
static void GBAVideoGLSLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
GLshort color = 1;
|
||||
color |= (value & 0x001F) << 11;
|
||||
color |= (value & 0x03E0) << 1;
|
||||
color |= (value & 0x7C00) >> 9;
|
||||
glslRenderer->vram[(address >> 1) + glslRenderer->y * 512] = color;
|
||||
}
|
||||
|
||||
static uint16_t GBAVideoGLSLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
glslRenderer->io[glslRenderer->y][address >> 1] = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void GBAVideoGLSLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
|
||||
glslRenderer->y = y + 1;
|
||||
if (y + 1 < VIDEO_VERTICAL_PIXELS) {
|
||||
memcpy(&glslRenderer->vram[(y + 1) * 512], &glslRenderer->vram[y * 512], 1024);
|
||||
memcpy(glslRenderer->io[y + 1], glslRenderer->io[y], sizeof(*glslRenderer->io));
|
||||
} else {
|
||||
glslRenderer->y = 0;
|
||||
memcpy(&glslRenderer->vram[0], &glslRenderer->vram[y * 512], 1024);
|
||||
memcpy(glslRenderer->io[0], glslRenderer->io[y], sizeof(*glslRenderer->io));
|
||||
}
|
||||
}
|
||||
|
||||
static void GBAVideoGLSLRendererFinishFrame(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer;
|
||||
|
||||
pthread_mutex_lock(&glslRenderer->mutex);
|
||||
glslRenderer->state = GLSL_NONE;
|
||||
if (renderer->frameskip > 0) {
|
||||
--renderer->frameskip;
|
||||
} else {
|
||||
renderer->framesPending++;
|
||||
pthread_cond_broadcast(&glslRenderer->upCond);
|
||||
if (!renderer->turbo) {
|
||||
pthread_cond_wait(&glslRenderer->downCond, &glslRenderer->mutex);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&glslRenderer->mutex);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#ifndef VIDEO_GLSL_H
|
||||
#define VIDEO_GLSL_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba-video.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
|
||||
struct GBAVideoGLSLRenderer {
|
||||
struct GBAVideoRenderer d;
|
||||
|
||||
int y;
|
||||
enum {
|
||||
GLSL_NONE,
|
||||
GLSL_DRAW_SCANLINE,
|
||||
GLSL_FINISH_FRAME
|
||||
} state;
|
||||
|
||||
GLuint fragmentShader;
|
||||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
|
||||
GLuint vramTexture;
|
||||
GLushort vram[512 * 256];
|
||||
GLushort io[160][0x30];
|
||||
|
||||
uint16_t* oldVram;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t upCond;
|
||||
pthread_cond_t downCond;
|
||||
};
|
||||
|
||||
void GBAVideoGLSLRendererCreate(struct GBAVideoGLSLRenderer* renderer);
|
||||
void GBAVideoGLSLRendererProcessEvents(struct GBAVideoGLSLRenderer* renderer);
|
||||
|
||||
#endif
|
|
@ -1,12 +1,14 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "video-software.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
|
||||
#include "util/arm-algo.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define VIDEO_CHECKS false
|
||||
|
@ -35,6 +37,7 @@ static const int _objSizes[32] = {
|
|||
|
||||
static void GBAVideoSoftwareRendererInit(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 GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
|
@ -75,7 +78,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
|||
|
||||
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
||||
renderer->d.init = GBAVideoSoftwareRendererInit;
|
||||
renderer->d.reset = GBAVideoSoftwareRendererInit;
|
||||
renderer->d.reset = GBAVideoSoftwareRendererReset;
|
||||
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
|
||||
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
|
||||
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
|
||||
|
@ -87,6 +90,21 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* 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;
|
||||
int i;
|
||||
|
||||
|
@ -807,7 +825,11 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
#define COMPOSITE_16_OBJWIN(BLEND) \
|
||||
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \
|
||||
_composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
mergedFlags = objwinFlags; \
|
||||
} \
|
||||
_composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \
|
||||
}
|
||||
|
||||
#define COMPOSITE_16_NO_OBJWIN(BLEND) \
|
||||
|
@ -816,7 +838,11 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
#define COMPOSITE_256_OBJWIN(BLEND) \
|
||||
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \
|
||||
_composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
mergedFlags = objwinFlags; \
|
||||
} \
|
||||
_composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \
|
||||
}
|
||||
|
||||
#define COMPOSITE_256_NO_OBJWIN(BLEND) \
|
||||
|
@ -851,16 +877,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
localY = 7 - localY; \
|
||||
}
|
||||
|
||||
// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5
|
||||
#define PREPARE_OBJWIN \
|
||||
int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \
|
||||
int objwinOnly = 0; \
|
||||
int objwinForceEnable = 0; \
|
||||
color_t* objwinPalette; \
|
||||
UNUSED(objwinForceEnable); \
|
||||
color_t* objwinPalette = renderer->normalPalette; \
|
||||
UNUSED(objwinPalette); \
|
||||
if (objwinSlowPath) { \
|
||||
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
|
||||
objwinPalette = renderer->variantPalette; \
|
||||
} else { \
|
||||
objwinPalette = renderer->normalPalette; \
|
||||
} \
|
||||
switch (background->index) { \
|
||||
case 0: \
|
||||
|
@ -933,11 +960,42 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
}
|
||||
|
||||
#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \
|
||||
for (; tileX < tileEnd; ++tileX) { \
|
||||
x = inX & 7; \
|
||||
if (mosaicWait) { \
|
||||
int baseX = x - (mosaicH - mosaicWait); \
|
||||
if (baseX < 0) { \
|
||||
int disturbX = (16 + baseX) >> 3; \
|
||||
inX -= disturbX << 3; \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
baseX -= disturbX << 3; \
|
||||
inX += disturbX << 3; \
|
||||
} else { \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
} \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= 4 * baseX; \
|
||||
} else { \
|
||||
tileData >>= 4 * (7 - baseX); \
|
||||
} \
|
||||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
for (; length; ++tileX) { \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
tileData = carryData; \
|
||||
for (x = 0; x < 8; ++x) { \
|
||||
for (; x < 8 && length; ++x, --length) { \
|
||||
if (!mosaicWait) { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
|
@ -962,6 +1020,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
|
||||
++pixel; \
|
||||
} \
|
||||
x = 0; \
|
||||
}
|
||||
|
||||
#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \
|
||||
|
@ -970,6 +1029,10 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
pixel += 8; \
|
||||
continue; \
|
||||
} \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (tileData) { \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
|
@ -1018,35 +1081,43 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
int end2 = end - 4; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
int shift = inX & 0x3; \
|
||||
if (end2 > outX) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= 8 * shift; \
|
||||
shift = 0; \
|
||||
for (; outX < end2; ++outX, ++pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
if (LIKELY(charBase < 0x10000)) { \
|
||||
if (end2 > outX) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= 8 * shift; \
|
||||
shift = 0; \
|
||||
for (; outX < end2; ++outX, ++pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= 8 * shift; \
|
||||
for (; outX < end; ++outX, ++pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
if (LIKELY(charBase < 0x10000)) { \
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= 8 * shift; \
|
||||
for (; outX < end; ++outX, ++pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
int start = outX; \
|
||||
outX = end - 1; \
|
||||
pixel = &renderer->row[outX]; \
|
||||
if (end2 > start) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
for (; outX >= end2; --outX, --pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
if (LIKELY(charBase < 0x10000)) { \
|
||||
if (end2 > start) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
for (; outX >= end2; --outX, --pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
} \
|
||||
charBase += 4; \
|
||||
} \
|
||||
charBase += 4; \
|
||||
} \
|
||||
\
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
for (; outX >= renderer->start; --outX, --pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
if (LIKELY(charBase < 0x10000)) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
for (; outX >= renderer->start; --outX, --pixel) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
} \
|
||||
} \
|
||||
outX = end; \
|
||||
pixel = &renderer->row[outX]; \
|
||||
|
@ -1054,6 +1125,9 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
|
||||
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
return; \
|
||||
} \
|
||||
int end = mod8 - 4; \
|
||||
pixel = &renderer->row[outX]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
|
@ -1099,6 +1173,10 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
for (; tileX < tileEnd; ++tileX) { \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
pixel += 8; \
|
||||
continue; \
|
||||
} \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (tileData) { \
|
||||
|
@ -1194,7 +1272,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \
|
||||
int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \
|
||||
int x; \
|
||||
int mosaicWait = outX % mosaicH; \
|
||||
int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \
|
||||
int carryData = 0; \
|
||||
paletteData = 0; /* Quiets compiler warning */ \
|
||||
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
|
||||
|
@ -1276,10 +1354,13 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
unsigned xBase;
|
||||
|
||||
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND;
|
||||
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA);
|
||||
flags |= FLAG_TARGET_2 * background->target2;
|
||||
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed));
|
||||
objwinFlags |= flags;
|
||||
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed));
|
||||
if (renderer->blda == 0x10 && renderer->bldb == 0) {
|
||||
flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2);
|
||||
objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
|
||||
}
|
||||
|
||||
uint32_t screenBase;
|
||||
|
@ -1303,7 +1384,7 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
uint16_t* vram = renderer->d.vram;
|
||||
|
||||
if (!objwinSlowPath) {
|
||||
if (!(flags & FLAG_TARGET_2)) {
|
||||
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
|
||||
if (!background->multipalette) {
|
||||
DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN);
|
||||
} else {
|
||||
|
@ -1317,7 +1398,7 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (!(flags & FLAG_TARGET_2)) {
|
||||
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
|
||||
if (!background->multipalette) {
|
||||
DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN);
|
||||
} else {
|
||||
|
@ -1349,16 +1430,20 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
int32_t localY; \
|
||||
\
|
||||
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \
|
||||
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA); \
|
||||
flags |= FLAG_TARGET_2 * background->target2; \
|
||||
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \
|
||||
objwinFlags |= flags; \
|
||||
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \
|
||||
if (renderer->blda == 0x10 && renderer->bldb == 0) { \
|
||||
flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
|
||||
objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
|
||||
} \
|
||||
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
|
||||
color_t* palette = renderer->normalPalette; \
|
||||
if (variant) { \
|
||||
palette = renderer->variantPalette; \
|
||||
} \
|
||||
UNUSED(palette); \
|
||||
PREPARE_OBJWIN;
|
||||
|
||||
#define BACKGROUND_BITMAP_ITERATE(W, H) \
|
||||
|
@ -1412,7 +1497,11 @@ static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
_compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current);
|
||||
} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
|
||||
color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
|
||||
_compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | flags, current);
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
}
|
||||
_compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1445,12 +1534,16 @@ static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
|
||||
uint32_t current = *pixel;
|
||||
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
}
|
||||
if (!variant) {
|
||||
_compositeBlendObjwin(renderer, pixel, color | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, color | mergedFlags, current);
|
||||
} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
|
||||
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current);
|
||||
} else if (renderer->blendEffect == BLEND_DARKEN) {
|
||||
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1484,7 +1577,11 @@ static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
_compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current);
|
||||
} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
|
||||
color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
|
||||
_compositeBlendObjwin(renderer, pixel, currentPalette[color] | flags, current);
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
}
|
||||
_compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1520,12 +1617,16 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
|
||||
uint32_t current = *pixel;
|
||||
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
}
|
||||
if (!variant) {
|
||||
_compositeBlendObjwin(renderer, pixel, color | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, color | mergedFlags, current);
|
||||
} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
|
||||
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current);
|
||||
} else if (renderer->blendEffect == BLEND_DARKEN) {
|
||||
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current);
|
||||
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1546,14 +1647,22 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
SPRITE_YBASE_ ## DEPTH(inY); \
|
||||
unsigned tileData; \
|
||||
if (outX % mosaicH) { \
|
||||
inX += (mosaicH - (outX % mosaicH)) * xOffset; \
|
||||
outX += mosaicH - (outX % mosaicH); \
|
||||
if (!inX && xOffset > 0) { \
|
||||
inX = mosaicH - (outX % mosaicH); \
|
||||
outX += mosaicH - (outX % mosaicH); \
|
||||
} else if (inX == width - xOffset) { \
|
||||
inX = mosaicH + (outX % mosaicH); \
|
||||
outX += mosaicH - (outX % mosaicH); \
|
||||
} \
|
||||
} \
|
||||
for (; outX < condition; ++outX, inX += xOffset) { \
|
||||
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
|
||||
continue; \
|
||||
} \
|
||||
int localX = inX - xOffset * (outX % mosaicH); \
|
||||
if (localX < 0 || localX > width - 1) { \
|
||||
continue; \
|
||||
} \
|
||||
SPRITE_XBASE_ ## DEPTH(localX); \
|
||||
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
|
||||
}
|
||||
|
@ -1584,8 +1693,13 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \
|
||||
renderer->spriteLayer[outX] = palette[tileData] | flags; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
if (tileData) { \
|
||||
renderer->spriteLayer[outX] = palette[tileData] | flags; \
|
||||
} else if (current != FLAG_UNWRITTEN) { \
|
||||
renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
|
||||
|
@ -1601,8 +1715,13 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \
|
||||
renderer->spriteLayer[outX] = palette[tileData] | flags; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
if (tileData) { \
|
||||
renderer->spriteLayer[outX] = palette[tileData] | flags; \
|
||||
} else if (current != FLAG_UNWRITTEN) { \
|
||||
renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
|
||||
|
@ -1636,6 +1755,7 @@ static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct G
|
|||
|
||||
int inY = y - (int) GBAObjAttributesAGetY(sprite->a);
|
||||
|
||||
uint32_t current;
|
||||
if (GBAObjAttributesAIsTransformed(sprite->a)) {
|
||||
int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||
int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba-video.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
typedef uint16_t color_t;
|
||||
|
@ -113,7 +113,7 @@ struct GBAVideoSoftwareRenderer {
|
|||
struct GBAVideoRenderer d;
|
||||
|
||||
color_t* outputBuffer;
|
||||
unsigned outputBufferStride;
|
||||
int outputBufferStride;
|
||||
|
||||
GBARegisterDISPCNT dispcnt;
|
||||
|
||||
|
|
|
@ -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*);
|
|
@ -1,11 +1,11 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-savedata.h"
|
||||
#include "savedata.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba/gba.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
@ -18,7 +18,7 @@ static void _flashErase(struct GBASavedata* savedata);
|
|||
static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
|
||||
|
||||
void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
|
||||
savedata->type = SAVEDATA_NONE;
|
||||
savedata->type = SAVEDATA_AUTODETECT;
|
||||
savedata->data = 0;
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
savedata->flashState = FLASH_STATE_RAW;
|
||||
|
@ -42,7 +42,8 @@ void GBASavedataDeinit(struct GBASavedata* savedata) {
|
|||
case SAVEDATA_EEPROM:
|
||||
savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_EEPROM);
|
||||
break;
|
||||
case SAVEDATA_NONE:
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
break;
|
||||
}
|
||||
savedata->vf = 0;
|
||||
|
@ -60,12 +61,13 @@ void GBASavedataDeinit(struct GBASavedata* savedata) {
|
|||
case SAVEDATA_EEPROM:
|
||||
mappedMemoryFree(savedata->data, SIZE_CART_EEPROM);
|
||||
break;
|
||||
case SAVEDATA_NONE:
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
savedata->data = 0;
|
||||
savedata->type = SAVEDATA_NONE;
|
||||
savedata->type = SAVEDATA_AUTODETECT;
|
||||
}
|
||||
|
||||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) {
|
||||
|
@ -94,7 +96,8 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
|||
return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
|
||||
case SAVEDATA_EEPROM:
|
||||
return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
|
||||
case SAVEDATA_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
return true;
|
||||
}
|
||||
} else if (savedata->vf) {
|
||||
|
@ -109,34 +112,62 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) {
|
||||
if (savedata->type != SAVEDATA_AUTODETECT) {
|
||||
struct VFile* vf = savedata->vf;
|
||||
GBASavedataDeinit(savedata);
|
||||
GBASavedataInit(savedata, vf);
|
||||
}
|
||||
switch (type) {
|
||||
case SAVEDATA_FLASH512:
|
||||
case SAVEDATA_FLASH1M:
|
||||
savedata->type = type;
|
||||
GBASavedataInitFlash(savedata);
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
GBASavedataInitEEPROM(savedata);
|
||||
break;
|
||||
case SAVEDATA_SRAM:
|
||||
GBASavedataInitSRAM(savedata);
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
savedata->type = SAVEDATA_FORCE_NONE;
|
||||
break;
|
||||
case SAVEDATA_AUTODETECT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
||||
if (savedata->type == SAVEDATA_NONE) {
|
||||
if (savedata->type == SAVEDATA_AUTODETECT) {
|
||||
savedata->type = SAVEDATA_FLASH512;
|
||||
}
|
||||
if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) {
|
||||
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
|
||||
return;
|
||||
}
|
||||
size_t flashSize = SIZE_CART_FLASH512;
|
||||
off_t end;
|
||||
if (!savedata->vf) {
|
||||
end = 0;
|
||||
savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M);
|
||||
} else {
|
||||
end = savedata->vf->seek(savedata->vf, 0, SEEK_END);
|
||||
end = savedata->vf->size(savedata->vf);
|
||||
if (end < SIZE_CART_FLASH512) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
|
||||
flashSize = SIZE_CART_FLASH1M;
|
||||
}
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
|
||||
}
|
||||
|
||||
savedata->currentBank = savedata->data;
|
||||
if (end < SIZE_CART_FLASH512) {
|
||||
memset(&savedata->data[end], 0xFF, SIZE_CART_FLASH512 - end);
|
||||
memset(&savedata->data[end], 0xFF, flashSize - end);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
|
||||
if (savedata->type == SAVEDATA_NONE) {
|
||||
if (savedata->type == SAVEDATA_AUTODETECT) {
|
||||
savedata->type = SAVEDATA_EEPROM;
|
||||
} else {
|
||||
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
|
||||
|
@ -147,7 +178,7 @@ void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
|
|||
end = 0;
|
||||
savedata->data = anonymousMemoryMap(SIZE_CART_EEPROM);
|
||||
} else {
|
||||
end = savedata->vf->seek(savedata->vf, 0, SEEK_END);
|
||||
end = savedata->vf->size(savedata->vf);
|
||||
if (end < SIZE_CART_EEPROM) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM);
|
||||
}
|
||||
|
@ -159,7 +190,7 @@ void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
|
|||
}
|
||||
|
||||
void GBASavedataInitSRAM(struct GBASavedata* savedata) {
|
||||
if (savedata->type == SAVEDATA_NONE) {
|
||||
if (savedata->type == SAVEDATA_AUTODETECT) {
|
||||
savedata->type = SAVEDATA_SRAM;
|
||||
} else {
|
||||
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
|
||||
|
@ -170,7 +201,7 @@ void GBASavedataInitSRAM(struct GBASavedata* savedata) {
|
|||
end = 0;
|
||||
savedata->data = anonymousMemoryMap(SIZE_CART_SRAM);
|
||||
} else {
|
||||
end = savedata->vf->seek(savedata->vf, 0, SEEK_END);
|
||||
end = savedata->vf->size(savedata->vf);
|
||||
if (end < SIZE_CART_SRAM) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM);
|
||||
}
|
||||
|
@ -348,27 +379,31 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
|||
}
|
||||
|
||||
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash bank switch to bank %i", bank);
|
||||
savedata->currentBank = &savedata->data[bank << 16];
|
||||
if (bank > 0) {
|
||||
if (bank > 0 && savedata->type == SAVEDATA_FLASH512) {
|
||||
savedata->type = SAVEDATA_FLASH1M;
|
||||
if (savedata->vf) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
|
||||
memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _flashErase(struct GBASavedata* savedata) {
|
||||
size_t size = 0x10000;
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash chip erase");
|
||||
size_t size = SIZE_CART_FLASH512;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
size = 0x20000;
|
||||
size = SIZE_CART_FLASH1M;
|
||||
}
|
||||
memset(savedata->data, 0xFF, size);
|
||||
}
|
||||
|
||||
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
|
||||
size_t size = 0x1000;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at %#04x", sectorStart);
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
|
||||
}
|
||||
memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -11,7 +11,8 @@
|
|||
struct VFile;
|
||||
|
||||
enum SavedataType {
|
||||
SAVEDATA_NONE = 0,
|
||||
SAVEDATA_AUTODETECT = -1,
|
||||
SAVEDATA_FORCE_NONE = 0,
|
||||
SAVEDATA_SRAM,
|
||||
SAVEDATA_FLASH512,
|
||||
SAVEDATA_FLASH1M,
|
||||
|
@ -83,6 +84,7 @@ void GBASavedataDeinit(struct GBASavedata* savedata);
|
|||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
|
||||
void GBASavedataUnmask(struct GBASavedata* savedata);
|
||||
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type);
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata);
|
||||
void GBASavedataInitEEPROM(struct GBASavedata* savedata);
|
|
@ -1,15 +1,15 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-serialize.h"
|
||||
#include "serialize.h"
|
||||
|
||||
#include "gba-audio.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-rr.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba-video.h"
|
||||
#include "gba/audio.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/supervisor/rr.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
@ -40,20 +40,22 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
memcpy(state->cpu.bankedRegisters, gba->cpu->bankedRegisters, 6 * 7 * sizeof(int32_t));
|
||||
memcpy(state->cpu.bankedSPSRs, gba->cpu->bankedSPSRs, 6 * sizeof(int32_t));
|
||||
|
||||
state->biosPrefetch = gba->memory.biosPrefetch;
|
||||
state->cpuPrefetch[0] = gba->cpu->prefetch[0];
|
||||
state->cpuPrefetch[1] = gba->cpu->prefetch[1];
|
||||
|
||||
GBAMemorySerialize(&gba->memory, state);
|
||||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
state->associatedStreamId = gba->rr->streamId;
|
||||
GBARRFinishSegment(gba->rr);
|
||||
} else {
|
||||
state->associatedStreamId = 0;
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
gba->rr->stateSaved(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
||||
return;
|
||||
|
@ -80,12 +82,29 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
memcpy(gba->cpu->bankedSPSRs, state->cpu.bankedSPSRs, 6 * sizeof(int32_t));
|
||||
gba->cpu->privilegeMode = gba->cpu->cpsr.priv;
|
||||
gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]);
|
||||
if (state->biosPrefetch) {
|
||||
gba->memory.biosPrefetch = state->biosPrefetch;
|
||||
}
|
||||
if (gba->cpu->cpsr.t) {
|
||||
gba->cpu->executionMode = MODE_THUMB;
|
||||
LOAD_16(gba->cpu->prefetch, (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
|
||||
gba->cpu->prefetch[0] = state->cpuPrefetch[0] & 0xFFFF;
|
||||
gba->cpu->prefetch[1] = state->cpuPrefetch[1] & 0xFFFF;
|
||||
} else {
|
||||
// Maintain backwards compat
|
||||
LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
LOAD_16(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
}
|
||||
} else {
|
||||
gba->cpu->executionMode = MODE_ARM;
|
||||
LOAD_32(gba->cpu->prefetch, (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
|
||||
gba->cpu->prefetch[0] = state->cpuPrefetch[0];
|
||||
gba->cpu->prefetch[1] = state->cpuPrefetch[1];
|
||||
} else {
|
||||
// Maintain backwards compat
|
||||
LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
LOAD_32(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
}
|
||||
}
|
||||
|
||||
GBAMemoryDeserialize(&gba->memory, state);
|
||||
|
@ -93,17 +112,8 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
if (state->associatedStreamId != gba->rr->streamId) {
|
||||
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);
|
||||
if (gba->rr) {
|
||||
gba->rr->stateLoaded(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,22 +183,23 @@ static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
|||
}
|
||||
#endif
|
||||
|
||||
bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot) {
|
||||
struct VFile* vf = GBAGetState(gba, dir, slot, true);
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBASaveStateNamed(gba, vf, screenshot);
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);
|
||||
vf->close(vf);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) {
|
||||
struct VFile* vf = GBAGetState(gba, dir, slot, false);
|
||||
bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBALoadStateNamed(gba, vf);
|
||||
threadContext->rewindBufferSize = 0;
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf);
|
||||
vf->close(vf);
|
||||
return success;
|
||||
}
|
||||
|
@ -247,6 +258,28 @@ void GBARecordFrame(struct GBAThread* thread) {
|
|||
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
|
||||
}
|
||||
|
||||
void GBARewindSettingsChanged(struct GBAThread* threadContext, int newCapacity, int newInterval) {
|
||||
if (newCapacity == threadContext->rewindBufferCapacity && newInterval == threadContext->rewindBufferInterval) {
|
||||
return;
|
||||
}
|
||||
threadContext->rewindBufferInterval = newInterval;
|
||||
threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
|
||||
threadContext->rewindBufferSize = 0;
|
||||
if (threadContext->rewindBuffer) {
|
||||
int i;
|
||||
for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
|
||||
GBADeallocateState(threadContext->rewindBuffer[i]);
|
||||
}
|
||||
free(threadContext->rewindBuffer);
|
||||
}
|
||||
threadContext->rewindBufferCapacity = newCapacity;
|
||||
if (threadContext->rewindBufferCapacity > 0) {
|
||||
threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*));
|
||||
} else {
|
||||
threadContext->rewindBuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GBARewind(struct GBAThread* thread, int nStates) {
|
||||
if (nStates > thread->rewindBufferSize || nStates < 0) {
|
||||
nStates = thread->rewindBufferSize;
|
||||
|
@ -262,7 +295,11 @@ void GBARewind(struct GBAThread* thread, int nStates) {
|
|||
if (!state) {
|
||||
return;
|
||||
}
|
||||
thread->rewindBufferSize -= nStates;
|
||||
thread->rewindBufferWriteOffset = (offset + thread->rewindBufferCapacity - nStates) % thread->rewindBufferCapacity;
|
||||
thread->rewindBufferSize -= nStates - 1;
|
||||
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
|
||||
GBADeserialize(thread->gba, state);
|
||||
}
|
||||
|
||||
void GBARewindAll(struct GBAThread* thread) {
|
||||
GBARewind(thread, thread->rewindBufferSize);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba/gba.h"
|
||||
|
||||
extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||
|
||||
|
@ -77,7 +77,7 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | 0x001F0 - 0x001F3: Next hblank IRQ
|
||||
* | 0x001F4 - 0x001F7: Next vblank IRQ
|
||||
* | 0x001F8 - 0x001FB: Next vcounter IRQ
|
||||
* | 0x001FC - 0x001FF: Reserved
|
||||
* | 0x001FC - 0x001FF: Frame counter
|
||||
* 0x00200 - 0x00213: Timer 0
|
||||
* | 0x00200 - 0x00201: Reload value
|
||||
* | 0x00202 - 0x00203: Old reload value
|
||||
|
@ -126,25 +126,35 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | 0x00284 - 0x00287: DMA next destination
|
||||
* | 0x00288 - 0x0028B: DMA next count
|
||||
* | 0x0028C - 0x0028F: DMA next event
|
||||
* 0x00290 - 0x002BF: GPIO state
|
||||
* 0x00290 - 0x002C3: GPIO state
|
||||
* | 0x00290 - 0x00291: Pin state
|
||||
* | 0x00292 - 0x00293: Direction state
|
||||
* | 0x00294 - 0x002B6: RTC state (see gba-gpio.h for format)
|
||||
* | 0x00294 - 0x002B6: RTC state (see gba-hardware.h for format)
|
||||
* | 0x002B7 - 0x002B7: GPIO devices
|
||||
* | bit 0: Has RTC values
|
||||
* | bit 1: Has rumble value (reserved)
|
||||
* | bit 2: Has light sensor value (reserved)
|
||||
* | bit 2: Has light sensor value
|
||||
* | bit 3: Has gyroscope value
|
||||
* | bit 4: Has tilt values (reserved)
|
||||
* | bit 4: Has tilt values
|
||||
* | bits 5 - 7: Reserved
|
||||
* | 0x002B8 - 0x002B9: Gyroscope sample
|
||||
* | 0x002BA - 0x002BB: Tilt x sample (reserved)
|
||||
* | 0x002BC - 0x002BD: Tilt y sample (reserved)
|
||||
* | 0x002BA - 0x002BB: Tilt x sample
|
||||
* | 0x002BC - 0x002BD: Tilt y sample
|
||||
* | 0x002BE - 0x002BF: Flags
|
||||
* | bit 0: Is read enabled
|
||||
* | bit 1: Gyroscope sample is edge
|
||||
* | bits 2 - 15: Reserved
|
||||
* 0x002C0 - 0x002FF: Reserved (leave zero)
|
||||
* | bit 2: Light sample is edge
|
||||
* | bit 3: Reserved
|
||||
* | bits 4 - 15: Light counter
|
||||
* | 0x002C0 - 0x002C0: Light sample
|
||||
* | 0x002C1 - 0x002C3: Flags
|
||||
* | bits 0 - 1: Tilt state machine
|
||||
* | bits 2 - 31: Reserved
|
||||
* 0x002C4 - 0x002F3: Reserved (leave zero)
|
||||
* 0x002F4 - 0x002FF: Prefetch
|
||||
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
|
||||
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
|
||||
* | 0x002FC - 0x002FF: CPU prefetch (fetch slot)
|
||||
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
|
||||
* 0x00304 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00400 - 0x007FF: I/O memory
|
||||
|
@ -230,7 +240,7 @@ struct GBASerializedState {
|
|||
int32_t nextHblankIRQ;
|
||||
int32_t nextVblankIRQ;
|
||||
int32_t nextVcounterIRQ;
|
||||
int32_t : 32;
|
||||
int32_t frameCounter;
|
||||
} video;
|
||||
|
||||
struct GBATimer timers[4];
|
||||
|
@ -247,15 +257,24 @@ struct GBASerializedState {
|
|||
uint16_t pinDirection;
|
||||
struct GBARTC rtc;
|
||||
uint8_t devices;
|
||||
// Do not change these to uint16_t, this breaks bincompat with some older compilers
|
||||
unsigned gyroSample : 16;
|
||||
unsigned tiltSampleX : 16;
|
||||
unsigned tiltSampleY : 16;
|
||||
unsigned readWrite : 1;
|
||||
unsigned gyroEdge : 1;
|
||||
unsigned reserved : 14;
|
||||
} gpio;
|
||||
unsigned lightEdge : 1;
|
||||
unsigned : 1;
|
||||
unsigned lightCounter : 12;
|
||||
unsigned lightSample : 8;
|
||||
unsigned tiltState : 2;
|
||||
unsigned : 22;
|
||||
} hw;
|
||||
|
||||
uint32_t reservedGpio[16];
|
||||
uint32_t reservedHardware[12];
|
||||
|
||||
uint32_t biosPrefetch;
|
||||
uint32_t cpuPrefetch[2];
|
||||
|
||||
uint32_t associatedStreamId;
|
||||
|
||||
|
@ -270,12 +289,13 @@ struct GBASerializedState {
|
|||
};
|
||||
|
||||
struct VDir;
|
||||
struct GBAThread;
|
||||
|
||||
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 GBA* gba, struct VDir* dir, int slot, bool screenshot);
|
||||
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot);
|
||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
|
||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write);
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);
|
||||
|
@ -284,8 +304,9 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf);
|
|||
struct GBASerializedState* GBAAllocateState(void);
|
||||
void GBADeallocateState(struct GBASerializedState* state);
|
||||
|
||||
struct GBAThread;
|
||||
void GBARecordFrame(struct GBAThread* thread);
|
||||
void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int newInterval);
|
||||
void GBARewind(struct GBAThread* thread, int nStates);
|
||||
void GBARewindAll(struct GBAThread* thread);
|
||||
|
||||
#endif
|
|
@ -1,11 +1,18 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-sio.h"
|
||||
#include "sio.h"
|
||||
|
||||
#include "gba-io.h"
|
||||
#include "gba/io.h"
|
||||
|
||||
const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = {
|
||||
{ 38326, 73003, 107680, 142356 },
|
||||
{ 9582, 18251, 26920, 35589 },
|
||||
{ 6388, 12167, 17947, 23726 },
|
||||
{ 3194, 6075, 8973, 11863 }
|
||||
};
|
||||
|
||||
static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) {
|
||||
switch (mode) {
|
||||
|
@ -22,18 +29,18 @@ static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mo
|
|||
}
|
||||
|
||||
static void _switchMode(struct GBASIO* sio) {
|
||||
unsigned mode = ((sio->rcnt >> 14) & 0xC) | ((sio->siocnt >> 12) & 0x3);
|
||||
unsigned mode = ((sio->rcnt & 0xC000) | (sio->siocnt & 0x3000)) >> 12;
|
||||
enum GBASIOMode oldMode = sio->mode;
|
||||
if (mode < 8) {
|
||||
sio->mode = (enum GBASIOMode) (mode & 0x3);
|
||||
} else {
|
||||
sio->mode = (enum GBASIOMode) (mode & 0xC);
|
||||
}
|
||||
if (oldMode != mode) {
|
||||
if (oldMode != sio->mode) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
sio->activeDriver = _lookupDriver(sio, mode);
|
||||
sio->activeDriver = _lookupDriver(sio, sio->mode);
|
||||
if (sio->activeDriver && sio->activeDriver->load) {
|
||||
sio->activeDriver->load(sio->activeDriver);
|
||||
}
|
||||
|
@ -52,6 +59,9 @@ void GBASIOInit(struct GBASIO* sio) {
|
|||
}
|
||||
|
||||
void GBASIODeinit(struct GBASIO* sio) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
if (sio->drivers.multiplayer && sio->drivers.multiplayer->deinit) {
|
||||
sio->drivers.multiplayer->deinit(sio->drivers.multiplayer);
|
||||
}
|
||||
|
@ -112,7 +122,8 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI
|
|||
}
|
||||
|
||||
void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) {
|
||||
sio->rcnt = value;
|
||||
sio->rcnt &= 0xF;
|
||||
sio->rcnt |= value & ~0xF;
|
||||
_switchMode(sio);
|
||||
if (sio->activeDriver && sio->activeDriver->writeRegister) {
|
||||
sio->activeDriver->writeRegister(sio->activeDriver, REG_RCNT, value);
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,6 +8,10 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#define MAX_GBAS 4
|
||||
|
||||
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
||||
|
||||
enum GBASIOMode {
|
||||
SIO_NORMAL_8 = 0,
|
||||
SIO_NORMAL_32 = 1,
|
|
@ -1,13 +1,13 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-cli.h"
|
||||
#include "cli.h"
|
||||
|
||||
#include "gba-io.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
|
||||
|
@ -15,13 +15,18 @@ static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share
|
|||
|
||||
static void _GBACLIDebuggerInit(struct CLIDebuggerSystem*);
|
||||
static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem*);
|
||||
static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem*);
|
||||
static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||
|
||||
static void _frame(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _load(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _rewind(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _save(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
||||
struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = {
|
||||
{ "frame", _frame, 0, "Frame advance" },
|
||||
{ "load", _load, CLIDVParse, "Load a savestate" },
|
||||
{ "rewind", _rewind, CLIDVParse, "Rewind the emulation a number of recorded intervals" },
|
||||
{ "save", _save, CLIDVParse, "Save a savestate" },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
@ -32,12 +37,15 @@ struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) {
|
|||
#ifdef USE_CLI_DEBUGGER
|
||||
debugger->d.init = _GBACLIDebuggerInit;
|
||||
debugger->d.deinit = _GBACLIDebuggerDeinit;
|
||||
debugger->d.custom = _GBACLIDebuggerCustom;
|
||||
debugger->d.lookupIdentifier = _GBACLIDebuggerLookupIdentifier;
|
||||
|
||||
debugger->d.name = "Game Boy Advance";
|
||||
debugger->d.commands = _GBACLIDebuggerCommands;
|
||||
|
||||
debugger->context = context;
|
||||
#else
|
||||
UNUSED(context);
|
||||
#endif
|
||||
|
||||
return debugger;
|
||||
|
@ -45,26 +53,52 @@ struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) {
|
|||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
static void _GBACLIDebuggerInit(struct CLIDebuggerSystem* debugger) {
|
||||
UNUSED(debugger);
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger;
|
||||
|
||||
gbaDebugger->frameAdvance = false;
|
||||
}
|
||||
|
||||
static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem* debugger) {
|
||||
UNUSED(debugger);
|
||||
}
|
||||
|
||||
static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) {
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger;
|
||||
|
||||
if (gbaDebugger->frameAdvance) {
|
||||
if (!gbaDebugger->inVblank && GBARegisterDISPSTATIsInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1])) {
|
||||
ARMDebuggerEnter(&gbaDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
gbaDebugger->frameAdvance = false;
|
||||
return false;
|
||||
}
|
||||
gbaDebugger->inVblank = GBARegisterDISPSTATGetInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
UNUSED(debugger);
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
const char* reg = GBAIORegisterNames[i >> 1];
|
||||
if (reg && strcasecmp(reg, name) == 0) {
|
||||
return GBALoad16(gbaDebugger->context->gba->cpu, BASE_IO | i, 0);
|
||||
return BASE_IO | i;
|
||||
}
|
||||
}
|
||||
dv->type = CLIDV_ERROR_TYPE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
debugger->d.state = DEBUGGER_CUSTOM;
|
||||
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
gbaDebugger->frameAdvance = true;
|
||||
gbaDebugger->inVblank = GBARegisterDISPSTATGetInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1]);
|
||||
}
|
||||
|
||||
static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
|
@ -78,7 +112,18 @@ static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
|
||||
GBALoadState(gbaDebugger->context->gba, gbaDebugger->context->stateDir, dv->intValue);
|
||||
GBALoadState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue);
|
||||
}
|
||||
|
||||
static void _rewind(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
if (!dv) {
|
||||
GBARewindAll(gbaDebugger->context);
|
||||
} else if (dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
} else {
|
||||
GBARewind(gbaDebugger->context, dv->intValue);
|
||||
}
|
||||
}
|
||||
|
||||
static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -94,6 +139,6 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
|
||||
GBASaveState(gbaDebugger->context->gba, gbaDebugger->context->stateDir, dv->intValue, true);
|
||||
GBASaveState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, true);
|
||||
}
|
||||
#endif
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -15,6 +15,9 @@ struct GBACLIDebugger {
|
|||
struct CLIDebuggerSystem d;
|
||||
|
||||
struct GBAThread* context;
|
||||
|
||||
bool frameAdvance;
|
||||
bool inVblank;
|
||||
#endif
|
||||
};
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-config.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "platform/commandline.h"
|
||||
#include "util/formatting.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
@ -13,6 +13,9 @@
|
|||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <strsafe.h>
|
||||
#define PATH_SEP "\\"
|
||||
#else
|
||||
#define PATH_SEP "/"
|
||||
#endif
|
||||
|
||||
#define SECTION_NAME_MAX 128
|
||||
|
@ -84,7 +87,7 @@ static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, f
|
|||
return false;
|
||||
}
|
||||
char* end;
|
||||
float value = strtof(charValue, &end);
|
||||
float value = strtof_u(charValue, &end);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
|
@ -111,34 +114,31 @@ void GBAConfigDeinit(struct GBAConfig* config) {
|
|||
|
||||
bool GBAConfigLoad(struct GBAConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
#ifndef _WIN32
|
||||
char* home = getenv("HOME");
|
||||
snprintf(path, PATH_MAX, "%s/.config/%s/config.ini", home, BINARY_NAME);
|
||||
#else
|
||||
char home[MAX_PATH];
|
||||
SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home);
|
||||
snprintf(path, PATH_MAX, "%s/%s/config.ini", home, PROJECT_NAME);
|
||||
#endif
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return ConfigurationRead(&config->configTable, path);
|
||||
}
|
||||
|
||||
bool GBAConfigSave(const struct GBAConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return ConfigurationWrite(&config->configTable, path);
|
||||
}
|
||||
|
||||
void GBAConfigDirectory(char* out, size_t outLength) {
|
||||
#ifndef _WIN32
|
||||
char* home = getenv("HOME");
|
||||
snprintf(path, PATH_MAX, "%s/.config", home);
|
||||
mkdir(path, 0755);
|
||||
snprintf(path, PATH_MAX, "%s/.config/%s", home, BINARY_NAME);
|
||||
mkdir(path, 0755);
|
||||
snprintf(path, PATH_MAX, "%s/.config/%s/config.ini", home, BINARY_NAME);
|
||||
snprintf(out, outLength, "%s/.config", home);
|
||||
mkdir(out, 0755);
|
||||
snprintf(out, outLength, "%s/.config/%s", home, BINARY_NAME);
|
||||
mkdir(out, 0755);
|
||||
#else
|
||||
char home[MAX_PATH];
|
||||
SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home);
|
||||
snprintf(path, PATH_MAX, "%s/%s", home, PROJECT_NAME);
|
||||
CreateDirectoryA(path, NULL);
|
||||
snprintf(path, PATH_MAX, "%s/%s/config.ini", home, PROJECT_NAME);
|
||||
snprintf(out, outLength, "%s\\%s", home, PROJECT_NAME);
|
||||
CreateDirectoryA(out, NULL);
|
||||
#endif
|
||||
return ConfigurationWrite(&config->configTable, path);
|
||||
}
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) {
|
||||
|
@ -196,16 +196,42 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
if (_lookupIntValue(config, "videoSync", &fakeBool)) {
|
||||
opts->videoSync = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) {
|
||||
opts->lockAspectRatio = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "resampleVideo", &fakeBool)) {
|
||||
opts->resampleVideo = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "skipBios", &fakeBool)) {
|
||||
opts->skipBios = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "rewindEnable", &fakeBool)) {
|
||||
opts->rewindEnable = fakeBool;
|
||||
}
|
||||
|
||||
_lookupIntValue(config, "fullscreen", &opts->fullscreen);
|
||||
_lookupIntValue(config, "width", &opts->width);
|
||||
_lookupIntValue(config, "height", &opts->height);
|
||||
|
||||
char* idleOptimization = 0;
|
||||
if (_lookupCharValue(config, "idleOptimization", &idleOptimization)) {
|
||||
if (strcasecmp(idleOptimization, "ignore") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_IGNORE;
|
||||
} else if (strcasecmp(idleOptimization, "remove") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
} else if (strcasecmp(idleOptimization, "detect") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_DETECT;
|
||||
}
|
||||
free(idleOptimization);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) {
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
|
||||
ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
|
||||
|
@ -215,6 +241,29 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
|
|||
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo);
|
||||
|
||||
switch (opts->idleOptimization) {
|
||||
case IDLE_LOOP_IGNORE:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "ignore");
|
||||
break;
|
||||
case IDLE_LOOP_REMOVE:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "remove");
|
||||
break;
|
||||
case IDLE_LOOP_DETECT:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "detect");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// These two are basically placeholders in case the internal layout changes, e.g. for loading separate files
|
||||
struct Configuration* GBAConfigGetInput(struct GBAConfig* config) {
|
||||
return &config->configTable;
|
||||
}
|
||||
|
||||
struct Configuration* GBAConfigGetOverrides(struct GBAConfig* config) {
|
||||
return &config->configTable;
|
||||
}
|
||||
|
||||
void GBAConfigFreeOpts(struct GBAOptions* opts) {
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
|
||||
struct GBAConfig {
|
||||
|
@ -18,8 +20,10 @@ struct GBAConfig {
|
|||
|
||||
struct GBAOptions {
|
||||
char* bios;
|
||||
bool skipBios;
|
||||
int logLevel;
|
||||
int frameskip;
|
||||
bool rewindEnable;
|
||||
int rewindBufferCapacity;
|
||||
int rewindBufferInterval;
|
||||
float fpsTarget;
|
||||
|
@ -28,9 +32,13 @@ struct GBAOptions {
|
|||
int fullscreen;
|
||||
int width;
|
||||
int height;
|
||||
bool lockAspectRatio;
|
||||
bool resampleVideo;
|
||||
|
||||
bool videoSync;
|
||||
bool audioSync;
|
||||
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
};
|
||||
|
||||
void GBAConfigInit(struct GBAConfig*, const char* port);
|
||||
|
@ -39,6 +47,8 @@ void GBAConfigDeinit(struct GBAConfig*);
|
|||
bool GBAConfigLoad(struct GBAConfig*);
|
||||
bool GBAConfigSave(const struct GBAConfig*);
|
||||
|
||||
void GBAConfigDirectory(char* out, size_t outLength);
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig*, const char* key);
|
||||
|
||||
void GBAConfigSetValue(struct GBAConfig*, const char* key, const char* value);
|
||||
|
@ -54,6 +64,9 @@ void GBAConfigSetDefaultFloatValue(struct GBAConfig*, const char* key, float val
|
|||
void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts);
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts);
|
||||
|
||||
struct Configuration* GBAConfigGetInput(struct GBAConfig*);
|
||||
struct Configuration* GBAConfigGetOverrides(struct GBAConfig*);
|
||||
|
||||
void GBAConfigFreeOpts(struct GBAOptions* opts);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,260 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "overrides.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/hardware.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
|
||||
static const struct GBACartridgeOverride _overrides[] = {
|
||||
// Boktai: The Sun is in Your Hand
|
||||
{ "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U3IP", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Boktai 2: Solar Boy Django
|
||||
{ "U32J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U32E", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U32P", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Drill Dozer
|
||||
{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
{ "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
|
||||
// Final Fantasy Tactics Advance
|
||||
{ "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 },
|
||||
|
||||
// Golden Sun: The Lost Age
|
||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
||||
|
||||
// Koro Koro Puzzle - Happy Panechu!
|
||||
{ "KHPJ", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE },
|
||||
|
||||
// Mega Man Battle Network
|
||||
{ "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E },
|
||||
|
||||
// Metal Slug Advance
|
||||
{ "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 },
|
||||
|
||||
// Pokemon Ruby
|
||||
{ "AXVJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVE", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVS", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVD", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXVF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon Sapphire
|
||||
{ "AXPJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPE", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPS", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPD", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon Emerald
|
||||
{ "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon Mystery Dungeon
|
||||
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon FireRed
|
||||
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon LeafGreen
|
||||
{ "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// RockMan EXE 4.5 - Real Operation
|
||||
{ "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Shin Bokura no Taiyou: Gyakushuu no Sabata
|
||||
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Super Mario Advance 2
|
||||
{ "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
|
||||
// Super Mario Advance 3
|
||||
{ "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
|
||||
|
||||
// Super Mario Advance 4
|
||||
{ "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Top Gun - Combat Zones
|
||||
{ "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Wario Ware Twisted
|
||||
{ "RZWJ", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE },
|
||||
{ "RZWE", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE },
|
||||
{ "RZWP", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE },
|
||||
|
||||
// Yoshi's Universal Gravitation
|
||||
{ "KYGJ", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE },
|
||||
{ "KYGE", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE },
|
||||
{ "KYGP", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE },
|
||||
|
||||
{ { 0, 0, 0, 0 }, 0, 0, IDLE_LOOP_NONE }
|
||||
};
|
||||
|
||||
bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOverride* override) {
|
||||
override->savetype = SAVEDATA_AUTODETECT;
|
||||
override->hardware = HW_NONE;
|
||||
override->idleLoop = IDLE_LOOP_NONE;
|
||||
bool found;
|
||||
|
||||
if (override->id[0] == 'F') {
|
||||
// Classic NES Series
|
||||
override->savetype = SAVEDATA_EEPROM;
|
||||
found = true;
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; _overrides[i].id[0]; ++i) {
|
||||
if (memcmp(override->id, _overrides[i].id, sizeof(override->id)) == 0) {
|
||||
*override = _overrides[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
char sectionName[16];
|
||||
snprintf(sectionName, sizeof(sectionName), "override.%c%c%c%c", override->id[0], override->id[1], override->id[2], override->id[3]);
|
||||
const char* savetype = ConfigurationGetValue(config, sectionName, "savetype");
|
||||
const char* hardware = ConfigurationGetValue(config, sectionName, "hardware");
|
||||
const char* idleLoop = ConfigurationGetValue(config, sectionName, "idleLoop");
|
||||
|
||||
if (savetype) {
|
||||
if (strcasecmp(savetype, "SRAM") == 0) {
|
||||
found = true;
|
||||
override->savetype = SAVEDATA_SRAM;
|
||||
} else if (strcasecmp(savetype, "EEPROM") == 0) {
|
||||
found = true;
|
||||
override->savetype = SAVEDATA_EEPROM;
|
||||
} else if (strcasecmp(savetype, "FLASH512") == 0) {
|
||||
found = true;
|
||||
override->savetype = SAVEDATA_FLASH512;
|
||||
} else if (strcasecmp(savetype, "FLASH1M") == 0) {
|
||||
found = true;
|
||||
override->savetype = SAVEDATA_FLASH1M;
|
||||
} else if (strcasecmp(savetype, "NONE") == 0) {
|
||||
found = true;
|
||||
override->savetype = SAVEDATA_FORCE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (hardware) {
|
||||
char* end;
|
||||
long type = strtoul(hardware, &end, 0);
|
||||
if (end && !*end) {
|
||||
override->hardware = type;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (idleLoop) {
|
||||
char* end;
|
||||
uint32_t address = strtoul(idleLoop, &end, 16);
|
||||
if (end && !*end) {
|
||||
override->idleLoop = address;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void GBAOverrideSave(struct Configuration* config, const struct GBACartridgeOverride* override) {
|
||||
char sectionName[16];
|
||||
snprintf(sectionName, sizeof(sectionName), "override.%c%c%c%c", override->id[0], override->id[1], override->id[2], override->id[3]);
|
||||
const char* savetype = 0;
|
||||
switch (override->savetype) {
|
||||
case SAVEDATA_SRAM:
|
||||
savetype = "SRAM";
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
savetype = "EEPROM";
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
savetype = "FLASH512";
|
||||
break;
|
||||
case SAVEDATA_FLASH1M:
|
||||
savetype = "FLASH1M";
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
savetype = "NONE";
|
||||
break;
|
||||
case SAVEDATA_AUTODETECT:
|
||||
break;
|
||||
}
|
||||
ConfigurationSetValue(config, sectionName, "savetype", savetype);
|
||||
|
||||
if (override->hardware != HW_NO_OVERRIDE) {
|
||||
ConfigurationSetIntValue(config, sectionName, "hardware", override->hardware);
|
||||
} else {
|
||||
ConfigurationClearValue(config, sectionName, "hardware");
|
||||
}
|
||||
|
||||
if (override->idleLoop != IDLE_LOOP_NONE) {
|
||||
ConfigurationSetUIntValue(config, sectionName, "idleLoop", override->idleLoop);
|
||||
} else {
|
||||
ConfigurationClearValue(config, sectionName, "idleLoop");
|
||||
}
|
||||
}
|
||||
|
||||
void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) {
|
||||
if (override->savetype != SAVEDATA_AUTODETECT) {
|
||||
GBASavedataForceType(&gba->memory.savedata, override->savetype);
|
||||
}
|
||||
|
||||
if (override->hardware != HW_NO_OVERRIDE) {
|
||||
GBAHardwareClear(&gba->memory.hw);
|
||||
|
||||
if (override->hardware & HW_RTC) {
|
||||
GBAHardwareInitRTC(&gba->memory.hw);
|
||||
}
|
||||
|
||||
if (override->hardware & HW_GYRO) {
|
||||
GBAHardwareInitGyro(&gba->memory.hw);
|
||||
}
|
||||
|
||||
if (override->hardware & HW_RUMBLE) {
|
||||
GBAHardwareInitRumble(&gba->memory.hw);
|
||||
}
|
||||
|
||||
if (override->hardware & HW_LIGHT_SENSOR) {
|
||||
GBAHardwareInitLight(&gba->memory.hw);
|
||||
}
|
||||
|
||||
if (override->hardware & HW_TILT) {
|
||||
GBAHardwareInitTilt(&gba->memory.hw);
|
||||
}
|
||||
}
|
||||
|
||||
if (override->idleLoop != IDLE_LOOP_NONE) {
|
||||
gba->idleLoop = override->idleLoop;
|
||||
if (gba->idleOptimization == IDLE_LOOP_DETECT) {
|
||||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_OVERRIDES_H
|
||||
#define GBA_OVERRIDES_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/savedata.h"
|
||||
|
||||
#define IDLE_LOOP_NONE 0xFFFFFFFF
|
||||
|
||||
struct GBACartridgeOverride {
|
||||
char id[4];
|
||||
enum SavedataType savetype;
|
||||
int hardware;
|
||||
uint32_t idleLoop;
|
||||
};
|
||||
|
||||
struct Configuration;
|
||||
bool GBAOverrideFind(const struct Configuration*, struct GBACartridgeOverride* override);
|
||||
void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* override);
|
||||
|
||||
struct GBA;
|
||||
void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,73 @@
|
|||
/* 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 "rr.h"
|
||||
|
||||
#include "util/vfs.h"
|
||||
|
||||
void GBARRInitRecord(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, false);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRInitPlay(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRDestroy(struct GBARRContext* rr) {
|
||||
if (rr->isPlaying(rr)) {
|
||||
rr->stopPlaying(rr);
|
||||
}
|
||||
if (rr->isRecording(rr)) {
|
||||
rr->stopRecording(rr);
|
||||
}
|
||||
if (rr->savedata) {
|
||||
rr->savedata->close(rr->savedata);
|
||||
rr->savedata = 0;
|
||||
}
|
||||
rr->destroy(rr);
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_RR_H
|
||||
#define GBA_RR_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/serialize.h"
|
||||
|
||||
struct VFile;
|
||||
|
||||
enum GBARRInitFrom {
|
||||
INIT_EX_NIHILO = 0,
|
||||
INIT_FROM_SAVEGAME = 1,
|
||||
INIT_FROM_SAVESTATE = 2,
|
||||
INIT_FROM_BOTH = 3,
|
||||
};
|
||||
|
||||
struct GBARRContext {
|
||||
void (*destroy)(struct GBARRContext*);
|
||||
|
||||
bool (*startPlaying)(struct GBARRContext*, bool autorecord);
|
||||
void (*stopPlaying)(struct GBARRContext*);
|
||||
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);
|
||||
|
||||
uint32_t frames;
|
||||
uint32_t lagFrames;
|
||||
enum GBARRInitFrom initFrom;
|
||||
|
||||
uint32_t rrCount;
|
||||
|
||||
struct VFile* savedata;
|
||||
};
|
||||
|
||||
void GBARRDestroy(struct GBARRContext*);
|
||||
|
||||
void GBARRInitRecord(struct GBA*);
|
||||
void GBARRInitPlay(struct GBA*);
|
||||
|
||||
#endif
|
|
@ -1,14 +1,17 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-thread.h"
|
||||
#include "thread.h"
|
||||
|
||||
#include "arm.h"
|
||||
#include "gba.h"
|
||||
#include "gba-config.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/config.h"
|
||||
#include "gba/rr/mgm.h"
|
||||
#include "gba/rr/vbm.h"
|
||||
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
|
@ -59,6 +62,11 @@ static void _waitOnInterrupt(struct GBAThread* threadContext) {
|
|||
}
|
||||
|
||||
static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
bool videoFrameWait = threadContext->sync.videoFrameWait;
|
||||
threadContext->sync.videoFrameWait = false;
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
|
||||
while (threadContext->state == oldState) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
|
@ -73,12 +81,13 @@ static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState
|
|||
MutexLock(&threadContext->stateMutex);
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
threadContext->sync.videoFrameWait = videoFrameWait;
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
|
||||
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
|
||||
threadContext->debugger->state = DEBUGGER_EXITING;
|
||||
}
|
||||
threadContext->state = THREAD_PAUSING;
|
||||
if (!onThread) {
|
||||
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||
|
@ -107,14 +116,11 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
struct GBA gba;
|
||||
struct ARMCore cpu;
|
||||
struct Patch patch;
|
||||
struct GBACheatDevice cheatDevice;
|
||||
struct GBAThread* threadContext = context;
|
||||
struct ARMComponent* components[1] = {};
|
||||
int numComponents = 0;
|
||||
|
||||
if (threadContext->debugger) {
|
||||
components[numComponents] = &threadContext->debugger->d;
|
||||
++numComponents;
|
||||
}
|
||||
struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
|
||||
struct GBARRContext* movie = 0;
|
||||
int numComponents = GBA_COMPONENT_MAX;
|
||||
|
||||
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||
sigset_t signals;
|
||||
|
@ -128,6 +134,9 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
gba.sync = &threadContext->sync;
|
||||
threadContext->gba = &gba;
|
||||
gba.logLevel = threadContext->logLevel;
|
||||
gba.logHandler = threadContext->logHandler;
|
||||
gba.stream = threadContext->stream;
|
||||
gba.idleOptimization = threadContext->idleOptimization;
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_setspecific(_contextKey, threadContext);
|
||||
#else
|
||||
|
@ -146,7 +155,18 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
|
||||
if (threadContext->rom) {
|
||||
GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
|
||||
if (threadContext->bios) {
|
||||
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(threadContext->overrides, &override)) {
|
||||
GBAOverrideApply(&gba, &override);
|
||||
}
|
||||
if (threadContext->hasOverride) {
|
||||
GBAOverrideApply(&gba, &threadContext->override);
|
||||
}
|
||||
|
||||
if (threadContext->bios && GBAIsBIOS(threadContext->bios)) {
|
||||
GBALoadBIOS(&gba, threadContext->bios);
|
||||
}
|
||||
|
||||
|
@ -155,12 +175,60 @@ 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);
|
||||
|
||||
if (movie) {
|
||||
gba.rr = movie;
|
||||
movie->startPlaying(movie, false);
|
||||
GBARRInitPlay(&gba);
|
||||
}
|
||||
|
||||
if (threadContext->skipBios) {
|
||||
GBASkipBIOS(&cpu);
|
||||
}
|
||||
|
||||
if (!threadContext->cheats) {
|
||||
GBACheatDeviceCreate(&cheatDevice);
|
||||
threadContext->cheats = &cheatDevice;
|
||||
}
|
||||
if (threadContext->cheatsFile) {
|
||||
GBACheatParseFile(threadContext->cheats, threadContext->cheatsFile);
|
||||
}
|
||||
GBACheatAttachDevice(&gba, threadContext->cheats);
|
||||
|
||||
if (threadContext->debugger) {
|
||||
threadContext->debugger->log = GBADebuggerLogShim;
|
||||
GBAAttachDebugger(&gba, threadContext->debugger);
|
||||
ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
|
||||
ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
}
|
||||
|
||||
GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
|
||||
|
@ -208,10 +276,13 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
MutexUnlock(&threadContext->stateMutex);
|
||||
if (resetScheduled) {
|
||||
ARMReset(&cpu);
|
||||
if (threadContext->skipBios) {
|
||||
GBASkipBIOS(&cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (threadContext->state != THREAD_SHUTDOWN) {
|
||||
while (threadContext->state < THREAD_SHUTDOWN) {
|
||||
_changeState(threadContext, THREAD_SHUTDOWN, false);
|
||||
}
|
||||
|
||||
|
@ -222,6 +293,14 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
threadContext->gba = 0;
|
||||
ARMDeinit(&cpu);
|
||||
GBADestroy(&gba);
|
||||
if (&cheatDevice == threadContext->cheats) {
|
||||
GBACheatDeviceDestroy(&cheatDevice);
|
||||
}
|
||||
|
||||
if (movie) {
|
||||
movie->destroy(movie);
|
||||
free(movie);
|
||||
}
|
||||
|
||||
threadContext->sync.videoFrameOn = false;
|
||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||
|
@ -234,8 +313,13 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr
|
|||
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
|
||||
threadContext->frameskip = opts->frameskip;
|
||||
threadContext->logLevel = opts->logLevel;
|
||||
threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
|
||||
threadContext->rewindBufferInterval = opts->rewindBufferInterval;
|
||||
if (opts->rewindEnable) {
|
||||
threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
|
||||
threadContext->rewindBufferInterval = opts->rewindBufferInterval;
|
||||
} else {
|
||||
threadContext->rewindBufferCapacity = 0;
|
||||
}
|
||||
threadContext->skipBios = opts->skipBios;
|
||||
threadContext->sync.audioWait = opts->audioSync;
|
||||
threadContext->sync.videoFrameWait = opts->videoSync;
|
||||
|
||||
|
@ -246,6 +330,8 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr
|
|||
if (opts->audioBuffers) {
|
||||
threadContext->audioBuffers = opts->audioBuffers;
|
||||
}
|
||||
|
||||
threadContext->idleOptimization = opts->idleOptimization;
|
||||
}
|
||||
|
||||
void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
|
||||
|
@ -254,12 +340,22 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
|
|||
threadContext->stateDir = threadContext->gameDir;
|
||||
} else {
|
||||
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
|
||||
#if ENABLE_LIBZIP
|
||||
threadContext->gameDir = VDirOpenZip(args->fname, 0);
|
||||
threadContext->gameDir = 0;
|
||||
#if USE_LIBZIP
|
||||
if (!threadContext->gameDir) {
|
||||
threadContext->gameDir = VDirOpenZip(args->fname, 0);
|
||||
}
|
||||
#endif
|
||||
#if USE_LZMA
|
||||
if (!threadContext->gameDir) {
|
||||
threadContext->gameDir = VDirOpen7z(args->fname, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
threadContext->fname = args->fname;
|
||||
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
|
||||
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
|
||||
threadContext->movie = args->movie;
|
||||
}
|
||||
|
||||
bool GBAThreadStart(struct GBAThread* threadContext) {
|
||||
|
@ -269,13 +365,12 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
threadContext->sync.videoFrameOn = true;
|
||||
threadContext->sync.videoFrameSkip = 0;
|
||||
|
||||
threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
|
||||
threadContext->rewindBufferSize = 0;
|
||||
if (threadContext->rewindBufferCapacity) {
|
||||
threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
|
||||
} else {
|
||||
threadContext->rewindBuffer = 0;
|
||||
}
|
||||
threadContext->rewindBuffer = 0;
|
||||
int newCapacity = threadContext->rewindBufferCapacity;
|
||||
int newInterval = threadContext->rewindBufferInterval;
|
||||
threadContext->rewindBufferCapacity = 0;
|
||||
threadContext->rewindBufferInterval = 0;
|
||||
GBARewindSettingsChanged(threadContext, newCapacity, newInterval);
|
||||
|
||||
if (!threadContext->fpsTarget) {
|
||||
threadContext->fpsTarget = _defaultFPSTarget;
|
||||
|
@ -293,6 +388,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
struct Patch patchTemp;
|
||||
struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
|
||||
if (!vf) {
|
||||
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
|
||||
continue;
|
||||
}
|
||||
if (!threadContext->rom && GBAIsROM(vf)) {
|
||||
|
@ -351,18 +447,42 @@ bool GBAThreadHasStarted(struct GBAThread* threadContext) {
|
|||
return hasStarted;
|
||||
}
|
||||
|
||||
bool GBAThreadHasExited(struct GBAThread* threadContext) {
|
||||
bool hasExited;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
hasExited = threadContext->state > THREAD_EXITING;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return hasExited;
|
||||
}
|
||||
|
||||
bool GBAThreadHasCrashed(struct GBAThread* threadContext) {
|
||||
bool hasExited;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
hasExited = threadContext->state == THREAD_CRASHED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return hasExited;
|
||||
}
|
||||
|
||||
void GBAThreadEnd(struct GBAThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
|
||||
threadContext->debugger->state = DEBUGGER_EXITING;
|
||||
}
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->state = THREAD_EXITING;
|
||||
if (threadContext->gba) {
|
||||
threadContext->gba->cpu->halted = false;
|
||||
}
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
MutexLock(&threadContext->sync.audioBufferMutex);
|
||||
threadContext->sync.audioWait = 0;
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.audioBufferMutex);
|
||||
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
threadContext->sync.videoFrameWait = false;
|
||||
threadContext->sync.videoFrameOn = false;
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBAThreadReset(struct GBAThread* threadContext) {
|
||||
|
@ -374,11 +494,6 @@ void GBAThreadReset(struct GBAThread* threadContext) {
|
|||
}
|
||||
|
||||
void GBAThreadJoin(struct GBAThread* threadContext) {
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
threadContext->sync.videoFrameWait = 0;
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
|
||||
ThreadJoin(threadContext->thread);
|
||||
|
||||
MutexDeinit(&threadContext->stateMutex);
|
||||
|
@ -450,9 +565,7 @@ void GBAThreadInterrupt(struct GBAThread* threadContext) {
|
|||
threadContext->savedState = threadContext->state;
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->state = THREAD_INTERRUPTING;
|
||||
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
|
||||
threadContext->debugger->state = DEBUGGER_EXITING;
|
||||
}
|
||||
threadContext->gba->cpu->nextEvent = 0;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
@ -580,25 +693,6 @@ void GBASyncPostFrame(struct GBASync* sync) {
|
|||
} while (sync->videoFrameWait && sync->videoFramePending);
|
||||
}
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (thread->rewindBuffer) {
|
||||
--thread->rewindBufferNext;
|
||||
if (thread->rewindBufferNext <= 0) {
|
||||
thread->rewindBufferNext = thread->rewindBufferInterval;
|
||||
GBARecordFrame(thread);
|
||||
}
|
||||
}
|
||||
if (thread->stream) {
|
||||
thread->stream->postVideoFrame(thread->stream, thread->renderer);
|
||||
}
|
||||
if (thread->frameCallback) {
|
||||
thread->frameCallback(thread);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
|
||||
|
@ -611,8 +705,10 @@ bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
|
|||
if (!sync->videoFrameOn && !sync->videoFramePending) {
|
||||
return false;
|
||||
}
|
||||
if (sync->videoFrameOn && !sync->videoFramePending) {
|
||||
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
|
||||
if (sync->videoFrameOn) {
|
||||
if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sync->videoFramePending = 0;
|
||||
sync->videoFrameSkip = frameskip;
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,16 +8,18 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-input.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/input.h"
|
||||
#include "gba/supervisor/overrides.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
struct GBAThread;
|
||||
struct GBAArguments;
|
||||
struct GBACheatSet;
|
||||
struct GBAOptions;
|
||||
|
||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INITIALIZED = -1,
|
||||
|
@ -28,7 +30,8 @@ enum ThreadState {
|
|||
THREAD_PAUSING,
|
||||
THREAD_RESETING,
|
||||
THREAD_EXITING,
|
||||
THREAD_SHUTDOWN
|
||||
THREAD_SHUTDOWN,
|
||||
THREAD_CRASHED
|
||||
};
|
||||
|
||||
struct GBASync {
|
||||
|
@ -45,11 +48,6 @@ struct GBASync {
|
|||
Mutex audioBufferMutex;
|
||||
};
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right);
|
||||
};
|
||||
|
||||
struct GBAThread {
|
||||
// Output
|
||||
enum ThreadState state;
|
||||
|
@ -66,14 +64,22 @@ struct GBAThread {
|
|||
struct VFile* save;
|
||||
struct VFile* bios;
|
||||
struct VFile* patch;
|
||||
struct VFile* cheatsFile;
|
||||
const char* fname;
|
||||
const char* movie;
|
||||
int activeKeys;
|
||||
struct GBAAVStream* stream;
|
||||
struct Configuration* overrides;
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
|
||||
bool hasOverride;
|
||||
struct GBACartridgeOverride override;
|
||||
|
||||
// Run-time options
|
||||
int frameskip;
|
||||
float fpsTarget;
|
||||
size_t audioBuffers;
|
||||
bool skipBios;
|
||||
|
||||
// Threading state
|
||||
Thread thread;
|
||||
|
@ -83,7 +89,7 @@ struct GBAThread {
|
|||
enum ThreadState savedState;
|
||||
int interruptDepth;
|
||||
|
||||
LogHandler logHandler;
|
||||
GBALogHandler logHandler;
|
||||
int logLevel;
|
||||
ThreadCallback startCallback;
|
||||
ThreadCallback cleanCallback;
|
||||
|
@ -98,6 +104,8 @@ struct GBAThread {
|
|||
int rewindBufferNext;
|
||||
struct GBASerializedState** rewindBuffer;
|
||||
int rewindBufferWriteOffset;
|
||||
|
||||
struct GBACheatDevice* cheats;
|
||||
};
|
||||
|
||||
void GBAMapOptionsToContext(const struct GBAOptions*, struct GBAThread*);
|
||||
|
@ -105,6 +113,8 @@ void GBAMapArgumentsToContext(const struct GBAArguments*, struct GBAThread*);
|
|||
|
||||
bool GBAThreadStart(struct GBAThread* threadContext);
|
||||
bool GBAThreadHasStarted(struct GBAThread* threadContext);
|
||||
bool GBAThreadHasExited(struct GBAThread* threadContext);
|
||||
bool GBAThreadHasCrashed(struct GBAThread* threadContext);
|
||||
void GBAThreadEnd(struct GBAThread* threadContext);
|
||||
void GBAThreadReset(struct GBAThread* threadContext);
|
||||
void GBAThreadJoin(struct GBAThread* threadContext);
|
|
@ -1,15 +1,15 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-video.h"
|
||||
#include "video.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-rr.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/rr.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
|
||||
|
@ -41,8 +41,7 @@ void GBAVideoInit(struct GBAVideo* video) {
|
|||
}
|
||||
|
||||
void GBAVideoReset(struct GBAVideo* video) {
|
||||
video->dispstat = 0;
|
||||
video->vcount = 0;
|
||||
video->vcount = VIDEO_VERTICAL_TOTAL_PIXELS - 1;
|
||||
|
||||
video->lastHblank = 0;
|
||||
video->nextHblank = VIDEO_HDRAW_LENGTH;
|
||||
|
@ -53,6 +52,8 @@ void GBAVideoReset(struct GBAVideo* video) {
|
|||
video->nextVblankIRQ = 0;
|
||||
video->nextVcounterIRQ = 0;
|
||||
|
||||
video->frameCounter = 0;
|
||||
|
||||
if (video->vram) {
|
||||
mappedMemoryFree(video->vram, SIZE_VRAM);
|
||||
}
|
||||
|
@ -94,52 +95,57 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
|
|||
video->nextHblank -= video->eventDiff;
|
||||
video->nextHblankIRQ -= video->eventDiff;
|
||||
video->nextVcounterIRQ -= video->eventDiff;
|
||||
video->eventDiff = 0;
|
||||
uint16_t dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
|
||||
if (GBARegisterDISPSTATIsInHblank(video->dispstat)) {
|
||||
if (GBARegisterDISPSTATIsInHblank(dispstat)) {
|
||||
// End Hblank
|
||||
video->dispstat = GBARegisterDISPSTATClearInHblank(video->dispstat);
|
||||
dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
|
||||
video->nextEvent = video->nextHblank;
|
||||
|
||||
++video->vcount;
|
||||
if (video->vcount == VIDEO_VERTICAL_TOTAL_PIXELS) {
|
||||
video->vcount = 0;
|
||||
}
|
||||
video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
|
||||
|
||||
if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
|
||||
dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
|
||||
if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
|
||||
GBARaiseIRQ(video->p, IRQ_VCOUNTER);
|
||||
video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH;
|
||||
}
|
||||
} else {
|
||||
dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
|
||||
}
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
|
||||
|
||||
// Note: state may be recorded during callbacks, so ensure it is consistent!
|
||||
switch (video->vcount) {
|
||||
case 0:
|
||||
GBAFrameStarted(video->p);
|
||||
break;
|
||||
case VIDEO_VERTICAL_PIXELS:
|
||||
video->dispstat = GBARegisterDISPSTATFillInVblank(video->dispstat);
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
|
||||
if (GBASyncDrawingFrame(video->p->sync)) {
|
||||
video->renderer->finishFrame(video->renderer);
|
||||
}
|
||||
video->nextVblankIRQ = video->nextEvent + VIDEO_TOTAL_LENGTH;
|
||||
GBAMemoryRunVblankDMAs(video->p, lastEvent);
|
||||
if (GBARegisterDISPSTATIsVblankIRQ(video->dispstat)) {
|
||||
if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
|
||||
GBARaiseIRQ(video->p, IRQ_VBLANK);
|
||||
}
|
||||
GBAFrameEnded(video->p);
|
||||
GBASyncPostFrame(video->p->sync);
|
||||
++video->frameCounter;
|
||||
break;
|
||||
case VIDEO_VERTICAL_TOTAL_PIXELS - 1:
|
||||
if (video->p->rr) {
|
||||
GBARRNextFrame(video->p->rr);
|
||||
}
|
||||
video->dispstat = GBARegisterDISPSTATClearInVblank(video->dispstat);
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
|
||||
break;
|
||||
case VIDEO_VERTICAL_TOTAL_PIXELS:
|
||||
video->vcount = 0;
|
||||
video->p->memory.io[REG_VCOUNT >> 1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (video->vcount == GBARegisterDISPSTATGetVcountSetting(video->dispstat)) {
|
||||
video->dispstat = GBARegisterDISPSTATFillVcounter(video->dispstat);
|
||||
if (GBARegisterDISPSTATIsVcounterIRQ(video->dispstat)) {
|
||||
GBARaiseIRQ(video->p, IRQ_VCOUNTER);
|
||||
video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH;
|
||||
}
|
||||
} else {
|
||||
video->dispstat = GBARegisterDISPSTATClearVcounter(video->dispstat);
|
||||
}
|
||||
} else {
|
||||
// Begin Hblank
|
||||
video->dispstat = GBARegisterDISPSTATFillInHblank(video->dispstat);
|
||||
dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
|
||||
video->lastHblank = video->nextHblank;
|
||||
video->nextEvent = video->lastHblank + VIDEO_HBLANK_LENGTH;
|
||||
video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH;
|
||||
|
@ -152,25 +158,24 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
|
|||
if (video->vcount < VIDEO_VERTICAL_PIXELS) {
|
||||
GBAMemoryRunHblankDMAs(video->p, lastEvent);
|
||||
}
|
||||
if (GBARegisterDISPSTATIsHblankIRQ(video->dispstat)) {
|
||||
if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
|
||||
GBARaiseIRQ(video->p, IRQ_HBLANK);
|
||||
}
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
|
||||
}
|
||||
|
||||
video->eventDiff = 0;
|
||||
}
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] &= 0xFFF8;
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] |= video->dispstat & 0x7;
|
||||
return video->nextEvent;
|
||||
}
|
||||
|
||||
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value) {
|
||||
video->dispstat &= 0x7;
|
||||
video->dispstat |= value & 0xFFF8;
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] &= 0x7;
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] |= value;
|
||||
|
||||
if (GBARegisterDISPSTATIsVcounterIRQ(video->dispstat)) {
|
||||
uint16_t dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
|
||||
if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
|
||||
// FIXME: this can be too late if we're in the middle of an Hblank
|
||||
video->nextVcounterIRQ = video->nextHblank + VIDEO_HBLANK_LENGTH + (GBARegisterDISPSTATGetVcountSetting(video->dispstat) - video->vcount) * VIDEO_HORIZONTAL_LENGTH;
|
||||
video->nextVcounterIRQ = video->nextHblank + VIDEO_HBLANK_LENGTH + (GBARegisterDISPSTATGetVcountSetting(dispstat) - video->vcount) * VIDEO_HORIZONTAL_LENGTH;
|
||||
if (video->nextVcounterIRQ < video->nextEvent) {
|
||||
video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH;
|
||||
}
|
||||
|
@ -230,11 +235,10 @@ 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->oam, video->oam.raw, SIZE_OAM);
|
||||
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
||||
state->io[REG_DISPSTAT >> 1] = video->dispstat;
|
||||
state->video.nextEvent = video->nextEvent;
|
||||
state->video.eventDiff = video->eventDiff;
|
||||
state->video.lastHblank = video->lastHblank;
|
||||
|
@ -242,9 +246,10 @@ void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state)
|
|||
state->video.nextHblankIRQ = video->nextHblankIRQ;
|
||||
state->video.nextVblankIRQ = video->nextVblankIRQ;
|
||||
state->video.nextVcounterIRQ = video->nextVcounterIRQ;
|
||||
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);
|
||||
int i;
|
||||
for (i = 0; i < SIZE_OAM; i += 2) {
|
||||
|
@ -253,7 +258,6 @@ void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* stat
|
|||
for (i = 0; i < SIZE_PALETTE_RAM; i += 2) {
|
||||
GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, state->pram[i >> 1], 0);
|
||||
}
|
||||
video->dispstat = state->io[REG_DISPSTAT >> 1];
|
||||
video->nextEvent = state->video.nextEvent;
|
||||
video->eventDiff = state->video.eventDiff;
|
||||
video->lastHblank = state->video.lastHblank;
|
||||
|
@ -261,5 +265,6 @@ void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* stat
|
|||
video->nextHblankIRQ = state->video.nextHblankIRQ;
|
||||
video->nextVblankIRQ = state->video.nextVblankIRQ;
|
||||
video->nextVcounterIRQ = state->video.nextVcounterIRQ;
|
||||
video->frameCounter = state->video.frameCounter;
|
||||
video->vcount = state->io[REG_VCOUNT >> 1];
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba-memory.h"
|
||||
#include "gba/memory.h"
|
||||
#include "macros.h"
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
|
@ -173,8 +173,6 @@ struct GBAVideo {
|
|||
struct GBA* p;
|
||||
struct GBAVideoRenderer* renderer;
|
||||
|
||||
GBARegisterDISPSTAT dispstat;
|
||||
|
||||
// VCOUNT
|
||||
int vcount;
|
||||
|
||||
|
@ -190,6 +188,8 @@ struct GBAVideo {
|
|||
uint16_t palette[SIZE_PALETTE_RAM >> 1];
|
||||
uint16_t* vram;
|
||||
union GBAOAM oam;
|
||||
|
||||
int32_t frameCounter;
|
||||
};
|
||||
|
||||
void GBAVideoInit(struct GBAVideo* video);
|
||||
|
@ -201,7 +201,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles);
|
|||
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
|
@ -21,15 +21,16 @@ static ssize_t _vf3dWrite(struct VFile* vf, const void* buffer, size_t size);
|
|||
static void* _vf3dMap(struct VFile* vf, size_t size, int flags);
|
||||
static void _vf3dUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void _vf3dTruncate(struct VFile* vf, size_t size);
|
||||
static ssize_t _vf3dSize(struct VFile* vf);
|
||||
|
||||
struct VFile* VFileOpen3DS(FS_archive archive, const char* path, int flags) {
|
||||
struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags) {
|
||||
struct VFile3DS* vf3d = malloc(sizeof(struct VFile3DS));
|
||||
if (!vf3d) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FS_path newPath = FS_makePath(PATH_CHAR, path);
|
||||
Result res = FSUSER_OpenFile(0, &vf3d->handle, archive, newPath, flags, FS_ATTRIBUTE_NONE);
|
||||
Result res = FSUSER_OpenFile(0, &vf3d->handle, *archive, newPath, flags, FS_ATTRIBUTE_NONE);
|
||||
if (res & 0xFFFC03FF) {
|
||||
free(vf3d);
|
||||
return 0;
|
||||
|
@ -45,6 +46,7 @@ struct VFile* VFileOpen3DS(FS_archive archive, const char* path, int flags) {
|
|||
vf3d->d.map = _vf3dMap;
|
||||
vf3d->d.unmap = _vf3dUnmap;
|
||||
vf3d->d.truncate = _vf3dTruncate;
|
||||
vf3d->d.size = _vf3dSize;
|
||||
|
||||
return &vf3d->d;
|
||||
}
|
||||
|
@ -117,3 +119,10 @@ static void _vf3dTruncate(struct VFile* vf, size_t size) {
|
|||
struct VFile3DS* vf3d = (struct VFile3DS*) vf;
|
||||
FSFILE_SetSize(vf3d->handle, size);
|
||||
}
|
||||
|
||||
ssize_t _vf3dSize(struct VFile* vf) {
|
||||
struct VFile3DS* vf3d = (struct VFile3DS*) vf;
|
||||
u64 size;
|
||||
FSFILE_GetSize(vf3d->handle, &size);
|
||||
return size;
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
|
||||
#include <3ds.h>
|
||||
|
||||
struct VFile* VFileOpen3DS(FS_archive archive, const char* path, int flags);
|
||||
struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags);
|
||||
|
|
|
@ -13,7 +13,7 @@ endif()
|
|||
set(toolchain_bin_dir ${DEVKITARM}/bin)
|
||||
set(cross_prefix ${toolchain_bin_dir}/arm-none-eabi-)
|
||||
set(inc_flags -I${DEVKITPRO}/libctru/include)
|
||||
set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=softfp")
|
||||
set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard")
|
||||
set(link_flags "-L${DEVKITPRO}/libctru/lib -lctru -lm -specs=3dsx.specs ${arch_flags}")
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name")
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba.h"
|
||||
#include "gba-video.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#include "renderers/video-software.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
#include "3ds-vfs.h"
|
||||
|
@ -17,11 +17,9 @@ int main() {
|
|||
srvInit();
|
||||
aptInit();
|
||||
hidInit(0);
|
||||
gfxInit();
|
||||
gfxInit(GSP_RGB565_OES, GSP_RGB565_OES, false);
|
||||
fsInit();
|
||||
|
||||
gfxSetScreenFormat(GFX_BOTTOM, GSP_RGB565_OES);
|
||||
|
||||
struct GBAVideoSoftwareRenderer renderer;
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
|
||||
|
@ -41,9 +39,9 @@ int main() {
|
|||
};
|
||||
FSUSER_OpenArchive(0, &sdmcArchive);
|
||||
|
||||
struct VFile* rom = VFileOpen3DS(sdmcArchive, "/rom.gba", FS_OPEN_READ);
|
||||
struct VFile* rom = VFileOpen3DS(&sdmcArchive, "/rom.gba", FS_OPEN_READ);
|
||||
|
||||
struct VFile* save = VFileOpen3DS(sdmcArchive, "/rom.sav", FS_OPEN_WRITE | FS_OPEN_CREATE);
|
||||
struct VFile* save = VFileOpen3DS(&sdmcArchive, "/rom.sav", FS_OPEN_WRITE | FS_OPEN_CREATE);
|
||||
|
||||
GBACreate(gba);
|
||||
ARMSetComponents(cpu, &gba->d, 0, 0);
|
||||
|
@ -58,33 +56,31 @@ int main() {
|
|||
|
||||
ARMReset(cpu);
|
||||
|
||||
bool inVblank = false;
|
||||
int frameCounter = 0;
|
||||
while (aptMainLoop()) {
|
||||
ARMRunLoop(cpu);
|
||||
|
||||
if (!inVblank) {
|
||||
if (GBARegisterDISPSTATIsInVblank(gba->video.dispstat)) {
|
||||
u16 width, height;
|
||||
u16* screen = (u16*) gfxGetFramebuffer(GFX_BOTTOM, GFX_BOTTOM, &height, &width);
|
||||
u32 startX = (width - VIDEO_HORIZONTAL_PIXELS) / 2;
|
||||
u32 startY = (height + VIDEO_VERTICAL_PIXELS) / 2 - 1;
|
||||
u32 x, y;
|
||||
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
screen[startY - y + (startX + x) * height] = videoBuffer[y * VIDEO_HORIZONTAL_PIXELS + x];
|
||||
}
|
||||
}
|
||||
gfxFlushBuffers();
|
||||
gfxSwapBuffersGpu();
|
||||
gspWaitForVBlank();
|
||||
hidScanInput();
|
||||
activeKeys = hidKeysHeld() & 0x3FF;
|
||||
if (hidKeysDown() & KEY_X) {
|
||||
break;
|
||||
if (frameCounter != gba->video.frameCounter) {
|
||||
u16 width, height;
|
||||
u16* screen = (u16*) gfxGetFramebuffer(GFX_BOTTOM, GFX_BOTTOM, &height, &width);
|
||||
u32 startX = (width - VIDEO_HORIZONTAL_PIXELS) / 2;
|
||||
u32 startY = (height + VIDEO_VERTICAL_PIXELS) / 2 - 1;
|
||||
u32 x, y;
|
||||
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
screen[startY - y + (startX + x) * height] = videoBuffer[y * VIDEO_HORIZONTAL_PIXELS + x];
|
||||
}
|
||||
}
|
||||
gfxFlushBuffers();
|
||||
gfxSwapBuffersGpu();
|
||||
gspWaitForVBlank();
|
||||
hidScanInput();
|
||||
activeKeys = hidKeysHeld() & 0x3FF;
|
||||
if (hidKeysDown() & KEY_X) {
|
||||
break;
|
||||
}
|
||||
frameCounter = gba->video.frameCounter;
|
||||
}
|
||||
inVblank = GBARegisterDISPSTATGetInVblank(gba->video.dispstat);
|
||||
}
|
||||
|
||||
ARMDeinit(cpu);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -9,30 +9,33 @@
|
|||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
#include "debugger/cli-debugger.h"
|
||||
#include "gba/gba-cli.h"
|
||||
#include "gba/supervisor/cli.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_GDB_STUB
|
||||
#include "debugger/gdb-stub.h"
|
||||
#endif
|
||||
|
||||
#include "gba/gba-video.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#define GRAPHICS_OPTIONS "1234f"
|
||||
#define GRAPHICS_OPTIONS "123456f"
|
||||
#define GRAPHICS_USAGE \
|
||||
"\nGraphics options:\n" \
|
||||
" -1 1x viewport\n" \
|
||||
" -2 2x viewport\n" \
|
||||
" -3 3x viewport\n" \
|
||||
" -4 4x viewport\n" \
|
||||
" -5 5x viewport\n" \
|
||||
" -6 6x viewport\n" \
|
||||
" -f Start full-screen"
|
||||
|
||||
static const struct option _options[] = {
|
||||
{ "bios", required_argument, 0, 'b' },
|
||||
{ "dirmode", required_argument, 0, 'D' },
|
||||
{ "cheats", required_argument, 0, 'c' },
|
||||
{ "dirmode", required_argument, 0, 'D' },
|
||||
{ "frameskip", required_argument, 0, 's' },
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
{ "debug", no_argument, 0, 'd' },
|
||||
|
@ -40,6 +43,7 @@ static const struct option _options[] = {
|
|||
#ifdef USE_GDB_STUB
|
||||
{ "gdb", no_argument, 0, 'g' },
|
||||
#endif
|
||||
{ "movie", required_argument, 0, 'v' },
|
||||
{ "patch", required_argument, 0, 'p' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
@ -49,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) {
|
||||
int ch;
|
||||
char options[64] =
|
||||
"b:Dl:p:s:"
|
||||
"b:c:Dl:p:s:v:"
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
"d"
|
||||
#endif
|
||||
|
@ -57,6 +61,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
"g"
|
||||
#endif
|
||||
;
|
||||
memset(opts, 0, sizeof(*opts));
|
||||
if (subparser && subparser->extraOptions) {
|
||||
// TODO: modularize options to subparsers
|
||||
strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);
|
||||
|
@ -66,6 +71,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
case 'b':
|
||||
GBAConfigSetDefaultValue(config, "bios", optarg);
|
||||
break;
|
||||
case 'c':
|
||||
opts->cheatsFile = strdup(optarg);
|
||||
break;
|
||||
case 'D':
|
||||
opts->dirmode = true;
|
||||
break;
|
||||
|
@ -94,6 +102,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
case 's':
|
||||
GBAConfigSetDefaultValue(config, "frameskip", optarg);
|
||||
break;
|
||||
case 'v':
|
||||
opts->movie = strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
if (subparser) {
|
||||
if (!subparser->parse(subparser, config, ch, optarg)) {
|
||||
|
@ -118,6 +129,9 @@ void freeArguments(struct GBAArguments* opts) {
|
|||
|
||||
free(opts->patch);
|
||||
opts->patch = 0;
|
||||
|
||||
free(opts->movie);
|
||||
opts->movie = 0;
|
||||
}
|
||||
|
||||
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
|
||||
|
@ -139,6 +153,8 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
|||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
if (graphicsOpts->multiplier) {
|
||||
return false;
|
||||
}
|
||||
|
@ -195,13 +211,15 @@ void usage(const char* arg0, const char* extraOptions) {
|
|||
printf("usage: %s [option ...] file\n", arg0);
|
||||
puts("\nGeneric options:");
|
||||
puts(" -b, --bios FILE GBA BIOS file to use");
|
||||
puts(" -c, --cheats FILE Apply cheat codes from a file");
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
puts(" -d, --debug Use command-line debugger");
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
puts(" -g, --gdb Start GDB session (default port 2345)");
|
||||
#endif
|
||||
puts(" -p, --patch Apply a specified patch file when running");
|
||||
puts(" -v, --movie FILE Play back a movie of recorded input");
|
||||
puts(" -p, --patch FILE Apply a specified patch file when running");
|
||||
puts(" -s, --frameskip N Skip every N frames");
|
||||
if (extraOptions) {
|
||||
puts(extraOptions);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba-config.h"
|
||||
#include "gba/supervisor/config.h"
|
||||
|
||||
enum DebuggerType {
|
||||
DEBUGGER_NONE = 0,
|
||||
|
@ -24,7 +24,9 @@ enum DebuggerType {
|
|||
struct GBAArguments {
|
||||
char* fname;
|
||||
char* patch;
|
||||
char* cheatsFile;
|
||||
bool dirmode;
|
||||
char* movie;
|
||||
|
||||
enum DebuggerType debuggerType;
|
||||
bool debugAtStart;
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ffmpeg-encoder.h"
|
||||
|
||||
#include "gba-video.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include <libavutil/version.h>
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
#include <libavutil/buffer.h>
|
||||
#endif
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include <libavresample/avresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
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 {
|
||||
PREFERRED_SAMPLE_RATE = 0x8000
|
||||
|
@ -113,12 +119,12 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
|
|||
{ AV_PIX_FMT_BGR565, 1 },
|
||||
{ AV_PIX_FMT_RGB24, 2 },
|
||||
{ AV_PIX_FMT_BGR24, 2 },
|
||||
#ifndef USE_LIBAV
|
||||
{ AV_PIX_FMT_BGR0, 3 },
|
||||
{ AV_PIX_FMT_RGB0, 3 },
|
||||
{ AV_PIX_FMT_0BGR, 3 },
|
||||
{ AV_PIX_FMT_0RGB, 3 },
|
||||
{ AV_PIX_FMT_RGB8, 3 },
|
||||
{ AV_PIX_FMT_BGR8, 3 },
|
||||
#endif
|
||||
{ AV_PIX_FMT_YUV422P, 4 },
|
||||
{ AV_PIX_FMT_YUV444P, 5 },
|
||||
{ AV_PIX_FMT_YUV420P, 6 }
|
||||
|
@ -190,9 +196,14 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->currentVideoFrame = 0;
|
||||
encoder->nextAudioPts = 0;
|
||||
|
||||
avformat_alloc_output_context2(&encoder->context, 0, 0, outfile);
|
||||
|
||||
encoder->context->oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
#ifndef USE_LIBAV
|
||||
avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile);
|
||||
#else
|
||||
encoder->context = avformat_alloc_context();
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename));
|
||||
encoder->context->oformat = oformat;
|
||||
#endif
|
||||
|
||||
if (acodec) {
|
||||
encoder->audioStream = avformat_new_stream(encoder->context, acodec);
|
||||
|
@ -209,7 +220,14 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
}
|
||||
avcodec_open2(encoder->audio, acodec, &opts);
|
||||
av_dict_free(&opts);
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
encoder->audioFrame = av_frame_alloc();
|
||||
#else
|
||||
encoder->audioFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
if (!encoder->audio->frame_size) {
|
||||
encoder->audio->frame_size = 1;
|
||||
}
|
||||
encoder->audioFrame->nb_samples = encoder->audio->frame_size;
|
||||
encoder->audioFrame->format = encoder->audio->sample_fmt;
|
||||
encoder->audioFrame->pts = 0;
|
||||
|
@ -260,12 +278,29 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0);
|
||||
}
|
||||
avcodec_open2(encoder->video, vcodec, 0);
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
encoder->videoFrame = av_frame_alloc();
|
||||
#else
|
||||
encoder->videoFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
encoder->videoFrame->format = encoder->video->pix_fmt;
|
||||
encoder->videoFrame->width = encoder->video->width;
|
||||
encoder->videoFrame->height = encoder->video->height;
|
||||
encoder->videoFrame->pts = 0;
|
||||
encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, AV_PIX_FMT_0BGR32,
|
||||
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
|
||||
AV_PIX_FMT_0BGR32,
|
||||
#else
|
||||
AV_PIX_FMT_BGR32,
|
||||
#endif
|
||||
#endif
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||
SWS_POINT, 0, 0, 0);
|
||||
av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32);
|
||||
|
@ -288,7 +323,11 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
if (encoder->audioBuffer) {
|
||||
av_free(encoder->audioBuffer);
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_free(&encoder->audioFrame);
|
||||
#else
|
||||
avcodec_free_frame(&encoder->audioFrame);
|
||||
#endif
|
||||
avcodec_close(encoder->audio);
|
||||
|
||||
if (encoder->resampleContext) {
|
||||
|
@ -301,7 +340,11 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
}
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_free(&encoder->videoFrame);
|
||||
#else
|
||||
avcodec_free_frame(&encoder->videoFrame);
|
||||
#endif
|
||||
avcodec_close(encoder->video);
|
||||
|
||||
sws_freeContext(encoder->scaleContext);
|
||||
|
@ -314,13 +357,12 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
|
|||
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;
|
||||
if (!encoder->context || !encoder->audioCodec) {
|
||||
return;
|
||||
}
|
||||
|
||||
av_frame_make_writable(encoder->audioFrame);
|
||||
encoder->audioBuffer[encoder->currentAudioSample * 2] = left;
|
||||
encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right;
|
||||
|
||||
|
@ -334,11 +376,14 @@ void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t rig
|
|||
|
||||
int channelSize = 2 * av_get_bytes_per_sample(encoder->audio->sample_fmt);
|
||||
avresample_convert(encoder->resampleContext,
|
||||
0, 0, encoder->postaudioBufferSize / channelSize,
|
||||
0, 0, 0,
|
||||
(uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4);
|
||||
if ((ssize_t) avresample_available(encoder->resampleContext) < (ssize_t) encoder->postaudioBufferSize / channelSize) {
|
||||
if (avresample_available(encoder->resampleContext) < encoder->audioFrame->nb_samples) {
|
||||
return;
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_make_writable(encoder->audioFrame);
|
||||
#endif
|
||||
avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize);
|
||||
|
||||
AVRational timeBase = { 1, PREFERRED_SAMPLE_RATE };
|
||||
|
@ -358,7 +403,9 @@ void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t rig
|
|||
&tempPacket.data, &tempPacket.size,
|
||||
packet.data, packet.size, 0);
|
||||
if (success > 0) {
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
tempPacket.buf = av_buffer_create(tempPacket.data, tempPacket.size, av_buffer_default_free, 0, 0);
|
||||
#endif
|
||||
av_free_packet(&packet);
|
||||
}
|
||||
packet = tempPacket;
|
||||
|
@ -377,14 +424,16 @@ void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
uint8_t* pixels;
|
||||
unsigned stride;
|
||||
renderer->getPixels(renderer, &stride, (void**) &pixels);
|
||||
stride *= 4;
|
||||
stride *= BYTES_PER_PIXEL;
|
||||
|
||||
AVPacket packet;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = 0;
|
||||
packet.size = 0;
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_make_writable(encoder->videoFrame);
|
||||
#endif
|
||||
encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base);
|
||||
++encoder->currentVideoFrame;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -6,7 +6,7 @@
|
|||
#ifndef FFMPEG_ENCODER
|
||||
#define FFMPEG_ENCODER
|
||||
|
||||
#include "gba-thread.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "imagemagick-gif-encoder.h"
|
||||
|
||||
#include "gba-video.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
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) {
|
||||
encoder->wand = 0;
|
||||
|
@ -70,7 +70,7 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
|
|||
++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(left);
|
||||
UNUSED(right);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -6,7 +6,7 @@
|
|||
#ifndef IMAGEMAGICK_GIF_ENCODER
|
||||
#define IMAGEMAGICK_GIF_ENCODER
|
||||
|
||||
#include "gba-thread.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
|
||||
#define MAGICKCORE_HDRI_ENABLE 0
|
||||
#define MAGICKCORE_QUANTUM_DEPTH 8
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
/* 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"
|
||||
|
||||
static retro_environment_t environCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
static retro_audio_sample_t audioCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputCallback;
|
||||
static retro_log_printf_t logCallback;
|
||||
|
||||
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
||||
|
||||
static void _postAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
static void _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 environ) {
|
||||
environCallback = environ;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t video) {
|
||||
videoCallback = video;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t audio) {
|
||||
audioCallback = audio;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
|
||||
UNUSED(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 = _postAudioFrame;
|
||||
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);
|
||||
}
|
||||
|
||||
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 _postAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||
UNUSED(stream);
|
||||
audioCallback(left, right);
|
||||
}
|
||||
|
||||
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
|
@ -1,12 +1,12 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba-thread.h"
|
||||
#include "gba-config.h"
|
||||
#include "gba.h"
|
||||
#include "renderers/video-software.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/supervisor/config.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
|
||||
#include "platform/commandline.h"
|
||||
|
||||
|
@ -36,6 +36,7 @@ static void _GBAPerfShutdown(int signal);
|
|||
static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
|
||||
|
||||
static struct GBAThread* _thread;
|
||||
static bool _dispatchExiting = false;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
signal(SIGINT, _GBAPerfShutdown);
|
||||
|
@ -53,9 +54,14 @@ int main(int argc, char** argv) {
|
|||
|
||||
struct GBAConfig config;
|
||||
GBAConfigInit(&config, "perf");
|
||||
GBAConfigLoad(&config);
|
||||
|
||||
struct GBAOptions opts = {};
|
||||
struct GBAArguments args = {};
|
||||
struct GBAOptions opts = {
|
||||
.idleOptimization = IDLE_LOOP_DETECT
|
||||
};
|
||||
GBAConfigLoadDefaults(&config, &opts);
|
||||
|
||||
struct GBAArguments args;
|
||||
if (!parseArguments(&args, &config, argc, argv, &subparser)) {
|
||||
usage(argv[0], PERF_USAGE);
|
||||
freeArguments(&args);
|
||||
|
@ -67,7 +73,7 @@ int main(int argc, char** argv) {
|
|||
renderer.outputBuffer = malloc(256 * 256 * 4);
|
||||
renderer.outputBufferStride = 256;
|
||||
|
||||
struct GBAThread context = { };
|
||||
struct GBAThread context = {};
|
||||
_thread = &context;
|
||||
|
||||
if (!perfOpts.noVideo) {
|
||||
|
@ -75,6 +81,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
context.debugger = createDebugger(&args, &context);
|
||||
context.overrides = GBAConfigGetOverrides(&config);
|
||||
char gameCode[5] = { 0 };
|
||||
|
||||
GBAConfigMap(&config, &opts);
|
||||
|
@ -83,7 +90,11 @@ int main(int argc, char** argv) {
|
|||
GBAMapArgumentsToContext(&args, &context);
|
||||
GBAMapOptionsToContext(&opts, &context);
|
||||
|
||||
GBAThreadStart(&context);
|
||||
int didStart = GBAThreadStart(&context);
|
||||
|
||||
if (!didStart) {
|
||||
goto cleanup;
|
||||
}
|
||||
GBAGetGameCode(context.gba, gameCode);
|
||||
|
||||
int frames = perfOpts.frames;
|
||||
|
@ -99,12 +110,6 @@ int main(int argc, char** argv) {
|
|||
uint64_t duration = end - start;
|
||||
|
||||
GBAThreadJoin(&context);
|
||||
GBAConfigFreeOpts(&opts);
|
||||
freeArguments(&args);
|
||||
GBAConfigDeinit(&config);
|
||||
free(context.debugger);
|
||||
|
||||
free(renderer.outputBuffer);
|
||||
|
||||
float scaledFrames = frames * 1000000.f;
|
||||
if (perfOpts.csv) {
|
||||
|
@ -120,7 +125,14 @@ int main(int argc, char** argv) {
|
|||
printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
|
||||
}
|
||||
|
||||
return 0;
|
||||
cleanup:
|
||||
GBAConfigFreeOpts(&opts);
|
||||
freeArguments(&args);
|
||||
GBAConfigDeinit(&config);
|
||||
free(context.debugger);
|
||||
free(renderer.outputBuffer);
|
||||
|
||||
return !didStart || GBAThreadHasCrashed(&context);
|
||||
}
|
||||
|
||||
static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) {
|
||||
|
@ -152,6 +164,9 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
|
|||
if (*frames == duration) {
|
||||
_GBAPerfShutdown(0);
|
||||
}
|
||||
if (_dispatchExiting) {
|
||||
GBAThreadEnd(context);
|
||||
}
|
||||
}
|
||||
if (!quiet) {
|
||||
printf("\033[2K\r");
|
||||
|
@ -160,7 +175,9 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
|
|||
|
||||
static void _GBAPerfShutdown(int signal) {
|
||||
UNUSED(signal);
|
||||
GBAThreadEnd(_thread);
|
||||
// This will come in ON the GBA thread, so we have to handle it carefully
|
||||
_dispatchExiting = true;
|
||||
ConditionWake(&_thread->sync.videoFrameAvailableCond);
|
||||
}
|
||||
|
||||
static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -6,9 +6,9 @@
|
|||
#include "AudioDevice.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba.h"
|
||||
#include "gba-audio.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/audio.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -22,12 +22,20 @@ AudioDevice::AudioDevice(QObject* parent)
|
|||
}
|
||||
|
||||
void AudioDevice::setFormat(const QAudioFormat& format) {
|
||||
if (!GBAThreadHasStarted(m_context)) {
|
||||
if (!GBAThreadIsActive(m_context)) {
|
||||
return;
|
||||
}
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
GBAThreadInterrupt(m_context);
|
||||
m_ratio = GBAAudioCalculateRatio(&m_context->gba->audio, m_context->fpsTarget, format.sampleRate());
|
||||
m_ratio = GBAAudioCalculateRatio(m_context->gba->audio.sampleRate, m_context->fpsTarget, format.sampleRate());
|
||||
GBAThreadContinue(m_context);
|
||||
#elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
double fauxClock = GBAAudioCalculateRatio(1, m_context->fpsTarget, 1);
|
||||
GBASyncLockAudio(&m_context->sync);
|
||||
blip_set_rates(m_context->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, format.sampleRate() * fauxClock);
|
||||
blip_set_rates(m_context->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, format.sampleRate() * fauxClock);
|
||||
GBASyncUnlockAudio(&m_context->sync);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioDevice::setInput(GBAThread* input) {
|
||||
|
@ -43,7 +51,19 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
return GBAAudioResampleNN(&m_context->gba->audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample);
|
||||
#elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
GBASyncLockAudio(&m_context->sync);
|
||||
int available = blip_samples_avail(m_context->gba->audio.left);
|
||||
if (available > maxSize / sizeof(GBAStereoSample)) {
|
||||
available = maxSize / sizeof(GBAStereoSample);
|
||||
}
|
||||
blip_read_samples(m_context->gba->audio.left, &reinterpret_cast<GBAStereoSample*>(data)->left, available, true);
|
||||
blip_read_samples(m_context->gba->audio.right, &reinterpret_cast<GBAStereoSample*>(data)->right, available, true);
|
||||
GBASyncConsumeAudio(&m_context->sync);
|
||||
return available * sizeof(GBAStereoSample);
|
||||
#endif
|
||||
}
|
||||
|
||||
qint64 AudioDevice::writeData(const char*, qint64) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_AUDIO_DEVICE
|
||||
#define QGBA_AUDIO_DEVICE
|
||||
|
||||
#include <QAudioFormat>
|
||||
#include <QIODevice>
|
||||
|
||||
|
|
|
@ -1,32 +1,49 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "AudioProcessor.h"
|
||||
|
||||
#include "AudioDevice.h"
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
#include "AudioProcessorSDL.h"
|
||||
#else
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
#include "AudioProcessorQt.h"
|
||||
#endif
|
||||
|
||||
#include <QAudioOutput>
|
||||
|
||||
extern "C" {
|
||||
#include "gba-thread.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AudioProcessor* AudioProcessor::create() {
|
||||
#ifdef BUILD_SDL
|
||||
return new AudioProcessorSDL();
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::QT_MULTIMEDIA;
|
||||
#else
|
||||
return new AudioProcessorQt();
|
||||
#endif
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::SDL;
|
||||
#endif
|
||||
|
||||
AudioProcessor* AudioProcessor::create() {
|
||||
switch (s_driver) {
|
||||
#ifdef BUILD_SDL
|
||||
case Driver::SDL:
|
||||
return new AudioProcessorSDL();
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
case Driver::QT_MULTIMEDIA:
|
||||
return new AudioProcessorQt();
|
||||
#endif
|
||||
|
||||
default:
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
return new AudioProcessorQt();
|
||||
#else
|
||||
return new AudioProcessorSDL();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
AudioProcessor::AudioProcessor(QObject* parent)
|
||||
|
@ -37,3 +54,7 @@ AudioProcessor::AudioProcessor(QObject* parent)
|
|||
void AudioProcessor::setInput(GBAThread* input) {
|
||||
m_context = input;
|
||||
}
|
||||
|
||||
void AudioProcessor::setBufferSamples(int samples) {
|
||||
m_samples = samples;
|
||||
}
|
||||
|
|
|
@ -15,10 +15,22 @@ class AudioProcessor : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Driver {
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
QT_MULTIMEDIA = 0,
|
||||
#endif
|
||||
#ifdef BUILD_SDL
|
||||
SDL = 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
static AudioProcessor* create();
|
||||
static void setDriver(Driver driver) { s_driver = driver; }
|
||||
|
||||
AudioProcessor(QObject* parent = nullptr);
|
||||
|
||||
virtual void setInput(GBAThread* input);
|
||||
int getBufferSamples() const { return m_samples; }
|
||||
|
||||
public slots:
|
||||
virtual void start() = 0;
|
||||
|
@ -29,8 +41,11 @@ public slots:
|
|||
|
||||
protected:
|
||||
GBAThread* input() { return m_context; }
|
||||
|
||||
private:
|
||||
GBAThread* m_context;
|
||||
int m_samples;
|
||||
static Driver s_driver;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -10,7 +10,7 @@
|
|||
#include <QAudioOutput>
|
||||
|
||||
extern "C" {
|
||||
#include "gba-thread.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -47,6 +47,7 @@ void AudioProcessorQt::start() {
|
|||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
|
||||
m_audioOutput = new QAudioOutput(format, this);
|
||||
m_audioOutput->setCategory("game");
|
||||
}
|
||||
|
||||
m_device->setInput(input());
|
||||
|
@ -63,6 +64,7 @@ void AudioProcessorQt::pause() {
|
|||
}
|
||||
|
||||
void AudioProcessorQt::setBufferSamples(int samples) {
|
||||
AudioProcessor::setBufferSamples(samples);
|
||||
if (m_audioOutput) {
|
||||
m_audioOutput->stop();
|
||||
m_audioOutput->setBufferSize(samples * 4);
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#define QGBA_AUDIO_PROCESSOR_QT
|
||||
#include "AudioProcessor.h"
|
||||
|
||||
class QAudioOutput;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class AudioDevice;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -6,7 +6,7 @@
|
|||
#include "AudioProcessorSDL.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba-thread.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -25,7 +25,9 @@ void AudioProcessorSDL::start() {
|
|||
if (m_audio.thread) {
|
||||
GBASDLResumeAudio(&m_audio);
|
||||
} else {
|
||||
m_audio.samples = input()->audioBuffers;
|
||||
if (!m_audio.samples) {
|
||||
m_audio.samples = input()->audioBuffers;
|
||||
}
|
||||
GBASDLInitAudio(&m_audio, input());
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +37,10 @@ void AudioProcessorSDL::pause() {
|
|||
}
|
||||
|
||||
void AudioProcessorSDL::setBufferSamples(int samples) {
|
||||
AudioProcessor::setBufferSamples(samples);
|
||||
m_audio.samples = samples;
|
||||
if (m_audio.thread) {
|
||||
GBASDLDeinitAudio(&m_audio);
|
||||
m_audio.samples = samples;
|
||||
GBASDLInitAudio(&m_audio, input());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,12 @@ enable_language(CXX)
|
|||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11")
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++")
|
||||
endif()
|
||||
|
||||
set(PLATFORM_SRC)
|
||||
|
||||
if(BUILD_SDL)
|
||||
if(NOT SDL_FOUND AND NOT SDL2_FOUND)
|
||||
find_package(SDL 1.2 REQUIRED)
|
||||
|
@ -10,8 +16,8 @@ if(BUILD_SDL)
|
|||
if(SDL2_FOUND)
|
||||
link_directories(${SDL2_LIBDIR})
|
||||
endif()
|
||||
set(PLATFORM_LIBRARY "${PLATFORM_LIBRARY};${SDL_LIBRARY};${SDLMAIN_LIBRARY}")
|
||||
set(PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c)
|
||||
list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY})
|
||||
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c)
|
||||
include_directories(${SDL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/platform/sdl)
|
||||
endif()
|
||||
|
||||
|
@ -30,41 +36,67 @@ if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
|
|||
endif()
|
||||
|
||||
set(SOURCE_FILES
|
||||
AudioDevice.cpp
|
||||
AudioProcessor.cpp
|
||||
CheatsModel.cpp
|
||||
CheatsView.cpp
|
||||
ConfigController.cpp
|
||||
Display.cpp
|
||||
GBAApp.cpp
|
||||
GBAKeyEditor.cpp
|
||||
GIFView.cpp
|
||||
GameController.cpp
|
||||
GamepadAxisEvent.cpp
|
||||
GamepadButtonEvent.cpp
|
||||
InputController.cpp
|
||||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogView.cpp
|
||||
OverrideView.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
SettingsView.cpp
|
||||
ShortcutController.cpp
|
||||
ShortcutView.cpp
|
||||
Window.cpp
|
||||
VFileDevice.cpp
|
||||
VideoView.cpp)
|
||||
|
||||
qt5_wrap_ui(UI_FILES
|
||||
CheatsView.ui
|
||||
GIFView.ui
|
||||
LoadSaveState.ui
|
||||
LogView.ui
|
||||
OverrideView.ui
|
||||
SensorView.ui
|
||||
SettingsView.ui
|
||||
ShortcutView.ui
|
||||
VideoView.ui)
|
||||
|
||||
set(QT_LIBRARIES)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE)
|
||||
|
||||
set(AUDIO_SRC)
|
||||
if(BUILD_SDL)
|
||||
list(APPEND SOURCE_FILES AudioProcessorSDL.cpp)
|
||||
elseif(Qt5Multimedia_FOUND)
|
||||
list(APPEND SOURCE_FILES AudioProcessorQt.cpp)
|
||||
else()
|
||||
list(APPEND AUDIO_SRC AudioProcessorSDL.cpp)
|
||||
endif()
|
||||
|
||||
if(Qt5Multimedia_FOUND)
|
||||
list(APPEND AUDIO_SRC
|
||||
AudioProcessorQt.cpp
|
||||
AudioDevice.cpp)
|
||||
list(APPEND QT_LIBRARIES Qt5::Multimedia)
|
||||
add_definitions(-DBUILD_QT_MULTIMEDIA)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(NOT AUDIO_SRC)
|
||||
message(WARNING "No supported audio modules found")
|
||||
set(BUILD_QT OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(USE_GDB_STUB)
|
||||
set(SOURCE_FILES ${PLATFORM_SRC} ${SOURCE_FILES} GDBController.cpp GDBWindow.cpp)
|
||||
list(APPEND PLATFORM_SRC GDBController.cpp GDBWindow.cpp)
|
||||
endif()
|
||||
set(MACOSX_BUNDLE_ICON_FILE mgba.icns)
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION ${LIB_VERSION_STRING})
|
||||
|
@ -76,22 +108,29 @@ qt5_add_resources(RESOURCES resources.qrc)
|
|||
if(WIN32)
|
||||
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
||||
endif()
|
||||
add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${UI_FILES} ${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})
|
||||
target_compile_definitions(${BINARY_NAME}-qt PRIVATE ${FEATURE_DEFINES})
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in)
|
||||
|
||||
set(QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
if(Qt5Multimedia_FOUND)
|
||||
list(APPEND QT_LIBRARIES Qt5::Multimedia)
|
||||
endif()
|
||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||
|
||||
install(TARGETS ${BINARY_NAME}-qt RUNTIME DESTINATION bin BUNDLE DESTINATION /Applications)
|
||||
install(TARGETS ${BINARY_NAME}-qt
|
||||
RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt
|
||||
BUNDLE DESTINATION Applications COMPONENT ${BINARY_NAME}-qt)
|
||||
if(APPLE OR WIN32)
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
endif()
|
||||
if(APPLE AND MACDEPLOYQT)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND ${MACDEPLOYQT} ${PROJECT_NAME}.app)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/bearer)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/imageformats)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/printsupport)
|
||||
if(APPLE)
|
||||
set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib)
|
||||
if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool")
|
||||
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -I ${CMAKE_INSTALL_NAME_TOOL})
|
||||
endif()
|
||||
if(DEFINED CMAKE_OTOOL AND NOT CMAKE_OTOOL EQUAL "otool")
|
||||
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -O ${CMAKE_OTOOL})
|
||||
endif()
|
||||
if(DEFINED CROSS_ROOT)
|
||||
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R ${CROSS_ROOT})
|
||||
endif()
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/tools/deploy-mac.py ${DEPLOY_OPTIONS} ${PROJECT_NAME}.app)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/* 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 "CheatsModel.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QSet>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/cheats.h"
|
||||
#include "util/vfs.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
CheatsModel::CheatsModel(GBACheatDevice* device, QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_device(device)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant CheatsModel::data(const QModelIndex& index, int role) const {
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (index.parent().isValid()) {
|
||||
int row = index.row();
|
||||
GBACheatSet* cheats = static_cast<GBACheatSet*>(index.internalPointer());
|
||||
const char* line = *StringListGetPointer(&cheats->lines, row);
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return line;
|
||||
case Qt::FontRole:
|
||||
return QFont("Courier New", 13);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int row = index.row();
|
||||
const GBACheatSet* cheats = *GBACheatSetsGetPointer(&m_device->cheats, index.row());
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
return cheats->name ? cheats->name : tr("(untitled)");
|
||||
case Qt::CheckStateRole:
|
||||
return cheats->enabled ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||
if (!index.isValid() || index.parent().isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int row = index.row();
|
||||
GBACheatSet* cheats = *GBACheatSetsGetPointer(&m_device->cheats, index.row());
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
if (cheats->name) {
|
||||
free(cheats->name);
|
||||
cheats->name = nullptr;
|
||||
}
|
||||
cheats->name = strdup(value.toString().toLocal8Bit().constData());
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
case Qt::CheckStateRole:
|
||||
cheats->enabled = value == Qt::Checked;
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex CheatsModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return createIndex(row, column, *GBACheatSetsGetPointer(&m_device->cheats, parent.row()));
|
||||
} else {
|
||||
return createIndex(row, column, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex CheatsModel::parent(const QModelIndex& index) const {
|
||||
if (!index.isValid()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
const GBACheatSet* cheats = static_cast<const GBACheatSet*>(index.internalPointer());
|
||||
if (!cheats) {
|
||||
return QModelIndex();
|
||||
}
|
||||
for (size_t i = 0; i < GBACheatSetsSize(&m_device->cheats); ++i) {
|
||||
if (cheats == *GBACheatSetsGetPointer(&m_device->cheats, i)) {
|
||||
return createIndex(i, 0, nullptr);
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags CheatsModel::flags(const QModelIndex &index) const {
|
||||
if (!index.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (index.parent().isValid()) {
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
int CheatsModel::columnCount(const QModelIndex& parent) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int CheatsModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
if (parent.internalPointer()) {
|
||||
return 0;
|
||||
}
|
||||
const GBACheatSet* set = *GBACheatSetsGetPointer(&m_device->cheats, parent.row());
|
||||
return StringListSize(&set->lines);
|
||||
}
|
||||
return GBACheatSetsSize(&m_device->cheats);
|
||||
}
|
||||
|
||||
GBACheatSet* CheatsModel::itemAt(const QModelIndex& index) {
|
||||
if (!index.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (index.parent().isValid()) {
|
||||
return static_cast<GBACheatSet*>(index.internalPointer());
|
||||
}
|
||||
return *GBACheatSetsGetPointer(&m_device->cheats, index.row());
|
||||
}
|
||||
|
||||
void CheatsModel::removeAt(const QModelIndex& index) {
|
||||
if (!index.isValid() || index.parent().isValid()) {
|
||||
return;
|
||||
}
|
||||
int row = index.row();
|
||||
GBACheatSet* set = *GBACheatSetsGetPointer(&m_device->cheats, index.row());
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
GBACheatRemoveSet(m_device, set);
|
||||
GBACheatSetDeinit(set);
|
||||
delete set;
|
||||
endInsertRows();
|
||||
|
||||
}
|
||||
|
||||
QString CheatsModel::toString(const QModelIndexList& indices) const {
|
||||
QMap<int, GBACheatSet*> setOrder;
|
||||
QMap<GBACheatSet*, QSet<size_t>> setIndices;
|
||||
for (const QModelIndex& index : indices) {
|
||||
GBACheatSet* set = static_cast<GBACheatSet*>(index.internalPointer());
|
||||
if (!set) {
|
||||
set = *GBACheatSetsGetPointer(&m_device->cheats, index.row());
|
||||
setOrder[index.row()] = set;
|
||||
QSet<size_t> range;
|
||||
for (size_t i = 0; i < StringListSize(&set->lines); ++i) {
|
||||
range.insert(i);
|
||||
}
|
||||
setIndices[set] = range;
|
||||
} else {
|
||||
setOrder[index.parent().row()] = set;
|
||||
setIndices[set].insert(index.row());
|
||||
}
|
||||
}
|
||||
|
||||
QStringList strings;
|
||||
QList<int> order = setOrder.keys();
|
||||
std::sort(order.begin(), order.end());
|
||||
for (int i : order) {
|
||||
GBACheatSet* set = setOrder[i];
|
||||
QList<size_t> indexOrdex = setIndices[set].toList();
|
||||
std::sort(indexOrdex.begin(), indexOrdex.end());
|
||||
for (size_t j : indexOrdex) {
|
||||
strings.append(*StringListGetPointer(&set->lines, j));
|
||||
}
|
||||
}
|
||||
|
||||
return strings.join('\n');
|
||||
}
|
||||
|
||||
void CheatsModel::beginAppendRow(const QModelIndex& index) {
|
||||
if (index.parent().isValid()) {
|
||||
beginInsertRows(index.parent(), rowCount(index.parent()), rowCount(index.parent()));
|
||||
return;
|
||||
}
|
||||
beginInsertRows(index, rowCount(index), rowCount(index));
|
||||
}
|
||||
|
||||
void CheatsModel::endAppendRow() {
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void CheatsModel::loadFile(const QString& path) {
|
||||
VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY);
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
beginResetModel();
|
||||
GBACheatParseFile(m_device, vf);
|
||||
endResetModel();
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
void CheatsModel::saveFile(const QString& path) {
|
||||
VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_TRUNC | O_CREAT | O_WRONLY);
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
GBACheatSaveFile(m_device, vf);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
void CheatsModel::addSet(GBACheatSet* set) {
|
||||
beginInsertRows(QModelIndex(), GBACheatSetsSize(&m_device->cheats), GBACheatSetsSize(&m_device->cheats));
|
||||
size_t size = GBACheatSetsSize(&m_device->cheats);
|
||||
if (size) {
|
||||
GBACheatSetCopyProperties(set, *GBACheatSetsGetPointer(&m_device->cheats, size - 1));
|
||||
}
|
||||
GBACheatAddSet(m_device, set);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void CheatsModel::invalidated() {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_CHEATS_MODEL
|
||||
#define QGBA_CHEATS_MODEL
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
struct GBACheatDevice;
|
||||
struct GBACheatSet;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CheatsModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatsModel(GBACheatDevice* m_device, QObject* parent = nullptr);
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override;
|
||||
virtual QModelIndex parent(const QModelIndex& index) const override;
|
||||
|
||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
GBACheatSet* itemAt(const QModelIndex& index);
|
||||
void removeAt(const QModelIndex& index);
|
||||
QString toString(const QModelIndexList& indices) const;
|
||||
|
||||
void beginAppendRow(const QModelIndex& index);
|
||||
void endAppendRow();
|
||||
|
||||
void loadFile(const QString& path);
|
||||
void saveFile(const QString& path);
|
||||
|
||||
void addSet(GBACheatSet* set);
|
||||
|
||||
public slots:
|
||||
void invalidated();
|
||||
|
||||
private:
|
||||
GBACheatDevice* m_device;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,116 @@
|
|||
/* 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 "CheatsView.h"
|
||||
|
||||
#include "GameController.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFileDialog>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/cheats.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
CheatsView::CheatsView(GameController* controller, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_controller(controller)
|
||||
, m_model(controller->cheatDevice())
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.cheatList->installEventFilter(this);
|
||||
m_ui.cheatList->setModel(&m_model);
|
||||
|
||||
connect(m_ui.load, SIGNAL(clicked()), this, SLOT(load()));
|
||||
connect(m_ui.save, SIGNAL(clicked()), this, SLOT(save()));
|
||||
connect(m_ui.addSet, SIGNAL(clicked()), this, SLOT(addSet()));
|
||||
connect(m_ui.remove, SIGNAL(clicked()), this, SLOT(removeSet()));
|
||||
connect(controller, SIGNAL(gameStopped(GBAThread*)), &m_model, SLOT(invalidated()));
|
||||
|
||||
connect(m_ui.add, &QPushButton::clicked, [this]() {
|
||||
enterCheat(GBACheatAddLine);
|
||||
});
|
||||
|
||||
connect(m_ui.addGSA, &QPushButton::clicked, [this]() {
|
||||
enterCheat(GBACheatAddGameSharkLine);
|
||||
});
|
||||
|
||||
connect(m_ui.addCB, &QPushButton::clicked, [this]() {
|
||||
enterCheat(GBACheatAddCodeBreakerLine);
|
||||
});
|
||||
}
|
||||
|
||||
bool CheatsView::eventFilter(QObject* object, QEvent* event) {
|
||||
if (object != m_ui.cheatList) {
|
||||
return false;
|
||||
}
|
||||
if (event->type() != QEvent::KeyPress) {
|
||||
return false;
|
||||
}
|
||||
if (static_cast<QKeyEvent*>(event) == QKeySequence::Copy) {
|
||||
QApplication::clipboard()->setText(m_model.toString(m_ui.cheatList->selectionModel()->selectedIndexes()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheatsView::load() {
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select cheats file"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_model.loadFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsView::save() {
|
||||
QString filename = QFileDialog::getSaveFileName(this, tr("Select cheats file"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_model.saveFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsView::addSet() {
|
||||
GBACheatSet* set = new GBACheatSet;
|
||||
GBACheatSetInit(set, nullptr);
|
||||
m_controller->threadInterrupt();
|
||||
m_model.addSet(set);
|
||||
m_controller->threadContinue();
|
||||
}
|
||||
|
||||
void CheatsView::removeSet() {
|
||||
GBACheatSet* set;
|
||||
QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes();
|
||||
if (selection.count() < 1) {
|
||||
return;
|
||||
}
|
||||
m_controller->threadInterrupt();
|
||||
for (const QModelIndex& index : selection) {
|
||||
m_model.removeAt(selection[0]);
|
||||
}
|
||||
m_controller->threadContinue();
|
||||
}
|
||||
|
||||
void CheatsView::enterCheat(std::function<bool(GBACheatSet*, const char*)> callback) {
|
||||
GBACheatSet* set;
|
||||
QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes();
|
||||
if (selection.count() != 1) {
|
||||
return;
|
||||
}
|
||||
set = m_model.itemAt(selection[0]);
|
||||
if (!set) {
|
||||
return;
|
||||
}
|
||||
m_controller->threadInterrupt();
|
||||
QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', QString::SkipEmptyParts);
|
||||
for (const QString& string : cheats) {
|
||||
m_model.beginAppendRow(selection[0]);
|
||||
callback(set, string.toLocal8Bit().constData());
|
||||
m_model.endAppendRow();
|
||||
}
|
||||
m_controller->threadContinue();
|
||||
m_ui.codeEntry->clear();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_CHEATS_VIEW
|
||||
#define QGBA_CHEATS_VIEW
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "CheatsModel.h"
|
||||
|
||||
#include "ui_CheatsView.h"
|
||||
|
||||
struct GBACheatDevice;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class GameController;
|
||||
|
||||
class CheatsView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatsView(GameController* controller, QWidget* parent = nullptr);
|
||||
|
||||
virtual bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
private slots:
|
||||
void load();
|
||||
void save();
|
||||
void addSet();
|
||||
void removeSet();
|
||||
|
||||
private:
|
||||
void enterCheat(std::function<bool(GBACheatSet*, const char*)> callback);
|
||||
|
||||
Ui::CheatsView m_ui;
|
||||
GameController* m_controller;
|
||||
CheatsModel m_model;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue