Merge branch 'master' into port/3ds
187
CHANGES
|
@ -1,14 +1,134 @@
|
|||
0.2.0: (Future)
|
||||
0.3.0: (Future)
|
||||
Features:
|
||||
- Ability to hide individual background layers, or OBJs
|
||||
- Ability to mute individual audio channels
|
||||
- Palette viewer and exporter
|
||||
- Volume control
|
||||
- More shortcuts are editable (e.g. quick save/load)
|
||||
- Rewind now shows the frame after rewinding
|
||||
- Import/Export of GameShark/Action Replay snapshots
|
||||
- Add "Step backwards" item for single increment rewind
|
||||
- Deadzone estimation for game controllers
|
||||
- Analog inputs can be used for shortcuts
|
||||
- Menu items for specific solar sensor brightness levels
|
||||
- Remappable controls for tilt and gyroscope sensors
|
||||
- Status messages for actions taken while a game is running (e.g. save/load state)
|
||||
- Memory inspector
|
||||
- Screensaver can now be suspended while a game is running
|
||||
- Load/save the most recent savestate slot
|
||||
- Support varible speed (PWM) rumble
|
||||
- Ability to cap fast forward speed
|
||||
- Finer control over FPS target
|
||||
- Holdable shortcut for rewinding one frame at a time
|
||||
- Ability to boot directly into the BIOS
|
||||
Bugfixes:
|
||||
- ARM7: Fix SWI and IRQ timings
|
||||
- GBA Audio: Force audio FIFOs to 32-bit
|
||||
- GBA Memory: Improve Thumb open bus behavior
|
||||
- VFS: Fix resource leaks if some allocations fail
|
||||
- Video: Fix an issue with very long filenames
|
||||
- GBA Video: Blended sprites should never have other effects applied
|
||||
- GBA: Fix crash if a 512kb flash save is loaded when a game has a 1Mb flash override
|
||||
- Qt: Better cleanup when a game crashes
|
||||
- Qt: Fix open ROM dialog filtering for archive formats
|
||||
- ARM7: Fix Thumb MUL timing
|
||||
- Qt: Cap the maximum number of multiplayer windows
|
||||
- Qt: Fix maximum year in sensor override
|
||||
- GBA: Cap audio FIFO read size during deserialization
|
||||
- GBA: Check for corrupted savestates when loading
|
||||
- GBA: Check for improperly sized savestates when loading
|
||||
- GBA: Check for savestates made from differently sized ROMs
|
||||
- GBA Video: Fix out-of-bounds tiles in mosaic
|
||||
- GBA Memory: Fix potential DMA issue when loading a savestate
|
||||
- GBA Audio: Fix audio pitch changing when adjusting buffer size
|
||||
- SDL: Fix SDL build when OpenGL is missing
|
||||
- ARM7: Fix timing of multiplies to use N cycles
|
||||
- GBA: Fix calls to endian-independent loadstores
|
||||
- GBA Video: Fix windows not affecting sprites
|
||||
- VFS: Fix line-reading to return proper values
|
||||
Misc:
|
||||
- Qt: Handle saving input settings better
|
||||
- Debugger: Free watchpoints in addition to breakpoints
|
||||
- Qt: Move GL frame drawing back onto its own thread
|
||||
- GBA: Add status log level
|
||||
- GBA Thread: Add functionality for running callbacks on the GBA thread
|
||||
- Qt: Fast forward (held) option moved from Other to Emulation menu
|
||||
- All: Add --help flag for command line programs
|
||||
- Qt: Show version info in window title
|
||||
- All: Fix sanitize-deb script to set file permissions properly if run as (fake)root
|
||||
- GBA SIO: Add a dummy driver for Normal mode
|
||||
- GBA: GBARewind now returns how many states it has rewound
|
||||
- All: Enable static linking for Windows
|
||||
- All: Enable static linking for OS X
|
||||
- Qt: Migrate multiplayer window handling into GBAApp
|
||||
- Qt: Unified file opening and saving with last location
|
||||
- Qt: Fix windows being resizable when they shouldn't have been
|
||||
- Qt: Only hide cursor in full screen
|
||||
- Perf: Ability to load savestates immediately on launch
|
||||
- Qt: Replace pause-after-frame mutex with an atomic
|
||||
- Util: Allow disabling the threading code entirely
|
||||
- GBA: SIO logging layer
|
||||
- Qt: Add application icon and XDG desktop files
|
||||
- GBA Thread: Split GBASync into a separate file
|
||||
- SDL: Properly check for initialization
|
||||
- SDL: Clean up initialization functions
|
||||
- All: Threads are now named
|
||||
- Qt: Rename "Fullscreen" to "Toggle fullscreen"
|
||||
- Qt: Don't save window size when entering fullscreen
|
||||
- Qt: Make the default fullscreen binding for Windows be Alt-Enter
|
||||
|
||||
0.2.1: (2015-05-13)
|
||||
Bugfixes:
|
||||
- All: Fix sanitize-deb script not cleaning up after itself
|
||||
- All: Fix dependencies for libavcodec on Debian-derived platforms
|
||||
- ARM7: Handle writeback for PC in addressing modes 2 and 3
|
||||
- ARM7: Make illegal instruction decoding consistent between ARM and Thumb
|
||||
- ARM7: Fix ARM multiply instructions when PC is a destination register
|
||||
- Debugger: Fix use-after-free in breakpoint clearing code
|
||||
- Debugger: Fix boundary conditions in tab completion
|
||||
- GBA: Fix timers not updating timing when writing to only the reload register
|
||||
- GBA: Fix rewind boundary conditions
|
||||
- GBA: Add initial I/O register settings for background matrix registers
|
||||
- GBA: Fix hang when loading a savestate if sync to video is enabled
|
||||
- GBA: Handle out-of-bounds I/O access
|
||||
- GBA: Fix bounds-checking on EEPROM access
|
||||
- GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio
|
||||
- GBA BIOS: Initialize a variable that may be uninitialized in very rare cases
|
||||
- GBA Memory: Allow SRAM to be 64kB
|
||||
- GBA Memory: Fix 32-bit loads from unaddress cartridge space
|
||||
- GBA Memory: Fix jumping to invalid memory when switching from Thumb to ARM
|
||||
- GBA Video: Fix second frame mode 5
|
||||
- Perf: Fix race condition if a game crashes immediately on start
|
||||
- Qt: Fix Display object leak when closing a window
|
||||
- Qt: Fix .deb dependencies
|
||||
- Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning
|
||||
- Qt: Fix window not regaining focus after exiting savestate window
|
||||
- Qt: Fix regression where video would not record if the game had already started
|
||||
- Qt: Fix potential crash if a gamepad causes focus to change
|
||||
- Qt: Fix controller axis querying
|
||||
- Qt: Fix multiplayer windows opening as the wrong size
|
||||
- Qt: Fix controllers sometimes not loading the right profile
|
||||
- SDL: Fix boundary conditions for joystick adjustments
|
||||
- SDL: Allocate properly sized input maps
|
||||
- SDL: Fix potential build issues when Qt and SDL2 are in use
|
||||
- Util: Fix resource leak in UTF-8 handling code
|
||||
- Util: Fix a null-pointer issue when attempting to delete a key
|
||||
- VFS: Fix resource leaks if some allocations fail
|
||||
- Video: Fix an issue with very long filenames
|
||||
Misc:
|
||||
- GBA Memory: Soft-crash if jumping past the end of a ROM
|
||||
- Qt: Show multiplayer numbers in window title
|
||||
- Qt: Solar sensor can have shortcuts set
|
||||
|
||||
0.2.0: (2015-04-03)
|
||||
Features:
|
||||
- Support for gamepad axes, e.g. analog sticks or triggers
|
||||
- Add scale presets for up to 6x
|
||||
- Debugger: Add CLI "frame", frame advance command
|
||||
- Settings window
|
||||
- Bilinear resampling option
|
||||
- Add option to skip BIOS start screen
|
||||
- List of recently opened games
|
||||
- Support for games using the Solar Sensor
|
||||
- Debugger: Add CLI functions for writing to memory
|
||||
- Better audio resampling via blip-buf
|
||||
- Game Pak overrides dialog for setting savetype and sensor values
|
||||
- Support for games using the tilt sensor
|
||||
|
@ -23,56 +143,73 @@ Features:
|
|||
- 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
|
||||
- Controller profiles for setting different bindings for different controllers
|
||||
- Ability to lock aspect ratio
|
||||
- Local link cable support
|
||||
- Ability to switch which game controller is in use per instance
|
||||
- Ability to prevent opposing directional input
|
||||
- Warning dialog if an unimplemented BIOS feature is called
|
||||
- Debugger: Add CLI "frame", frame advance command
|
||||
- Debugger: Add CLI functions for writing to memory
|
||||
- Debugger: Add CLI functions for examining memory regions
|
||||
- Debugger: Add CLI function for writing a register
|
||||
Bugfixes:
|
||||
- ARM7: Extend prefetch by one stage
|
||||
- ARM7: Fix cycle counting for loads
|
||||
- Debugger: Disassembly now lists PSR bitmasks (fixes #191)
|
||||
- GBA: Fix savestate loading of DISPSTAT and WAITCNT registers
|
||||
- GBA: Initialize gba.sync to null
|
||||
- GBA: Fix timer initialization
|
||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||
- GBA Audio: Audio buffer sizes are now correct sizes for both sample rates
|
||||
- GBA BIOS: Fix BIOS prefetch after returning from an IRQ
|
||||
- GBA BIOS: Fix BIOS prefetch after reset
|
||||
- GBA 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 BIOS: Fix BIOS decompression routines with invalid source addresses
|
||||
- GBA Memory: Fix alignment of open bus 8- and 16-bit loads
|
||||
- GBA Memory: Fix I cycles that had been moved to ARM7 core
|
||||
- GBA Memory: Fix cycle counting for 32-bit load/stores
|
||||
- GBA RR: Fix fallthrough error when reading tags from a movie
|
||||
- GBA Thread: Fix possible hang when loading an archive
|
||||
- GBA Thread: Fix possible deadlock in video sync
|
||||
- GBA: Fix savestate loading of DISPSTAT and WAITCNT registers
|
||||
- Perf: Fix crash when the GBA thread fails to start
|
||||
- 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
|
||||
- Qt: Pause game while open file dialogs are open (fixes #6 on GitHub)
|
||||
- Qt: Fix crash when attempting to pause if a game is not running
|
||||
- SDL: Properly clean up if a game doesn't launch
|
||||
- Util: Fix sockets on Windows
|
||||
Misc:
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
- GBA Video: Start video at the last scanline instead of the first
|
||||
- All: Enable link-time optimization
|
||||
- Debugger: Watchpoints now work on STM/LDM instructions
|
||||
- GBA: Improve accuracy of event timing
|
||||
- Debugger: Clean up GDB stub network interfacing
|
||||
- Debugger: Simplify debugger state machine to play nicer with the GBA thread loop
|
||||
- Debugger: Merge Thumb BL instructions when disassembling
|
||||
- Debugger: Clean up debugger interface, removing obsolete state (fixes #67)
|
||||
- Debugger: Watchpoints now report address watched (fixes #68)
|
||||
- Debugger: Add support for soft breakpoints
|
||||
- Debugger: Make I/O register names be addresses instead of values
|
||||
- Debugger: Rename read/write commands
|
||||
- GBA: Improve accuracy of event timing
|
||||
- GBA: Add API for getting Configuration structs for overrides and input
|
||||
- GBA: Refactor gba-sensors and gba-gpio into gba-hardware
|
||||
- GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory
|
||||
- Debugger: Add support for soft breakpoints
|
||||
- Util: Use proper locale for reading and writing float values
|
||||
- Debugger: Make I/O register names be addresses instead of values
|
||||
- Debugger: Rename read/write commands
|
||||
- GBA: Move A/V stream interface into core
|
||||
- GBA: Savestates now take into account savedata state machines (fixes #109)
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA Video: Start video at the last scanline instead of the first
|
||||
- Qt: Optimize logo drawing
|
||||
- Qt: Move frame upload back onto main thread
|
||||
- All: Enable link-time optimization
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA: Move A/V stream interface into core
|
||||
- Qt: Remember window position
|
||||
- Qt: Double-clicking on the window toggles full screen
|
||||
- Util: Use proper locale for reading and writing float values
|
||||
|
||||
0.1.1: (2015-01-24)
|
||||
Bugfixes:
|
||||
|
|
140
CMakeLists.txt
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(mGBA C)
|
||||
set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu99")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99")
|
||||
set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger")
|
||||
set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger")
|
||||
set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
|
||||
|
@ -16,19 +16,22 @@ set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
|
|||
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
||||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
set(BUILD_GL ON CACHE STRING "Build with OpenGL")
|
||||
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
|
||||
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
|
||||
file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)
|
||||
file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c)
|
||||
file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)
|
||||
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
||||
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
||||
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
|
||||
file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c)
|
||||
file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||
set(VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-mem.c)
|
||||
source_group("ARM core" FILES ${ARM_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC})
|
||||
source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC})
|
||||
source_group("GBA supervisor" FILES ${GBA_CHEATS_SRC} ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
|
||||
|
@ -70,11 +73,52 @@ endfunction()
|
|||
|
||||
# Version information
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 2)
|
||||
set(LIB_VERSION_MINOR 3)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_ABI 0.2)
|
||||
set(LIB_VERSION_ABI 0.3)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(GIT_REV STREQUAL "")
|
||||
set(GIT_REV -1)
|
||||
endif()
|
||||
if(NOT GIT_TAG STREQUAL "")
|
||||
set(VERSION_STRING ${GIT_TAG})
|
||||
elseif(GIT_BRANCH STREQUAL "")
|
||||
set(VERSION_STRING ${LIB_VERSION_STRING})
|
||||
else()
|
||||
if(GIT_BRANCH STREQUAL "master")
|
||||
set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||
else()
|
||||
set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||
endif()
|
||||
|
||||
if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH)
|
||||
set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_custom_target(version-info ALL touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DGIT_COMMIT=${GIT_COMMIT}
|
||||
-DGIT_COMMIT_SHORT=${GIT_COMMIT_SHORT}
|
||||
-DGIT_BRANCH=${GIT_BRANCH}
|
||||
-DGIT_REV=${GIT_REV}
|
||||
-DBINARY_NAME=${BINARY_NAME}
|
||||
-DPROJECT_NAME=${PROJECT_NAME}
|
||||
-DVERSION_STRING=${VERSION_STRING}
|
||||
-D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-P ${CMAKE_SOURCE_DIR}/version.cmake
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/version.cmake)
|
||||
list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c)
|
||||
|
||||
# Advanced settings
|
||||
set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization")
|
||||
set(BUILD_PGO OFF CACHE BOOL "Build with profiling-guided optimization")
|
||||
|
@ -94,15 +138,22 @@ elseif(BUILD_PGO AND PGO_STAGE_2)
|
|||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_POST_FLAGS}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}")
|
||||
|
||||
# Feature dependencies
|
||||
set(FEATURES)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
|
||||
set(LIBEDIT_LIBRARIES -ledit)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL OpenBSD)
|
||||
list(APPEND LIBEDIT_LIBRARIES -ltermcap)
|
||||
endif()
|
||||
else()
|
||||
find_feature(USE_CLI_DEBUGGER "libedit")
|
||||
endif()
|
||||
if(BUILD_GL)
|
||||
find_package(OpenGL QUIET)
|
||||
if(NOT OPENGL_FOUND)
|
||||
set(BUILD_GL OFF)
|
||||
endif()
|
||||
endif()
|
||||
find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale")
|
||||
find_feature(USE_PNG "ZLIB;PNG")
|
||||
find_feature(USE_LIBZIP "libzip")
|
||||
|
@ -110,15 +161,23 @@ find_feature(USE_MAGICK "MagickWand")
|
|||
|
||||
# Platform support
|
||||
if(WIN32)
|
||||
set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}")
|
||||
add_definitions(-D_WIN32_WINNT=0x0600)
|
||||
list(APPEND OS_LIB ws2_32)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
|
||||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
elseif(UNIX)
|
||||
add_definitions(-DUSE_PTHREADS)
|
||||
if(NOT APPLE)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
endif()
|
||||
if(NOT APPLE AND NOT HAIKU)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||
endif()
|
||||
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
elseif(3DS)
|
||||
|
@ -129,25 +188,35 @@ elseif(3DS)
|
|||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_definitions(-D_DARWIN_C_SOURCE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
||||
endif()
|
||||
|
||||
if(NOT HAIKU)
|
||||
list(APPEND OS_LIB m)
|
||||
endif()
|
||||
|
||||
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
||||
endif()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI)
|
||||
if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
|
||||
if(NOT BUILD_EGL)
|
||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm")
|
||||
if(BUILD_PANDORA)
|
||||
add_definitions(-DBUILD_PANDORA)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*")
|
||||
enable_language(ASM)
|
||||
endif()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(strdup HAVE_STRDUP)
|
||||
check_function_exists(strndup HAVE_STRNDUP)
|
||||
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
|
@ -160,6 +229,10 @@ check_function_exists(newlocale HAVE_NEWLOCALE)
|
|||
check_function_exists(freelocale HAVE_FREELOCALE)
|
||||
check_function_exists(uselocale HAVE_USELOCALE)
|
||||
|
||||
if(HAVE_STRDUP)
|
||||
add_definitions(-DHAVE_STRDUP)
|
||||
endif()
|
||||
|
||||
if(HAVE_STRNDUP)
|
||||
add_definitions(-DHAVE_STRNDUP)
|
||||
endif()
|
||||
|
@ -214,12 +287,16 @@ if(USE_FFMPEG)
|
|||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra")
|
||||
if(APPLE)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework VideoDecodeAcceleration -framework CoreVideo")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework VideoDecodeAcceleration -framework CoreVideo")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_BLIP)
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
list(APPEND THIRD_PARTY_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF)
|
||||
else()
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)
|
||||
|
@ -252,12 +329,14 @@ if(USE_LIBZIP)
|
|||
link_directories(${LIBZIP_LIBRARY_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
|
||||
list(APPEND FEATURES LIBZIP)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-zip.c)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
||||
endif()
|
||||
|
||||
if (USE_LZMA)
|
||||
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
||||
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c)
|
||||
set(LZMA_SRC
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
|
||||
|
@ -289,10 +368,12 @@ endforeach()
|
|||
set(CORE_SRC
|
||||
${ARM_SRC}
|
||||
${GBA_SRC}
|
||||
${GBA_CHEATS_SRC}
|
||||
${GBA_RR_SRC}
|
||||
${GBA_SV_SRC}
|
||||
${DEBUGGER_SRC}
|
||||
${RENDERER_SRC}
|
||||
${SIO_SRC}
|
||||
${UTIL_SRC}
|
||||
${VFS_SRC}
|
||||
${OS_SRC}
|
||||
|
@ -310,24 +391,40 @@ if(BUILD_SHARED)
|
|||
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
target_compile_definitions(${BINARY_NAME}-static PRIVATE ${FEATURE_DEFINES})
|
||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
add_dependencies(${BINARY_NAME}-static version-info)
|
||||
endif()
|
||||
else()
|
||||
add_library(${BINARY_NAME} STATIC ${SRC})
|
||||
endif()
|
||||
|
||||
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE ${FEATURE_DEFINES})
|
||||
add_dependencies(${BINARY_NAME} version-info)
|
||||
|
||||
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-48.png DESTINATION share/icons/hicolor/48x48/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-64.png DESTINATION share/icons/hicolor/64x64/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-96.png DESTINATION share/icons/hicolor/96x96/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
endif()
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
|
||||
if(BUILD_GL)
|
||||
add_definitions(-DBUILD_GL)
|
||||
endif()
|
||||
|
||||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "")
|
||||
target_compile_definitions(${BINARY_NAME}_libretro PRIVATE COLOR_16_BIT;COLOR_5_6_5 PUBLIC "" INTERFACE "")
|
||||
target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5")
|
||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL)
|
||||
|
@ -358,6 +455,7 @@ if(3DS)
|
|||
endif()
|
||||
|
||||
# Packaging
|
||||
set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${LIB_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${LIB_VERSION_PATCH})
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
Contribution Guidelines
|
||||
=======================
|
||||
|
||||
In order to contribute to mGBA, there are a few things to be mindful of so as to ease the process.
|
||||
|
||||
Filing issues
|
||||
-------------
|
||||
New issues should be filed on the [mGBA GitHub Issues tracker](http://mgba.io/i/). When filing issues, please include the following information:
|
||||
|
||||
* The build you are using. For recent builds, this is visible in the title bar. For example, `0.3-2134-4ec19aa`. On older builds, such as 0.2.1, this is not present, so please specify the version you downloaded or built. If present, this contains the version, branch name (if not `master`), a revision number and a truncated revision hash. For this example, it means that it's version 0.3, on `master`, commit number 2134 and revision hash `4ec19aa`. Additionally, `-dirty` will be appended if there are local changes that haven't been commited.
|
||||
* The operating system you're using, for example Windows 7 32-bit or Ubuntu 15.04 64-bit.
|
||||
* Your CPU and graphics card (usually not necessary). For example, Core i5-3570K and AMD Radeon R9 280X.
|
||||
|
||||
Please also describe the issue in as much detail as possible, including the name of the games you have reproduced the issue on, and how you managed to enter the buggy state. If applicable, savestates can be renamed to be .png files and attached to the issue directly.
|
||||
|
||||
Filing pull requests
|
||||
--------------------
|
||||
When filing a pull request, please make sure you adhere to the coding style as outlined below, and are aware of the requirements for licensing. Furthermore, please make sure all commits in the pull request have coherent commit messages as well as the name of the component being modified in the commit message.
|
||||
|
||||
Some components are as follows:
|
||||
|
||||
* ARM7: The ARM core
|
||||
* GBA: GBA code
|
||||
* GBA Memory: Memory-specific
|
||||
* GBA Video: Video, rendering
|
||||
* GBA Audio: Audio processing
|
||||
* GBA SIO: Serial I/O, multiplayer, link
|
||||
* GBA Hardware: Extra devices, e.g. gyro, light sensor
|
||||
* GBA RR: Rerecording features
|
||||
* GBA Thread: Thread-layer abstractions
|
||||
* GBA BIOS: High-level BIOS
|
||||
* Qt: Qt port-related code
|
||||
* SDL: SDL port-related code (including as used in other ports)
|
||||
* Video: Video recording code
|
||||
* Util: Common utility code
|
||||
* Tools: Miscellaneous tools
|
||||
* Debugger: Included debugging functionality
|
||||
* All: Changes that don't touch specific components but affect the project overall
|
||||
|
||||
|
||||
Coding Style
|
||||
------------
|
||||
mGBA aims to have a consistent, clean codebase, so when contributing code to mGBA, please adhere to the following rules. If a pull request has style errors, you will be asked to fix them before the PR will be accepted.
|
||||
|
||||
### Naming
|
||||
|
||||
Variable names, including parameters, should all be in camelCase. File-scoped static variables must start with an underscore.
|
||||
|
||||
C struct names should start with a capital letter, and functions relating to these structs should start with the name of the class (including the capital letter) and be in camelCase after. C struct should not be `typedef`ed.
|
||||
|
||||
Functions not associated with structs should be in camelCase throughout. Static functions not associated with structs must start with an underscore.
|
||||
|
||||
Enum values and `#define`s should be all caps with underscores.
|
||||
|
||||
Good:
|
||||
|
||||
static int _localVariable;
|
||||
|
||||
struct LocalStruct {
|
||||
void (*methodName)(struct LocalStruct struct, param);
|
||||
|
||||
int memberName;
|
||||
};
|
||||
|
||||
enum {
|
||||
ENUM_ITEM_1,
|
||||
ENUM_ITEM_2
|
||||
};
|
||||
|
||||
void LocalStructCreate(struct LocalStruct* struct);
|
||||
|
||||
void functionName(int argument);
|
||||
|
||||
static void _LocalStructUse(struct LocalStruct* struct);
|
||||
static void _function2(int argument2);
|
||||
|
||||
C++ classes should be confined to namespaces. For the Qt port, this namespace is called `QGBA`.
|
||||
|
||||
Class names should be handled similarly to C structs. Fields should be prefixed according to their scoping:
|
||||
|
||||
* `m_` for non-static member.
|
||||
* `s_` for static member.
|
||||
|
||||
### Braces
|
||||
|
||||
Braces do not go on their own lines, apart from the terminating brace. There should be a single space between the condition clause and the brace. Furthermore, braces must be used even for single-line blocks.
|
||||
|
||||
Good:
|
||||
|
||||
if (condition) {
|
||||
block;
|
||||
} else if (condition2) {
|
||||
block2;
|
||||
} else {
|
||||
block3;
|
||||
}
|
||||
|
||||
Bad (separate line):
|
||||
|
||||
if (condition)
|
||||
{
|
||||
block;
|
||||
}
|
||||
else if (condition2)
|
||||
{
|
||||
block2;
|
||||
}
|
||||
else
|
||||
{
|
||||
block3;
|
||||
}
|
||||
|
||||
Bad (missing braces):
|
||||
|
||||
if (condition)
|
||||
statement;
|
||||
else if (condition2)
|
||||
statement2;
|
||||
else
|
||||
statement3;
|
||||
|
||||
Bad (missing space):
|
||||
|
||||
if (condition){
|
||||
block;
|
||||
}
|
||||
|
||||
### Spacing
|
||||
|
||||
Indentation should be done using tabs and should match the level of braces. Alignment within a line should be done sparingly, but only done with spaces.
|
||||
|
||||
### Header guards
|
||||
|
||||
For C headers guards, the define should be the filename (including H), all-caps, with underscores instead of punctuation.
|
||||
|
||||
Good:
|
||||
|
||||
#ifndef FILE_NAME_H
|
||||
#define FILE_NAME_H
|
||||
|
||||
// Header
|
||||
|
||||
#endif
|
||||
|
||||
There should be no comment on the `#endif`.
|
||||
|
||||
For Qt (C++ header guards), the define should start with `QGBA_` and not include `_H`, but is otherwise the same. This is mostly for legacy reasons., and may change in the future.
|
||||
|
||||
Good:
|
||||
|
||||
#ifndef QGBA_FILE_NAME
|
||||
#define QGBA_FILE_NAME
|
||||
|
||||
// Header
|
||||
|
||||
#endif
|
||||
|
||||
### Other
|
||||
|
||||
Block statements such as `if`, `while` and `for` should have a space between the type of block and the parenthesis.
|
||||
|
||||
Good:
|
||||
|
||||
while (condition) {
|
||||
block;
|
||||
}
|
||||
|
||||
Bad:
|
||||
|
||||
while(condition) {
|
||||
block;
|
||||
}
|
||||
|
||||
In C code, use `0` instead of `NULL`. This is mostly for legacy reasons and may change in the future. C code should also use `bool` types and values `true` and `false` instead of `1` and `0` where applicable. In C++ code, use `nullptr` instead of `NULL` or `0`.
|
||||
|
||||
If a statement has no body, putting braces is not required, and a semicolon can be used. This is not required, but is suggested.
|
||||
|
||||
Good:
|
||||
|
||||
while (f());
|
||||
|
||||
Bad:
|
||||
|
||||
while (f()) {}
|
||||
|
||||
|
||||
For infinite loops that `break` statements internally, `while (true)` is preferred over `for (;;)`.
|
||||
|
||||
Licensing
|
||||
---------
|
||||
|
||||
mGBA is licensed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). This entails a few things when it comes to adding code to mGBA.
|
||||
|
||||
* New code to mGBA will be licensed under the MPL 2.0 license.
|
||||
* GPL-licensed code cannot be added to mGBA upstream, but can be linked with mGBA when compiled.
|
||||
* MIT, BSD, CC0, etc., code can be added to mGBA upstream, but preferably in the `third-party` section if applicable.
|
36
README.md
|
@ -3,7 +3,7 @@ mGBA
|
|||
|
||||
mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack.
|
||||
|
||||
Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/).
|
||||
Up-to-date news and downloads can be found at [mgba.io](http://mgba.io/).
|
||||
|
||||
![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)
|
||||
|
||||
|
@ -13,11 +13,12 @@ Features
|
|||
- Near full Game Boy Advance hardware support[<sup>[1]</sup>](#missing).
|
||||
- Fast emulation. Known to run at full speed even on low end hardware, such as netbooks.
|
||||
- Qt and SDL ports for a heavy-weight and a light-weight frontend.
|
||||
- Local (same computer) link cable support.
|
||||
- Save type detection, even for flash memory size[<sup>[2]</sup>](#flashdetect).
|
||||
- Real-time clock support, even without configuration.
|
||||
- A built-in BIOS implementation, and ability to load external BIOS files.
|
||||
- Turbo/fast-forward support by holding Tab.
|
||||
- Frameskip, configurable up to 9.
|
||||
- Frameskip, configurable up to 10.
|
||||
- Screenshot support.
|
||||
- Cheat code support.
|
||||
- 9 savestate slots. Savestates are also viewable as screenshots.
|
||||
|
@ -30,12 +31,13 @@ Features
|
|||
|
||||
### Planned features
|
||||
|
||||
- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)).
|
||||
- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)).
|
||||
- Networked multiplayer link cable support ([Bug #1](http://mgba.io/b/1)).
|
||||
- Dolphin/JOY bus link cable support ([Bug #73](http://mgba.io/b/73)).
|
||||
- Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker))
|
||||
- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)).
|
||||
- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)).
|
||||
- libretro core for RetroArch and OpenEmu ([Bug #86](https://endrift.com/mgba/bugs/show_bug.cgi?id=86)).
|
||||
- Lua support for scripting ([Bug #62](http://mgba.io/b/62)).
|
||||
- A comprehensive debug suite ([Bug #132](http://mgba.io/b/132)).
|
||||
- OpenEmu core.
|
||||
- e-Reader support. ([Bug #171](http://mgba.io/b/171))
|
||||
|
||||
|
||||
Supported Platforms
|
||||
|
@ -46,7 +48,7 @@ Supported Platforms
|
|||
- Linux
|
||||
- FreeBSD
|
||||
|
||||
Other Unix-like platforms work as well, but are untested.
|
||||
Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported.
|
||||
|
||||
### System requirements
|
||||
|
||||
|
@ -72,7 +74,7 @@ Controls are configurable in the menu. The default gamepad controls are mapped s
|
|||
Compiling
|
||||
---------
|
||||
|
||||
Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows:
|
||||
Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
|
@ -99,18 +101,18 @@ Footnotes
|
|||
|
||||
<a name="missing">[1]</a> Currently missing features are
|
||||
|
||||
- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141))
|
||||
- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142))
|
||||
- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199))
|
||||
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141))
|
||||
- Audio channel reset flags ([Bug #142](http://mgba.io/b/142))
|
||||
- Game Pak prefetch ([Bug #195](http://mgba.io/b/195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199))
|
||||
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered.
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
|
||||
|
||||
<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
|
||||
|
||||
[downloads]: https://endrift.com/mgba/downloads.html
|
||||
[downloads]: http://mgba.io/downloads.html
|
||||
[source]: https://github.com/mgba-emu/mgba/
|
||||
|
||||
Copyright
|
||||
|
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 799 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 157 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Icon=mgba
|
||||
Exec=mgba-qt %f
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Name=mGBA
|
||||
GenericName=Game Boy Advance Emulator
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;
|
||||
|
|
@ -1 +0,0 @@
|
|||
IDI_ICON1 ICON DISCARDABLE "mgba.ico"
|
|
@ -0,0 +1,28 @@
|
|||
IDI_ICON1 ICON DISCARDABLE "${CMAKE_SOURCE_DIR}/res/mgba.ico"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION ${WIN32_VERSION},0
|
||||
PRODUCTVERSION ${WIN32_VERSION},0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "endrift"
|
||||
VALUE "FileDescription", "mGBA Game Boy Advance emulator"
|
||||
VALUE "FileVersion", "${LIB_VERSION_STRING}.0"
|
||||
VALUE "InternalName", "${BINARY_NAME}"
|
||||
VALUE "LegalCopyright", "(c) 2013 - 2015 Jeffrey Pfau"
|
||||
VALUE "OriginalFilename", "${BINARY_NAME}"
|
||||
VALUE "ProductName", "${PROJECT_NAME}"
|
||||
VALUE "ProductVersion", "${BINARY_NAME}"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
|
@ -164,10 +164,10 @@ void ARMRaiseIRQ(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_PC] = BASE_IRQ;
|
||||
int currentCycles = 0;
|
||||
ARM_WRITE_PC;
|
||||
cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]);
|
||||
_ARMSetMode(cpu, MODE_ARM);
|
||||
cpu->spsr = cpsr;
|
||||
cpu->cpsr.i = 1;
|
||||
cpu->cycles += currentCycles;
|
||||
}
|
||||
|
||||
void ARMRaiseSWI(struct ARMCore* cpu) {
|
||||
|
@ -184,10 +184,10 @@ void ARMRaiseSWI(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_PC] = BASE_SWI;
|
||||
int currentCycles = 0;
|
||||
ARM_WRITE_PC;
|
||||
cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]);
|
||||
_ARMSetMode(cpu, MODE_ARM);
|
||||
cpu->spsr = cpsr;
|
||||
cpu->cpsr.i = 1;
|
||||
cpu->cycles += currentCycles;
|
||||
}
|
||||
|
||||
static inline void ARMStep(struct ARMCore* cpu) {
|
||||
|
|
|
@ -380,8 +380,12 @@ DEFINE_DECODER_ARM(MRC, ILL, info->operandFormat = ARM_OPERAND_NONE;)
|
|||
|
||||
// Begin miscellaneous definitions
|
||||
|
||||
DEFINE_DECODER_ARM(BKPT, BKPT, info->operandFormat = ARM_OPERAND_NONE;) // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_DECODER_ARM(ILL, ILL, info->operandFormat = ARM_OPERAND_NONE;) // Illegal opcode
|
||||
DEFINE_DECODER_ARM(BKPT, BKPT,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;) // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_DECODER_ARM(ILL, ILL,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;) // Illegal opcode
|
||||
|
||||
DEFINE_DECODER_ARM(MSR, MSR,
|
||||
info->affectsCPSR = 1;
|
||||
|
|
|
@ -281,8 +281,13 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POPR, ARM_SP, LDM, ARM_MEMORY_INCREMENT_AFTE
|
|||
DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSH, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 0)
|
||||
DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSHR, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 1 << ARM_LR)
|
||||
|
||||
DEFINE_THUMB_DECODER(ILL, ILL, info->traps = 1;)
|
||||
DEFINE_THUMB_DECODER(BKPT, BKPT, info->traps = 1;)
|
||||
DEFINE_THUMB_DECODER(ILL, ILL,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;)
|
||||
|
||||
DEFINE_THUMB_DECODER(BKPT, BKPT,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;)
|
||||
|
||||
DEFINE_THUMB_DECODER(B, B,
|
||||
int16_t immediate = (opcode & 0x07FF) << 5;
|
||||
|
|
|
@ -233,7 +233,12 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
#define ADDR_MODE_2_RM (cpu->gprs[rm])
|
||||
#define ADDR_MODE_2_IMMEDIATE (opcode & 0x00000FFF)
|
||||
#define ADDR_MODE_2_INDEX(U_OP, M) (cpu->gprs[rn] U_OP M)
|
||||
#define ADDR_MODE_2_WRITEBACK(ADDR) (cpu->gprs[rn] = ADDR)
|
||||
#define ADDR_MODE_2_WRITEBACK(ADDR) \
|
||||
cpu->gprs[rn] = ADDR; \
|
||||
if (UNLIKELY(rn == ARM_PC)) { \
|
||||
ARM_WRITE_PC; \
|
||||
}
|
||||
|
||||
#define ADDR_MODE_2_LSL (cpu->gprs[rm] << ADDR_MODE_2_I)
|
||||
#define ADDR_MODE_2_LSR (ADDR_MODE_2_I_TEST ? ((uint32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : 0)
|
||||
#define ADDR_MODE_2_ASR (ADDR_MODE_2_I_TEST ? ((int32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : ((int32_t) cpu->gprs[rm]) >> 31)
|
||||
|
@ -254,7 +259,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address;
|
||||
|
||||
#define ARM_LOAD_POST_BODY \
|
||||
++currentCycles; \
|
||||
currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \
|
||||
if (rd == ARM_PC) { \
|
||||
ARM_WRITE_PC; \
|
||||
}
|
||||
|
@ -322,13 +327,13 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
int rdHi = (opcode >> 16) & 0xF; \
|
||||
int rs = (opcode >> 8) & 0xF; \
|
||||
int rm = opcode & 0xF; \
|
||||
UNUSED(rdHi); \
|
||||
if (rdHi == ARM_PC || rd == ARM_PC) { \
|
||||
return; \
|
||||
} \
|
||||
ARM_WAIT_MUL(cpu->gprs[rs]); \
|
||||
BODY; \
|
||||
S_BODY; \
|
||||
if (rd == ARM_PC) { \
|
||||
ARM_WRITE_PC; \
|
||||
})
|
||||
currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32)
|
||||
|
||||
#define DEFINE_MULTIPLY_INSTRUCTION_ARM(NAME, BODY, S_BODY) \
|
||||
DEFINE_MULTIPLY_INSTRUCTION_EX_ARM(NAME, BODY, ) \
|
||||
|
@ -562,14 +567,14 @@ DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRT,
|
|||
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(LDM,
|
||||
load,
|
||||
++currentCycles;
|
||||
currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32;
|
||||
if (rs & 0x8000) {
|
||||
ARM_WRITE_PC;
|
||||
})
|
||||
|
||||
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(STM,
|
||||
store,
|
||||
currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32)
|
||||
ARM_STORE_POST_BODY;)
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(SWP,
|
||||
int rm = opcode & 0xF;
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
|
||||
#define THUMB_PREFETCH_CYCLES (1 + cpu->memory.activeSeqCycles16)
|
||||
|
||||
#define THUMB_LOAD_POST_BODY ++currentCycles;
|
||||
#define THUMB_LOAD_POST_BODY \
|
||||
currentCycles += 1 + cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;
|
||||
|
||||
#define THUMB_STORE_POST_BODY \
|
||||
currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;
|
||||
|
@ -230,7 +231,7 @@ DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(NEG, THUMB_SUBTRACTION(cpu->gprs[rd], 0, cp
|
|||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(CMP2, int32_t aluOut = cpu->gprs[rd] - cpu->gprs[rn]; THUMB_SUBTRACTION_S(cpu->gprs[rd], cpu->gprs[rn], aluOut))
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(CMN, int32_t aluOut = cpu->gprs[rd] + cpu->gprs[rn]; THUMB_ADDITION_S(cpu->gprs[rd], cpu->gprs[rn], aluOut))
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(ORR, cpu->gprs[rd] = cpu->gprs[rd] | cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]))
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MUL, ARM_WAIT_MUL(cpu->gprs[rn]); cpu->gprs[rd] *= cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]))
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MUL, ARM_WAIT_MUL(cpu->gprs[rd]); cpu->gprs[rd] *= cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]); currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16)
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(BIC, cpu->gprs[rd] = cpu->gprs[rd] & ~cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]))
|
||||
DEFINE_DATA_FORM_5_INSTRUCTION_THUMB(MVN, cpu->gprs[rd] = ~cpu->gprs[rn]; THUMB_NEUTRAL_S( , , cpu->gprs[rd]))
|
||||
|
||||
|
|
|
@ -8,68 +8,9 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#if defined(__PPC__) || defined(__POWERPC__)
|
||||
#define LOAD_32(DEST, ADDR, ARR) { \
|
||||
uint32_t _addr = (ADDR); \
|
||||
void* _ptr = (ARR); \
|
||||
asm("lwbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \
|
||||
}
|
||||
|
||||
#define LOAD_16(DEST, ADDR, ARR) { \
|
||||
uint32_t _addr = (ADDR); \
|
||||
void* _ptr = (ARR); \
|
||||
asm("lhbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \
|
||||
}
|
||||
|
||||
#define STORE_32(SRC, ADDR, ARR) { \
|
||||
uint32_t _addr = (ADDR); \
|
||||
void* _ptr = (ARR); \
|
||||
asm("stwbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \
|
||||
}
|
||||
|
||||
#define STORE_16(SRC, ADDR, ARR) { \
|
||||
uint32_t _addr = (ADDR); \
|
||||
void* _ptr = (ARR); \
|
||||
asm("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \
|
||||
}
|
||||
#else
|
||||
#define LOAD_32(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2]
|
||||
#define LOAD_16(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1]
|
||||
#define STORE_32(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC
|
||||
#define STORE_16(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC
|
||||
#endif
|
||||
|
||||
#define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START))
|
||||
#define CHECK_BITS(SRC, START, END) ((SRC) & MAKE_MASK(START, END))
|
||||
#define EXT_BITS(SRC, START, END) (((SRC) >> (START)) & ((1 << ((END) - (START))) - 1))
|
||||
#define INS_BITS(SRC, START, END, BITS) (CLEAR_BITS(SRC, START, END) | (((BITS) << (START)) & MAKE_MASK(START, END)))
|
||||
#define CLEAR_BITS(SRC, START, END) ((SRC) & ~MAKE_MASK(START, END))
|
||||
#define FILL_BITS(SRC, START, END) ((SRC) | MAKE_MASK(START, END))
|
||||
|
||||
#define DECL_BITFIELD(NAME, TYPE) typedef TYPE NAME
|
||||
|
||||
#define DECL_BITS(TYPE, FIELD, START, SIZE) \
|
||||
__attribute__((unused)) static inline TYPE TYPE ## Is ## FIELD (TYPE src) { \
|
||||
return CHECK_BITS(src, (START), (START) + (SIZE)); \
|
||||
} \
|
||||
__attribute__((unused)) static inline TYPE TYPE ## Get ## FIELD (TYPE src) { \
|
||||
return EXT_BITS(src, (START), (START) + (SIZE)); \
|
||||
} \
|
||||
__attribute__((unused)) static inline TYPE TYPE ## Clear ## FIELD (TYPE src) { \
|
||||
return CLEAR_BITS(src, (START), (START) + (SIZE)); \
|
||||
} \
|
||||
__attribute__((unused)) static inline TYPE TYPE ## Fill ## FIELD (TYPE src) { \
|
||||
return FILL_BITS(src, (START), (START) + (SIZE)); \
|
||||
} \
|
||||
__attribute__((unused)) static inline TYPE TYPE ## Set ## FIELD (TYPE src, TYPE bits) { \
|
||||
return INS_BITS(src, (START), (START) + (SIZE), bits); \
|
||||
}
|
||||
|
||||
#define DECL_BIT(TYPE, FIELD, BIT) DECL_BITS(TYPE, FIELD, BIT, 1)
|
||||
|
||||
#define LIKELY(X) __builtin_expect(!!(X), 1)
|
||||
#define UNLIKELY(X) __builtin_expect(!!(X), 0)
|
||||
|
||||
#define ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31)))
|
||||
#define LOAD_32 LOAD_32LE
|
||||
#define LOAD_16 LOAD_16LE
|
||||
#define STORE_32 STORE_32LE
|
||||
#define STORE_16 STORE_16LE
|
||||
|
||||
#endif
|
||||
|
|
|
@ -519,6 +519,7 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector
|
|||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerClearBreakpoint(&debugger->d, address);
|
||||
ARMDebuggerClearWatchpoint(&debugger->d, address);
|
||||
}
|
||||
|
||||
static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -816,7 +817,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
}
|
||||
|
||||
const char* commandPtr;
|
||||
int cmd = 0, len = 0;
|
||||
size_t cmd = 0, len = 0;
|
||||
const char* name = 0;
|
||||
for (commandPtr = li->buffer; commandPtr <= li->cursor; ++commandPtr, ++len) {
|
||||
for (; (name = _debuggerCommands[cmd].name); ++cmd) {
|
||||
|
@ -832,7 +833,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
if (!name) {
|
||||
return CC_ERROR;
|
||||
}
|
||||
if (_debuggerCommands[cmd + 1].name && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) {
|
||||
if (_debuggerCommands[cmd + 1].name && strlen(_debuggerCommands[cmd + 1].name) >= len - 1 && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) {
|
||||
--len;
|
||||
const char* next = 0;
|
||||
int i;
|
||||
|
@ -842,6 +843,9 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
}
|
||||
next = _debuggerCommands[i].name;
|
||||
}
|
||||
if (!next) {
|
||||
return CC_ERROR;
|
||||
}
|
||||
|
||||
for (; name[len]; ++len) {
|
||||
if (name[len] != next[len]) {
|
||||
|
@ -861,7 +865,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
static void _cliDebuggerInit(struct ARMDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
// TODO: get argv[0]
|
||||
cliDebugger->elstate = el_init(BINARY_NAME, stdin, stdout, stderr);
|
||||
cliDebugger->elstate = el_init(binaryName, stdin, stdout, stderr);
|
||||
el_set(cliDebugger->elstate, EL_PROMPT, _prompt);
|
||||
el_set(cliDebugger->elstate, EL_EDITOR, "emacs");
|
||||
|
||||
|
|
|
@ -149,11 +149,14 @@ bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t add
|
|||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint** previous = &debugger->breakpoints;
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
for (; (breakpoint = *previous); previous = &breakpoint->next) {
|
||||
struct DebugBreakpoint** next;
|
||||
while ((breakpoint = *previous)) {
|
||||
next = &breakpoint->next;
|
||||
if (breakpoint->address == address) {
|
||||
*previous = breakpoint->next;
|
||||
*previous = *next;
|
||||
free(breakpoint);
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,12 +172,15 @@ void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
|||
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugWatchpoint** previous = &debugger->watchpoints;
|
||||
struct DebugWatchpoint* breakpoint;
|
||||
for (; (breakpoint = *previous); previous = &breakpoint->next) {
|
||||
if (breakpoint->address == address) {
|
||||
*previous = breakpoint->next;
|
||||
free(breakpoint);
|
||||
struct DebugWatchpoint* watchpoint;
|
||||
struct DebugWatchpoint** next;
|
||||
while ((watchpoint = *previous)) {
|
||||
next = &watchpoint->next;
|
||||
if (watchpoint->address == address) {
|
||||
*previous = *next;
|
||||
free(watchpoint);
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
if (!debugger->watchpoints) {
|
||||
ARMDebuggerRemoveMemoryShim(debugger);
|
||||
|
|
|
@ -91,7 +91,7 @@ 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)))
|
||||
ATTRIBUTE_FORMAT(printf, 3, 4)
|
||||
void (*log)(struct ARMDebugger*, enum DebuggerLogLevel, const char* format, ...);
|
||||
};
|
||||
|
||||
|
|
|
@ -464,6 +464,8 @@ void GDBStubCreate(struct GDBStub* stub) {
|
|||
stub->d.custom = _gdbStubPoll;
|
||||
stub->d.log = 0;
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
stub->lineAck = GDB_ACK_PENDING;
|
||||
stub->shouldBlock = false;
|
||||
}
|
||||
|
||||
bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
|
||||
|
|
154
src/gba/audio.c
|
@ -14,8 +14,13 @@
|
|||
const unsigned GBA_AUDIO_SAMPLES = 2048;
|
||||
const unsigned BLIP_BUFFER_SIZE = 0x4000;
|
||||
const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t);
|
||||
const int GBA_AUDIO_VOLUME_MAX = 0x100;
|
||||
#define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128)
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
static const int CLOCKS_PER_FRAME = 0x400;
|
||||
#endif
|
||||
|
||||
static bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value);
|
||||
static int32_t _updateSquareChannel(struct GBAAudioSquareControl* envelope, int duty);
|
||||
static void _updateEnvelope(struct GBAAudioEnvelope* envelope);
|
||||
|
@ -41,6 +46,14 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
|
|||
#endif
|
||||
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
|
||||
audio->forceDisableCh[0] = false;
|
||||
audio->forceDisableCh[1] = false;
|
||||
audio->forceDisableCh[2] = false;
|
||||
audio->forceDisableCh[3] = false;
|
||||
audio->forceDisableChA = false;
|
||||
audio->forceDisableChB = false;
|
||||
audio->masterVolume = GBA_AUDIO_VOLUME_MAX;
|
||||
}
|
||||
|
||||
void GBAAudioReset(struct GBAAudio* audio) {
|
||||
|
@ -480,30 +493,6 @@ void 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) {
|
||||
|
@ -514,11 +503,16 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
|||
GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", fifoId);
|
||||
return;
|
||||
}
|
||||
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t)) {
|
||||
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) {
|
||||
struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource];
|
||||
dma->nextCount = 4;
|
||||
dma->nextEvent = 0;
|
||||
GBAMemoryUpdateDMAs(audio->p, -cycles);
|
||||
if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) {
|
||||
dma->nextCount = 4;
|
||||
dma->nextEvent = 0;
|
||||
dma->reg = GBADMARegisterSetWidth(dma->reg, 1);
|
||||
GBAMemoryUpdateDMAs(audio->p, -cycles);
|
||||
} else {
|
||||
channel->dmaSource = 0;
|
||||
}
|
||||
}
|
||||
CircleBufferRead8(&channel->fifo, &channel->sample);
|
||||
}
|
||||
|
@ -738,7 +732,7 @@ static int _applyBias(struct GBAAudio* audio, int sample) {
|
|||
} else if (sample < 0) {
|
||||
sample = 0;
|
||||
}
|
||||
return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5;
|
||||
return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume) >> 3;
|
||||
}
|
||||
|
||||
static void _sample(struct GBAAudio* audio) {
|
||||
|
@ -746,55 +740,67 @@ static void _sample(struct GBAAudio* audio) {
|
|||
int16_t sampleRight = 0;
|
||||
int psgShift = 5 - audio->volume;
|
||||
|
||||
if (audio->ch1Left) {
|
||||
sampleLeft += audio->ch1.sample;
|
||||
if (audio->playingCh1 && !audio->forceDisableCh[0]) {
|
||||
if (audio->ch1Left) {
|
||||
sampleLeft += audio->ch1.sample;
|
||||
}
|
||||
|
||||
if (audio->ch1Right) {
|
||||
sampleRight += audio->ch1.sample;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch1Right) {
|
||||
sampleRight += audio->ch1.sample;
|
||||
if (audio->playingCh2 && !audio->forceDisableCh[1]) {
|
||||
if (audio->ch2Left) {
|
||||
sampleLeft += audio->ch2.sample;
|
||||
}
|
||||
|
||||
if (audio->ch2Right) {
|
||||
sampleRight += audio->ch2.sample;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch2Left) {
|
||||
sampleLeft += audio->ch2.sample;
|
||||
if (audio->playingCh3 && !audio->forceDisableCh[2]) {
|
||||
if (audio->ch3Left) {
|
||||
sampleLeft += audio->ch3.sample;
|
||||
}
|
||||
|
||||
if (audio->ch3Right) {
|
||||
sampleRight += audio->ch3.sample;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch2Right) {
|
||||
sampleRight += audio->ch2.sample;
|
||||
}
|
||||
if (audio->playingCh4 && !audio->forceDisableCh[3]) {
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += audio->ch4.sample;
|
||||
}
|
||||
|
||||
if (audio->ch3Left) {
|
||||
sampleLeft += audio->ch3.sample;
|
||||
}
|
||||
|
||||
if (audio->ch3Right) {
|
||||
sampleRight += audio->ch3.sample;
|
||||
}
|
||||
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += audio->ch4.sample;
|
||||
}
|
||||
|
||||
if (audio->ch4Right) {
|
||||
sampleRight += audio->ch4.sample;
|
||||
if (audio->ch4Right) {
|
||||
sampleRight += audio->ch4.sample;
|
||||
}
|
||||
}
|
||||
|
||||
sampleLeft = (sampleLeft * (1 + audio->volumeLeft)) >> psgShift;
|
||||
sampleRight = (sampleRight * (1 + audio->volumeRight)) >> psgShift;
|
||||
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
if (!audio->forceDisableChA) {
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
}
|
||||
if (!audio->forceDisableChB) {
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
}
|
||||
|
||||
sampleLeft = _applyBias(audio, sampleLeft);
|
||||
|
@ -813,19 +819,23 @@ static void _sample(struct GBAAudio* audio) {
|
|||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
int clockNeeded = blip_clocks_needed(audio->left, audio->samples / 32);
|
||||
if (audio->clock >= clockNeeded) {
|
||||
if (audio->clock >= CLOCKS_PER_FRAME) {
|
||||
blip_end_frame(audio->left, audio->clock);
|
||||
blip_end_frame(audio->right, audio->clock);
|
||||
audio->clock -= clockNeeded;
|
||||
audio->clock -= CLOCKS_PER_FRAME;
|
||||
}
|
||||
}
|
||||
produced = blip_samples_avail(audio->left);
|
||||
#endif
|
||||
if (audio->p->stream) {
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= audio->samples);
|
||||
bool wait = produced >= audio->samples;
|
||||
GBASyncProduceAudio(audio->p->sync, wait);
|
||||
|
||||
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
|
||||
audio->p->stream->postAudioBuffer(audio->p->stream, audio);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||
|
@ -897,8 +907,12 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
|
|||
|
||||
CircleBufferClear(&audio->chA.fifo);
|
||||
CircleBufferClear(&audio->chB.fifo);
|
||||
int i;
|
||||
for (i = 0; i < state->audio.fifoSize; ++i) {
|
||||
size_t fifoSize = state->audio.fifoSize;
|
||||
if (state->audio.fifoSize > CircleBufferCapacity(&audio->chA.fifo)) {
|
||||
fifoSize = CircleBufferCapacity(&audio->chA.fifo);
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < fifoSize; ++i) {
|
||||
CircleBufferWrite8(&audio->chA.fifo, state->audio.fifoA[i]);
|
||||
CircleBufferWrite8(&audio->chB.fifo, state->audio.fifoB[i]);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
#include "util/circle-buffer.h"
|
||||
|
||||
#define RESAMPLE_NN 0
|
||||
#define RESAMPLE_BLIP_BUF 2
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
#include "third-party/blip_buf/blip_buf.h"
|
||||
#endif
|
||||
|
@ -18,9 +21,7 @@
|
|||
struct GBADMA;
|
||||
|
||||
extern const unsigned GBA_AUDIO_SAMPLES;
|
||||
|
||||
#define RESAMPLE_NN 0
|
||||
#define RESAMPLE_BLIP_BUF 2
|
||||
extern const int GBA_AUDIO_VOLUME_MAX;
|
||||
|
||||
DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t);
|
||||
DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6);
|
||||
|
@ -236,6 +237,11 @@ struct GBAAudio {
|
|||
int32_t nextSample;
|
||||
|
||||
int32_t sampleInterval;
|
||||
|
||||
bool forceDisableCh[4];
|
||||
bool forceDisableChA;
|
||||
bool forceDisableChB;
|
||||
int masterVolume;
|
||||
};
|
||||
|
||||
struct GBAStereoSample {
|
||||
|
@ -268,7 +274,6 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
|
|||
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value);
|
||||
|
||||
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value);
|
||||
void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value);
|
||||
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
|
||||
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
|
||||
|
||||
|
|
|
@ -88,8 +88,8 @@ static void _BgAffineSet(struct GBA* gba) {
|
|||
// [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ]
|
||||
// [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ]
|
||||
// [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
|
||||
ox = cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||
oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f;
|
||||
ox = (int32_t) cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||
oy = (int32_t) cpu->memory.load32(cpu, offset + 4, 0) / 256.f;
|
||||
cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0);
|
||||
cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0);
|
||||
sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
||||
|
@ -238,6 +238,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination");
|
||||
// Fall through
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
|
@ -253,6 +254,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination");
|
||||
// Fall through
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
|
@ -269,6 +271,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination");
|
||||
// Fall through
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
|
@ -286,6 +289,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination");
|
||||
// Fall through
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_VRAM:
|
||||
|
@ -327,7 +331,7 @@ static void _unLz77(struct GBA* gba, int width) {
|
|||
uint32_t disp;
|
||||
int bytes;
|
||||
int byte;
|
||||
int halfword;
|
||||
int halfword = 0;
|
||||
while (remaining > 0) {
|
||||
if (blocksRemaining) {
|
||||
if (blockheader & 0x80) {
|
||||
|
|
525
src/gba/cheats.c
|
@ -5,8 +5,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "cheats.h"
|
||||
|
||||
#include "gba/cheats/gameshark.h"
|
||||
#include "gba/cheats/parv3.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "util/string.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define MAX_LINE_LENGTH 128
|
||||
|
@ -17,85 +19,6 @@ DEFINE_VECTOR(GBACheatList, struct GBACheat);
|
|||
DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*);
|
||||
DEFINE_VECTOR(StringList, char*);
|
||||
|
||||
static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 };
|
||||
static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 };
|
||||
|
||||
static const uint8_t _gsa1T1[256] = {
|
||||
0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
|
||||
0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
|
||||
0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
|
||||
0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
|
||||
0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
|
||||
0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
|
||||
0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
|
||||
0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
|
||||
0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
|
||||
0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
|
||||
0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
|
||||
0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
|
||||
0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
|
||||
0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
|
||||
0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
|
||||
0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
|
||||
};
|
||||
|
||||
static const uint8_t _gsa1T2[256] = {
|
||||
0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
|
||||
0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
|
||||
0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
|
||||
0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
|
||||
0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
|
||||
0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
|
||||
0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
|
||||
0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
|
||||
0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
|
||||
0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
|
||||
0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
|
||||
0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
|
||||
0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
|
||||
0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
|
||||
0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
|
||||
0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
|
||||
};
|
||||
|
||||
static const uint8_t _par3T1[256] = {
|
||||
0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
|
||||
0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
|
||||
0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
|
||||
0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
|
||||
0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
|
||||
0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
|
||||
0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
|
||||
0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
|
||||
0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
|
||||
0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
|
||||
0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
|
||||
0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
|
||||
0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
|
||||
0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
|
||||
0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
|
||||
0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
|
||||
};
|
||||
|
||||
static const uint8_t _par3T2[256] = {
|
||||
0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
|
||||
0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
|
||||
0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
|
||||
0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
|
||||
0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
|
||||
0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
|
||||
0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
|
||||
0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
|
||||
0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
|
||||
0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
|
||||
0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
|
||||
0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
|
||||
0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
|
||||
0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
|
||||
0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
|
||||
0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
|
||||
};
|
||||
|
||||
static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
|
@ -122,225 +45,10 @@ static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t
|
|||
}
|
||||
}
|
||||
|
||||
static int _hexDigit(char digit) {
|
||||
switch (digit) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return digit - '0';
|
||||
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return digit - 'a' + 10;
|
||||
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
return digit - 'A' + 10;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* _hex32(const char* line, uint32_t* out) {
|
||||
uint32_t value = 0;
|
||||
int i;
|
||||
for (i = 0; i < 8; ++i, ++line) {
|
||||
char digit = *line;
|
||||
value <<= 4;
|
||||
int nybble = _hexDigit(digit);
|
||||
if (nybble < 0) {
|
||||
return 0;
|
||||
}
|
||||
value |= nybble;
|
||||
}
|
||||
*out = value;
|
||||
return line;
|
||||
}
|
||||
|
||||
static const char* _hex16(const char* line, uint16_t* out) {
|
||||
uint16_t value = 0;
|
||||
*out = 0;
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i, ++line) {
|
||||
char digit = *line;
|
||||
value <<= 4;
|
||||
int nybble = _hexDigit(digit);
|
||||
if (nybble < 0) {
|
||||
return 0;
|
||||
}
|
||||
value |= nybble;
|
||||
}
|
||||
*out = value;
|
||||
return line;
|
||||
}
|
||||
|
||||
static void _registerLine(struct GBACheatSet* cheats, const char* line) {
|
||||
void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) {
|
||||
*StringListAppend(&cheats->lines) = strdup(line);
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
|
||||
static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) {
|
||||
uint32_t sum = 0xC6EF3720;
|
||||
int i;
|
||||
for (i = 0; i < 32; ++i) {
|
||||
*op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]);
|
||||
*op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]);
|
||||
sum -= 0x9E3779B9;
|
||||
}
|
||||
}
|
||||
|
||||
static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) {
|
||||
int x, y;
|
||||
int s0 = params >> 8;
|
||||
int s1 = params & 0xFF;
|
||||
for (y = 0; y < 4; ++y) {
|
||||
for (x = 0; x < 4; ++x) {
|
||||
uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF];
|
||||
seeds[y] <<= 8;
|
||||
seeds[y] |= z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) {
|
||||
cheats->gsaVersion = 1;
|
||||
switch (version) {
|
||||
case 1:
|
||||
memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
case 3:
|
||||
memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
enum GBAGameSharkType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses == 0) {
|
||||
cheats->incompleteCheat = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case GSA_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_4:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_LIST:
|
||||
cheats->remainingAddresses = (op1 & 0xFFFF) - 1;
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case GSA_PATCH:
|
||||
cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1;
|
||||
cheats->romPatches[0].newValue = op2;
|
||||
cheats->romPatches[0].applied = false;
|
||||
cheats->romPatches[0].exists = true;
|
||||
return true;
|
||||
case GSA_BUTTON:
|
||||
// TODO: Implement button
|
||||
return false;
|
||||
case GSA_IF_EQ:
|
||||
if (op1 == 0xDEADFACE) {
|
||||
_reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);
|
||||
return true;
|
||||
}
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_IF_EQ_RANGE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op2 & 0x0FFFFFFF;
|
||||
cheat->operand = op1 & 0xFFFF;
|
||||
cheat->repeat = (op1 >> 16) & 0xFF;
|
||||
return true;
|
||||
case GSA_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
// TODO
|
||||
UNUSED(cheats);
|
||||
UNUSED(op1);
|
||||
UNUSED(op2);
|
||||
UNUSED(_par3T1);
|
||||
UNUSED(_par3T2);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p || !cheats->hook) {
|
||||
return;
|
||||
|
@ -415,6 +123,8 @@ void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
|
|||
GBACheatListInit(&set->list, 4);
|
||||
StringListInit(&set->lines, 4);
|
||||
set->incompleteCheat = 0;
|
||||
set->incompletePatch = 0;
|
||||
set->currentBlock = 0;
|
||||
set->gsaVersion = 0;
|
||||
set->remainingAddresses = 0;
|
||||
set->hook = 0;
|
||||
|
@ -476,201 +186,35 @@ void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats
|
|||
_removeBreakpoint(device, cheats);
|
||||
}
|
||||
|
||||
bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
|
||||
char line[14] = "XXXXXXXX XXXX";
|
||||
snprintf(line, sizeof(line), "%08X %04X", op1, op2);
|
||||
_registerLine(cheats, line);
|
||||
|
||||
enum GBACodeBreakerType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
cheats->incompleteCheat->repeat = op1 & 0xFFFF;
|
||||
cheats->incompleteCheat->addressOffset = op2;
|
||||
cheats->incompleteCheat->operandOffset = 0;
|
||||
cheats->incompleteCheat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case CB_GAME_ID:
|
||||
// TODO: Run checksum
|
||||
return true;
|
||||
case CB_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
case CB_OR_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_OR;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
break;
|
||||
case CB_FILL:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case CB_FILL_8:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
case CB_AND_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_EQ:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ENCRYPT:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
|
||||
return false;
|
||||
case CB_IF_NE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_NE;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_GT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_GT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_LT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_LT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_SPECIAL:
|
||||
switch (op1 & 0x0FFFFFFF) {
|
||||
case 0x20:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
cheat->address = BASE_IO | REG_JOYSTAT;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
default:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
}
|
||||
case CB_ADD_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ADD;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_AND:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex16(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
||||
|
||||
bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
_registerLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
_setGameSharkVersion(set, 1);
|
||||
// Fall through
|
||||
case 1:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addGSA1(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddGameShark(cheats, op1, op2);
|
||||
}
|
||||
|
||||
bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
_registerLine(set, line);
|
||||
GBACheatRegisterLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
// Try to detect GameShark version
|
||||
_decryptGameShark(&o1, &o2, _gsa1S);
|
||||
GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
|
||||
if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
|
||||
_setGameSharkVersion(set, 1);
|
||||
return _addGSA1(set, o1, o2);
|
||||
GBACheatSetGameSharkVersion(set, 1);
|
||||
return GBACheatAddGameSharkRaw(set, o1, o2);
|
||||
}
|
||||
o1 = op1;
|
||||
o2 = op2;
|
||||
_decryptGameShark(&o1, &o2, _par3S);
|
||||
GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
|
||||
if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
|
||||
_setGameSharkVersion(set, 3);
|
||||
return _addPAR3(set, o1, o2);
|
||||
GBACheatSetGameSharkVersion(set, 3);
|
||||
return GBACheatAddProActionReplayRaw(set, o1, o2);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addGSA1(set, o1, o2);
|
||||
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return GBACheatAddGameSharkRaw(set, o1, o2);
|
||||
case 3:
|
||||
_decryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return _addPAR3(set, o1, o2);
|
||||
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return GBACheatAddProActionReplayRaw(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -678,14 +222,14 @@ bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2)
|
|||
bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = _hex32(line, &op1);
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = _hex32(line, &op2);
|
||||
line = hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
|
@ -726,7 +270,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
|
|||
if (set && !reset) {
|
||||
GBACheatSetCopyProperties(newSet, set);
|
||||
} else {
|
||||
_setGameSharkVersion(newSet, gsaVersion);
|
||||
GBACheatSetGameSharkVersion(newSet, gsaVersion);
|
||||
}
|
||||
reset = false;
|
||||
set = newSet;
|
||||
|
@ -755,7 +299,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
|
|||
GBACheatSetInit(set, 0);
|
||||
set->enabled = !nextDisabled;
|
||||
nextDisabled = false;
|
||||
_setGameSharkVersion(set, gsaVersion);
|
||||
GBACheatSetGameSharkVersion(set, gsaVersion);
|
||||
}
|
||||
GBACheatAddLine(set, cheat);
|
||||
break;
|
||||
|
@ -819,21 +363,21 @@ bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
|
|||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
uint16_t op3;
|
||||
line = _hex32(line, &op1);
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (isspace(line[0])) {
|
||||
++line;
|
||||
}
|
||||
line = _hex16(line, &op2);
|
||||
line = hex16(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
if (!line[0] || isspace(line[0])) {
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
||||
line = _hex16(line, &op3);
|
||||
line = hex16(line, &op3);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
|
@ -849,6 +393,7 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
|
|||
}
|
||||
bool condition = true;
|
||||
int conditionRemaining = 0;
|
||||
int negativeConditionRemaining = 0;
|
||||
_patchROM(device, cheats);
|
||||
|
||||
size_t nCodes = GBACheatListSize(&cheats->list);
|
||||
|
@ -859,6 +404,13 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
|
|||
if (!condition) {
|
||||
continue;
|
||||
}
|
||||
} else if (negativeConditionRemaining > 0) {
|
||||
conditionRemaining = negativeConditionRemaining - 1;
|
||||
negativeConditionRemaining = 0;
|
||||
condition = !condition;
|
||||
if (!condition) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
condition = true;
|
||||
}
|
||||
|
@ -874,6 +426,11 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
|
|||
value = operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_ASSIGN_INDIRECT:
|
||||
value = operand;
|
||||
address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_AND:
|
||||
value = _readMem(device->p->cpu, address, cheat->width) & operand;
|
||||
performAssignment = true;
|
||||
|
@ -889,34 +446,42 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
|
|||
case CHEAT_IF_EQ:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) == operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_NE:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) != operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_LT:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) < operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_GT:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) > operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_ULT:
|
||||
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_UGT:
|
||||
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_AND:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) & operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_LAND:
|
||||
condition = _readMem(device->p->cpu, address, cheat->width) && operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
enum GBACheatType {
|
||||
CHEAT_ASSIGN,
|
||||
CHEAT_ASSIGN_INDIRECT,
|
||||
CHEAT_AND,
|
||||
CHEAT_ADD,
|
||||
CHEAT_OR,
|
||||
|
@ -85,6 +86,11 @@ enum GBAActionReplay3Action {
|
|||
};
|
||||
|
||||
enum GBAActionReplay3Base {
|
||||
PAR3_BASE_ASSIGN = 0x00000000,
|
||||
PAR3_BASE_INDIRECT = 0x40000000,
|
||||
PAR3_BASE_ADD = 0x80000000,
|
||||
PAR3_BASE_OTHER = 0xC0000000,
|
||||
|
||||
PAR3_BASE_ASSIGN_1 = 0x00000000,
|
||||
PAR3_BASE_ASSIGN_2 = 0x02000000,
|
||||
PAR3_BASE_ASSIGN_4 = 0x04000000,
|
||||
|
@ -119,7 +125,10 @@ enum GBAActionReplay3Other {
|
|||
enum {
|
||||
PAR3_COND = 0x38000000,
|
||||
PAR3_WIDTH = 0x06000000,
|
||||
PAR3_ACTION = 0xC0000000
|
||||
PAR3_ACTION = 0xC0000000,
|
||||
PAR3_BASE = 0xC0000000,
|
||||
|
||||
PAR3_WIDTH_BASE = 25
|
||||
};
|
||||
|
||||
struct GBACheat {
|
||||
|
@ -128,6 +137,7 @@ struct GBACheat {
|
|||
uint32_t address;
|
||||
uint32_t operand;
|
||||
uint32_t repeat;
|
||||
uint32_t negativeRepeat;
|
||||
|
||||
int32_t addressOffset;
|
||||
int32_t operandOffset;
|
||||
|
@ -148,8 +158,6 @@ struct GBACheatSet {
|
|||
struct GBACheatHook* hook;
|
||||
struct GBACheatList list;
|
||||
|
||||
struct GBACheat* incompleteCheat;
|
||||
|
||||
struct GBACheatPatch {
|
||||
uint32_t address;
|
||||
int16_t newValue;
|
||||
|
@ -158,6 +166,10 @@ struct GBACheatSet {
|
|||
bool exists;
|
||||
} romPatches[MAX_ROM_PATCHES];
|
||||
|
||||
struct GBACheat* incompleteCheat;
|
||||
struct GBACheatPatch* incompletePatch;
|
||||
struct GBACheat* currentBlock;
|
||||
|
||||
int gsaVersion;
|
||||
uint32_t gsaSeeds[4];
|
||||
int remainingAddresses;
|
||||
|
@ -196,6 +208,9 @@ 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 GBACheatAddProActionReplay(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddProActionReplayLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
|
@ -206,4 +221,4 @@ bool GBACheatAddLine(struct GBACheatSet*, const char* line);
|
|||
|
||||
void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_CHEATS_PRIVATE_H
|
||||
#define GBA_CHEATS_PRIVATE_H
|
||||
|
||||
#include "gba/cheats.h"
|
||||
|
||||
void GBACheatRegisterLine(struct GBACheatSet* set, const char* line);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,143 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba/cheats.h"
|
||||
|
||||
#include "gba/cheats/cheats-private.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
#include "util/string.h"
|
||||
|
||||
bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
|
||||
char line[14] = "XXXXXXXX XXXX";
|
||||
snprintf(line, sizeof(line), "%08X %04X", op1, op2);
|
||||
GBACheatRegisterLine(cheats, line);
|
||||
|
||||
enum GBACodeBreakerType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
cheats->incompleteCheat->repeat = op1 & 0xFFFF;
|
||||
cheats->incompleteCheat->addressOffset = op2;
|
||||
cheats->incompleteCheat->operandOffset = 0;
|
||||
cheats->incompleteCheat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case CB_GAME_ID:
|
||||
// TODO: Run checksum
|
||||
return true;
|
||||
case CB_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
case CB_OR_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_OR;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
break;
|
||||
case CB_FILL:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case CB_FILL_8:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
case CB_AND_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_EQ:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_ENCRYPT:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
|
||||
return false;
|
||||
case CB_IF_NE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_NE;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_GT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_GT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_LT:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_LT;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_SPECIAL:
|
||||
switch (op1 & 0x0FFFFFFF) {
|
||||
case 0x20:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
cheat->address = BASE_IO | REG_JOYSTAT;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
default:
|
||||
GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
|
||||
return false;
|
||||
}
|
||||
case CB_ADD_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ADD;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
case CB_IF_AND:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_AND;
|
||||
cheat->width = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
cheat->negativeRepeat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = hex16(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gameshark.h"
|
||||
|
||||
#include "gba/cheats/cheats-private.h"
|
||||
#include "gba/cheats/parv3.h"
|
||||
#include "gba/gba.h"
|
||||
#include "util/string.h"
|
||||
|
||||
const uint32_t GBACheatGameSharkSeeds[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 };
|
||||
|
||||
static const uint8_t _gsa1T1[256] = {
|
||||
0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
|
||||
0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
|
||||
0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
|
||||
0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
|
||||
0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
|
||||
0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
|
||||
0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
|
||||
0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
|
||||
0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
|
||||
0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
|
||||
0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
|
||||
0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
|
||||
0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
|
||||
0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
|
||||
0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
|
||||
0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
|
||||
};
|
||||
|
||||
static const uint8_t _gsa1T2[256] = {
|
||||
0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
|
||||
0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
|
||||
0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
|
||||
0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
|
||||
0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
|
||||
0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
|
||||
0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
|
||||
0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
|
||||
0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
|
||||
0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
|
||||
0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
|
||||
0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
|
||||
0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
|
||||
0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
|
||||
0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
|
||||
0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
|
||||
};
|
||||
|
||||
// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
|
||||
void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) {
|
||||
uint32_t sum = 0xC6EF3720;
|
||||
int i;
|
||||
for (i = 0; i < 32; ++i) {
|
||||
*op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]);
|
||||
*op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]);
|
||||
sum -= 0x9E3779B9;
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) {
|
||||
int x, y;
|
||||
int s0 = params >> 8;
|
||||
int s1 = params & 0xFF;
|
||||
for (y = 0; y < 4; ++y) {
|
||||
for (x = 0; x < 4; ++x) {
|
||||
uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF];
|
||||
seeds[y] <<= 8;
|
||||
seeds[y] |= z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version) {
|
||||
cheats->gsaVersion = 1;
|
||||
switch (version) {
|
||||
case 1:
|
||||
memcpy(cheats->gsaSeeds, GBACheatGameSharkSeeds, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
case 3:
|
||||
memcpy(cheats->gsaSeeds, GBACheatProActionReplaySeeds, 4 * sizeof(uint32_t));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
enum GBAGameSharkType type = op1 >> 28;
|
||||
struct GBACheat* cheat = 0;
|
||||
|
||||
if (cheats->incompleteCheat) {
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses > 0) {
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheat->operand = cheats->incompleteCheat->operand;
|
||||
cheat->repeat = 1;
|
||||
--cheats->remainingAddresses;
|
||||
}
|
||||
if (cheats->remainingAddresses == 0) {
|
||||
cheats->incompleteCheat = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case GSA_ASSIGN_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_4:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_ASSIGN_LIST:
|
||||
cheats->remainingAddresses = (op1 & 0xFFFF) - 1;
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 4;
|
||||
cheat->address = op2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case GSA_PATCH:
|
||||
cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1;
|
||||
cheats->romPatches[0].newValue = op2;
|
||||
cheats->romPatches[0].applied = false;
|
||||
cheats->romPatches[0].exists = true;
|
||||
return true;
|
||||
case GSA_BUTTON:
|
||||
// TODO: Implement button
|
||||
GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented");
|
||||
return false;
|
||||
case GSA_IF_EQ:
|
||||
if (op1 == 0xDEADFACE) {
|
||||
GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);
|
||||
return true;
|
||||
}
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op1 & 0x0FFFFFFF;
|
||||
break;
|
||||
case GSA_IF_EQ_RANGE:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
cheat->width = 2;
|
||||
cheat->address = op2 & 0x0FFFFFFF;
|
||||
cheat->operand = op1 & 0xFFFF;
|
||||
cheat->repeat = (op1 >> 16) & 0xFF;
|
||||
return true;
|
||||
case GSA_HOOK:
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
cheat->operand = op2;
|
||||
cheat->repeat = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
GBACheatRegisterLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
GBACheatSetGameSharkVersion(set, 1);
|
||||
// Fall through
|
||||
case 1:
|
||||
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return GBACheatAddGameSharkRaw(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddGameShark(cheats, op1, op2);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_CHEATS_GAMESHARK_H
|
||||
#define GBA_CHEATS_GAMESHARK_H
|
||||
|
||||
#include "gba/cheats.h"
|
||||
|
||||
extern const uint32_t GBACheatGameSharkSeeds[4];
|
||||
|
||||
void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds);
|
||||
void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2);
|
||||
void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version);
|
||||
bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,324 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "parv3.h"
|
||||
|
||||
#include "gba/cheats/cheats-private.h"
|
||||
#include "gba/cheats/gameshark.h"
|
||||
#include "gba/gba.h"
|
||||
#include "util/string.h"
|
||||
|
||||
const uint32_t GBACheatProActionReplaySeeds[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 };
|
||||
|
||||
static const uint8_t _par3T1[256] = {
|
||||
0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
|
||||
0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
|
||||
0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
|
||||
0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
|
||||
0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
|
||||
0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
|
||||
0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
|
||||
0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
|
||||
0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
|
||||
0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
|
||||
0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
|
||||
0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
|
||||
0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
|
||||
0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
|
||||
0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
|
||||
0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
|
||||
};
|
||||
|
||||
static const uint8_t _par3T2[256] = {
|
||||
0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
|
||||
0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
|
||||
0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
|
||||
0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
|
||||
0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
|
||||
0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
|
||||
0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
|
||||
0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
|
||||
0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
|
||||
0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
|
||||
0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
|
||||
0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
|
||||
0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
|
||||
0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
|
||||
0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
|
||||
0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
|
||||
};
|
||||
static uint32_t _parAddr(uint32_t x) {
|
||||
return (x & 0xFFFFF) | ((x << 4) & 0x0F000000);
|
||||
}
|
||||
|
||||
static void _parEndBlock(struct GBACheatSet* cheats) {
|
||||
size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock);
|
||||
if (cheats->currentBlock->repeat) {
|
||||
cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat;
|
||||
} else {
|
||||
cheats->currentBlock->repeat = size;
|
||||
}
|
||||
cheats->currentBlock = 0;
|
||||
}
|
||||
|
||||
static void _parElseBlock(struct GBACheatSet* cheats) {
|
||||
size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock);
|
||||
cheats->currentBlock->repeat = size;
|
||||
}
|
||||
|
||||
static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
enum GBAActionReplay3Condition condition = op1 & PAR3_COND;
|
||||
int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE);
|
||||
if (width > 4) {
|
||||
// TODO: Always false conditions
|
||||
return false;
|
||||
}
|
||||
if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) {
|
||||
// TODO: Codes that disable
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = _parAddr(op1);
|
||||
cheat->width = width;
|
||||
cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8));
|
||||
cheat->addressOffset = 0;
|
||||
cheat->operandOffset = 0;
|
||||
|
||||
switch (op1 & PAR3_ACTION) {
|
||||
case PAR3_ACTION_NEXT:
|
||||
cheat->repeat = 1;
|
||||
cheat->negativeRepeat = 0;
|
||||
break;
|
||||
case PAR3_ACTION_NEXT_TWO:
|
||||
cheat->repeat = 2;
|
||||
cheat->negativeRepeat = 0;
|
||||
break;
|
||||
case PAR3_ACTION_BLOCK:
|
||||
cheat->repeat = 0;
|
||||
cheat->negativeRepeat = 0;
|
||||
if (cheats->currentBlock) {
|
||||
_parEndBlock(cheats);
|
||||
}
|
||||
cheats->currentBlock = cheat;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (condition) {
|
||||
case PAR3_COND_OTHER:
|
||||
// We shouldn't be able to get here
|
||||
GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code");
|
||||
cheat->type = CHEAT_IF_LAND;
|
||||
cheat->operand = 0;
|
||||
break;
|
||||
case PAR3_COND_EQ:
|
||||
cheat->type = CHEAT_IF_EQ;
|
||||
break;
|
||||
case PAR3_COND_NE:
|
||||
cheat->type = CHEAT_IF_NE;
|
||||
break;
|
||||
case PAR3_COND_LT:
|
||||
cheat->type = CHEAT_IF_LT;
|
||||
break;
|
||||
case PAR3_COND_GT:
|
||||
cheat->type = CHEAT_IF_GT;
|
||||
break;
|
||||
case PAR3_COND_ULT:
|
||||
cheat->type = CHEAT_IF_ULT;
|
||||
break;
|
||||
case PAR3_COND_UGT:
|
||||
cheat->type = CHEAT_IF_UGT;
|
||||
break;
|
||||
case PAR3_COND_LAND:
|
||||
cheat->type = CHEAT_IF_LAND;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) {
|
||||
struct GBACheat* cheat;
|
||||
switch (op2 & 0xFF000000) {
|
||||
case PAR3_OTHER_SLOWDOWN:
|
||||
// TODO: Slowdown
|
||||
return false;
|
||||
case PAR3_OTHER_BUTTON_1:
|
||||
case PAR3_OTHER_BUTTON_2:
|
||||
case PAR3_OTHER_BUTTON_4:
|
||||
// TODO: Button
|
||||
GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented");
|
||||
return false;
|
||||
// TODO: Fix overriding existing patches
|
||||
case PAR3_OTHER_PATCH_1:
|
||||
cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[0].applied = false;
|
||||
cheats->romPatches[0].exists = true;
|
||||
cheats->incompletePatch = &cheats->romPatches[0];
|
||||
break;
|
||||
case PAR3_OTHER_PATCH_2:
|
||||
cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[1].applied = false;
|
||||
cheats->romPatches[1].exists = true;
|
||||
cheats->incompletePatch = &cheats->romPatches[1];
|
||||
break;
|
||||
case PAR3_OTHER_PATCH_3:
|
||||
cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[2].applied = false;
|
||||
cheats->romPatches[2].exists = true;
|
||||
cheats->incompletePatch = &cheats->romPatches[2];
|
||||
break;
|
||||
case PAR3_OTHER_PATCH_4:
|
||||
cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[3].applied = false;
|
||||
cheats->romPatches[3].exists = true;
|
||||
cheats->incompletePatch = &cheats->romPatches[3];
|
||||
break;
|
||||
case PAR3_OTHER_ENDIF:
|
||||
if (cheats->currentBlock) {
|
||||
_parEndBlock(cheats);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case PAR3_OTHER_ELSE:
|
||||
if (cheats->currentBlock) {
|
||||
_parElseBlock(cheats);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case PAR3_OTHER_FILL_1:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = _parAddr(op2);
|
||||
cheat->width = 1;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case PAR3_OTHER_FILL_2:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = _parAddr(op2);
|
||||
cheat->width = 2;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
case PAR3_OTHER_FILL_4:
|
||||
cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = _parAddr(op2);
|
||||
cheat->width = 3;
|
||||
cheats->incompleteCheat = cheat;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
if (cheats->incompletePatch) {
|
||||
cheats->incompletePatch->newValue = op1;
|
||||
cheats->incompletePatch = 0;
|
||||
return true;
|
||||
}
|
||||
if (cheats->incompleteCheat) {
|
||||
cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8));
|
||||
cheats->incompleteCheat->addressOffset = op2 >> 24;
|
||||
cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF;
|
||||
cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width;
|
||||
cheats->incompleteCheat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (op2 == 0x001DC0DE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (op1) {
|
||||
case 0x00000000:
|
||||
return _addPAR3Special(cheats, op2);
|
||||
case 0xDEADFACE:
|
||||
GBACheatReseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (op1 >> 24 == 0xC4) {
|
||||
if (cheats->hook) {
|
||||
return false;
|
||||
}
|
||||
cheats->hook = malloc(sizeof(*cheats->hook));
|
||||
cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
|
||||
cheats->hook->mode = MODE_THUMB;
|
||||
cheats->hook->refs = 1;
|
||||
cheats->hook->reentries = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (op1 & PAR3_COND) {
|
||||
return _addPAR3Cond(cheats, op1, op2);
|
||||
}
|
||||
|
||||
int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE);
|
||||
struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = _parAddr(op1);
|
||||
cheat->operandOffset = 0;
|
||||
cheat->addressOffset = 0;
|
||||
cheat->repeat = 1;
|
||||
|
||||
switch (op1 & PAR3_BASE) {
|
||||
case PAR3_BASE_ASSIGN:
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->addressOffset = width;
|
||||
if (width < 4) {
|
||||
cheat->repeat = (op2 >> (width * 8)) + 1;
|
||||
}
|
||||
break;
|
||||
case PAR3_BASE_INDIRECT:
|
||||
cheat->type = CHEAT_ASSIGN_INDIRECT;
|
||||
if (width < 4) {
|
||||
cheat->addressOffset = (op2 >> (width * 8)) * width;
|
||||
}
|
||||
break;
|
||||
case PAR3_BASE_ADD:
|
||||
cheat->type = CHEAT_ADD;
|
||||
break;
|
||||
case PAR3_BASE_OTHER:
|
||||
width = ((op1 >> 24) & 1) + 1;
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->address = BASE_IO | (op1 & OFFSET_MASK);
|
||||
break;
|
||||
}
|
||||
|
||||
cheat->width = width;
|
||||
cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
||||
uint32_t o1 = op1;
|
||||
uint32_t o2 = op2;
|
||||
char line[18] = "XXXXXXXX XXXXXXXX";
|
||||
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
|
||||
GBACheatRegisterLine(set, line);
|
||||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
GBACheatSetGameSharkVersion(set, 3);
|
||||
// Fall through
|
||||
case 1:
|
||||
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return GBACheatAddProActionReplayRaw(set, o1, o2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint32_t op2;
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
while (*line == ' ') {
|
||||
++line;
|
||||
}
|
||||
line = hex32(line, &op2);
|
||||
if (!line) {
|
||||
return false;
|
||||
}
|
||||
return GBACheatAddProActionReplay(cheats, op1, op2);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_CHEATS_PARV3_H
|
||||
#define GBA_CHEATS_PARV3_H
|
||||
|
||||
#include "gba/cheats.h"
|
||||
|
||||
extern const uint32_t GBACheatProActionReplaySeeds[4];
|
||||
|
||||
bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2);
|
||||
|
||||
#endif
|
|
@ -87,8 +87,12 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
gba->idleLoop = IDLE_LOOP_NONE;
|
||||
gba->lastJump = 0;
|
||||
gba->haltPending = false;
|
||||
gba->idleDetectionStep = 0;
|
||||
gba->idleDetectionFailures = 0;
|
||||
|
||||
gba->realisticTiming = false;
|
||||
|
||||
gba->performingDMA = false;
|
||||
}
|
||||
|
||||
|
@ -430,6 +434,7 @@ void GBATimerUpdateRegister(struct GBA* gba, int timer) {
|
|||
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
|
||||
gba->timers[timer].reload = reload;
|
||||
gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << gba->timers[timer].prescaleBits;
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
||||
|
@ -464,7 +469,7 @@ void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
|||
}
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
|
||||
currentTimer->oldReload = currentTimer->reload;
|
||||
currentTimer->lastEvent = 0;
|
||||
currentTimer->lastEvent = gba->cpu->cycles;
|
||||
gba->timersEnabled |= 1 << timer;
|
||||
} else if (wasEnabled && !currentTimer->enable) {
|
||||
if (!currentTimer->countUp) {
|
||||
|
@ -525,7 +530,7 @@ void GBAHalt(struct GBA* gba) {
|
|||
|
||||
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||
enum GBALogLevel logLevel = -1;
|
||||
enum GBALogLevel logLevel = GBA_LOG_ALL;
|
||||
|
||||
if (gba) {
|
||||
logLevel = gba->logLevel;
|
||||
|
@ -628,10 +633,18 @@ bool GBAIsBIOS(struct VFile* vf) {
|
|||
}
|
||||
|
||||
void GBAGetGameCode(struct GBA* gba, char* out) {
|
||||
if (!gba->memory.rom) {
|
||||
out[0] = '\0';
|
||||
return;
|
||||
}
|
||||
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->id, 4);
|
||||
}
|
||||
|
||||
void GBAGetGameTitle(struct GBA* gba, char* out) {
|
||||
if (!gba->memory.rom) {
|
||||
strncpy(out, "(BIOS)", 12);
|
||||
return;
|
||||
}
|
||||
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12);
|
||||
}
|
||||
|
||||
|
@ -730,16 +743,15 @@ void GBAFrameEnded(struct GBA* gba) {
|
|||
}
|
||||
}
|
||||
|
||||
if (gba->stream) {
|
||||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||
}
|
||||
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->stream) {
|
||||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||
}
|
||||
|
||||
if (thread->frameCallback) {
|
||||
thread->frameCallback(thread);
|
||||
}
|
||||
|
|
|
@ -45,8 +45,10 @@ enum GBALogLevel {
|
|||
|
||||
GBA_LOG_GAME_ERROR = 0x100,
|
||||
GBA_LOG_SWI = 0x200,
|
||||
GBA_LOG_STATUS = 0x400,
|
||||
GBA_LOG_SIO = 0x800,
|
||||
|
||||
GBA_LOG_ALL = 0x33F,
|
||||
GBA_LOG_ALL = 0xF3F,
|
||||
|
||||
#ifdef NDEBUG
|
||||
GBA_LOG_DANGER = GBA_LOG_ERROR
|
||||
|
@ -99,6 +101,7 @@ typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* f
|
|||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||
};
|
||||
|
||||
struct GBATimer {
|
||||
|
@ -156,10 +159,13 @@ struct GBA {
|
|||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
uint32_t idleLoop;
|
||||
uint32_t lastJump;
|
||||
bool haltPending;
|
||||
int idleDetectionStep;
|
||||
int idleDetectionFailures;
|
||||
int32_t cachedRegisters[16];
|
||||
bool taintedRegisters[16];
|
||||
|
||||
bool realisticTiming;
|
||||
};
|
||||
|
||||
struct GBACartridge {
|
||||
|
@ -211,10 +217,10 @@ void GBAGetGameTitle(struct GBA* gba, char* out);
|
|||
void GBAFrameStarted(struct GBA* gba);
|
||||
void GBAFrameEnded(struct GBA* gba);
|
||||
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
ATTRIBUTE_FORMAT(printf,3,4)
|
||||
void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...);
|
||||
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
ATTRIBUTE_FORMAT(printf, 3, 4)
|
||||
void GBADebuggerLogShim(struct ARMDebugger* debugger, enum DebuggerLogLevel level, const char* format, ...);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static void _readPins(struct GBACartridgeHardware* hw);
|
||||
static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
|
||||
|
||||
|
@ -81,8 +79,8 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.packed = 0;
|
||||
hw->rtc.control.packed = 0x40;
|
||||
hw->rtc.command = 0;
|
||||
hw->rtc.control = 0x40;
|
||||
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
||||
}
|
||||
|
||||
|
@ -139,14 +137,14 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!hw->p0) {
|
||||
if (!(hw->pinState & 1)) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead;
|
||||
hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead;
|
||||
} else {
|
||||
if (hw->p2) {
|
||||
if (hw->pinState & 4) {
|
||||
// GPIO direction should always != reading
|
||||
if (hw->dir1) {
|
||||
if (hw->rtc.command.reading) {
|
||||
if (hw->direction & 2) {
|
||||
if (RTCCommandDataIsReading(hw->rtc.command)) {
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
||||
}
|
||||
++hw->rtc.bitsRead;
|
||||
|
@ -160,7 +158,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
}
|
||||
hw->rtc.bitsRead = 0;
|
||||
}
|
||||
|
@ -169,7 +167,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
hw->rtc.transferStep = 0;
|
||||
}
|
||||
}
|
||||
|
@ -180,16 +178,16 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (!hw->rtc.commandActive) {
|
||||
union RTCCommandData command;
|
||||
command.packed = hw->rtc.bits;
|
||||
if (command.magic == 0x06) {
|
||||
RTCCommandData command;
|
||||
command = hw->rtc.bits;
|
||||
if (RTCCommandDataGetMagic(command) == 0x06) {
|
||||
hw->rtc.command = command;
|
||||
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command];
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)];
|
||||
hw->rtc.commandActive = hw->rtc.bytesRemaining > 0;
|
||||
switch (command.command) {
|
||||
switch (RTCCommandDataGetCommand(command)) {
|
||||
case RTC_RESET:
|
||||
hw->rtc.control.packed = 0;
|
||||
hw->rtc.control = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
|
@ -203,12 +201,12 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
|||
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||
}
|
||||
} else {
|
||||
switch (hw->rtc.command.command) {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
hw->rtc.control.packed = hw->rtc.bits;
|
||||
hw->rtc.control = hw->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command);
|
||||
GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command));
|
||||
break;
|
||||
case RTC_RESET:
|
||||
case RTC_DATETIME:
|
||||
|
@ -221,15 +219,15 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
if (!hw->rtc.bytesRemaining) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
||||
uint8_t outputByte = 0;
|
||||
switch (hw->rtc.command.command) {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
outputByte = hw->rtc.control.packed;
|
||||
outputByte = hw->rtc.control;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
|
@ -262,7 +260,7 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
||||
hw->rtc.time[2] = _rtcBCD(date.tm_mday);
|
||||
hw->rtc.time[3] = _rtcBCD(date.tm_wday);
|
||||
if (hw->rtc.control.hour24) {
|
||||
if (RTCControlIsHour24(hw->rtc.control)) {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour);
|
||||
} else {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
||||
|
@ -288,11 +286,11 @@ void GBAHardwareInitGyro(struct GBACartridgeHardware* hw) {
|
|||
|
||||
void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||
struct GBARotationSource* gyro = hw->p->rotationSource;
|
||||
if (!gyro) {
|
||||
if (!gyro || !gyro->readGyroZ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hw->p0) {
|
||||
if (hw->pinState & 1) {
|
||||
if (gyro->sample) {
|
||||
gyro->sample(gyro);
|
||||
}
|
||||
|
@ -302,14 +300,14 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
||||
}
|
||||
|
||||
if (hw->gyroEdge && !hw->p1) {
|
||||
if (hw->gyroEdge && !(hw->pinState & 2)) {
|
||||
// Write bit on falling edge
|
||||
unsigned bit = hw->gyroSample >> 15;
|
||||
hw->gyroSample <<= 1;
|
||||
_outputPins(hw, bit << 2);
|
||||
}
|
||||
|
||||
hw->gyroEdge = hw->p1;
|
||||
hw->gyroEdge = !!(hw->pinState & 2);
|
||||
}
|
||||
|
||||
// == Rumble
|
||||
|
@ -324,7 +322,7 @@ void _rumbleReadPins(struct GBACartridgeHardware* hw) {
|
|||
return;
|
||||
}
|
||||
|
||||
rumble->setRumble(rumble, hw->p3);
|
||||
rumble->setRumble(rumble, !!(hw->pinState & 8));
|
||||
}
|
||||
|
||||
// == Light sensor
|
||||
|
@ -337,11 +335,11 @@ void GBAHardwareInitLight(struct GBACartridgeHardware* hw) {
|
|||
}
|
||||
|
||||
void _lightReadPins(struct GBACartridgeHardware* hw) {
|
||||
if (hw->p2) {
|
||||
if (hw->pinState & 4) {
|
||||
// Boktai chip select
|
||||
return;
|
||||
}
|
||||
if (hw->p1) {
|
||||
if (hw->pinState & 2) {
|
||||
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
||||
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset");
|
||||
hw->lightCounter = 0;
|
||||
|
@ -352,10 +350,10 @@ void _lightReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->lightSample = 0xFF;
|
||||
}
|
||||
}
|
||||
if (hw->p0 && hw->lightEdge) {
|
||||
if ((hw->pinState & 1) && hw->lightEdge) {
|
||||
++hw->lightCounter;
|
||||
}
|
||||
hw->lightEdge = !hw->p0;
|
||||
hw->lightEdge = !(hw->pinState & 1);
|
||||
|
||||
bool sendBit = hw->lightCounter >= hw->lightSample;
|
||||
_outputPins(hw, sendBit << 3);
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
struct GBARotationSource {
|
||||
|
@ -52,16 +56,10 @@ enum GPIODirection {
|
|||
GPIO_READ_WRITE = 1
|
||||
};
|
||||
|
||||
union RTCControl {
|
||||
struct {
|
||||
unsigned : 3;
|
||||
unsigned minIRQ : 1;
|
||||
unsigned : 2;
|
||||
unsigned hour24 : 1;
|
||||
unsigned poweroff : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
DECL_BITFIELD(RTCControl, uint32_t);
|
||||
DECL_BIT(RTCControl, MinIRQ, 3);
|
||||
DECL_BIT(RTCControl, Hour24, 6);
|
||||
DECL_BIT(RTCControl, Poweroff, 7);
|
||||
|
||||
enum RTCCommand {
|
||||
RTC_RESET = 0,
|
||||
|
@ -71,55 +69,38 @@ enum RTCCommand {
|
|||
RTC_TIME = 6
|
||||
};
|
||||
|
||||
union RTCCommandData {
|
||||
struct {
|
||||
unsigned magic : 4;
|
||||
enum RTCCommand command : 3;
|
||||
unsigned reading : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
DECL_BITFIELD(RTCCommandData, uint32_t);
|
||||
DECL_BITS(RTCCommandData, Magic, 0, 4);
|
||||
DECL_BITS(RTCCommandData, Command, 4, 3);
|
||||
DECL_BIT(RTCCommandData, Reading, 7);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct GBARTC {
|
||||
int bytesRemaining;
|
||||
int transferStep;
|
||||
int bitsRead;
|
||||
int bits;
|
||||
int commandActive;
|
||||
union RTCCommandData command;
|
||||
union RTCControl control;
|
||||
int32_t bytesRemaining;
|
||||
int32_t transferStep;
|
||||
int32_t bitsRead;
|
||||
int32_t bits;
|
||||
int32_t commandActive;
|
||||
RTCCommandData command;
|
||||
RTCControl control;
|
||||
uint8_t time[7];
|
||||
} __attribute__((packed));
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct GBARumble {
|
||||
void (*setRumble)(struct GBARumble*, int enable);
|
||||
};
|
||||
|
||||
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||
|
||||
struct GBACartridgeHardware {
|
||||
struct GBA* p;
|
||||
int devices;
|
||||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned p0 : 1;
|
||||
unsigned p1 : 1;
|
||||
unsigned p2 : 1;
|
||||
unsigned p3 : 1;
|
||||
};
|
||||
uint16_t pinState;
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned dir0 : 1;
|
||||
unsigned dir1 : 1;
|
||||
unsigned dir2 : 1;
|
||||
unsigned dir3 : 1;
|
||||
};
|
||||
uint16_t direction;
|
||||
};
|
||||
uint16_t pinState;
|
||||
uint16_t direction;
|
||||
|
||||
struct GBARTC rtc;
|
||||
|
||||
|
|
213
src/gba/input.c
|
@ -24,7 +24,7 @@ struct GBAInputMapImpl {
|
|||
|
||||
struct GBAAxisSave {
|
||||
struct Configuration* config;
|
||||
uint32_t type;
|
||||
const char* sectionName;
|
||||
};
|
||||
|
||||
struct GBAAxisEnumerate {
|
||||
|
@ -45,6 +45,11 @@ const char* GBAKeyNames[] = {
|
|||
"L"
|
||||
};
|
||||
|
||||
static void _makeSectionName(char* sectionName, size_t len, uint32_t type) {
|
||||
snprintf(sectionName, len, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[len - 1] = '\0';
|
||||
}
|
||||
|
||||
static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) {
|
||||
const char* strValue = ConfigurationGetValue(config, section, key);
|
||||
if (!strValue) {
|
||||
|
@ -90,7 +95,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
TableInit(&impl->axes, 2, free);
|
||||
} else {
|
||||
impl = _lookupMap(map, type);
|
||||
|
@ -105,7 +110,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
|
@ -115,18 +120,14 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -138,11 +139,7 @@ static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Config
|
|||
GBAInputBindKey(map, type, value, key);
|
||||
}
|
||||
|
||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -179,11 +176,7 @@ static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Confi
|
|||
GBAInputBindAxis(map, type, axis, &realDescription);
|
||||
}
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -195,11 +188,7 @@ static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Config
|
|||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||
}
|
||||
|
||||
static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _clearAxis(const char* sectionName, struct Configuration* config, const char* axisName) {
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -214,10 +203,7 @@ static void _saveAxis(uint32_t axis, void* dp, void* up) {
|
|||
struct GBAAxisSave* user = up;
|
||||
const struct GBAAxis* description = dp;
|
||||
|
||||
uint32_t type = user->type;
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
const char* sectionName = user->sectionName;
|
||||
|
||||
if (description->lowDirection != GBA_KEY_NONE) {
|
||||
const char* keyName = GBAKeyNames[description->lowDirection];
|
||||
|
@ -271,6 +257,64 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
}
|
||||
|
||||
static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_clearAxis(sectionName, config, "A");
|
||||
_clearAxis(sectionName, config, "B");
|
||||
_clearAxis(sectionName, config, "L");
|
||||
_clearAxis(sectionName, config, "R");
|
||||
_clearAxis(sectionName, config, "Start");
|
||||
_clearAxis(sectionName, config, "Select");
|
||||
_clearAxis(sectionName, config, "Up");
|
||||
_clearAxis(sectionName, config, "Down");
|
||||
_clearAxis(sectionName, config, "Left");
|
||||
_clearAxis(sectionName, config, "Right");
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct GBAAxisSave save = {
|
||||
config,
|
||||
sectionName
|
||||
};
|
||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
||||
}
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap* map) {
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
|
@ -414,59 +458,68 @@ void GBAInputEnumerateAxes(const struct GBAInputMap* map, uint32_t type, void (h
|
|||
}
|
||||
|
||||
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");
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
_loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) {
|
||||
_saveKey(map, type, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_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);
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
const char* GBAInputGetPreferredDevice(const struct Configuration* config, uint32_t type, int playerId) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
|
||||
char deviceId[KEY_NAME_MAX];
|
||||
snprintf(deviceId, sizeof(deviceId), "device%i", playerId);
|
||||
return ConfigurationGetValue(config, sectionName, deviceId);
|
||||
}
|
||||
|
||||
void GBAInputSetPreferredDevice(struct Configuration* config, uint32_t type, int playerId, const char* deviceName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
|
||||
char deviceId[KEY_NAME_MAX];
|
||||
snprintf(deviceId, sizeof(deviceId), "device%i", playerId);
|
||||
return ConfigurationSetValue(config, sectionName, deviceId, deviceName);
|
||||
}
|
||||
|
||||
const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
if (profile) {
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
const char* value = ConfigurationGetValue(config, sectionName, key);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
return ConfigurationGetValue(config, sectionName, key);
|
||||
}
|
||||
|
||||
void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
if (profile) {
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
ConfigurationSetValue(config, sectionName, key, value);
|
||||
}
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
ConfigurationSetValue(config, sectionName, key, value);
|
||||
}
|
||||
|
|
|
@ -45,4 +45,13 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
|
|||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||
|
||||
const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId);
|
||||
void GBAInputSetPreferredDevice(struct Configuration*, uint32_t type, int playerId, const char* deviceName);
|
||||
|
||||
const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile);
|
||||
void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile);
|
||||
|
||||
#endif
|
||||
|
|
28
src/gba/io.c
|
@ -286,6 +286,10 @@ void GBAIOInit(struct GBA* gba) {
|
|||
gba->memory.io[REG_RCNT >> 1] = RCNT_INITIAL;
|
||||
gba->memory.io[REG_KEYINPUT >> 1] = 0x3FF;
|
||||
gba->memory.io[REG_SOUNDBIAS >> 1] = 0x200;
|
||||
gba->memory.io[REG_BG2PA >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG2PD >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PA >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PD >> 1] = 0x100;
|
||||
}
|
||||
|
||||
void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||
|
@ -366,23 +370,14 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
break;
|
||||
|
||||
// TODO: Confirm this behavior on real hardware
|
||||
case REG_FIFO_A_LO:
|
||||
case REG_FIFO_B_LO:
|
||||
if (gba->performingDMA) {
|
||||
GBAAudioWriteFIFO16(&gba->audio, address, value);
|
||||
} else {
|
||||
GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
|
||||
}
|
||||
GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
|
||||
break;
|
||||
|
||||
case REG_FIFO_A_HI:
|
||||
case REG_FIFO_B_HI:
|
||||
if (gba->performingDMA) {
|
||||
GBAAudioWriteFIFO16(&gba->audio, address, value);
|
||||
} else {
|
||||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
}
|
||||
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
|
||||
break;
|
||||
|
||||
// DMA
|
||||
|
@ -494,6 +489,10 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register write: %03x", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Write to unused I/O register: %03X", address);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -567,7 +566,7 @@ 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
|
||||
gba->haltPending = false; // IO reads need to invalidate detected idle loops
|
||||
switch (address) {
|
||||
case REG_TM0CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 0);
|
||||
|
@ -648,6 +647,10 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register read: %03x", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return 0; // TODO: Reuse LOAD_BAD
|
||||
}
|
||||
break;
|
||||
}
|
||||
return gba->memory.io[address >> 1];
|
||||
|
@ -701,5 +704,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
gba->timersEnabled |= 1 << i;
|
||||
}
|
||||
}
|
||||
GBAMemoryUpdateDMAs(gba, 0);
|
||||
GBAHardwareDeserialize(&gba->memory.hw, state);
|
||||
}
|
||||
|
|
368
src/gba/memory.c
|
@ -17,6 +17,7 @@
|
|||
#define IDLE_LOOP_THRESHOLD 10000
|
||||
|
||||
static uint32_t _popcount32(unsigned bits);
|
||||
static void _pristineCow(struct GBA* gba);
|
||||
static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE };
|
||||
|
||||
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region);
|
||||
|
@ -46,6 +47,7 @@ void GBAMemoryInit(struct GBA* gba) {
|
|||
gba->memory.wram = 0;
|
||||
gba->memory.iwram = 0;
|
||||
gba->memory.rom = 0;
|
||||
gba->memory.romSize = 0;
|
||||
gba->memory.hw.p = gba;
|
||||
|
||||
int i;
|
||||
|
@ -197,8 +199,13 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
|
||||
int newRegion = address >> BASE_OFFSET;
|
||||
if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) {
|
||||
if (address == gba->lastJump && address == gba->idleLoop) {
|
||||
GBAHalt(gba);
|
||||
if (address == gba->idleLoop) {
|
||||
if (gba->haltPending) {
|
||||
gba->haltPending = false;
|
||||
GBAHalt(gba);
|
||||
} else {
|
||||
gba->haltPending = true;
|
||||
}
|
||||
} else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) {
|
||||
if (address == gba->lastJump) {
|
||||
switch (gba->idleDetectionStep) {
|
||||
|
@ -225,7 +232,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
}
|
||||
|
||||
gba->lastJump = address;
|
||||
if (newRegion == memory->activeRegion) {
|
||||
if (newRegion == memory->activeRegion && (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -233,33 +240,37 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
memory->biosPrefetch = cpu->prefetch[1];
|
||||
}
|
||||
memory->activeRegion = newRegion;
|
||||
switch (address & ~OFFSET_MASK) {
|
||||
case BASE_BIOS:
|
||||
switch (newRegion) {
|
||||
case REGION_BIOS:
|
||||
cpu->memory.activeRegion = memory->bios;
|
||||
cpu->memory.activeMask = SIZE_BIOS - 1;
|
||||
break;
|
||||
case BASE_WORKING_RAM:
|
||||
case REGION_WORKING_RAM:
|
||||
cpu->memory.activeRegion = memory->wram;
|
||||
cpu->memory.activeMask = SIZE_WORKING_RAM - 1;
|
||||
break;
|
||||
case BASE_WORKING_IRAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
cpu->memory.activeRegion = memory->iwram;
|
||||
cpu->memory.activeMask = SIZE_WORKING_IRAM - 1;
|
||||
break;
|
||||
case BASE_VRAM:
|
||||
case REGION_VRAM:
|
||||
cpu->memory.activeRegion = (uint32_t*) gba->video.renderer->vram;
|
||||
cpu->memory.activeMask = 0x0000FFFF;
|
||||
break;
|
||||
case BASE_CART0:
|
||||
case BASE_CART0_EX:
|
||||
case BASE_CART1:
|
||||
case BASE_CART1_EX:
|
||||
case BASE_CART2:
|
||||
case BASE_CART2_EX:
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
cpu->memory.activeRegion = memory->rom;
|
||||
cpu->memory.activeMask = SIZE_CART0 - 1;
|
||||
break;
|
||||
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
|
||||
break;
|
||||
}
|
||||
// Fall through
|
||||
default:
|
||||
memory->activeRegion = 0;
|
||||
cpu->memory.activeRegion = _deadbeef;
|
||||
cpu->memory.activeMask = 0;
|
||||
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
|
||||
|
@ -274,13 +285,30 @@ 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 (gba->performingDMA) { \
|
||||
value = gba->bus; \
|
||||
} else { \
|
||||
value = cpu->prefetch[1]; \
|
||||
if (cpu->executionMode == MODE_THUMB) { \
|
||||
value |= value << 16; \
|
||||
/* http://ngemu.com/threads/gba-open-bus.170809/ */ \
|
||||
switch (cpu->gprs[ARM_PC] >> BASE_OFFSET) { \
|
||||
case REGION_BIOS: \
|
||||
case REGION_OAM: \
|
||||
/* This isn't right half the time, but we don't have $+6 handy */ \
|
||||
value <<= 16; \
|
||||
value |= cpu->prefetch[0]; \
|
||||
break; \
|
||||
case REGION_WORKING_IRAM: \
|
||||
/* This doesn't handle prefetch clobbering */ \
|
||||
if (cpu->gprs[ARM_PC] & 2) { \
|
||||
value |= cpu->prefetch[0] << 16; \
|
||||
} else { \
|
||||
value <<= 16; \
|
||||
value |= cpu->prefetch[0]; \
|
||||
} \
|
||||
default: \
|
||||
value |= value << 16; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@ -293,38 +321,39 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
value = memory->biosPrefetch; \
|
||||
} \
|
||||
} else { \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \
|
||||
LOAD_BAD; \
|
||||
}
|
||||
|
||||
#define LOAD_WORKING_RAM \
|
||||
LOAD_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); \
|
||||
LOAD_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram); \
|
||||
wait += waitstatesRegion[REGION_WORKING_RAM];
|
||||
|
||||
#define LOAD_WORKING_IRAM LOAD_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
#define LOAD_WORKING_IRAM LOAD_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram);
|
||||
#define LOAD_IO value = GBAIORead(gba, (address & (SIZE_IO - 1)) & ~2) | (GBAIORead(gba, (address & (SIZE_IO - 1)) | 2) << 16);
|
||||
|
||||
#define LOAD_PALETTE_RAM \
|
||||
LOAD_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); \
|
||||
LOAD_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \
|
||||
++wait;
|
||||
|
||||
#define LOAD_VRAM \
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
|
||||
LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \
|
||||
LOAD_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \
|
||||
} else { \
|
||||
LOAD_32(value, address & 0x00017FFF, gba->video.renderer->vram); \
|
||||
LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
|
||||
} \
|
||||
++wait;
|
||||
|
||||
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
|
||||
|
||||
#define LOAD_CART \
|
||||
wait += waitstatesRegion[address >> BASE_OFFSET]; \
|
||||
if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \
|
||||
LOAD_32(value, address & (SIZE_CART0 - 1), memory->rom); \
|
||||
LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \
|
||||
} else { \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \
|
||||
value = (address >> 1) & 0xFFFF; \
|
||||
value |= value << 16; \
|
||||
value |= ((address + 2) >> 1) << 16; \
|
||||
}
|
||||
|
||||
#define LOAD_SRAM \
|
||||
|
@ -375,12 +404,13 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
LOAD_SRAM;
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address);
|
||||
LOAD_BAD;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter) {
|
||||
*cycleCounter += 2 + wait;
|
||||
*cycleCounter += 1 + wait;
|
||||
}
|
||||
// Unaligned 32-bit loads are "rotated" so they make some semblance of sense
|
||||
int rotate = (address & 3) << 3;
|
||||
|
@ -404,39 +434,33 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
|
||||
if (gba->performingDMA) {
|
||||
LOAD_16(value, address & 2, &gba->bus);
|
||||
} else {
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
LOAD_16(value, address & 2, &prefetch);
|
||||
}
|
||||
LOAD_BAD;
|
||||
uint32_t v2 = value;
|
||||
LOAD_16(value, address & 2, &v2);
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
LOAD_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
LOAD_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram);
|
||||
wait = memory->waitstatesNonseq16[REGION_WORKING_RAM];
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
LOAD_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
LOAD_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
value = GBAIORead(gba, address & (SIZE_IO - 1));
|
||||
value = GBAIORead(gba, address & (SIZE_IO - 2));
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
LOAD_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
LOAD_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
LOAD_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
LOAD_16(value, address & 0x00017FFE, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
|
@ -445,7 +469,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
case REGION_CART2:
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
|
||||
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
|
||||
LOAD_16(value, address & (SIZE_CART0 - 1), memory->rom);
|
||||
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address);
|
||||
value = (address >> 1) & 0xFFFF; \
|
||||
|
@ -456,7 +480,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
if (memory->savedata.type == SAVEDATA_EEPROM) {
|
||||
value = GBASavedataReadEEPROM(&memory->savedata);
|
||||
} else if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
|
||||
LOAD_16(value, address & (SIZE_CART0 - 1), memory->rom);
|
||||
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address);
|
||||
value = (address >> 1) & 0xFFFF; \
|
||||
|
@ -470,20 +494,14 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
|
||||
if (gba->performingDMA) {
|
||||
LOAD_16(value, address & 2, &gba->bus);
|
||||
} else {
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
LOAD_16(value, address & 2, &prefetch);
|
||||
}
|
||||
LOAD_BAD;
|
||||
uint32_t v2 = value;
|
||||
LOAD_16(value, address & 2, &v2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter) {
|
||||
*cycleCounter += 2 + wait;
|
||||
*cycleCounter += 1 + wait;
|
||||
}
|
||||
// Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too.
|
||||
int rotate = (address & 1) << 3;
|
||||
|
@ -493,49 +511,42 @@ uint32_t GBALoad16(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;
|
||||
uint8_t value = 0;
|
||||
uint32_t value = 0;
|
||||
int wait = 0;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_BIOS:
|
||||
if (address < SIZE_BIOS) {
|
||||
if (memory->activeRegion == REGION_BIOS) {
|
||||
value = ((int8_t*) memory->bios)[address];
|
||||
value = ((uint8_t*) memory->bios)[address];
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address);
|
||||
value = ((uint8_t*) &memory->biosPrefetch)[address & 3];
|
||||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
if (gba->performingDMA) {
|
||||
value = ((uint8_t*) &gba->bus)[address & 3];
|
||||
} else {
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
value = ((uint8_t*) &prefetch)[address & 3];
|
||||
}
|
||||
LOAD_BAD;
|
||||
value = ((uint8_t*) &value)[address & 3];
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
value = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)];
|
||||
value = ((uint8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)];
|
||||
wait = memory->waitstatesNonseq16[REGION_WORKING_RAM];
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
value = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
|
||||
value = ((uint8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
|
||||
break;
|
||||
case REGION_IO:
|
||||
value = (GBAIORead(gba, address & 0xFFFE) >> ((address & 0x0001) << 3)) & 0xFF;
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];
|
||||
value = ((uint8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
|
||||
value = ((uint8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
|
||||
} else {
|
||||
value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF];
|
||||
value = ((uint8_t*) gba->video.renderer->vram)[address & 0x00017FFF];
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
|
@ -549,7 +560,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
case REGION_CART2_EX:
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
|
||||
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
|
||||
value = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
|
||||
value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address);
|
||||
value = (address >> 1) & 0xFF; \
|
||||
|
@ -572,57 +583,52 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address);
|
||||
value = 0xFF;
|
||||
}
|
||||
value &= 0xFF;
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
if (gba->performingDMA) {
|
||||
value = ((uint8_t*) &gba->bus)[address & 3];
|
||||
} else {
|
||||
uint32_t prefetch = cpu->prefetch[1];
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
prefetch |= prefetch << 16;
|
||||
}
|
||||
value = ((uint8_t*) &prefetch)[address & 3];
|
||||
}
|
||||
LOAD_BAD;
|
||||
value = ((uint8_t*) &value)[address & 3];
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter) {
|
||||
*cycleCounter += 2 + wait;
|
||||
*cycleCounter += 1 + wait;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
#define STORE_WORKING_RAM \
|
||||
STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); \
|
||||
STORE_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram); \
|
||||
wait += waitstatesRegion[REGION_WORKING_RAM];
|
||||
|
||||
#define STORE_WORKING_IRAM \
|
||||
STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram);
|
||||
|
||||
#define STORE_IO \
|
||||
GBAIOWrite32(gba, address & (SIZE_IO - 1), value);
|
||||
GBAIOWrite32(gba, address & (SIZE_IO - 4), value);
|
||||
|
||||
#define STORE_PALETTE_RAM \
|
||||
STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); \
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16); \
|
||||
STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); \
|
||||
++wait; \
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value);
|
||||
|
||||
#define STORE_VRAM \
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
|
||||
STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \
|
||||
STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \
|
||||
} else { \
|
||||
STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); \
|
||||
STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
|
||||
} \
|
||||
++wait;
|
||||
|
||||
#define STORE_OAM \
|
||||
STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw); \
|
||||
STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); \
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 4)) >> 1); \
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) >> 1) + 1);
|
||||
|
||||
#define STORE_CART \
|
||||
wait += waitstatesRegion[address >> BASE_OFFSET]; \
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address);
|
||||
|
||||
#define STORE_SRAM \
|
||||
|
@ -685,33 +691,33 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
|||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
STORE_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram);
|
||||
wait = memory->waitstatesNonseq16[REGION_WORKING_RAM];
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBAIOWrite(gba, address & (SIZE_IO - 1), value);
|
||||
GBAIOWrite(gba, address & (SIZE_IO - 2), value);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
STORE_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
|
||||
} else {
|
||||
STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
STORE_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 2)) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFF)) {
|
||||
uint32_t reg = address & 0xFFFFFF;
|
||||
if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFE)) {
|
||||
uint32_t reg = address & 0xFFFFFE;
|
||||
GBAHardwareGPIOWrite(&memory->hw, reg, value);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad cartridge Store16: 0x%08X", address);
|
||||
|
@ -777,7 +783,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
|
||||
if (address == SAVEDATA_FLASH_BASE) {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame");
|
||||
GBASavedataInitFlash(&memory->savedata);
|
||||
GBASavedataInitFlash(&memory->savedata, gba->realisticTiming);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame");
|
||||
GBASavedataInitSRAM(&memory->savedata);
|
||||
|
@ -811,36 +817,36 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
|
|||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 4), memory->wram);
|
||||
STORE_32(value, address & (SIZE_WORKING_RAM - 4), memory->wram);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 4), memory->iwram);
|
||||
STORE_32(value, address & (SIZE_WORKING_IRAM - 4), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch32: 0x%08X", address);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
LOAD_32(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16);
|
||||
STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_32(oldValue, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
LOAD_32(oldValue, address & 0x0001FFFC, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_32(oldValue, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
LOAD_32(oldValue, address & 0x00017FFC, gba->video.renderer->vram);
|
||||
STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_32(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 1)) + 2) >> 1);
|
||||
LOAD_32(oldValue, address & (SIZE_OAM - 4), gba->video.oam.raw);
|
||||
STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 4)) >> 1);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) + 2) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
|
@ -848,18 +854,18 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
|
|||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
|
||||
LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address);
|
||||
_pristineCow(gba);
|
||||
if ((address & (SIZE_CART0 - 4)) >= gba->memory.romSize) {
|
||||
gba->memory.romSize = (address & (SIZE_CART0 - 4)) + 4;
|
||||
}
|
||||
LOAD_32(oldValue, address & (SIZE_CART0 - 4), gba->memory.rom);
|
||||
STORE_32(value, address & (SIZE_CART0 - 4), gba->memory.rom);
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
LOAD_32(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
STORE_32(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
LOAD_32(oldValue, address & (SIZE_CART_SRAM - 4), memory->savedata.data);
|
||||
STORE_32(value, address & (SIZE_CART_SRAM - 4), memory->savedata.data);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
|
@ -880,34 +886,34 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
|
|||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram);
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 2), memory->wram);
|
||||
STORE_16(value, address & (SIZE_WORKING_RAM - 2), memory->wram);
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram);
|
||||
LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 2), memory->iwram);
|
||||
STORE_16(value, address & (SIZE_WORKING_IRAM - 2), memory->iwram);
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch16: 0x%08X", address);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value);
|
||||
LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 2), gba->video.palette);
|
||||
STORE_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_16(oldValue, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram);
|
||||
LOAD_16(oldValue, address & 0x0001FFFE, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
|
||||
} else {
|
||||
LOAD_16(oldValue, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram);
|
||||
LOAD_16(oldValue, address & 0x00017FFE, gba->video.renderer->vram);
|
||||
STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_16(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1);
|
||||
LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||
STORE_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 2)) >> 1);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
|
@ -915,18 +921,18 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
|
|||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
|
||||
LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address);
|
||||
_pristineCow(gba);
|
||||
if ((address & (SIZE_CART0 - 1)) >= gba->memory.romSize) {
|
||||
gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2;
|
||||
}
|
||||
LOAD_16(oldValue, address & (SIZE_CART0 - 2), gba->memory.rom);
|
||||
STORE_16(value, address & (SIZE_CART0 - 2), gba->memory.rom);
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
LOAD_16(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
STORE_16(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data);
|
||||
LOAD_16(oldValue, address & (SIZE_CART_SRAM - 2), memory->savedata.data);
|
||||
STORE_16(value, address & (SIZE_CART_SRAM - 2), memory->savedata.data);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
|
@ -940,6 +946,63 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
|
|||
}
|
||||
}
|
||||
|
||||
void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
int8_t oldValue = -1;
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
oldValue = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)];
|
||||
((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)] = value;
|
||||
break;
|
||||
case REGION_WORKING_IRAM:
|
||||
oldValue = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
|
||||
((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)] = value;
|
||||
break;
|
||||
case REGION_IO:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address);
|
||||
break;
|
||||
case REGION_OAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch8: 0x%08X", address);
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
_pristineCow(gba);
|
||||
if ((address & (SIZE_CART0 - 1)) >= gba->memory.romSize) {
|
||||
gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2;
|
||||
}
|
||||
oldValue = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
|
||||
((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)] = value;
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
oldValue = ((int8_t*) memory->savedata.data)[address & (SIZE_CART_SRAM - 1)];
|
||||
((int8_t*) memory->savedata.data)[address & (SIZE_CART_SRAM - 1)] = value;
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_WARN, "Bad memory Patch8: 0x%08X", address);
|
||||
break;
|
||||
}
|
||||
if (old) {
|
||||
*old = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
#define LDM_LOOP(LDM) \
|
||||
for (i = 0; i < 16; i += 4) { \
|
||||
if (UNLIKELY(mask & (1 << i))) { \
|
||||
|
@ -1180,9 +1243,9 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
|
|||
memory->waitstatesSeq16[REGION_CART1] = memory->waitstatesSeq16[REGION_CART1_EX] = GBA_ROM_WAITSTATES_SEQ[ws1seq + 2];
|
||||
memory->waitstatesSeq16[REGION_CART2] = memory->waitstatesSeq16[REGION_CART2_EX] = GBA_ROM_WAITSTATES_SEQ[ws2seq + 4];
|
||||
|
||||
memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesSeq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0];
|
||||
memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesSeq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1];
|
||||
memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesSeq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2];
|
||||
memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0];
|
||||
memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1];
|
||||
memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2];
|
||||
|
||||
memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0_EX] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1;
|
||||
memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1_EX] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1;
|
||||
|
@ -1476,3 +1539,12 @@ uint32_t _popcount32(unsigned bits) {
|
|||
bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333);
|
||||
return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||
}
|
||||
|
||||
void _pristineCow(struct GBA* gba) {
|
||||
if (gba->memory.rom != gba->pristineRom) {
|
||||
return;
|
||||
}
|
||||
gba->memory.rom = anonymousMemoryMap(SIZE_CART0);
|
||||
memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize);
|
||||
memset(((uint8_t*) gba->memory.rom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ enum {
|
|||
SIZE_CART0 = 0x02000000,
|
||||
SIZE_CART1 = 0x02000000,
|
||||
SIZE_CART2 = 0x02000000,
|
||||
SIZE_CART_SRAM = 0x00008000,
|
||||
SIZE_CART_SRAM = 0x00010000,
|
||||
SIZE_CART_FLASH512 = 0x00010000,
|
||||
SIZE_CART_FLASH1M = 0x00020000,
|
||||
SIZE_CART_EEPROM = 0x00002000
|
||||
|
@ -154,6 +154,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
|
||||
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old);
|
||||
void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old);
|
||||
void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old);
|
||||
|
||||
uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);
|
||||
uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);
|
||||
|
|
|
@ -87,6 +87,12 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
|
||||
renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
|
||||
renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
|
||||
|
||||
renderer->d.disableBG[0] = false;
|
||||
renderer->d.disableBG[1] = false;
|
||||
renderer->d.disableBG[2] = false;
|
||||
renderer->d.disableBG[3] = false;
|
||||
renderer->d.disableOBJ = false;
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
|
@ -522,6 +528,8 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
softwareRenderer->windows[0].control.packed = 0xFF;
|
||||
}
|
||||
|
||||
GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
|
||||
|
||||
int w;
|
||||
x = 0;
|
||||
for (w = 0; w < softwareRenderer->nWindows; ++w) {
|
||||
|
@ -560,7 +568,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
}
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef __arm__
|
||||
#ifdef __ARM_NEON
|
||||
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
||||
#else
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
|
@ -599,10 +607,10 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer,
|
|||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
|
||||
renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt);
|
||||
renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt);
|
||||
renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt);
|
||||
renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt);
|
||||
renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0];
|
||||
renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1];
|
||||
renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2];
|
||||
renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3];
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
|
||||
|
@ -691,7 +699,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
|
|||
int w;
|
||||
renderer->end = 0;
|
||||
int spriteLayers = 0;
|
||||
if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt)) {
|
||||
if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
|
||||
if (renderer->oamDirty) {
|
||||
_cleanOAM(renderer);
|
||||
}
|
||||
|
@ -973,23 +981,27 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
} \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= 4 * baseX; \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
carryData = 0; \
|
||||
} else { \
|
||||
tileData >>= 4 * (7 - baseX); \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= 4 * baseX; \
|
||||
} else { \
|
||||
tileData >>= 4 * (7 - baseX); \
|
||||
} \
|
||||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
for (; length; ++tileX) { \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
|
@ -997,23 +1009,27 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
tileData = carryData; \
|
||||
for (; x < 8 && length; ++x, --length) { \
|
||||
if (!mosaicWait) { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= x * 4; \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
carryData = 0; \
|
||||
} else { \
|
||||
tileData >>= (7 - x) * 4; \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= x * 4; \
|
||||
} else { \
|
||||
tileData >>= (7 - x) * 4; \
|
||||
} \
|
||||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
mosaicWait = mosaicH; \
|
||||
} \
|
||||
--mosaicWait; \
|
||||
|
@ -1239,25 +1255,29 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
tileData = carryData; \
|
||||
for (x = 0; x < 8; ++x) { \
|
||||
if (!mosaicWait) { \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
if (x >= 4) { \
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= (x - 4) * 8; \
|
||||
} else { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= x * 8; \
|
||||
} \
|
||||
if (UNLIKELY(charBase >= 0x10000)) { \
|
||||
carryData = 0; \
|
||||
} else { \
|
||||
if (x >= 4) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= (7 - x) * 8; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
if (x >= 4) { \
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= (x - 4) * 8; \
|
||||
} else { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= x * 8; \
|
||||
} \
|
||||
} else { \
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= (3 - x) * 8; \
|
||||
if (x >= 4) { \
|
||||
LOAD_32(tileData, charBase, vram); \
|
||||
tileData >>= (7 - x) * 8; \
|
||||
} else { \
|
||||
LOAD_32(tileData, charBase + 4, vram); \
|
||||
tileData >>= (3 - x) * 8; \
|
||||
} \
|
||||
} \
|
||||
tileData &= 0xFF; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
tileData &= 0xFF; \
|
||||
carryData = tileData; \
|
||||
mosaicWait = mosaicH; \
|
||||
} \
|
||||
tileData |= tileData << 8; \
|
||||
|
@ -1526,6 +1546,12 @@ static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
color32 |= (color << 6) & 0xF800;
|
||||
color32 |= (color << 9) & 0xF80000;
|
||||
color = color32;
|
||||
#elif COLOR_5_6_5
|
||||
uint16_t color16 = 0;
|
||||
color16 |= (color & 0x001F) << 11;
|
||||
color16 |= (color & 0x03E0) << 1;
|
||||
color16 |= (color & 0x7C00) >> 10;
|
||||
color = color16;
|
||||
#endif
|
||||
mosaicWait = mosaicH;
|
||||
} else {
|
||||
|
@ -1602,13 +1628,19 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
BACKGROUND_BITMAP_ITERATE(160, 128);
|
||||
|
||||
if (!mosaicWait) {
|
||||
LOAD_16(color, (offset + (localX >> 8) + (localY >> 8) * 160) << 1, renderer->d.vram);
|
||||
LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram);
|
||||
#ifndef COLOR_16_BIT
|
||||
unsigned color32 = 0;
|
||||
color32 |= (color << 9) & 0xF80000;
|
||||
color32 |= (color << 3) & 0xF8;
|
||||
color32 |= (color << 6) & 0xF800;
|
||||
color = color32;
|
||||
#elif COLOR_5_6_5
|
||||
uint16_t color16 = 0;
|
||||
color16 |= (color & 0x001F) << 11;
|
||||
color16 |= (color & 0x03E0) << 1;
|
||||
color16 |= (color & 0x7C00) >> 10;
|
||||
color = color16;
|
||||
#endif
|
||||
mosaicWait = mosaicH;
|
||||
} else {
|
||||
|
@ -1691,7 +1723,7 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4;
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -1703,7 +1735,7 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
if (tileData) { \
|
||||
renderer->row[outX] |= FLAG_OBJWIN; \
|
||||
|
@ -1713,7 +1745,7 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8;
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -1725,7 +1757,7 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
if (tileData) { \
|
||||
renderer->row[outX] |= FLAG_OBJWIN; \
|
||||
|
@ -1744,9 +1776,15 @@ static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct G
|
|||
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
|
||||
unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;
|
||||
int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT && renderer->target2Bd) {
|
||||
// Hack: if a sprite is blended, then the variant palette is not used, but we don't know if it's blended in advance
|
||||
variant = 0;
|
||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) {
|
||||
int target2 = renderer->target2Bd << 4;
|
||||
target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority);
|
||||
target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority);
|
||||
target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority);
|
||||
target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority);
|
||||
if (GBAObjAttributesCGetPriority(sprite->c) < target2) {
|
||||
variant = 0;
|
||||
}
|
||||
}
|
||||
color_t* palette = &renderer->normalPalette[0x100];
|
||||
if (variant) {
|
||||
|
@ -1845,6 +1883,9 @@ static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsign
|
|||
if (objwinSlowPath) {
|
||||
objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
|
||||
objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
|
||||
if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (objwinDisable) {
|
||||
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
|
||||
|
@ -1874,6 +1915,8 @@ static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsign
|
|||
}
|
||||
return;
|
||||
}
|
||||
} else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
|
||||
return;
|
||||
}
|
||||
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
|
||||
uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
|
||||
|
|
|
@ -94,7 +94,12 @@ bool GBAVBMIsRecording(const struct GBARRContext* rr) {
|
|||
}
|
||||
|
||||
void GBAVBMNextFrame(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR);
|
||||
}
|
||||
|
||||
uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
||||
|
@ -105,6 +110,7 @@ uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
|||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
uint16_t input;
|
||||
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
|
||||
return input & 0x3FF;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "savedata.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
@ -13,6 +14,8 @@
|
|||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define FLASH_SETTLE_CYCLES 18000
|
||||
|
||||
static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
|
||||
static void _flashErase(struct GBASavedata* savedata);
|
||||
static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
|
||||
|
@ -112,7 +115,7 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) {
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
|
||||
if (savedata->type != SAVEDATA_AUTODETECT) {
|
||||
struct VFile* vf = savedata->vf;
|
||||
GBASavedataDeinit(savedata);
|
||||
|
@ -122,7 +125,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type)
|
|||
case SAVEDATA_FLASH512:
|
||||
case SAVEDATA_FLASH1M:
|
||||
savedata->type = type;
|
||||
GBASavedataInitFlash(savedata);
|
||||
GBASavedataInitFlash(savedata, realisticTiming);
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
GBASavedataInitEEPROM(savedata);
|
||||
|
@ -138,7 +141,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type)
|
|||
}
|
||||
}
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) {
|
||||
if (savedata->type == SAVEDATA_AUTODETECT) {
|
||||
savedata->type = SAVEDATA_FLASH512;
|
||||
}
|
||||
|
@ -146,14 +149,17 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
|||
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
|
||||
return;
|
||||
}
|
||||
size_t flashSize = SIZE_CART_FLASH512;
|
||||
int32_t flashSize = SIZE_CART_FLASH512;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
flashSize = SIZE_CART_FLASH1M;
|
||||
}
|
||||
off_t end;
|
||||
if (!savedata->vf) {
|
||||
end = 0;
|
||||
savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M);
|
||||
} else {
|
||||
end = savedata->vf->size(savedata->vf);
|
||||
if (end < SIZE_CART_FLASH512) {
|
||||
if (end < flashSize) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
|
||||
flashSize = SIZE_CART_FLASH1M;
|
||||
}
|
||||
|
@ -161,6 +167,8 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
|||
}
|
||||
|
||||
savedata->currentBank = savedata->data;
|
||||
savedata->dust = 0;
|
||||
savedata->realisticTiming = realisticTiming;
|
||||
if (end < SIZE_CART_FLASH512) {
|
||||
memset(&savedata->data[end], 0xFF, flashSize - end);
|
||||
}
|
||||
|
@ -225,6 +233,10 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (savedata->dust > 0 && (address >> 12) == savedata->settling) {
|
||||
--savedata->dust;
|
||||
return 0x5F;
|
||||
}
|
||||
return savedata->currentBank[address];
|
||||
}
|
||||
|
||||
|
@ -323,10 +335,8 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
|||
savedata->command <<= 1;
|
||||
savedata->command |= value & 0x1;
|
||||
if (savedata->command == EEPROM_COMMAND_WRITE) {
|
||||
savedata->addressBits = writeSize - 64 - 2;
|
||||
savedata->writeAddress = 0;
|
||||
} else {
|
||||
savedata->addressBits = writeSize - 2;
|
||||
savedata->readAddress = 0;
|
||||
}
|
||||
break;
|
||||
|
@ -338,13 +348,14 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
|||
savedata->writeAddress |= (value & 0x1) << 6;
|
||||
} else if (writeSize == 1) {
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
savedata->writePending = 1;
|
||||
} else {
|
||||
} else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) {
|
||||
uint8_t current = savedata->data[savedata->writeAddress >> 3];
|
||||
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
||||
current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
|
||||
savedata->data[savedata->writeAddress >> 3] = current;
|
||||
++savedata->writeAddress;
|
||||
} else {
|
||||
GBALog(0, GBA_LOG_GAME_ERROR, "Writing beyond end of EEPROM: %08X", (savedata->writeAddress >> 3));
|
||||
}
|
||||
break;
|
||||
case EEPROM_COMMAND_READ_PENDING:
|
||||
|
@ -369,7 +380,12 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
|||
--savedata->readBitsRemaining;
|
||||
if (savedata->readBitsRemaining < 64) {
|
||||
int step = 63 - savedata->readBitsRemaining;
|
||||
uint8_t data = savedata->data[(savedata->readAddress + step) >> 3] >> (0x7 - (step & 0x7));
|
||||
uint32_t address = (savedata->readAddress + step) >> 3;
|
||||
if (address >= SIZE_CART_EEPROM) {
|
||||
GBALog(0, GBA_LOG_GAME_ERROR, "Reading beyond end of EEPROM: %08X", address);
|
||||
return 0xFF;
|
||||
}
|
||||
uint8_t data = savedata->data[address] >> (0x7 - (step & 0x7));
|
||||
if (!savedata->readBitsRemaining) {
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
}
|
||||
|
@ -378,6 +394,42 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) {
|
||||
state->savedata.type = savedata->type;
|
||||
state->savedata.command = savedata->command;
|
||||
state->savedata.flashState = savedata->flashState;
|
||||
state->savedata.flashBank = savedata->currentBank == &savedata->data[0x10000];
|
||||
state->savedata.readBitsRemaining = savedata->readBitsRemaining;
|
||||
state->savedata.readAddress = savedata->readAddress;
|
||||
state->savedata.writeAddress = savedata->writeAddress;
|
||||
state->savedata.settlingSector = savedata->settling;
|
||||
state->savedata.settlingDust = savedata->dust;
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData) {
|
||||
if (state->savedata.type == SAVEDATA_FORCE_NONE) {
|
||||
return;
|
||||
}
|
||||
if (savedata->type != state->savedata.type) {
|
||||
GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming);
|
||||
}
|
||||
savedata->command = state->savedata.command;
|
||||
savedata->flashState = state->savedata.flashState;
|
||||
savedata->readBitsRemaining = state->savedata.readBitsRemaining;
|
||||
savedata->readAddress = state->savedata.readAddress;
|
||||
savedata->writeAddress = state->savedata.writeAddress;
|
||||
savedata->settling = state->savedata.settlingSector;
|
||||
savedata->dust = state->savedata.settlingDust;
|
||||
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
_flashSwitchBank(savedata, state->savedata.flashBank);
|
||||
}
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash bank switch to bank %i", bank);
|
||||
savedata->currentBank = &savedata->data[bank << 16];
|
||||
|
@ -405,5 +457,9 @@ void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
|
|||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
|
||||
}
|
||||
savedata->settling = sectorStart >> 12;
|
||||
if (savedata->realisticTiming) {
|
||||
savedata->dust = FLASH_SETTLE_CYCLES;
|
||||
}
|
||||
memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ struct VFile;
|
|||
enum SavedataType {
|
||||
SAVEDATA_AUTODETECT = -1,
|
||||
SAVEDATA_FORCE_NONE = 0,
|
||||
SAVEDATA_SRAM,
|
||||
SAVEDATA_FLASH512,
|
||||
SAVEDATA_FLASH1M,
|
||||
SAVEDATA_EEPROM
|
||||
SAVEDATA_SRAM = 1,
|
||||
SAVEDATA_FLASH512 = 2,
|
||||
SAVEDATA_FLASH1M = 3,
|
||||
SAVEDATA_EEPROM = 4
|
||||
};
|
||||
|
||||
enum SavedataCommand {
|
||||
|
@ -42,8 +42,8 @@ enum SavedataCommand {
|
|||
|
||||
enum FlashStateMachine {
|
||||
FLASH_STATE_RAW = 0,
|
||||
FLASH_STATE_START,
|
||||
FLASH_STATE_CONTINUE
|
||||
FLASH_STATE_START = 1,
|
||||
FLASH_STATE_CONTINUE = 2,
|
||||
};
|
||||
|
||||
enum FlashManufacturer {
|
||||
|
@ -67,14 +67,16 @@ struct GBASavedata {
|
|||
int mapMode;
|
||||
struct VFile* realVf;
|
||||
|
||||
int readBitsRemaining;
|
||||
int readAddress;
|
||||
int writeAddress;
|
||||
int writePending;
|
||||
int addressBits;
|
||||
int32_t readBitsRemaining;
|
||||
uint32_t readAddress;
|
||||
uint32_t writeAddress;
|
||||
|
||||
uint8_t* currentBank;
|
||||
|
||||
bool realisticTiming;
|
||||
unsigned settling;
|
||||
int dust;
|
||||
|
||||
enum FlashStateMachine flashState;
|
||||
};
|
||||
|
||||
|
@ -84,9 +86,9 @@ void 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 GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming);
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata);
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming);
|
||||
void GBASavedataInitEEPROM(struct GBASavedata* savedata);
|
||||
void GBASavedataInitSRAM(struct GBASavedata* savedata);
|
||||
|
||||
|
@ -96,4 +98,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
|
|||
uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata);
|
||||
void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -29,8 +29,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
state->biosChecksum = gba->biosChecksum;
|
||||
state->romCrc32 = gba->romCrc32;
|
||||
|
||||
state->id = ((struct GBACartridge*) gba->memory.rom)->id;
|
||||
memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title));
|
||||
if (gba->memory.rom) {
|
||||
state->id = ((struct GBACartridge*) gba->memory.rom)->id;
|
||||
memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title));
|
||||
} else {
|
||||
state->id = 0;
|
||||
memset(state->title, 0, sizeof(state->title));
|
||||
}
|
||||
|
||||
memcpy(state->cpu.gprs, gba->cpu->gprs, sizeof(state->cpu.gprs));
|
||||
state->cpu.cpsr = gba->cpu->cpsr;
|
||||
|
@ -48,6 +53,7 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state, false);
|
||||
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
|
@ -66,13 +72,57 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title))) {
|
||||
if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game");
|
||||
return;
|
||||
} else if (!gba->memory.rom && state->id != 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded");
|
||||
return;
|
||||
}
|
||||
if (state->romCrc32 != gba->romCrc32) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game");
|
||||
}
|
||||
if (state->cpu.cycles < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
|
||||
return;
|
||||
}
|
||||
if (state->video.eventDiff < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
|
||||
return;
|
||||
}
|
||||
if (state->video.nextHblank - state->video.eventDiff < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative");
|
||||
return;
|
||||
}
|
||||
if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
|
||||
return;
|
||||
}
|
||||
if (state->audio.eventDiff < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
|
||||
return;
|
||||
}
|
||||
if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative");
|
||||
return;
|
||||
}
|
||||
if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative");
|
||||
return;
|
||||
}
|
||||
if (state->audio.ch3.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative");
|
||||
return;
|
||||
}
|
||||
if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative");
|
||||
return;
|
||||
}
|
||||
int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);
|
||||
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
|
||||
return;
|
||||
}
|
||||
memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs));
|
||||
gba->cpu->cpsr = state->cpu.cpsr;
|
||||
gba->cpu->spsr = state->cpu.spsr;
|
||||
|
@ -111,17 +161,20 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBAIODeserialize(gba, state);
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state, false);
|
||||
|
||||
if (gba->rr) {
|
||||
gba->rr->stateLoaded(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _3DS
|
||||
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) {
|
||||
char suffix[5] = { '\0' };
|
||||
snprintf(suffix, sizeof(suffix), ".ss%d", slot);
|
||||
return VDirOptionalOpenFile(dir, gba->activeFile, "savestate", suffix, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
||||
|
@ -133,20 +186,31 @@ static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
|||
}
|
||||
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
if (!png || !info) {
|
||||
PNGWriteClose(png, info);
|
||||
GBADeallocateState(state);
|
||||
return false;
|
||||
}
|
||||
uLongf len = compressBound(sizeof(*state));
|
||||
void* buffer = malloc(len);
|
||||
if (state && png && info && buffer) {
|
||||
GBASerialize(gba, state);
|
||||
compress(buffer, &len, (const Bytef*) state, sizeof(*state));
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
if (!buffer) {
|
||||
PNGWriteClose(png, info);
|
||||
GBADeallocateState(state);
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
compress(buffer, &len, (const Bytef*) state, sizeof(*state));
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
GBADeallocateState(state);
|
||||
return state && png && info && buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||
|
@ -176,13 +240,14 @@ static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
|||
PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels);
|
||||
GBASyncPostFrame(gba->sync);
|
||||
GBASyncForceFrame(gba->sync);
|
||||
|
||||
free(pixels);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _3DS
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true);
|
||||
if (!vf) {
|
||||
|
@ -190,6 +255,9 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
|
|||
}
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -201,8 +269,12 @@ bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
|
|||
threadContext->rewindBufferSize = 0;
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) {
|
||||
if (!screenshot) {
|
||||
|
@ -229,6 +301,9 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
|||
return _loadPNGState(gba, vf);
|
||||
}
|
||||
#endif
|
||||
if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
|
||||
return false;
|
||||
}
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);
|
||||
if (!state) {
|
||||
return false;
|
||||
|
@ -254,6 +329,18 @@ void GBARecordFrame(struct GBAThread* thread) {
|
|||
thread->rewindBuffer[offset] = state;
|
||||
}
|
||||
GBASerialize(thread->gba, state);
|
||||
|
||||
if (thread->rewindScreenBuffer) {
|
||||
unsigned stride;
|
||||
uint8_t* pixels = 0;
|
||||
thread->gba->video.renderer->getPixels(thread->gba->video.renderer, &stride, (void*) &pixels);
|
||||
if (pixels) {
|
||||
size_t y;
|
||||
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
memcpy(&thread->rewindScreenBuffer[(offset * VIDEO_VERTICAL_PIXELS + y) * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL], &pixels[y * stride * BYTES_PER_PIXEL], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
thread->rewindBufferSize = thread->rewindBufferSize == thread->rewindBufferCapacity ? thread->rewindBufferCapacity : thread->rewindBufferSize + 1;
|
||||
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
|
||||
}
|
||||
|
@ -271,33 +358,40 @@ void GBARewindSettingsChanged(struct GBAThread* threadContext, int newCapacity,
|
|||
GBADeallocateState(threadContext->rewindBuffer[i]);
|
||||
}
|
||||
free(threadContext->rewindBuffer);
|
||||
free(threadContext->rewindScreenBuffer);
|
||||
}
|
||||
threadContext->rewindBufferCapacity = newCapacity;
|
||||
if (threadContext->rewindBufferCapacity > 0) {
|
||||
threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*));
|
||||
threadContext->rewindScreenBuffer = calloc(threadContext->rewindBufferCapacity, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
|
||||
} else {
|
||||
threadContext->rewindBuffer = 0;
|
||||
threadContext->rewindScreenBuffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GBARewind(struct GBAThread* thread, int nStates) {
|
||||
int GBARewind(struct GBAThread* thread, int nStates) {
|
||||
if (nStates > thread->rewindBufferSize || nStates < 0) {
|
||||
nStates = thread->rewindBufferSize;
|
||||
}
|
||||
if (nStates == 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
int offset = thread->rewindBufferWriteOffset - nStates;
|
||||
if (offset < 0) {
|
||||
offset += thread->rewindBufferSize;
|
||||
offset += thread->rewindBufferCapacity;
|
||||
}
|
||||
struct GBASerializedState* state = thread->rewindBuffer[offset];
|
||||
if (!state) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
thread->rewindBufferSize -= nStates - 1;
|
||||
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
|
||||
thread->rewindBufferSize -= nStates;
|
||||
thread->rewindBufferWriteOffset = offset;
|
||||
GBADeserialize(thread->gba, state);
|
||||
if (thread->rewindScreenBuffer) {
|
||||
thread->gba->video.renderer->putPixels(thread->gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, &thread->rewindScreenBuffer[offset * VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL]);
|
||||
}
|
||||
return nStates;
|
||||
}
|
||||
|
||||
void GBARewindAll(struct GBAThread* thread) {
|
||||
|
|
|
@ -129,7 +129,7 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* 0x00290 - 0x002C3: GPIO state
|
||||
* | 0x00290 - 0x00291: Pin state
|
||||
* | 0x00292 - 0x00293: Direction state
|
||||
* | 0x00294 - 0x002B6: RTC state (see gba-hardware.h for format)
|
||||
* | 0x00294 - 0x002B6: RTC state (see hardware.h for format)
|
||||
* | 0x002B7 - 0x002B7: GPIO devices
|
||||
* | bit 0: Has RTC values
|
||||
* | bit 1: Has rumble value (reserved)
|
||||
|
@ -150,7 +150,21 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | 0x002C1 - 0x002C3: Flags
|
||||
* | bits 0 - 1: Tilt state machine
|
||||
* | bits 2 - 31: Reserved
|
||||
* 0x002C4 - 0x002F3: Reserved (leave zero)
|
||||
* 0x002C4 - 0x002DF: Reserved (leave zero)
|
||||
* 0x002E0 - 0x002EF: Savedata state
|
||||
* | 0x002E0 - 0x002E0: Savedata type
|
||||
* | 0x002E1 - 0x002E1: Savedata command (see savedata.h)
|
||||
* | 0x002E2 - 0x002E2: Flags
|
||||
* | bits 0 - 1: Flash state machine
|
||||
* | bits 2 - 3: Reserved
|
||||
* | bit 4: Flash bank
|
||||
* | bits 5 - 7: Reserved
|
||||
* | 0x002E3 - 0x002E3: Reserved
|
||||
* | 0x002E4 - 0x002E7: EEPROM read bits remaining
|
||||
* | 0x002E8 - 0x002EB: EEPROM read address
|
||||
* | 0x002EC - 0x002EF: EEPROM write address
|
||||
* | 0x002F0 - 0x002F1: Flash settling sector
|
||||
* | 0x002F2 - 0x002F3: Flash settling remaining
|
||||
* 0x002F4 - 0x002FF: Prefetch
|
||||
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
|
||||
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
|
||||
|
@ -217,7 +231,7 @@ struct GBASerializedState {
|
|||
int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
int32_t nextSample;
|
||||
int32_t fifoSize;
|
||||
uint32_t fifoSize;
|
||||
unsigned ch1Volume : 4;
|
||||
unsigned ch1Dead : 1;
|
||||
unsigned ch1Hi : 1;
|
||||
|
@ -271,7 +285,22 @@ struct GBASerializedState {
|
|||
unsigned : 22;
|
||||
} hw;
|
||||
|
||||
uint32_t reservedHardware[12];
|
||||
uint32_t reservedHardware[7];
|
||||
|
||||
struct {
|
||||
unsigned type : 8;
|
||||
unsigned command : 8;
|
||||
unsigned flashState : 2;
|
||||
unsigned : 2;
|
||||
unsigned flashBank : 1;
|
||||
unsigned : 3;
|
||||
unsigned : 8;
|
||||
int32_t readBitsRemaining;
|
||||
uint32_t readAddress;
|
||||
uint32_t writeAddress;
|
||||
uint16_t settlingSector;
|
||||
uint16_t settlingDust;
|
||||
} savedata;
|
||||
|
||||
uint32_t biosPrefetch;
|
||||
uint32_t cpuPrefetch[2];
|
||||
|
@ -306,7 +335,7 @@ void GBADeallocateState(struct GBASerializedState* state);
|
|||
|
||||
void GBARecordFrame(struct GBAThread* thread);
|
||||
void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int newInterval);
|
||||
void GBARewind(struct GBAThread* thread, int nStates);
|
||||
int GBARewind(struct GBAThread* thread, int nStates);
|
||||
void GBARewindAll(struct GBAThread* thread);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "sharkport.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
static const char* const SHARKPORT_HEADER = "SharkPortSave";
|
||||
|
||||
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) {
|
||||
union {
|
||||
char c[0x1C];
|
||||
int32_t i;
|
||||
} buffer;
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
int32_t size;
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (size != (int32_t) strlen(SHARKPORT_HEADER)) {
|
||||
return false;
|
||||
}
|
||||
if (vf->read(vf, buffer.c, size) < size) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(SHARKPORT_HEADER, buffer.c, size) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (size != 0x000F0000) {
|
||||
// What is this value?
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip first three fields
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (vf->seek(vf, size, SEEK_CUR) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (vf->seek(vf, size, SEEK_CUR) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (vf->seek(vf, size, SEEK_CUR) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read payload
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(size, 0, &buffer.i);
|
||||
if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) {
|
||||
return false;
|
||||
}
|
||||
char* payload = malloc(size);
|
||||
if (vf->read(vf, payload, size) < size) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom;
|
||||
memcpy(buffer.c, &cart->title, 16);
|
||||
buffer.c[0x10] = 0;
|
||||
buffer.c[0x11] = 0;
|
||||
buffer.c[0x12] = cart->checksum;
|
||||
buffer.c[0x13] = cart->maker;
|
||||
buffer.c[0x14] = 1;
|
||||
buffer.c[0x15] = 0;
|
||||
buffer.c[0x16] = 0;
|
||||
buffer.c[0x17] = 0;
|
||||
buffer.c[0x18] = 0;
|
||||
buffer.c[0x19] = 0;
|
||||
buffer.c[0x1A] = 0;
|
||||
buffer.c[0x1B] = 0;
|
||||
if (memcmp(buffer.c, payload, 0x1C) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
uint32_t checksum;
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
goto cleanup;
|
||||
}
|
||||
LOAD_32(checksum, 0, &buffer.i);
|
||||
|
||||
if (testChecksum) {
|
||||
uint32_t calcChecksum = 0;
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
calcChecksum += ((int32_t) payload[i]) << (calcChecksum % 24);
|
||||
}
|
||||
|
||||
if (calcChecksum != checksum) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t copySize = size - 0x1C;
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_SRAM:
|
||||
if (copySize > SIZE_CART_SRAM) {
|
||||
copySize = SIZE_CART_SRAM;
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
if (copySize > SIZE_CART_FLASH512) {
|
||||
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming);
|
||||
}
|
||||
// Fall through
|
||||
case SAVEDATA_FLASH1M:
|
||||
if (copySize > SIZE_CART_FLASH1M) {
|
||||
copySize = SIZE_CART_FLASH1M;
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
if (copySize > SIZE_CART_EEPROM) {
|
||||
copySize = SAVEDATA_EEPROM;
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memcpy(gba->memory.savedata.data, &payload[0x1C], copySize);
|
||||
|
||||
free(payload);
|
||||
return true;
|
||||
|
||||
cleanup:
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
|
||||
union {
|
||||
char c[0x1C];
|
||||
int32_t i;
|
||||
} buffer;
|
||||
int32_t size = strlen(SHARKPORT_HEADER);
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (vf->write(vf, SHARKPORT_HEADER, size) < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size = 0x000F0000;
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom;
|
||||
size = sizeof(cart->title);
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (vf->write(vf, cart->title, size) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t t = time(0);
|
||||
struct tm* tm = localtime(&t);
|
||||
size = strftime(&buffer.c[4], sizeof(buffer.c) - 4, "%m/%d/%Y %I:%M:%S %p", tm);
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, buffer.c, size + 4) < size + 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Last field is blank
|
||||
size = 0;
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write payload
|
||||
size = 0x1C;
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_SRAM:
|
||||
size += SIZE_CART_SRAM;
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
size += SIZE_CART_FLASH512;
|
||||
break;
|
||||
case SAVEDATA_FLASH1M:
|
||||
size += SIZE_CART_FLASH1M;
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
size += SIZE_CART_EEPROM;
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
return false;
|
||||
}
|
||||
STORE_32(size, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
size -= 0x1C;
|
||||
|
||||
memcpy(buffer.c, &cart->title, 16);
|
||||
buffer.c[0x10] = 0;
|
||||
buffer.c[0x11] = 0;
|
||||
buffer.c[0x12] = cart->checksum;
|
||||
buffer.c[0x13] = cart->maker;
|
||||
buffer.c[0x14] = 1;
|
||||
buffer.c[0x15] = 0;
|
||||
buffer.c[0x16] = 0;
|
||||
buffer.c[0x17] = 0;
|
||||
buffer.c[0x18] = 0;
|
||||
buffer.c[0x19] = 0;
|
||||
buffer.c[0x1A] = 0;
|
||||
buffer.c[0x1B] = 0;
|
||||
if (vf->write(vf, buffer.c, 0x1C) < 0x1C) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t checksum = 0;
|
||||
int i;
|
||||
for (i = 0; i < 0x1C; ++i) {
|
||||
checksum += buffer.c[i] << (checksum % 24);
|
||||
}
|
||||
|
||||
if (vf->write(vf, gba->memory.savedata.data, size) < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; ++i) {
|
||||
checksum += ((char) gba->memory.savedata.data[i]) << (checksum % 24);
|
||||
}
|
||||
|
||||
STORE_32(checksum, 0, &buffer.i);
|
||||
if (vf->write(vf, &buffer.i, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_SHARKPORT_H
|
||||
#define GBA_SHARKPORT_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GBA;
|
||||
struct VFile;
|
||||
|
||||
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum);
|
||||
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf);
|
||||
|
||||
#endif
|
|
@ -131,11 +131,30 @@ void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) {
|
|||
}
|
||||
|
||||
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
|
||||
if ((value ^ sio->siocnt) & 0x3000) {
|
||||
sio->siocnt = value & 0x3000;
|
||||
_switchMode(sio);
|
||||
}
|
||||
if (sio->activeDriver && sio->activeDriver->writeRegister) {
|
||||
value = sio->activeDriver->writeRegister(sio->activeDriver, REG_SIOCNT, value);
|
||||
} else {
|
||||
// Dummy drivers
|
||||
switch (sio->mode) {
|
||||
case SIO_NORMAL_8:
|
||||
case SIO_NORMAL_32:
|
||||
value |= 0x0004;
|
||||
if ((value & 0x4080) == 0x4080) {
|
||||
// TODO: Test this on hardware to see if this is correct
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO);
|
||||
}
|
||||
value &= ~0x0080;
|
||||
break;
|
||||
default:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
sio->siocnt = value;
|
||||
_switchMode(sio);
|
||||
}
|
||||
|
||||
void GBASIOWriteSIOMLT_SEND(struct GBASIO* sio, uint16_t value) {
|
||||
|
|
|
@ -30,11 +30,11 @@ struct GBASIO;
|
|||
struct GBASIODriver {
|
||||
struct GBASIO* p;
|
||||
|
||||
int (*init)(struct GBASIODriver* driver);
|
||||
bool (*init)(struct GBASIODriver* driver);
|
||||
void (*deinit)(struct GBASIODriver* driver);
|
||||
int (*load)(struct GBASIODriver* driver);
|
||||
int (*unload)(struct GBASIODriver* driver);
|
||||
int (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
bool (*load)(struct GBASIODriver* driver);
|
||||
bool (*unload)(struct GBASIODriver* driver);
|
||||
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "lockstep.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
|
||||
#define LOCKSTEP_INCREMENT 2048
|
||||
|
||||
static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
|
||||
static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
||||
lockstep->players[0] = 0;
|
||||
lockstep->players[1] = 0;
|
||||
lockstep->players[2] = 0;
|
||||
lockstep->players[3] = 0;
|
||||
lockstep->multiRecv[0] = 0xFFFF;
|
||||
lockstep->multiRecv[1] = 0xFFFF;
|
||||
lockstep->multiRecv[2] = 0xFFFF;
|
||||
lockstep->multiRecv[3] = 0xFFFF;
|
||||
lockstep->attached = 0;
|
||||
lockstep->loaded = 0;
|
||||
lockstep->transferActive = false;
|
||||
lockstep->waiting = 0;
|
||||
lockstep->nextEvent = LOCKSTEP_INCREMENT;
|
||||
ConditionInit(&lockstep->barrier);
|
||||
MutexInit(&lockstep->mutex);
|
||||
}
|
||||
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
|
||||
ConditionDeinit(&lockstep->barrier);
|
||||
MutexDeinit(&lockstep->mutex);
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
||||
node->d.init = GBASIOLockstepNodeInit;
|
||||
node->d.deinit = GBASIOLockstepNodeDeinit;
|
||||
node->d.load = GBASIOLockstepNodeLoad;
|
||||
node->d.unload = GBASIOLockstepNodeUnload;
|
||||
node->d.writeRegister = GBASIOLockstepNodeWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeProcessEvents;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
lockstep->players[lockstep->attached] = node;
|
||||
node->p = lockstep;
|
||||
node->id = lockstep->attached;
|
||||
++lockstep->attached;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == 0) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lockstep->attached; ++i) {
|
||||
if (lockstep->players[i] != node) {
|
||||
continue;
|
||||
}
|
||||
for (++i; i < lockstep->attached; ++i) {
|
||||
lockstep->players[i - 1] = lockstep->players[i];
|
||||
lockstep->players[i - 1]->id = i - 1;
|
||||
}
|
||||
--lockstep->attached;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->d.p->multiplayerControl.slave = node->id > 0;
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Node init", node->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
|
||||
UNUSED(driver);
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->loaded;
|
||||
node->d.p->rcnt |= 3;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
MutexLock(&node->p->mutex);
|
||||
--node->p->loaded;
|
||||
ConditionWake(&node->p->barrier);
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||
if (value & 0x0080) {
|
||||
if (!node->id) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Transfer initiated", node->id);
|
||||
MutexLock(&node->p->mutex);
|
||||
node->p->transferActive = true;
|
||||
node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
|
||||
node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
MutexUnlock(&node->p->mutex);
|
||||
} else {
|
||||
value &= ~0x0080;
|
||||
}
|
||||
}
|
||||
value &= 0xFF83;
|
||||
value |= driver->p->siocnt & 0x00FC;
|
||||
} else if (address == REG_SIOMLT_SEND) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOMLT_SEND <- %04x", node->id, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent -= cycles;
|
||||
while (node->nextEvent <= 0) {
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->waiting;
|
||||
if (node->p->waiting < node->p->loaded) {
|
||||
ConditionWait(&node->p->barrier, &node->p->mutex);
|
||||
} else {
|
||||
if (node->p->transferActive) {
|
||||
node->p->transferCycles -= node->p->nextEvent;
|
||||
if (node->p->transferCycles > 0) {
|
||||
if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
|
||||
node->p->nextEvent = node->p->transferCycles;
|
||||
}
|
||||
} else {
|
||||
node->p->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->p->transferActive = false;
|
||||
int i;
|
||||
for (i = 0; i < node->p->attached; ++i) {
|
||||
node->p->multiRecv[i] = node->p->players[i]->multiSend;
|
||||
node->p->players[i]->state = LOCKSTEP_FINISHED;
|
||||
}
|
||||
for (; i < MAX_GBAS; ++i) {
|
||||
node->p->multiRecv[i] = 0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
node->p->waiting = 0;
|
||||
ConditionWake(&node->p->barrier);
|
||||
}
|
||||
if (node->state == LOCKSTEP_FINISHED) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: Finishing transfer: %04x %04x %04x %04x", node->id, node->p->multiRecv[0], node->p->multiRecv[1], node->p->multiRecv[2], node->p->multiRecv[3]);
|
||||
node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
||||
node->d.p->rcnt |= 1;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
if (node->d.p->multiplayerControl.irq) {
|
||||
GBARaiseIRQ(node->d.p->p, IRQ_SIO);
|
||||
}
|
||||
node->d.p->multiplayerControl.id = node->id;
|
||||
node->d.p->multiplayerControl.busy = 0;
|
||||
} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
|
||||
node->state = LOCKSTEP_STARTED;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
||||
node->d.p->rcnt &= ~1;
|
||||
if (node->id) {
|
||||
node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
node->d.p->multiplayerControl.busy = 1;
|
||||
}
|
||||
}
|
||||
node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached;
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef SIO_LOCKSTEP_H
|
||||
#define SIO_LOCKSTEP_H
|
||||
|
||||
#include "gba/sio.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
enum LockstepState {
|
||||
LOCKSTEP_IDLE = 0,
|
||||
LOCKSTEP_STARTED = 1,
|
||||
LOCKSTEP_FINISHED = 2
|
||||
};
|
||||
|
||||
struct GBASIOLockstep {
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attached;
|
||||
int loaded;
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
bool transferActive;
|
||||
int32_t transferCycles;
|
||||
int32_t nextEvent;
|
||||
|
||||
int waiting;
|
||||
Mutex mutex;
|
||||
Condition barrier;
|
||||
};
|
||||
|
||||
struct GBASIOLockstepNode {
|
||||
struct GBASIODriver d;
|
||||
struct GBASIOLockstep* p;
|
||||
|
||||
int32_t nextEvent;
|
||||
uint16_t multiSend;
|
||||
enum LockstepState state;
|
||||
int id;
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep*);
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
|
||||
#endif
|
|
@ -6,6 +6,8 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "util/formatting.h"
|
||||
#include "util/string.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
@ -13,9 +15,6 @@
|
|||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <strsafe.h>
|
||||
#define PATH_SEP "\\"
|
||||
#else
|
||||
#define PATH_SEP "/"
|
||||
#endif
|
||||
|
||||
#define SECTION_NAME_MAX 128
|
||||
|
@ -131,12 +130,12 @@ void GBAConfigDirectory(char* out, size_t outLength) {
|
|||
char* home = getenv("HOME");
|
||||
snprintf(out, outLength, "%s/.config", home);
|
||||
mkdir(out, 0755);
|
||||
snprintf(out, outLength, "%s/.config/%s", home, BINARY_NAME);
|
||||
snprintf(out, outLength, "%s/.config/%s", home, binaryName);
|
||||
mkdir(out, 0755);
|
||||
#else
|
||||
char home[MAX_PATH];
|
||||
SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home);
|
||||
snprintf(out, outLength, "%s\\%s", home, PROJECT_NAME);
|
||||
snprintf(out, outLength, "%s\\%s", home, projectName);
|
||||
CreateDirectoryA(out, NULL);
|
||||
#endif
|
||||
}
|
||||
|
@ -181,6 +180,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
_lookupCharValue(config, "bios", &opts->bios);
|
||||
_lookupIntValue(config, "logLevel", &opts->logLevel);
|
||||
_lookupIntValue(config, "frameskip", &opts->frameskip);
|
||||
_lookupIntValue(config, "volume", &opts->volume);
|
||||
_lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
|
||||
_lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval);
|
||||
_lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
|
||||
|
@ -190,6 +190,9 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
}
|
||||
|
||||
int fakeBool;
|
||||
if (_lookupIntValue(config, "useBios", &fakeBool)) {
|
||||
opts->useBios = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "audioSync", &fakeBool)) {
|
||||
opts->audioSync = fakeBool;
|
||||
}
|
||||
|
@ -202,6 +205,12 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
if (_lookupIntValue(config, "resampleVideo", &fakeBool)) {
|
||||
opts->resampleVideo = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "suspendScreensaver", &fakeBool)) {
|
||||
opts->suspendScreensaver = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "mute", &fakeBool)) {
|
||||
opts->mute = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "skipBios", &fakeBool)) {
|
||||
opts->skipBios = fakeBool;
|
||||
}
|
||||
|
@ -229,6 +238,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) {
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
|
||||
|
@ -241,8 +251,11 @@ 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, "volume", opts->volume);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver);
|
||||
|
||||
switch (opts->idleOptimization) {
|
||||
case IDLE_LOOP_IGNORE:
|
||||
|
|
|
@ -21,6 +21,7 @@ struct GBAConfig {
|
|||
struct GBAOptions {
|
||||
char* bios;
|
||||
bool skipBios;
|
||||
bool useBios;
|
||||
int logLevel;
|
||||
int frameskip;
|
||||
bool rewindEnable;
|
||||
|
@ -34,6 +35,10 @@ struct GBAOptions {
|
|||
int height;
|
||||
bool lockAspectRatio;
|
||||
bool resampleVideo;
|
||||
bool suspendScreensaver;
|
||||
|
||||
int volume;
|
||||
bool mute;
|
||||
|
||||
bool videoSync;
|
||||
bool audioSync;
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "export.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
bool GBAExportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) {
|
||||
if (entries > 0xFFFF) {
|
||||
return false;
|
||||
}
|
||||
uint32_t chunkSize = 4 + 4 * entries;
|
||||
uint32_t size = chunkSize + 12;
|
||||
|
||||
// Header
|
||||
if (vf->write(vf, "RIFF", 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (VFileWrite32LE(vf, size) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (vf->write(vf, "PAL ", 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data chunk
|
||||
if (vf->write(vf, "data", 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (VFileWrite32LE(vf, chunkSize) < 4) {
|
||||
return false;
|
||||
}
|
||||
if (VFileWrite16LE(vf, 0x0300) < 2) {
|
||||
return false;
|
||||
}
|
||||
if (VFileWrite16LE(vf, entries) < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < entries; ++i) {
|
||||
uint8_t block[4] = {
|
||||
GBA_R8(colors[i]),
|
||||
GBA_G8(colors[i]),
|
||||
GBA_B8(colors[i]),
|
||||
0
|
||||
};
|
||||
if (vf->write(vf, block, 4) < 4) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAExportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors) {
|
||||
if (entries > 256) {
|
||||
return false;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < entries; ++i) {
|
||||
uint8_t block[3] = {
|
||||
GBA_R8(colors[i]),
|
||||
GBA_G8(colors[i]),
|
||||
GBA_B8(colors[i]),
|
||||
};
|
||||
if (vf->write(vf, block, 3) < 3) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (; i < 256; ++i) {
|
||||
uint8_t block[3] = { 0, 0, 0 };
|
||||
if (vf->write(vf, block, 3) < 3) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_EXPORT_H
|
||||
#define GBA_EXPORT_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct VFile;
|
||||
|
||||
bool GBAExportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors);
|
||||
bool GBAExportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors);
|
||||
|
||||
#endif
|
|
@ -11,6 +11,10 @@
|
|||
#include "util/configuration.h"
|
||||
|
||||
static const struct GBACartridgeOverride _overrides[] = {
|
||||
// Advance Wars
|
||||
{ "AWRE", SAVEDATA_FLASH512, HW_NONE, 0x8038810 },
|
||||
{ "AWRP", SAVEDATA_FLASH512, HW_NONE, 0x8038810 },
|
||||
|
||||
// Boktai: The Sun is in Your Hand
|
||||
{ "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
@ -21,6 +25,13 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "U32E", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U32P", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Dragon Ball Z - The Legacy of Goku
|
||||
{ "ALGP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Dragon Ball Z - Taiketsu
|
||||
{ "BDBE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BDBP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Drill Dozer
|
||||
{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
{ "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
|
@ -28,6 +39,9 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
// Final Fantasy Tactics Advance
|
||||
{ "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 },
|
||||
|
||||
// F-Zero - Climax
|
||||
{ "BFTJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Golden Sun: The Lost Age
|
||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
||||
|
||||
|
@ -37,6 +51,9 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
// Mega Man Battle Network
|
||||
{ "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E },
|
||||
|
||||
// Mega Man Zero
|
||||
{ "AZCE", SAVEDATA_SRAM, HW_NONE, 0x80004E8 },
|
||||
|
||||
// Metal Slug Advance
|
||||
{ "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 },
|
||||
|
||||
|
@ -59,13 +76,13 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon Emerald
|
||||
{ "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEJ", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
{ "BPEP", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPEI", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPES", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPED", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
|
||||
|
||||
// Pokemon Mystery Dungeon
|
||||
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
@ -77,28 +94,47 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon LeafGreen
|
||||
{ "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// RockMan EXE 4.5 - Real Operation
|
||||
{ "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Rocky
|
||||
{ "AR8E", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AROP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Sennen Kazoku
|
||||
{ "BKAJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Shin Bokura no Taiyou: Gyakushuu no Sabata
|
||||
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Super Mario Advance 2
|
||||
{ "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
{ "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
{ "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
|
||||
// Super Mario Advance 3
|
||||
{ "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
|
||||
{ "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
|
||||
{ "A3AP", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
|
||||
|
||||
// Super Mario Advance 4
|
||||
{ "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AX4J", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
|
||||
{ "AX4E", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
|
||||
{ "AX4P", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
|
||||
|
||||
// Top Gun - Combat Zones
|
||||
{ "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
@ -120,7 +156,7 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver
|
|||
override->savetype = SAVEDATA_AUTODETECT;
|
||||
override->hardware = HW_NONE;
|
||||
override->idleLoop = IDLE_LOOP_NONE;
|
||||
bool found;
|
||||
bool found = false;
|
||||
|
||||
if (override->id[0] == 'F') {
|
||||
// Classic NES Series
|
||||
|
@ -224,7 +260,7 @@ void GBAOverrideSave(struct Configuration* config, const struct GBACartridgeOver
|
|||
|
||||
void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) {
|
||||
if (override->savetype != SAVEDATA_AUTODETECT) {
|
||||
GBASavedataForceType(&gba->memory.savedata, override->savetype);
|
||||
GBASavedataForceType(&gba->memory.savedata, override->savetype, gba->realisticTiming);
|
||||
}
|
||||
|
||||
if (override->hardware != HW_NO_OVERRIDE) {
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "thread.h"
|
||||
|
||||
static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
|
||||
// Make sure the video thread can process events while the GBA thread is paused
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
if (frameOn != sync->videoFrameOn) {
|
||||
sync->videoFrameOn = frameOn;
|
||||
ConditionWake(&sync->videoFrameAvailableCond);
|
||||
}
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
++sync->videoFramePending;
|
||||
--sync->videoFrameSkip;
|
||||
if (sync->videoFrameSkip < 0) {
|
||||
do {
|
||||
ConditionWake(&sync->videoFrameAvailableCond);
|
||||
if (sync->videoFrameWait) {
|
||||
ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
|
||||
}
|
||||
} while (sync->videoFrameWait && sync->videoFramePending);
|
||||
}
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBASyncForceFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
ConditionWake(&sync->videoFrameAvailableCond);
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
ConditionWake(&sync->videoFrameRequiredCond);
|
||||
if (!sync->videoFrameOn && !sync->videoFramePending) {
|
||||
return false;
|
||||
}
|
||||
if (sync->videoFrameOn) {
|
||||
if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sync->videoFramePending = 0;
|
||||
sync->videoFrameSkip = frameskip;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
bool GBASyncDrawingFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return sync->videoFrameSkip <= 0;
|
||||
}
|
||||
|
||||
void GBASyncSetVideoSync(struct GBASync* sync, bool wait) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
_changeVideoSync(sync, wait);
|
||||
}
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync->audioWait && wait) {
|
||||
// TODO loop properly in event of spurious wakeups
|
||||
ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
|
||||
}
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncLockAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncUnlockAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncConsumeAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConditionWake(&sync->audioRequiredCond);
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_SYNC_H
|
||||
#define GBA_SYNC_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
struct GBASync {
|
||||
int videoFramePending;
|
||||
bool videoFrameWait;
|
||||
int videoFrameSkip;
|
||||
bool videoFrameOn;
|
||||
Mutex videoFrameMutex;
|
||||
Condition videoFrameAvailableCond;
|
||||
Condition videoFrameRequiredCond;
|
||||
|
||||
bool audioWait;
|
||||
Condition audioRequiredCond;
|
||||
Mutex audioBufferMutex;
|
||||
};
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync);
|
||||
void GBASyncForceFrame(struct GBASync* sync);
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync);
|
||||
bool GBASyncDrawingFrame(struct GBASync* sync);
|
||||
void GBASyncSetVideoSync(struct GBASync* sync, bool wait);
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, bool wait);
|
||||
void GBASyncLockAudio(struct GBASync* sync);
|
||||
void GBASyncUnlockAudio(struct GBASync* sync);
|
||||
void GBASyncConsumeAudio(struct GBASync* sync);
|
||||
|
||||
#endif
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
static const float _defaultFPSTarget = 60.f;
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
#ifdef USE_PTHREADS
|
||||
static pthread_key_t _contextKey;
|
||||
static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
|
||||
|
@ -45,7 +46,6 @@ static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
threadContext->state = newState;
|
||||
|
@ -93,19 +93,7 @@ static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
|
|||
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
|
||||
// Make sure the video thread can process events while the GBA thread is paused
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
if (frameOn != sync->videoFrameOn) {
|
||||
sync->videoFrameOn = frameOn;
|
||||
ConditionWake(&sync->videoFrameAvailableCond);
|
||||
}
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_once(&_contextOnce, _createTLS);
|
||||
|
@ -118,10 +106,12 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
struct Patch patch;
|
||||
struct GBACheatDevice cheatDevice;
|
||||
struct GBAThread* threadContext = context;
|
||||
struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
|
||||
struct ARMComponent* components[GBA_COMPONENT_MAX] = {0};
|
||||
struct GBARRContext* movie = 0;
|
||||
int numComponents = GBA_COMPONENT_MAX;
|
||||
|
||||
ThreadSetName("CPU Thread");
|
||||
|
||||
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
|
@ -133,6 +123,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
ARMInit(&cpu);
|
||||
gba.sync = &threadContext->sync;
|
||||
threadContext->gba = &gba;
|
||||
threadContext->cpu = &cpu;
|
||||
gba.logLevel = threadContext->logLevel;
|
||||
gba.logHandler = threadContext->logHandler;
|
||||
gba.stream = threadContext->stream;
|
||||
|
@ -166,15 +157,15 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
GBAOverrideApply(&gba, &threadContext->override);
|
||||
}
|
||||
|
||||
if (threadContext->bios && GBAIsBIOS(threadContext->bios)) {
|
||||
GBALoadBIOS(&gba, threadContext->bios);
|
||||
}
|
||||
|
||||
if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
|
||||
GBAApplyPatch(&gba, &patch);
|
||||
}
|
||||
}
|
||||
|
||||
if (threadContext->bios && GBAIsBIOS(threadContext->bios)) {
|
||||
GBALoadBIOS(&gba, threadContext->bios);
|
||||
}
|
||||
|
||||
if (threadContext->movie) {
|
||||
struct VDir* movieDir = VDirOpen(threadContext->movie);
|
||||
#ifdef USE_LIBZIP
|
||||
|
@ -233,6 +224,15 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
|
||||
GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
|
||||
|
||||
if (threadContext->volume == 0) {
|
||||
threadContext->volume = GBA_AUDIO_VOLUME_MAX;
|
||||
}
|
||||
if (threadContext->mute) {
|
||||
gba.audio.masterVolume = 0;
|
||||
} else {
|
||||
gba.audio.masterVolume = threadContext->volume;
|
||||
}
|
||||
|
||||
gba.keySource = &threadContext->activeKeys;
|
||||
|
||||
if (threadContext->startCallback) {
|
||||
|
@ -265,6 +265,13 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
threadContext->state = THREAD_INTERRUPTED;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
if (threadContext->state == THREAD_RUN_ON) {
|
||||
if (threadContext->run) {
|
||||
threadContext->run(threadContext);
|
||||
}
|
||||
threadContext->state = threadContext->savedState;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
if (threadContext->state == THREAD_RESETING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
resetScheduled = 1;
|
||||
|
@ -310,8 +317,14 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
}
|
||||
|
||||
void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
|
||||
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
|
||||
if (opts->useBios) {
|
||||
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
|
||||
} else {
|
||||
threadContext->bios = 0;
|
||||
}
|
||||
threadContext->frameskip = opts->frameskip;
|
||||
threadContext->volume = opts->volume;
|
||||
threadContext->mute = opts->mute;
|
||||
threadContext->logLevel = opts->logLevel;
|
||||
if (opts->rewindEnable) {
|
||||
threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
|
||||
|
@ -366,6 +379,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
threadContext->sync.videoFrameSkip = 0;
|
||||
|
||||
threadContext->rewindBuffer = 0;
|
||||
threadContext->rewindScreenBuffer = 0;
|
||||
int newCapacity = threadContext->rewindBufferCapacity;
|
||||
int newInterval = threadContext->rewindBufferInterval;
|
||||
threadContext->rewindBufferCapacity = 0;
|
||||
|
@ -376,7 +390,9 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
threadContext->fpsTarget = _defaultFPSTarget;
|
||||
}
|
||||
|
||||
if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
|
||||
bool bootBios = threadContext->bootBios && threadContext->bios;
|
||||
|
||||
if (threadContext->rom && (!GBAIsROM(threadContext->rom) || bootBios)) {
|
||||
threadContext->rom->close(threadContext->rom);
|
||||
threadContext->rom = 0;
|
||||
}
|
||||
|
@ -403,7 +419,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
|
||||
}
|
||||
|
||||
if (!threadContext->rom) {
|
||||
if (!threadContext->rom && !bootBios) {
|
||||
threadContext->state = THREAD_SHUTDOWN;
|
||||
return false;
|
||||
}
|
||||
|
@ -516,6 +532,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
|
|||
}
|
||||
}
|
||||
free(threadContext->rewindBuffer);
|
||||
free(threadContext->rewindScreenBuffer);
|
||||
|
||||
if (threadContext->rom) {
|
||||
threadContext->rom->close(threadContext->rom);
|
||||
|
@ -581,29 +598,44 @@ void GBAThreadContinue(struct GBAThread* threadContext) {
|
|||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void GBARunOnThread(struct GBAThread* threadContext, void (*run)(struct GBAThread*)) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
threadContext->run = run;
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->savedState = threadContext->state;
|
||||
threadContext->state = THREAD_RUN_ON;
|
||||
threadContext->gba->cpu->nextEvent = 0;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
_waitUntilNotState(threadContext, THREAD_RUN_ON);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void GBAThreadPause(struct GBAThread* threadContext) {
|
||||
bool frameOn = true;
|
||||
bool frameOn = threadContext->sync.videoFrameOn;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, false);
|
||||
threadContext->frameWasOn = frameOn;
|
||||
frameOn = false;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
_changeVideoSync(&threadContext->sync, frameOn);
|
||||
GBASyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
void GBAThreadUnpause(struct GBAThread* threadContext) {
|
||||
bool frameOn = threadContext->sync.videoFrameOn;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
frameOn = threadContext->frameWasOn;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
_changeVideoSync(&threadContext->sync, true);
|
||||
GBASyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
bool GBAThreadIsPaused(struct GBAThread* threadContext) {
|
||||
|
@ -616,19 +648,21 @@ bool GBAThreadIsPaused(struct GBAThread* threadContext) {
|
|||
}
|
||||
|
||||
void GBAThreadTogglePause(struct GBAThread* threadContext) {
|
||||
bool frameOn = true;
|
||||
bool frameOn = threadContext->sync.videoFrameOn;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
frameOn = threadContext->frameWasOn;
|
||||
} else if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, false);
|
||||
threadContext->frameWasOn = frameOn;
|
||||
frameOn = false;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
_changeVideoSync(&threadContext->sync, frameOn);
|
||||
GBASyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
|
||||
|
@ -641,7 +675,7 @@ void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
|
|||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
_changeVideoSync(&threadContext->sync, frameOn);
|
||||
GBASyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
|
@ -664,9 +698,12 @@ void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
|
|||
threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
bool success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteClose(png, info);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "Screenshot saved");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -675,111 +712,3 @@ struct GBAThread* GBAThreadGetContext(void) {
|
|||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
++sync->videoFramePending;
|
||||
--sync->videoFrameSkip;
|
||||
if (sync->videoFrameSkip < 0) {
|
||||
do {
|
||||
ConditionWake(&sync->videoFrameAvailableCond);
|
||||
if (sync->videoFrameWait) {
|
||||
ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
|
||||
}
|
||||
} while (sync->videoFrameWait && sync->videoFramePending);
|
||||
}
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
ConditionWake(&sync->videoFrameRequiredCond);
|
||||
if (!sync->videoFrameOn && !sync->videoFramePending) {
|
||||
return false;
|
||||
}
|
||||
if (sync->videoFrameOn) {
|
||||
if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sync->videoFramePending = 0;
|
||||
sync->videoFrameSkip = frameskip;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
bool GBASyncDrawingFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return sync->videoFrameSkip <= 0;
|
||||
}
|
||||
|
||||
void GBASyncSuspendDrawing(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
_changeVideoSync(sync, false);
|
||||
}
|
||||
|
||||
void GBASyncResumeDrawing(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
_changeVideoSync(sync, true);
|
||||
}
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync->audioWait && wait) {
|
||||
// TODO loop properly in event of spurious wakeups
|
||||
ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
|
||||
}
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncLockAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncUnlockAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncConsumeAudio(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConditionWake(&sync->audioRequiredCond);
|
||||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "gba/gba.h"
|
||||
#include "gba/input.h"
|
||||
#include "gba/supervisor/overrides.h"
|
||||
#include "gba/supervisor/sync.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
|
@ -28,26 +29,13 @@ enum ThreadState {
|
|||
THREAD_INTERRUPTING,
|
||||
THREAD_PAUSED,
|
||||
THREAD_PAUSING,
|
||||
THREAD_RUN_ON,
|
||||
THREAD_RESETING,
|
||||
THREAD_EXITING,
|
||||
THREAD_SHUTDOWN,
|
||||
THREAD_CRASHED
|
||||
};
|
||||
|
||||
struct GBASync {
|
||||
int videoFramePending;
|
||||
bool videoFrameWait;
|
||||
int videoFrameSkip;
|
||||
bool videoFrameOn;
|
||||
Mutex videoFrameMutex;
|
||||
Condition videoFrameAvailableCond;
|
||||
Condition videoFrameRequiredCond;
|
||||
|
||||
bool audioWait;
|
||||
Condition audioRequiredCond;
|
||||
Mutex audioBufferMutex;
|
||||
};
|
||||
|
||||
struct GBAThread {
|
||||
// Output
|
||||
enum ThreadState state;
|
||||
|
@ -71,6 +59,7 @@ struct GBAThread {
|
|||
struct GBAAVStream* stream;
|
||||
struct Configuration* overrides;
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
bool bootBios;
|
||||
|
||||
bool hasOverride;
|
||||
struct GBACartridgeOverride override;
|
||||
|
@ -80,6 +69,8 @@ struct GBAThread {
|
|||
float fpsTarget;
|
||||
size_t audioBuffers;
|
||||
bool skipBios;
|
||||
int volume;
|
||||
bool mute;
|
||||
|
||||
// Threading state
|
||||
Thread thread;
|
||||
|
@ -88,6 +79,7 @@ struct GBAThread {
|
|||
Condition stateCond;
|
||||
enum ThreadState savedState;
|
||||
int interruptDepth;
|
||||
bool frameWasOn;
|
||||
|
||||
GBALogHandler logHandler;
|
||||
int logLevel;
|
||||
|
@ -95,6 +87,7 @@ struct GBAThread {
|
|||
ThreadCallback cleanCallback;
|
||||
ThreadCallback frameCallback;
|
||||
void* userData;
|
||||
void (*run)(struct GBAThread*);
|
||||
|
||||
struct GBASync sync;
|
||||
|
||||
|
@ -104,6 +97,7 @@ struct GBAThread {
|
|||
int rewindBufferNext;
|
||||
struct GBASerializedState** rewindBuffer;
|
||||
int rewindBufferWriteOffset;
|
||||
uint8_t* rewindScreenBuffer;
|
||||
|
||||
struct GBACheatDevice* cheats;
|
||||
};
|
||||
|
@ -123,6 +117,8 @@ bool GBAThreadIsActive(struct GBAThread* threadContext);
|
|||
void GBAThreadInterrupt(struct GBAThread* threadContext);
|
||||
void GBAThreadContinue(struct GBAThread* threadContext);
|
||||
|
||||
void GBARunOnThread(struct GBAThread* threadContext, void (*run)(struct GBAThread*));
|
||||
|
||||
void GBAThreadPause(struct GBAThread* threadContext);
|
||||
void GBAThreadUnpause(struct GBAThread* threadContext);
|
||||
bool GBAThreadIsPaused(struct GBAThread* threadContext);
|
||||
|
@ -134,17 +130,4 @@ struct GBAThread* GBAThreadGetContext(void);
|
|||
void GBAThreadTakeScreenshot(struct GBAThread* threadContext);
|
||||
#endif
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync);
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync);
|
||||
bool GBASyncDrawingFrame(struct GBASync* sync);
|
||||
|
||||
void GBASyncSuspendDrawing(struct GBASync* sync);
|
||||
void GBASyncResumeDrawing(struct GBASync* sync);
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, bool wait);
|
||||
void GBASyncLockAudio(struct GBASync* sync);
|
||||
void GBASyncUnlockAudio(struct GBASync* sync);
|
||||
void GBASyncConsumeAudio(struct GBASync* sync);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/rr.h"
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/supervisor/sync.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
|
||||
|
|
|
@ -17,6 +17,14 @@
|
|||
#define BYTES_PER_PIXEL 4
|
||||
#endif
|
||||
|
||||
#define GBA_R5(X) ((X) & 0x1F)
|
||||
#define GBA_G5(X) (((X) >> 5) & 0x1F)
|
||||
#define GBA_B5(X) (((X) >> 10) & 0x1F)
|
||||
|
||||
#define GBA_R8(X) (((X) << 3) & 0xF8)
|
||||
#define GBA_G8(X) (((X) >> 2) & 0xF8)
|
||||
#define GBA_B8(X) (((X) >> 7) & 0xF8)
|
||||
|
||||
enum {
|
||||
VIDEO_CYCLES_PER_PIXEL = 4,
|
||||
|
||||
|
@ -167,6 +175,9 @@ struct GBAVideoRenderer {
|
|||
uint16_t* palette;
|
||||
uint16_t* vram;
|
||||
union GBAOAM* oam;
|
||||
|
||||
bool disableBG[4];
|
||||
bool disableOBJ;
|
||||
};
|
||||
|
||||
struct GBAVideo {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/memory.h"
|
||||
|
||||
#define asm __asm__
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
void* anonymousMemoryMap(size_t size) {
|
||||
|
|
|
@ -3,8 +3,15 @@
|
|||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef N3DS_VFS_H
|
||||
#define N3DS_VFS_H
|
||||
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define asm __asm__
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#endif
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
|
@ -43,6 +44,7 @@ static const struct option _options[] = {
|
|||
#ifdef USE_GDB_STUB
|
||||
{ "gdb", no_argument, 0, 'g' },
|
||||
#endif
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ "movie", required_argument, 0, 'v' },
|
||||
{ "patch", required_argument, 0, 'p' },
|
||||
{ 0, 0, 0, 0 }
|
||||
|
@ -53,7 +55,7 @@ bool _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:c:Dl:p:s:v:"
|
||||
"b:c:Dhl:p:s:v:"
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
"d"
|
||||
#endif
|
||||
|
@ -93,6 +95,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
opts->debuggerType = DEBUGGER_GDB;
|
||||
break;
|
||||
#endif
|
||||
case 'h':
|
||||
opts->showHelp = true;
|
||||
break;
|
||||
case 'l':
|
||||
GBAConfigSetDefaultValue(config, "logLevel", optarg);
|
||||
break;
|
||||
|
@ -117,7 +122,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc != 1) {
|
||||
return false;
|
||||
return opts->showHelp;
|
||||
}
|
||||
opts->fname = strdup(argv[0]);
|
||||
return true;
|
||||
|
|
|
@ -30,6 +30,7 @@ struct GBAArguments {
|
|||
|
||||
enum DebuggerType debuggerType;
|
||||
bool debugAtStart;
|
||||
bool showHelp;
|
||||
};
|
||||
|
||||
struct SubParser {
|
||||
|
|
|
@ -33,6 +33,7 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
|||
|
||||
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
||||
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->audioCodec = 0;
|
||||
encoder->videoCodec = 0;
|
||||
|
@ -201,7 +202,8 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile);
|
||||
#else
|
||||
encoder->context = avformat_alloc_context();
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename));
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename) - 1);
|
||||
encoder->context->filename[sizeof(encoder->context->filename) - 1] = '\0';
|
||||
encoder->context->oformat = oformat;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#ifndef FFMPEG_ENCODER
|
||||
#define FFMPEG_ENCODER
|
||||
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/gba.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "imagemagick-gif-encoder.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _magickPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
|
@ -15,6 +16,7 @@ void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
|||
|
||||
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
||||
encoder->d.postAudioFrame = _magickPostAudioFrame;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->frameskip = 2;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#ifndef IMAGEMAGICK_GIF_ENCODER
|
||||
#define IMAGEMAGICK_GIF_ENCODER
|
||||
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/gba.h"
|
||||
|
||||
#define MAGICKCORE_HDRI_ENABLE 0
|
||||
#define MAGICKCORE_QUANTUM_DEPTH 8
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "libretro.h"
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
|
@ -12,16 +14,18 @@
|
|||
#include "gba/video.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define SAMPLES 1024
|
||||
|
||||
static retro_environment_t environCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
static retro_audio_sample_t audioCallback;
|
||||
static retro_audio_sample_batch_t audioCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputCallback;
|
||||
static retro_log_printf_t logCallback;
|
||||
|
||||
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
||||
|
||||
static void _postAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
||||
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
|
||||
static struct GBA gba;
|
||||
|
@ -37,8 +41,8 @@ unsigned retro_api_version(void) {
|
|||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t environ) {
|
||||
environCallback = environ;
|
||||
void retro_set_environment(retro_environment_t env) {
|
||||
environCallback = env;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t video) {
|
||||
|
@ -46,11 +50,11 @@ void retro_set_video_refresh(retro_video_refresh_t video) {
|
|||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t audio) {
|
||||
audioCallback = audio;
|
||||
UNUSED(audio);
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
|
||||
UNUSED(audioBatch);
|
||||
audioCallback = audioBatch;
|
||||
}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t inputPoll) {
|
||||
|
@ -64,8 +68,8 @@ void retro_set_input_state(retro_input_state_t 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->library_version = projectVersion;
|
||||
info->library_name = projectName;
|
||||
info->block_extract = false;
|
||||
}
|
||||
|
||||
|
@ -117,7 +121,8 @@ void retro_init(void) {
|
|||
logCallback = 0;
|
||||
}
|
||||
|
||||
stream.postAudioFrame = _postAudioFrame;
|
||||
stream.postAudioFrame = 0;
|
||||
stream.postAudioBuffer = _postAudioBuffer;
|
||||
stream.postVideoFrame = _postVideoFrame;
|
||||
|
||||
GBACreate(&gba);
|
||||
|
@ -133,6 +138,13 @@ void retro_init(void) {
|
|||
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer.outputBufferStride = 256;
|
||||
GBAVideoAssociateRenderer(&gba.video, &renderer.d);
|
||||
|
||||
GBAAudioResizeBuffer(&gba.audio, SAMPLES);
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
#endif
|
||||
}
|
||||
|
||||
void retro_deinit(void) {
|
||||
|
@ -160,7 +172,6 @@ void retro_run(void) {
|
|||
while (gba.video.frameCounter == frameCount) {
|
||||
ARMRunLoop(&cpu);
|
||||
}
|
||||
videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * 256);
|
||||
}
|
||||
|
||||
void retro_reset(void) {
|
||||
|
@ -316,9 +327,22 @@ void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* f
|
|||
logCallback(retroLevel, "%s\n", message);
|
||||
}
|
||||
|
||||
static void _postAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||
static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) {
|
||||
UNUSED(stream);
|
||||
audioCallback(left, right);
|
||||
int16_t samples[SAMPLES * 2];
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_read_samples(audio->left, samples, SAMPLES, true);
|
||||
blip_read_samples(audio->right, samples + 1, SAMPLES, true);
|
||||
#else
|
||||
int16_t samplesR[SAMPLES];
|
||||
GBAAudioCopy(audio, &samples[SAMPLES], samplesR, SAMPLES);
|
||||
size_t i;
|
||||
for (i = 0; i < SAMPLES; ++i) {
|
||||
samples[i * 2] = samples[SAMPLES + i];
|
||||
samples[i * 2 + 1] = samplesR[i];
|
||||
}
|
||||
#endif
|
||||
audioCallback(samples, SAMPLES);
|
||||
}
|
||||
|
||||
static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gl.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
|
||||
static const GLint _glVertices[] = {
|
||||
0, 0,
|
||||
256, 0,
|
||||
256, 256,
|
||||
0, 256
|
||||
};
|
||||
|
||||
static const GLint _glTexCoords[] = {
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1
|
||||
};
|
||||
|
||||
static void GBAGLContextInit(struct VideoBackend* v, WHandle handle) {
|
||||
UNUSED(handle);
|
||||
struct GBAGLContext* context = (struct GBAGLContext*) v;
|
||||
glGenTextures(1, &context->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
#ifndef _WIN32
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
#endif
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void GBAGLContextDeinit(struct VideoBackend* v) {
|
||||
struct GBAGLContext* context = (struct GBAGLContext*) v;
|
||||
glDeleteTextures(1, &context->tex);
|
||||
}
|
||||
|
||||
static void GBAGLContextResized(struct VideoBackend* v, int w, int h) {
|
||||
int drawW = w;
|
||||
int drawH = h;
|
||||
if (v->lockAspectRatio) {
|
||||
if (w * 2 > h * 3) {
|
||||
drawW = h * 3 / 2;
|
||||
} else if (w * 2 < h * 3) {
|
||||
drawH = w * 2 / 3;
|
||||
}
|
||||
}
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
}
|
||||
|
||||
static void GBAGLContextClear(struct VideoBackend* v) {
|
||||
UNUSED(v);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void GBAGLContextDrawFrame(struct VideoBackend* v) {
|
||||
struct GBAGLContext* context = (struct GBAGLContext*) v;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2, GL_INT, 0, _glVertices);
|
||||
glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
|
||||
glMatrixMode (GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
if (v->filter) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
|
||||
void GBAGLContextPostFrame(struct VideoBackend* v, const void* frame) {
|
||||
struct GBAGLContext* context = (struct GBAGLContext*) v;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBAGLContextCreate(struct GBAGLContext* context) {
|
||||
context->d.init = GBAGLContextInit;
|
||||
context->d.deinit = GBAGLContextDeinit;
|
||||
context->d.resized = GBAGLContextResized;
|
||||
context->d.swap = 0;
|
||||
context->d.clear = GBAGLContextClear;
|
||||
context->d.postFrame = GBAGLContextPostFrame;
|
||||
context->d.drawFrame = GBAGLContextDrawFrame;
|
||||
context->d.setMessage = 0;
|
||||
context->d.clearMessage = 0;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GL_H
|
||||
#define GL_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
|
||||
#include "platform/video-backend.h"
|
||||
|
||||
struct GBAGLContext {
|
||||
struct VideoBackend d;
|
||||
|
||||
GLuint tex;
|
||||
};
|
||||
|
||||
void GBAGLContextCreate(struct GBAGLContext*);
|
||||
|
||||
#endif
|
|
@ -7,8 +7,11 @@
|
|||
#include "gba/supervisor/config.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include "platform/commandline.h"
|
||||
#include "util/string.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -16,27 +19,31 @@
|
|||
#include <inttypes.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define PERF_OPTIONS "F:NPS:"
|
||||
#define PERF_OPTIONS "F:L:NPS:"
|
||||
#define PERF_USAGE \
|
||||
"\nBenchmark options:\n" \
|
||||
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
|
||||
" -N Disable video rendering entirely\n" \
|
||||
" -P CSV output, useful for parsing\n" \
|
||||
" -S SEC Run for SEC in-game seconds before exiting"
|
||||
" -S SEC Run for SEC in-game seconds before exiting\n" \
|
||||
" -L FILE Load a savestate when starting the test"
|
||||
|
||||
struct PerfOpts {
|
||||
bool noVideo;
|
||||
bool csv;
|
||||
unsigned duration;
|
||||
unsigned frames;
|
||||
char* savestate;
|
||||
};
|
||||
|
||||
static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet);
|
||||
static void _GBAPerfShutdown(int signal);
|
||||
static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
|
||||
static void _loadSavestate(struct GBAThread* context);
|
||||
|
||||
static struct GBAThread* _thread;
|
||||
static bool _dispatchExiting = false;
|
||||
static struct VFile* _savestate = 0;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
signal(SIGINT, _GBAPerfShutdown);
|
||||
|
@ -44,7 +51,7 @@ int main(int argc, char** argv) {
|
|||
struct GBAVideoSoftwareRenderer renderer;
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
|
||||
struct PerfOpts perfOpts = { false, false, 0, 0 };
|
||||
struct PerfOpts perfOpts = { false, false, 0, 0, 0 };
|
||||
struct SubParser subparser = {
|
||||
.usage = PERF_USAGE,
|
||||
.parse = _parsePerfOpts,
|
||||
|
@ -62,12 +69,13 @@ int main(int argc, char** argv) {
|
|||
GBAConfigLoadDefaults(&config, &opts);
|
||||
|
||||
struct GBAArguments args;
|
||||
if (!parseArguments(&args, &config, argc, argv, &subparser)) {
|
||||
bool parsed = parseArguments(&args, &config, argc, argv, &subparser);
|
||||
if (!parsed || args.showHelp) {
|
||||
usage(argv[0], PERF_USAGE);
|
||||
freeArguments(&args);
|
||||
GBAConfigFreeOpts(&opts);
|
||||
GBAConfigDeinit(&config);
|
||||
return 1;
|
||||
return !parsed;
|
||||
}
|
||||
|
||||
renderer.outputBuffer = malloc(256 * 256 * 4);
|
||||
|
@ -79,6 +87,13 @@ int main(int argc, char** argv) {
|
|||
if (!perfOpts.noVideo) {
|
||||
context.renderer = &renderer.d;
|
||||
}
|
||||
if (perfOpts.savestate) {
|
||||
_savestate = VFileOpen(perfOpts.savestate, O_RDONLY);
|
||||
free(perfOpts.savestate);
|
||||
}
|
||||
if (_savestate) {
|
||||
context.startCallback = _loadSavestate;
|
||||
}
|
||||
|
||||
context.debugger = createDebugger(&args, &context);
|
||||
context.overrides = GBAConfigGetOverrides(&config);
|
||||
|
@ -95,7 +110,14 @@ int main(int argc, char** argv) {
|
|||
if (!didStart) {
|
||||
goto cleanup;
|
||||
}
|
||||
GBAThreadInterrupt(&context);
|
||||
if (GBAThreadHasCrashed(&context)) {
|
||||
GBAThreadJoin(&context);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
GBAGetGameCode(context.gba, gameCode);
|
||||
GBAThreadContinue(&context);
|
||||
|
||||
int frames = perfOpts.frames;
|
||||
if (!frames) {
|
||||
|
@ -126,6 +148,9 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
cleanup:
|
||||
if (_savestate) {
|
||||
_savestate->close(_savestate);
|
||||
}
|
||||
GBAConfigFreeOpts(&opts);
|
||||
freeArguments(&args);
|
||||
GBAConfigDeinit(&config);
|
||||
|
@ -197,7 +222,16 @@ static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, i
|
|||
case 'S':
|
||||
opts->duration = strtoul(arg, 0, 10);
|
||||
return !errno;
|
||||
case 'L':
|
||||
opts->savestate = strdup(arg);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void _loadSavestate(struct GBAThread* context) {
|
||||
GBALoadStateNamed(context->gba, _savestate);
|
||||
_savestate->close(_savestate);
|
||||
_savestate = 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef POSIX_THREADING_H
|
||||
#define POSIX_THREADING_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#define THREAD_ENTRY void*
|
||||
typedef THREAD_ENTRY (*ThreadEntry)(void*);
|
||||
|
||||
typedef pthread_t Thread;
|
||||
typedef pthread_mutex_t Mutex;
|
||||
typedef pthread_cond_t Condition;
|
||||
|
||||
static inline int MutexInit(Mutex* mutex) {
|
||||
return pthread_mutex_init(mutex, 0);
|
||||
}
|
||||
|
||||
static inline int MutexDeinit(Mutex* mutex) {
|
||||
return pthread_mutex_destroy(mutex);
|
||||
}
|
||||
|
||||
static inline int MutexLock(Mutex* mutex) {
|
||||
return pthread_mutex_lock(mutex);
|
||||
}
|
||||
|
||||
static inline int MutexUnlock(Mutex* mutex) {
|
||||
return pthread_mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static inline int ConditionInit(Condition* cond) {
|
||||
return pthread_cond_init(cond, 0);
|
||||
}
|
||||
|
||||
static inline int ConditionDeinit(Condition* cond) {
|
||||
return pthread_cond_destroy(cond);
|
||||
}
|
||||
|
||||
static inline int ConditionWait(Condition* cond, Mutex* mutex) {
|
||||
return pthread_cond_wait(cond, mutex);
|
||||
}
|
||||
|
||||
static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) {
|
||||
struct timespec ts;
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, 0);
|
||||
ts.tv_sec = tv.tv_sec;
|
||||
ts.tv_nsec = (tv.tv_usec + timeoutMs * 1000L) * 1000L;
|
||||
if (ts.tv_nsec >= 1000000000L) {
|
||||
ts.tv_nsec -= 1000000000L;
|
||||
++ts.tv_sec;
|
||||
}
|
||||
|
||||
return pthread_cond_timedwait(cond, mutex, &ts);
|
||||
}
|
||||
|
||||
static inline int ConditionWake(Condition* cond) {
|
||||
return pthread_cond_broadcast(cond);
|
||||
}
|
||||
|
||||
static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) {
|
||||
return pthread_create(thread, 0, entry, context);
|
||||
}
|
||||
|
||||
static inline int ThreadJoin(Thread thread) {
|
||||
return pthread_join(thread, 0);
|
||||
}
|
||||
|
||||
static inline int ThreadSetName(const char* name) {
|
||||
#ifdef __APPLE__
|
||||
return pthread_setname_np(name);
|
||||
#elif defined(__FreeBSD__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
return 0;
|
||||
#else
|
||||
return pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
|
@ -17,12 +17,13 @@ AudioDevice::AudioDevice(QObject* parent)
|
|||
: QIODevice(parent)
|
||||
, m_context(nullptr)
|
||||
, m_drift(0)
|
||||
, m_ratio(1.f)
|
||||
{
|
||||
setOpenMode(ReadOnly);
|
||||
}
|
||||
|
||||
void AudioDevice::setFormat(const QAudioFormat& format) {
|
||||
if (!GBAThreadIsActive(m_context)) {
|
||||
if (!m_context || !GBAThreadIsActive(m_context)) {
|
||||
return;
|
||||
}
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
|
|
|
@ -19,7 +19,7 @@ extern "C" {
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
#ifndef BUILD_SDL
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::QT_MULTIMEDIA;
|
||||
#else
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::SDL;
|
||||
|
@ -48,6 +48,8 @@ AudioProcessor* AudioProcessor::create() {
|
|||
|
||||
AudioProcessor::AudioProcessor(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_context(nullptr)
|
||||
, m_samples(GBA_AUDIO_SAMPLES)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ void AudioProcessorQt::setInput(GBAThread* input) {
|
|||
}
|
||||
|
||||
void AudioProcessorQt::start() {
|
||||
if (!input()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_device) {
|
||||
m_device = new AudioDevice(this);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ AudioProcessorSDL::~AudioProcessorSDL() {
|
|||
}
|
||||
|
||||
void AudioProcessorSDL::start() {
|
||||
if (!input()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_audio.thread) {
|
||||
GBASDLResumeAudio(&m_audio);
|
||||
} else {
|
||||
|
|
|
@ -4,10 +4,14 @@ 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++")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(PLATFORM_SRC)
|
||||
set(QT_STATIC OFF)
|
||||
|
||||
if(BUILD_SDL)
|
||||
if(NOT SDL_FOUND AND NOT SDL2_FOUND)
|
||||
|
@ -27,7 +31,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|||
find_package(Qt5Multimedia)
|
||||
find_package(Qt5OpenGL)
|
||||
find_package(Qt5Widgets)
|
||||
find_package(OpenGL)
|
||||
|
||||
if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
|
||||
message(WARNING "Cannot find Qt modules")
|
||||
|
@ -35,12 +38,22 @@ if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
|
|||
return()
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
|
||||
|
||||
get_target_property(QT_TYPE Qt5::Core TYPE)
|
||||
if(QT_TYPE STREQUAL STATIC_LIBRARY)
|
||||
set(QT_STATIC ON)
|
||||
add_definitions(-DQT_STATIC)
|
||||
endif()
|
||||
|
||||
set(SOURCE_FILES
|
||||
AudioProcessor.cpp
|
||||
CheatsModel.cpp
|
||||
CheatsView.cpp
|
||||
ConfigController.cpp
|
||||
Display.cpp
|
||||
DisplayGL.cpp
|
||||
DisplayQt.cpp
|
||||
GBAApp.cpp
|
||||
GBAKeyEditor.cpp
|
||||
GIFView.cpp
|
||||
|
@ -51,12 +64,18 @@ set(SOURCE_FILES
|
|||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogView.cpp
|
||||
MemoryModel.cpp
|
||||
MemoryView.cpp
|
||||
MessagePainter.cpp
|
||||
MultiplayerController.cpp
|
||||
OverrideView.cpp
|
||||
PaletteView.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
SettingsView.cpp
|
||||
ShortcutController.cpp
|
||||
ShortcutView.cpp
|
||||
Swatch.cpp
|
||||
Window.cpp
|
||||
VFileDevice.cpp
|
||||
VideoView.cpp)
|
||||
|
@ -66,14 +85,16 @@ qt5_wrap_ui(UI_FILES
|
|||
GIFView.ui
|
||||
LoadSaveState.ui
|
||||
LogView.ui
|
||||
MemoryView.ui
|
||||
OverrideView.ui
|
||||
PaletteView.ui
|
||||
SensorView.ui
|
||||
SettingsView.ui
|
||||
ShortcutView.ui
|
||||
VideoView.ui)
|
||||
|
||||
set(QT_LIBRARIES)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5")
|
||||
|
||||
set(AUDIO_SRC)
|
||||
if(BUILD_SDL)
|
||||
|
@ -84,9 +105,12 @@ if(Qt5Multimedia_FOUND)
|
|||
list(APPEND AUDIO_SRC
|
||||
AudioProcessorQt.cpp
|
||||
AudioDevice.cpp)
|
||||
if (WIN32 AND QT_STATIC)
|
||||
list(APPEND QT_LIBRARIES qtaudio_windows strmiids winmm)
|
||||
endif()
|
||||
list(APPEND QT_LIBRARIES Qt5::Multimedia)
|
||||
add_definitions(-DBUILD_QT_MULTIMEDIA)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5")
|
||||
endif()
|
||||
|
||||
if(NOT AUDIO_SRC)
|
||||
|
@ -106,18 +130,28 @@ set_source_files_properties(${CMAKE_SOURCE_DIR}/res/mgba.icns PROPERTIES MACOSX_
|
|||
|
||||
qt5_add_resources(RESOURCES resources.qrc)
|
||||
if(WIN32)
|
||||
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/res/mgba.rc.in ${CMAKE_BINARY_DIR}/res/mgba.rc)
|
||||
list(APPEND RESOURCES ${CMAKE_BINARY_DIR}/res/mgba.rc)
|
||||
if(QT_STATIC)
|
||||
list(APPEND QT_LIBRARIES qwindows imm32)
|
||||
endif()
|
||||
endif()
|
||||
add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES})
|
||||
target_compile_definitions(${BINARY_NAME}-qt PRIVATE ${FEATURE_DEFINES})
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in)
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
|
||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}-qt
|
||||
RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt
|
||||
BUNDLE DESTINATION Applications COMPONENT ${BINARY_NAME}-qt)
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_program(DESKTOP_FILE_INSTALL desktop-file-install)
|
||||
if(DESKTOP_FILE_INSTALL)
|
||||
install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/applications/\")")
|
||||
endif()
|
||||
endif()
|
||||
if(APPLE OR WIN32)
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
endif()
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "CheatsModel.h"
|
||||
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QSet>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/cheats.h"
|
||||
#include "util/vfs.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -201,7 +202,7 @@ void CheatsModel::endAppendRow() {
|
|||
}
|
||||
|
||||
void CheatsModel::loadFile(const QString& path) {
|
||||
VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY);
|
||||
VFile* vf = VFileDevice::open(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
|
@ -212,7 +213,7 @@ void CheatsModel::loadFile(const QString& path) {
|
|||
}
|
||||
|
||||
void CheatsModel::saveFile(const QString& path) {
|
||||
VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_TRUNC | O_CREAT | O_WRONLY);
|
||||
VFile* vf = VFileDevice::open(path, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "CheatsView.h"
|
||||
|
||||
#include "GBAApp.h"
|
||||
#include "GameController.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFileDialog>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/cheats.h"
|
||||
|
@ -40,6 +40,10 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent)
|
|||
enterCheat(GBACheatAddGameSharkLine);
|
||||
});
|
||||
|
||||
connect(m_ui.addPAR, &QPushButton::clicked, [this]() {
|
||||
enterCheat(GBACheatAddProActionReplayLine);
|
||||
});
|
||||
|
||||
connect(m_ui.addCB, &QPushButton::clicked, [this]() {
|
||||
enterCheat(GBACheatAddCodeBreakerLine);
|
||||
});
|
||||
|
@ -60,14 +64,14 @@ bool CheatsView::eventFilter(QObject* object, QEvent* event) {
|
|||
}
|
||||
|
||||
void CheatsView::load() {
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select cheats file"));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_model.loadFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsView::save() {
|
||||
QString filename = QFileDialog::getSaveFileName(this, tr("Select cheats file"));
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_model.saveFile(filename);
|
||||
}
|
||||
|
@ -113,4 +117,4 @@ void CheatsView::enterCheat(std::function<bool(GBACheatSet*, const char*)> callb
|
|||
}
|
||||
m_controller->threadContinue();
|
||||
m_ui.codeEntry->clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,6 @@
|
|||
</item>
|
||||
<item row="8" column="1" colspan="2">
|
||||
<widget class="QPushButton" name="addPAR">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Pro Action Replay</string>
|
||||
</property>
|
||||
|
|
|
@ -23,8 +23,11 @@ ConfigOption::ConfigOption(QObject* parent)
|
|||
{
|
||||
}
|
||||
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot) {
|
||||
m_slot = slot;
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) {
|
||||
m_slots[parent] = slot;
|
||||
QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() {
|
||||
m_slots.remove(parent);
|
||||
});
|
||||
}
|
||||
|
||||
QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) {
|
||||
|
@ -33,6 +36,9 @@ QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMen
|
|||
QObject::connect(action, &QAction::triggered, [this, value]() {
|
||||
emit valueChanged(value);
|
||||
});
|
||||
QObject::connect(parent, &QAction::destroyed, [this, action, value]() {
|
||||
m_actions.removeAll(qMakePair(action, value));
|
||||
});
|
||||
parent->addAction(action);
|
||||
m_actions.append(qMakePair(action, value));
|
||||
return action;
|
||||
|
@ -48,6 +54,9 @@ QAction* ConfigOption::addBoolean(const QString& text, QMenu* parent) {
|
|||
QObject::connect(action, &QAction::triggered, [this, action]() {
|
||||
emit valueChanged(action->isChecked());
|
||||
});
|
||||
QObject::connect(parent, &QAction::destroyed, [this, action]() {
|
||||
m_actions.removeAll(qMakePair(action, 1));
|
||||
});
|
||||
parent->addAction(action);
|
||||
m_actions.append(qMakePair(action, 1));
|
||||
return action;
|
||||
|
@ -76,7 +85,10 @@ void ConfigOption::setValue(const QVariant& value) {
|
|||
action.first->setChecked(value == action.second);
|
||||
action.first->blockSignals(signalsEnabled);
|
||||
}
|
||||
m_slot(value);
|
||||
std::function<void(const QVariant&)> slot;
|
||||
foreach(slot, m_slots.values()) {
|
||||
slot(value);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigController::ConfigController(QObject* parent)
|
||||
|
@ -96,18 +108,19 @@ ConfigController::ConfigController(QObject* parent)
|
|||
m_opts.videoSync = GameController::VIDEO_SYNC;
|
||||
m_opts.fpsTarget = 60;
|
||||
m_opts.audioBuffers = 2048;
|
||||
m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||
m_opts.volume = GBA_AUDIO_VOLUME_MAX;
|
||||
m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS;
|
||||
m_opts.rewindEnable = false;
|
||||
m_opts.rewindBufferInterval = 0;
|
||||
m_opts.rewindBufferCapacity = 0;
|
||||
m_opts.useBios = true;
|
||||
m_opts.suspendScreensaver = true;
|
||||
GBAConfigLoadDefaults(&m_config, &m_opts);
|
||||
GBAConfigLoad(&m_config);
|
||||
GBAConfigMap(&m_config, &m_opts);
|
||||
}
|
||||
|
||||
ConfigController::~ConfigController() {
|
||||
write();
|
||||
|
||||
GBAConfigDeinit(&m_config);
|
||||
GBAConfigFreeOpts(&m_opts);
|
||||
}
|
||||
|
|
|
@ -32,11 +32,11 @@ Q_OBJECT
|
|||
public:
|
||||
ConfigOption(QObject* parent = nullptr);
|
||||
|
||||
void connect(std::function<void(const QVariant&)>);
|
||||
void connect(std::function<void(const QVariant&)>, QObject* parent = nullptr);
|
||||
|
||||
QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = 0);
|
||||
QAction* addValue(const QString& text, const char* value, QMenu* parent = 0);
|
||||
QAction* addBoolean(const QString& text, QMenu* parent = 0);
|
||||
QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr);
|
||||
QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr);
|
||||
QAction* addBoolean(const QString& text, QMenu* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setValue(bool value);
|
||||
|
@ -49,7 +49,7 @@ signals:
|
|||
void valueChanged(const QVariant& value);
|
||||
|
||||
private:
|
||||
std::function<void(const QVariant&)> m_slot;
|
||||
QMap<QObject*, std::function<void(const QVariant&)>> m_slots;
|
||||
QList<QPair<QAction*, QVariant>> m_actions;
|
||||
};
|
||||
|
||||
|
@ -79,6 +79,8 @@ public:
|
|||
Configuration* overrides() { return GBAConfigGetOverrides(&m_config); }
|
||||
void saveOverride(const GBACartridgeOverride&);
|
||||
|
||||
Configuration* input() { return GBAConfigGetInput(&m_config); }
|
||||
|
||||
public slots:
|
||||
void setOption(const char* key, bool value);
|
||||
void setOption(const char* key, int value);
|
||||
|
@ -90,11 +92,8 @@ public slots:
|
|||
void write();
|
||||
|
||||
private:
|
||||
Configuration* configuration() { return &m_config.configTable; }
|
||||
Configuration* defaults() { return &m_config.defaultsTable; }
|
||||
|
||||
friend class InputController; // TODO: Do this without friends
|
||||
|
||||
GBAConfig m_config;
|
||||
GBAOptions m_opts;
|
||||
|
||||
|
|
|
@ -5,283 +5,48 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "Display.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QResizeEvent>
|
||||
#include "DisplayGL.h"
|
||||
#include "DisplayQt.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "gba/video.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
static const GLint _glVertices[] = {
|
||||
0, 0,
|
||||
256, 0,
|
||||
256, 256,
|
||||
0, 256
|
||||
};
|
||||
#ifdef BUILD_GL
|
||||
Display::Driver Display::s_driver = Display::Driver::OPENGL;
|
||||
#else
|
||||
Display::Driver Display::s_driver = Display::Driver::QT;
|
||||
#endif
|
||||
|
||||
static const GLint _glTexCoords[] = {
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1
|
||||
};
|
||||
Display* Display::create(QWidget* parent) {
|
||||
#ifdef BUILD_GL
|
||||
QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
|
||||
format.setSwapInterval(1);
|
||||
#endif
|
||||
|
||||
Display::Display(QGLFormat format, QWidget* parent)
|
||||
: QGLWidget(format, parent)
|
||||
, m_painter(nullptr)
|
||||
, m_started(false)
|
||||
switch (s_driver) {
|
||||
#ifdef BUILD_GL
|
||||
case Driver::OPENGL:
|
||||
return new DisplayGL(format, parent);
|
||||
#endif
|
||||
|
||||
case Driver::QT:
|
||||
return new DisplayQt(parent);
|
||||
|
||||
default:
|
||||
#ifdef BUILD_GL
|
||||
return new DisplayGL(format, parent);
|
||||
#else
|
||||
return new DisplayQt(parent);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Display::Display(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
setAutoBufferSwap(false);
|
||||
setCursor(Qt::BlankCursor);
|
||||
}
|
||||
|
||||
void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) {
|
||||
if (m_started) {
|
||||
return;
|
||||
}
|
||||
m_painter = new Painter(this);
|
||||
m_painter->setContext(thread);
|
||||
m_painter->setBacking(buffer);
|
||||
m_context = thread;
|
||||
doneCurrent();
|
||||
m_painter->start();
|
||||
m_started = true;
|
||||
|
||||
lockAspectRatio(m_lockAspectRatio);
|
||||
filter(m_filter);
|
||||
}
|
||||
|
||||
void Display::stopDrawing() {
|
||||
if (m_started) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
GBASyncSuspendDrawing(&m_context->sync);
|
||||
}
|
||||
m_painter->stop();
|
||||
m_started = false;
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBASyncResumeDrawing(&m_context->sync);
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::pauseDrawing() {
|
||||
if (m_started) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
GBASyncSuspendDrawing(&m_context->sync);
|
||||
}
|
||||
m_painter->pause();
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBASyncResumeDrawing(&m_context->sync);
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::unpauseDrawing() {
|
||||
if (m_started) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
GBASyncSuspendDrawing(&m_context->sync);
|
||||
}
|
||||
m_painter->unpause();
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBASyncResumeDrawing(&m_context->sync);
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::forceDraw() {
|
||||
if (m_started) {
|
||||
m_painter->forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Display::lockAspectRatio(bool lock) {
|
||||
m_lockAspectRatio = lock;
|
||||
if (m_started) {
|
||||
m_painter->lockAspectRatio(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void Display::filter(bool filter) {
|
||||
m_filter = filter;
|
||||
if (m_started) {
|
||||
m_painter->filter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_PNG
|
||||
void Display::screenshot() {
|
||||
GBAThreadInterrupt(m_context);
|
||||
GBAThreadTakeScreenshot(m_context);
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Display::initializeGL() {
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
void Display::resizeEvent(QResizeEvent* event) {
|
||||
if (m_started) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
GBASyncSuspendDrawing(&m_context->sync);
|
||||
m_painter->resize(event->size());
|
||||
GBASyncResumeDrawing(&m_context->sync);
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
|
||||
Painter::Painter(Display* parent)
|
||||
: m_gl(parent)
|
||||
, m_lockAspectRatio(false)
|
||||
, m_filter(false)
|
||||
{
|
||||
m_size = parent->size();
|
||||
}
|
||||
|
||||
void Painter::setContext(GBAThread* context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
void Painter::setBacking(const uint32_t* backing) {
|
||||
m_backing = backing;
|
||||
}
|
||||
|
||||
void Painter::resize(const QSize& size) {
|
||||
m_size = size;
|
||||
forceDraw();
|
||||
forceDraw();
|
||||
}
|
||||
|
||||
void Painter::lockAspectRatio(bool lock) {
|
||||
m_lockAspectRatio = lock;
|
||||
forceDraw();
|
||||
forceDraw();
|
||||
}
|
||||
|
||||
void Painter::filter(bool filter) {
|
||||
m_filter = filter;
|
||||
m_gl->makeCurrent();
|
||||
if (m_filter) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
m_gl->doneCurrent();
|
||||
forceDraw();
|
||||
}
|
||||
|
||||
void Painter::start() {
|
||||
m_gl->makeCurrent();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glGenTextures(1, &m_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, m_tex);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
if (m_filter) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2, GL_INT, 0, _glVertices);
|
||||
glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, 240, 160, 0, 0, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
m_gl->doneCurrent();
|
||||
|
||||
m_drawTimer = new QTimer;
|
||||
m_drawTimer->moveToThread(QThread::currentThread());
|
||||
m_drawTimer->setInterval(0);
|
||||
connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw()));
|
||||
m_drawTimer->start();
|
||||
}
|
||||
|
||||
void Painter::draw() {
|
||||
m_gl->makeCurrent();
|
||||
GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip);
|
||||
performDraw();
|
||||
GBASyncWaitFrameEnd(&m_context->sync);
|
||||
m_gl->swapBuffers();
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
|
||||
void Painter::forceDraw() {
|
||||
m_gl->makeCurrent();
|
||||
glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
performDraw();
|
||||
m_gl->swapBuffers();
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
|
||||
void Painter::stop() {
|
||||
m_drawTimer->stop();
|
||||
delete m_drawTimer;
|
||||
m_gl->makeCurrent();
|
||||
glDeleteTextures(1, &m_tex);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
m_gl->swapBuffers();
|
||||
m_gl->doneCurrent();
|
||||
m_gl->context()->moveToThread(QApplication::instance()->thread());
|
||||
}
|
||||
|
||||
void Painter::pause() {
|
||||
m_drawTimer->stop();
|
||||
// Make sure both buffers are filled
|
||||
forceDraw();
|
||||
forceDraw();
|
||||
}
|
||||
|
||||
void Painter::unpause() {
|
||||
m_drawTimer->start();
|
||||
}
|
||||
|
||||
void Painter::performDraw() {
|
||||
int w = m_size.width() * m_gl->devicePixelRatio();
|
||||
int h = m_size.height() * m_gl->devicePixelRatio();
|
||||
#ifndef Q_OS_MAC
|
||||
// TODO: This seems to cause framerates to drag down to 120 FPS on OS X,
|
||||
// even if the emulator can go faster. Look into why.
|
||||
glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
#endif
|
||||
int drawW = w;
|
||||
int drawH = h;
|
||||
if (m_lockAspectRatio) {
|
||||
if (w * 2 > h * 3) {
|
||||
drawW = h * 3 / 2;
|
||||
} else if (w * 2 < h * 3) {
|
||||
drawH = w * 2 / 3;
|
||||
}
|
||||
}
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
|
||||
#endif
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
if (m_context->sync.videoFrameWait) {
|
||||
glFlush();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -6,77 +6,42 @@
|
|||
#ifndef QGBA_DISPLAY
|
||||
#define QGBA_DISPLAY
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
struct GBAThread;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class Painter;
|
||||
class Display : public QGLWidget {
|
||||
class Display : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Display(QGLFormat format, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void startDrawing(const uint32_t* buffer, GBAThread* context);
|
||||
void stopDrawing();
|
||||
void pauseDrawing();
|
||||
void unpauseDrawing();
|
||||
void forceDraw();
|
||||
void lockAspectRatio(bool lock);
|
||||
void filter(bool filter);
|
||||
#ifdef USE_PNG
|
||||
void screenshot();
|
||||
enum class Driver {
|
||||
QT = 0,
|
||||
#ifdef BUILD_GL
|
||||
OPENGL = 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual void initializeGL() override;
|
||||
virtual void paintEvent(QPaintEvent*) override {};
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
Display(QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
Painter* m_painter;
|
||||
bool m_started;
|
||||
GBAThread* m_context;
|
||||
bool m_lockAspectRatio;
|
||||
bool m_filter;
|
||||
};
|
||||
|
||||
class Painter : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Painter(Display* parent);
|
||||
|
||||
void setContext(GBAThread*);
|
||||
void setBacking(const uint32_t*);
|
||||
static Display* create(QWidget* parent = nullptr);
|
||||
static void setDriver(Driver driver) { s_driver = driver; }
|
||||
|
||||
public slots:
|
||||
void forceDraw();
|
||||
void draw();
|
||||
void start();
|
||||
void stop();
|
||||
void pause();
|
||||
void unpause();
|
||||
void resize(const QSize& size);
|
||||
void lockAspectRatio(bool lock);
|
||||
void filter(bool filter);
|
||||
virtual void startDrawing(GBAThread* context) = 0;
|
||||
virtual void stopDrawing() = 0;
|
||||
virtual void pauseDrawing() = 0;
|
||||
virtual void unpauseDrawing() = 0;
|
||||
virtual void forceDraw() = 0;
|
||||
virtual void lockAspectRatio(bool lock) = 0;
|
||||
virtual void filter(bool filter) = 0;
|
||||
virtual void framePosted(const uint32_t*) = 0;
|
||||
|
||||
virtual void showMessage(const QString& message) = 0;
|
||||
|
||||
private:
|
||||
void performDraw();
|
||||
|
||||
QTimer* m_drawTimer;
|
||||
GBAThread* m_context;
|
||||
const uint32_t* m_backing;
|
||||
GLuint m_tex;
|
||||
QGLWidget* m_gl;
|
||||
QSize m_size;
|
||||
bool m_lockAspectRatio;
|
||||
bool m_filter;
|
||||
static Driver s_driver;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "DisplayGL.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QResizeEvent>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/supervisor/thread.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
|
||||
: Display(parent)
|
||||
, m_gl(new EmptyGLWidget(format, this))
|
||||
, m_painter(new PainterGL(m_gl))
|
||||
, m_drawThread(nullptr)
|
||||
, m_lockAspectRatio(false)
|
||||
, m_filter(false)
|
||||
, m_context(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
DisplayGL::~DisplayGL() {
|
||||
delete m_painter;
|
||||
}
|
||||
|
||||
void DisplayGL::startDrawing(GBAThread* thread) {
|
||||
if (m_drawThread) {
|
||||
return;
|
||||
}
|
||||
m_painter->setContext(thread);
|
||||
m_context = thread;
|
||||
m_painter->resize(size());
|
||||
m_gl->move(0, 0);
|
||||
m_drawThread = new QThread(this);
|
||||
m_drawThread->setObjectName("Painter Thread");
|
||||
m_gl->context()->doneCurrent();
|
||||
m_gl->context()->moveToThread(m_drawThread);
|
||||
m_painter->moveToThread(m_drawThread);
|
||||
connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
|
||||
m_drawThread->start();
|
||||
GBASyncSetVideoSync(&m_context->sync, false);
|
||||
|
||||
lockAspectRatio(m_lockAspectRatio);
|
||||
filter(m_filter);
|
||||
resizePainter();
|
||||
}
|
||||
|
||||
void DisplayGL::stopDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
|
||||
m_drawThread->exit();
|
||||
m_drawThread = nullptr;
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::pauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::unpauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadContinue(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::forceDraw() {
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter, "forceDraw");
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::lockAspectRatio(bool lock) {
|
||||
m_lockAspectRatio = lock;
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::filter(bool filter) {
|
||||
m_filter = filter;
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::framePosted(const uint32_t* buffer) {
|
||||
if (m_drawThread && buffer) {
|
||||
QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::showMessage(const QString& message) {
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter, "showMessage", Q_ARG(const QString&, message));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::resizeEvent(QResizeEvent*) {
|
||||
resizePainter();
|
||||
}
|
||||
|
||||
void DisplayGL::resizePainter() {
|
||||
m_gl->resize(size());
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
|
||||
}
|
||||
}
|
||||
|
||||
PainterGL::PainterGL(QGLWidget* parent)
|
||||
: m_gl(parent)
|
||||
, m_active(false)
|
||||
, m_context(nullptr)
|
||||
, m_messagePainter(nullptr)
|
||||
{
|
||||
GBAGLContextCreate(&m_backend);
|
||||
m_backend.d.swap = [](VideoBackend* v) {
|
||||
PainterGL* painter = static_cast<PainterGL*>(v->user);
|
||||
painter->m_gl->swapBuffers();
|
||||
};
|
||||
m_backend.d.user = this;
|
||||
m_backend.d.filter = false;
|
||||
m_backend.d.lockAspectRatio = false;
|
||||
}
|
||||
|
||||
void PainterGL::setContext(GBAThread* context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
void PainterGL::setBacking(const uint32_t* backing) {
|
||||
m_gl->makeCurrent();
|
||||
m_backend.d.postFrame(&m_backend.d, backing);
|
||||
if (m_active) {
|
||||
draw();
|
||||
}
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
|
||||
void PainterGL::resize(const QSize& size) {
|
||||
m_size = size;
|
||||
if (m_active) {
|
||||
m_messagePainter->resize(size, m_backend.d.lockAspectRatio);
|
||||
forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::lockAspectRatio(bool lock) {
|
||||
m_backend.d.lockAspectRatio = lock;
|
||||
if (m_active) {
|
||||
m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio);
|
||||
forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::filter(bool filter) {
|
||||
m_backend.d.filter = filter;
|
||||
if (m_active) {
|
||||
forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::start() {
|
||||
m_messagePainter = new MessagePainter(this);
|
||||
m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio);
|
||||
m_gl->makeCurrent();
|
||||
m_backend.d.init(&m_backend.d, reinterpret_cast<WHandle>(m_gl->winId()));
|
||||
m_gl->doneCurrent();
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
void PainterGL::draw() {
|
||||
if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) {
|
||||
m_painter.begin(m_gl->context()->device());
|
||||
performDraw();
|
||||
m_painter.end();
|
||||
GBASyncWaitFrameEnd(&m_context->sync);
|
||||
m_backend.d.swap(&m_backend.d);
|
||||
} else {
|
||||
GBASyncWaitFrameEnd(&m_context->sync);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::forceDraw() {
|
||||
m_painter.begin(m_gl->context()->device());
|
||||
performDraw();
|
||||
m_painter.end();
|
||||
m_backend.d.swap(&m_backend.d);
|
||||
}
|
||||
|
||||
void PainterGL::stop() {
|
||||
m_active = false;
|
||||
m_gl->makeCurrent();
|
||||
m_backend.d.clear(&m_backend.d);
|
||||
m_backend.d.swap(&m_backend.d);
|
||||
m_backend.d.deinit(&m_backend.d);
|
||||
m_gl->doneCurrent();
|
||||
m_gl->context()->moveToThread(m_gl->thread());
|
||||
m_messagePainter->clearMessage();
|
||||
delete m_messagePainter;
|
||||
m_messagePainter = nullptr;
|
||||
moveToThread(m_gl->thread());
|
||||
}
|
||||
|
||||
void PainterGL::pause() {
|
||||
m_active = false;
|
||||
// Make sure both buffers are filled
|
||||
forceDraw();
|
||||
forceDraw();
|
||||
}
|
||||
|
||||
void PainterGL::unpause() {
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
void PainterGL::performDraw() {
|
||||
m_painter.beginNativePainting();
|
||||
float r = m_gl->devicePixelRatio();
|
||||
m_backend.d.resized(&m_backend.d, m_size.width() * r, m_size.height() * r);
|
||||
m_backend.d.drawFrame(&m_backend.d);
|
||||
m_painter.endNativePainting();
|
||||
m_messagePainter->paint(&m_painter);
|
||||
}
|
||||
|
||||
void PainterGL::showMessage(const QString& message) {
|
||||
m_messagePainter->showMessage(message);
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_DISPLAY_GL
|
||||
#define QGBA_DISPLAY_GL
|
||||
|
||||
#include "Display.h"
|
||||
|
||||
#include "MessagePainter.h"
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
extern "C" {
|
||||
#include "platform/opengl/gl.h"
|
||||
}
|
||||
|
||||
struct GBAThread;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class EmptyGLWidget : public QGLWidget {
|
||||
public:
|
||||
EmptyGLWidget(const QGLFormat& format, QWidget* parent) : QGLWidget(format, parent) { setAutoBufferSwap(false); }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override {}
|
||||
void resizeEvent(QResizeEvent*) override {}
|
||||
};
|
||||
|
||||
class PainterGL;
|
||||
class DisplayGL : public Display {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
|
||||
~DisplayGL();
|
||||
|
||||
public slots:
|
||||
void startDrawing(GBAThread* context) override;
|
||||
void stopDrawing() override;
|
||||
void pauseDrawing() override;
|
||||
void unpauseDrawing() override;
|
||||
void forceDraw() override;
|
||||
void lockAspectRatio(bool lock) override;
|
||||
void filter(bool filter) override;
|
||||
void framePosted(const uint32_t*) override;
|
||||
|
||||
void showMessage(const QString& message) override;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*) override {}
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
|
||||
private:
|
||||
void resizePainter();
|
||||
|
||||
QGLWidget* m_gl;
|
||||
PainterGL* m_painter;
|
||||
QThread* m_drawThread;
|
||||
GBAThread* m_context;
|
||||
bool m_lockAspectRatio;
|
||||
bool m_filter;
|
||||
};
|
||||
|
||||
class PainterGL : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PainterGL(QGLWidget* parent);
|
||||
|
||||
void setContext(GBAThread*);
|
||||
|
||||
public slots:
|
||||
void setBacking(const uint32_t*);
|
||||
void forceDraw();
|
||||
void draw();
|
||||
void start();
|
||||
void stop();
|
||||
void pause();
|
||||
void unpause();
|
||||
void resize(const QSize& size);
|
||||
void lockAspectRatio(bool lock);
|
||||
void filter(bool filter);
|
||||
|
||||
void showMessage(const QString& message);
|
||||
|
||||
private:
|
||||
void performDraw();
|
||||
|
||||
QPainter m_painter;
|
||||
QGLWidget* m_gl;
|
||||
bool m_active;
|
||||
GBAThread* m_context;
|
||||
GBAGLContext m_backend;
|
||||
QSize m_size;
|
||||
MessagePainter* m_messagePainter;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,81 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "DisplayQt.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
DisplayQt::DisplayQt(QWidget* parent)
|
||||
: Display(parent)
|
||||
, m_backing(nullptr)
|
||||
, m_lockAspectRatio(false)
|
||||
, m_filter(false)
|
||||
{
|
||||
}
|
||||
|
||||
void DisplayQt::startDrawing(GBAThread*) {
|
||||
}
|
||||
|
||||
void DisplayQt::lockAspectRatio(bool lock) {
|
||||
m_lockAspectRatio = lock;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::filter(bool filter) {
|
||||
m_filter = filter;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::framePosted(const uint32_t* buffer) {
|
||||
update();
|
||||
if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) {
|
||||
return;
|
||||
}
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB16);
|
||||
#else
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB555);
|
||||
#endif
|
||||
#else
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB32);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisplayQt::showMessage(const QString& message) {
|
||||
m_messagePainter.showMessage(message);
|
||||
}
|
||||
|
||||
void DisplayQt::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||
if (m_filter) {
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
}
|
||||
QSize s = size();
|
||||
QSize ds = s;
|
||||
if (m_lockAspectRatio) {
|
||||
if (s.width() * 2 > s.height() * 3) {
|
||||
ds.setWidth(s.height() * 3 / 2);
|
||||
} else if (s.width() * 2 < s.height() * 3) {
|
||||
ds.setHeight(s.width() * 2 / 3);
|
||||
}
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
|
||||
#ifdef COLOR_5_6_5
|
||||
painter.drawImage(full, m_backing, QRect(0, 0, 240, 160));
|
||||
#else
|
||||
painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, 240, 160));
|
||||
#endif
|
||||
m_messagePainter.paint(&painter);
|
||||
}
|
||||
|
||||
void DisplayQt::resizeEvent(QResizeEvent*) {
|
||||
m_messagePainter.resize(size(), m_lockAspectRatio);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_DISPLAY_QT
|
||||
#define QGBA_DISPLAY_QT
|
||||
|
||||
#include "Display.h"
|
||||
#include "MessagePainter.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QTimer>
|
||||
|
||||
struct GBAThread;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class DisplayQt : public Display {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayQt(QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void startDrawing(GBAThread* context) override;
|
||||
void stopDrawing() override {}
|
||||
void pauseDrawing() override {}
|
||||
void unpauseDrawing() override {}
|
||||
void forceDraw() override { update(); }
|
||||
void lockAspectRatio(bool lock) override;
|
||||
void filter(bool filter) override;
|
||||
void framePosted(const uint32_t*) override;
|
||||
|
||||
void showMessage(const QString& message) override;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
virtual void resizeEvent(QResizeEvent*) override;;
|
||||
|
||||
private:
|
||||
QImage m_backing;
|
||||
bool m_lockAspectRatio;
|
||||
bool m_filter;
|
||||
MessagePainter m_messagePainter;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -6,9 +6,13 @@
|
|||
#include "GBAApp.h"
|
||||
|
||||
#include "AudioProcessor.h"
|
||||
#include "Display.h"
|
||||
#include "GameController.h"
|
||||
#include "Window.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QIcon>
|
||||
|
||||
extern "C" {
|
||||
#include "platform/commandline.h"
|
||||
|
@ -17,43 +21,144 @@ extern "C" {
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
static GBAApp* g_app = nullptr;
|
||||
|
||||
GBAApp::GBAApp(int& argc, char* argv[])
|
||||
: QApplication(argc, argv)
|
||||
, m_window(&m_configController)
|
||||
, m_windows{}
|
||||
{
|
||||
g_app = this;
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
SDL_Init(SDL_INIT_NOPARACHUTE);
|
||||
#endif
|
||||
|
||||
SocketSubsystemInit();
|
||||
setWindowIcon(QIcon(":/res/mgba-1024.png"));
|
||||
|
||||
QApplication::setApplicationName(PROJECT_NAME);
|
||||
QApplication::setApplicationVersion(PROJECT_VERSION);
|
||||
SocketSubsystemInit();
|
||||
qRegisterMetaType<const uint32_t*>("const uint32_t*");
|
||||
|
||||
QApplication::setApplicationName(projectName);
|
||||
QApplication::setApplicationVersion(projectVersion);
|
||||
|
||||
Window* w = new Window(&m_configController);
|
||||
m_windows[0] = w;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
m_window.show();
|
||||
w->show();
|
||||
#endif
|
||||
|
||||
GBAArguments args;
|
||||
if (m_configController.parseArguments(&args, argc, argv)) {
|
||||
m_window.argumentsPassed(&args);
|
||||
w->argumentsPassed(&args);
|
||||
} else {
|
||||
m_window.loadConfig();
|
||||
w->loadConfig();
|
||||
}
|
||||
freeArguments(&args);
|
||||
|
||||
Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("videoDriver").toInt()));
|
||||
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
|
||||
m_window.controller()->reloadAudioDriver();
|
||||
w->controller()->reloadAudioDriver();
|
||||
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
#ifdef Q_OS_MAC
|
||||
m_window.show();
|
||||
w->show();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GBAApp::event(QEvent* event) {
|
||||
if (event->type() == QEvent::FileOpen) {
|
||||
m_window.controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file());
|
||||
m_windows[0]->controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file());
|
||||
return true;
|
||||
}
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
Window* GBAApp::newWindow() {
|
||||
if (m_multiplayer.attached() >= MAX_GBAS) {
|
||||
return nullptr;
|
||||
}
|
||||
Window* w = new Window(&m_configController, m_multiplayer.attached());
|
||||
m_windows[m_multiplayer.attached()] = w;
|
||||
w->setAttribute(Qt::WA_DeleteOnClose);
|
||||
#ifndef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
w->loadConfig();
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
#ifdef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
return w;
|
||||
}
|
||||
|
||||
GBAApp* GBAApp::app() {
|
||||
return g_app;
|
||||
}
|
||||
|
||||
void GBAApp::interruptAll() {
|
||||
for (int i = 0; i < MAX_GBAS; ++i) {
|
||||
if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) {
|
||||
continue;
|
||||
}
|
||||
m_windows[i]->controller()->threadInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void GBAApp::continueAll() {
|
||||
for (int i = 0; i < MAX_GBAS; ++i) {
|
||||
if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) {
|
||||
continue;
|
||||
}
|
||||
m_windows[i]->controller()->threadContinue();
|
||||
}
|
||||
}
|
||||
|
||||
QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
|
||||
interruptAll();
|
||||
QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter);
|
||||
continueAll();
|
||||
if (!filename.isEmpty()) {
|
||||
m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) {
|
||||
interruptAll();
|
||||
QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter);
|
||||
continueAll();
|
||||
if (!filename.isEmpty()) {
|
||||
m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
QFileDialog* GBAApp::getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter) {
|
||||
FileDialog* dialog = new FileDialog(this, owner, title, filter);
|
||||
dialog->setAcceptMode(QFileDialog::AcceptOpen);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
QFileDialog* GBAApp::getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter) {
|
||||
FileDialog* dialog = new FileDialog(this, owner, title, filter);
|
||||
dialog->setAcceptMode(QFileDialog::AcceptSave);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
GBAApp::FileDialog::FileDialog(GBAApp* app, QWidget* parent, const QString& caption, const QString& filter)
|
||||
: QFileDialog(parent, caption, app->m_configController.getQtOption("lastDirectory").toString(), filter)
|
||||
, m_app(app)
|
||||
{
|
||||
}
|
||||
|
||||
int GBAApp::FileDialog::exec() {
|
||||
m_app->interruptAll();
|
||||
bool didAccept = QFileDialog::exec() == QDialog::Accepted;
|
||||
QStringList filenames = selectedFiles();
|
||||
if (!filenames.isEmpty()) {
|
||||
m_app->m_configController.setQtOption("lastDirectory", QFileInfo(filenames[0]).dir().path());
|
||||
}
|
||||
m_app->continueAll();
|
||||
return didAccept;
|
||||
}
|
||||
|
|
|
@ -7,26 +7,57 @@
|
|||
#define QGBA_APP_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "Window.h"
|
||||
#include "MultiplayerController.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba/sio.h"
|
||||
}
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class GameController;
|
||||
class Window;
|
||||
|
||||
class GBAApp : public QApplication {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GBAApp(int& argc, char* argv[]);
|
||||
static GBAApp* app();
|
||||
|
||||
Window* newWindow();
|
||||
|
||||
QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
|
||||
QFileDialog* getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
QFileDialog* getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
|
||||
public slots:
|
||||
void interruptAll();
|
||||
void continueAll();
|
||||
|
||||
protected:
|
||||
bool event(QEvent*);
|
||||
|
||||
private:
|
||||
class FileDialog : public QFileDialog {
|
||||
public:
|
||||
FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), const QString& filter = QString());
|
||||
virtual int exec() override;
|
||||
|
||||
private:
|
||||
GBAApp* m_app;
|
||||
};
|
||||
|
||||
Window* newWindowInternal();
|
||||
|
||||
ConfigController m_configController;
|
||||
Window m_window;
|
||||
Window* m_windows[MAX_GBAS];
|
||||
MultiplayerController m_multiplayer;
|
||||
};
|
||||
|
||||
}
|
||||
|
|