mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/thread-proxy-renderer
This commit is contained in:
commit
67905d281b
|
@ -3,17 +3,10 @@ if [ $TRAVIS_OS_NAME = "osx" ]; then
|
|||
brew update
|
||||
brew install qt5 ffmpeg imagemagick sdl2 libzip libpng
|
||||
else
|
||||
sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y
|
||||
sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y
|
||||
sudo add-apt-repository ppa:immerrr-k/qt5-backport -y
|
||||
sudo add-apt-repository ppa:spvkgn/ffmpeg+mpv -y
|
||||
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get purge cmake -qq
|
||||
sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev \
|
||||
g++-4.8 libpng-dev libsdl2-dev libzip-dev qtbase5-dev \
|
||||
sudo apt-get clean
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -q cmake libedit-dev libmagickwand-dev \
|
||||
libpng-dev libsdl2-dev libzip-dev qtbase5-dev \
|
||||
libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \
|
||||
libavutil-dev libavformat-dev libavresample-dev libswscale-dev
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
fi
|
||||
|
|
|
@ -2,17 +2,15 @@ os:
|
|||
- linux
|
||||
- osx
|
||||
|
||||
env:
|
||||
- CMAKE_PREFIX_PATH=/usr/local/opt/qt5
|
||||
|
||||
language: c
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
before_install:
|
||||
- ./.travis-deps.sh
|
||||
|
||||
script: mkdir build && cd build && cmake .. && make
|
||||
script: mkdir build && cd build && cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 .. && make
|
||||
|
|
237
CHANGES
237
CHANGES
|
@ -1,48 +1,223 @@
|
|||
0.4.0: (Future)
|
||||
0.5.0: (Future)
|
||||
Features:
|
||||
- I/O viewer
|
||||
- Game Boy support
|
||||
- Support for encrypted CodeBreaker GBA cheats
|
||||
- Emulation of Vast Fame protected GBA carts (taizou)
|
||||
- Tile viewer
|
||||
Bugfixes:
|
||||
- SDL: Fix axes being mapped wrong
|
||||
- GBA Memory: Fix mirror on non-overdumped Classic NES games
|
||||
- Util: Fix realloc semantics in utf16to8
|
||||
- PSP2: Fix GPU crash while exiting
|
||||
- PSP2: Fix VSync
|
||||
- ARM7: Fix decoding of Thumb ADD (variants 5 and 6)
|
||||
- GBA Serialize: Savestates now properly store prefetch
|
||||
Misc:
|
||||
- 3DS: Use blip_add_delta_fast for a small speed improvement
|
||||
- OpenGL: Log shader compilation failure
|
||||
- Qt: Remove some C99isms from C++ code
|
||||
- Windows: Add native VDir support
|
||||
- All: Add QUIET parameter to silence CMake
|
||||
- ARM7: Support forcing Thumb mode via MSR
|
||||
- ARM7: Flush prefetch cache when loading CPSR via MSR
|
||||
- OpenGL: Add texSize uniform
|
||||
- ARM7: Clean up instruction decoding for future expandability
|
||||
- Qt: Make -g flag work in Qt build
|
||||
- Qt: Simplify OpenGL context creation
|
||||
- Debugger: Support register and memory writes via GDB stub
|
||||
- GBA Audio: Force audio DMAs to not increment destination
|
||||
- Qt: Thread startup improvements
|
||||
- 3DS: Allow UTF-16 filenames
|
||||
- 3DS: Port to using citro3D
|
||||
- 3DS: Use system font for menus
|
||||
- PSP2: Use system font for menus
|
||||
- All: Faster memory read/write
|
||||
- Qt: Make audio channel/video layer options shortcut mappable
|
||||
- GBA Memory: Optimize stalling behavior
|
||||
|
||||
0.4.1: (2016-07-11)
|
||||
Bugfixes:
|
||||
- All: Fix several file handle leaks
|
||||
- All: Fix instruction tables getting zeroed when linking sometimes
|
||||
- ARM7: Fix flags on SBC/RSC
|
||||
- ARM7: Fix setting spsr privilege bits when spsr is empty
|
||||
- GBA Audio: Reset audio FIFO DMA if an invalid destination is set
|
||||
- GBA BIOS: Fix RegisterRamReset setting DISPCNT to the wrong value
|
||||
- GBA BIOS: Fix ArcTan2 accuracy and boundary conditions
|
||||
- GBA Memory: Fix executing code from OBJ region of VRAM
|
||||
- GBA Serialize: Fix memory corruption bug in GBAExtdataSerialize
|
||||
- GBA Serialize: Fix loading savegames from savestates
|
||||
- OpenGL: Correct boolean vector strcmp strings for uniforms
|
||||
- Qt: Fix sending gameStopped twice
|
||||
- Qt: Fix hang if audio sync is enabled and audio fails to initialize
|
||||
- Qt: Fix initial state of key mapping
|
||||
- Qt: Initialize m_useBios
|
||||
- SDL: Fix joystick initialization on BSD
|
||||
- SDL: Fix potential joystick crash in games with rumble
|
||||
- SDL: Fix SDL 1.2 build
|
||||
- SDL: Fix sporadic crash when deinitializing audio
|
||||
- Shaders: Fix AGS-001 shader with some bad drivers
|
||||
- Util: Use closesocket on Windows
|
||||
- Util: Fix socket bind addresses
|
||||
- VFS: Fix reading 7z archives without rewinding first
|
||||
- VFS: VFileFromFD should not open directories
|
||||
- Wii: Fix tilting direction
|
||||
- Util: Fix realloc semantics in utf16to8
|
||||
Misc:
|
||||
- All: Allow use of external minizip library
|
||||
- Debugger: CLI debugger now exits when end-of-stream is reached
|
||||
- FFmpeg: Update dependencies on Ubuntu
|
||||
- GBA: Slightly optimize GBAProcessEvents
|
||||
- GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku
|
||||
- GBA Video: Null renderer should return proper register values
|
||||
- Libretro: Disable logging game errors, BIOS calls and stubs in release builds
|
||||
- Qt: Add preset for DualShock 4
|
||||
- Qt: Update 360 input profile on OS X to reflect newer drivers
|
||||
- Qt: Remove use of NaN
|
||||
- Qt: Canonicalize file paths when loading games
|
||||
- Qt: Add refresh button to controller editing
|
||||
- SDL: Remove default gamepad mappings
|
||||
- Util: Fix intermittent build failure on OS X
|
||||
- VFS: VFile.sync now updates modified time
|
||||
|
||||
0.4.0: (2016-02-02)
|
||||
Features:
|
||||
- Officially supported ports for the Nintendo 3DS, Wii, and PlayStation Vita
|
||||
- I/O viewer
|
||||
- Booting of multiboot images
|
||||
- Customization of GIF recording
|
||||
- Libretro: Cheat code support
|
||||
- Support for GLSL shaders
|
||||
- ROM information view
|
||||
- Support for VBA-style cheat codes
|
||||
- Savestates now store creation timestamps
|
||||
- Key autofire
|
||||
- Libretro: Allow blocking opposing directional input
|
||||
- OpenEmu core for OS X
|
||||
- Libretro: Settings for using BIOS and skipping intro
|
||||
- Libretro: Customizable idle loop removal
|
||||
- Implemented cycle counting for sprite rendering
|
||||
- Cleaner, unified settings window
|
||||
- Added a setting for pausing when the emulator is not in focus
|
||||
- Customizable paths for save games, save states, screenshots and patches
|
||||
- Controller hotplugging
|
||||
- Ability to store save games and active cheats within savestates
|
||||
Bugfixes:
|
||||
- ARM7: Fix sign of unaligned LDRSH
|
||||
- ARM7: Fix decoding of some ARM ALU instructions with shifters
|
||||
- Debugger: Fix watchpoints in gdb
|
||||
- GBA: Fix warnings when creating and loading savestates
|
||||
- GBA: Fix Iridion II savetype
|
||||
- GBA BIOS: Fix misaligned CpuSet
|
||||
- GBA Cheats: Fix cheats setting the Action Replay version
|
||||
- GBA Hardware: Fix GPIO on big endian
|
||||
- GBA Memory: Fix DMA register writing behavior
|
||||
- GBA Memory: Fix DMAs triggering two cycles early
|
||||
- Libretro: Fix aspect ratio
|
||||
- Qt: Fix some potential crashes with the gamepad mapping
|
||||
- Qt: Fix keys being mapped incorrectly when loading configuration file
|
||||
- Util: Fix PowerPC PNG read/write pixel order
|
||||
- Util: Fix excessive memory allocation when decoding a PNG
|
||||
- VFS: Fix VFileReadline and remove _vfdReadline
|
||||
Misc:
|
||||
- All: Improved PowerPC support
|
||||
- All: Fix some undefined behavior warnings
|
||||
- ARM7: Combine shifter-immediate and shifter-register functions to reduce binary size
|
||||
- Debugger: Convert breakpoints and watchpoints from linked-lists to vectors
|
||||
- GBA: Implement bad I/O register loading
|
||||
- GBA: Allow jumping to OAM and palette RAM
|
||||
- GBA BIOS: Finish implementing RegisterRamReset
|
||||
- GBA Config: Add "override" layer for better one-time configuration
|
||||
- GBA Input: Consolidate GBA_KEY_NONE and GBA_NO_MAPPING
|
||||
- GBA Memory: Use a dynamically sized mask for ROM memory
|
||||
- GBA Memory: Implement several unimplemented memory access types
|
||||
- GBA Memory: Add GBAView* functions for viewing memory directly without bus issues
|
||||
- GBA RR: Starting from savestate now embeds the savegame
|
||||
- GBA RR: Add preliminary SRAM support for VBM loading
|
||||
- GBA RR: Add support for resets in movies
|
||||
- GBA Video: Remove lastHblank, as it is implied
|
||||
- Libretro: Use anonymous memory mappers for large blocks of memory
|
||||
- Libretro: Add install target for libretro core
|
||||
- Qt: Window size command line options are now supported
|
||||
- Qt: Increase usability of key mapper
|
||||
- Qt: Add 'Apply' button to settings window
|
||||
- Qt: Gray out "Skip BIOS intro" while "Use BIOS file" is unchecked
|
||||
- Qt: Allow use of modifier keys as input
|
||||
- Qt: Optimize log viewer
|
||||
- Qt: Added button for breaking into the GDB debugger
|
||||
- Qt: Add box for showing duration of rewind
|
||||
- SDL: Support fullscreen in SDL 1.2
|
||||
- SDL: Allow GBASDLAudio to be used without a thread context
|
||||
- Util: Use VFile for configuration
|
||||
- Util: Add MutexTryLock
|
||||
|
||||
0.3.2: (2015-12-16)
|
||||
Bugfixes:
|
||||
- ARM7: Fix STRT/STRBT
|
||||
- ARM7: Implement undefined STRH/LDRH/LDRSH/LDRSB versions
|
||||
- ARM7: Fix bank switching with LDR[B]T/STR[B]T
|
||||
- Libretro: Fix problems with rumble not turning off
|
||||
- GBA: Fix idle skip state being retained between games
|
||||
- GBA: Initialize uninitialized pristineRom and pristineRomSize members
|
||||
- GBA BIOS: Fix CpuSet on 0x01XXXXXX addresses
|
||||
- GBA BIOS: Fix Sqrt sign
|
||||
- GBA BIOS: Fix misaligned RLUnCompReadNormalWrite*
|
||||
- GBA Hardware: Fix Game Boy Player rumble in Pokemon Pinball
|
||||
- GBA Memory: Fix DMA behavior for SRAM accesses
|
||||
- GBA Memory: Fix Store8 to OBJ VRAM
|
||||
- GBA Memory: Fix alignment of LDM/STM on SRAM
|
||||
- GBA Memory: Fix unaligned out-of-bounds ROM loads
|
||||
- GBA Memory: Fix timing of DMAs
|
||||
- GBA Video: Fix _mix for 15-bit color
|
||||
- GBA Video: Fix OAM and palette initialization
|
||||
- OpenGL: Fix fast-forward on some OpenGL drivers where it may block early
|
||||
- Qt: Use safer isLoaded check in GameController
|
||||
- Qt: Fix a race condition in PainterGL that could lead to a crash
|
||||
- Qt: Fix clear button/analog buttons in gamepad mapper on some platforms
|
||||
- Qt: Fix font size in memory viewer
|
||||
- Qt: Fix a crash in the memory viewer
|
||||
- Qt: Add additional checks in CheatModel to prevent crashes
|
||||
- Qt: Fix race condition with setting sample rate
|
||||
- Qt: Fix crash when closing multiplayer windows
|
||||
- Qt: Fix resetting while paused
|
||||
Misc:
|
||||
- GBA Audio: Implement missing flags on SOUNDCNT_X register
|
||||
- Qt: Add mute option to menu
|
||||
|
||||
0.3.1: (2015-10-24)
|
||||
Bugfixes:
|
||||
- ARM7: Fix instruction decoding of Thumb shifts
|
||||
- GBA: Deinit savegame when unloading a ROM
|
||||
- GBA: Fix BIOS check on big endian
|
||||
- GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2
|
||||
- GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers
|
||||
- GBA Audio: Fix audio channels being silenced at the wrong time
|
||||
- GBA Memory: Fix bad BIOS Load16 on big endian
|
||||
- GBA Memory: Fix bad Load8 on big endian
|
||||
- GBA Video: Start on the scanline BIOS finishes on if no BIOS is loaded
|
||||
- GBA Video: Fix edge case with sprite blend modes and semitransparency
|
||||
- GBA Video: Fix objwin and blending interaction on sprites
|
||||
- GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops
|
||||
- Libretro: Fix a memory leak with the render buffer
|
||||
- Qt: Windows no longer spawn in the top left on first launch
|
||||
- Qt: Fix install path of XDG desktop file with DESTDIR
|
||||
- Qt: Fix drag and drop on Windows
|
||||
- Qt: Reenable double buffering, as disabling it broke some Windows configs
|
||||
- GBA Video: Start on the scanline BIOS finishes on if no BIOS is loaded
|
||||
- GBA: Deinit savegame when unloading a ROM
|
||||
- GBA: Fix BIOS check on big endian
|
||||
- Libretro: Fix a memory leak with the render buffer
|
||||
- GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers
|
||||
- GBA Audio: Fix audio channels being silenced at the wrong time
|
||||
- VFS: Fix return values of VFileFILE.read and .write
|
||||
- Util: Fix PowerPC PNG read/write pixel order
|
||||
- GBA Video: Fix edge case with sprite blend modes and semitransparency
|
||||
- GBA Video: Fix objwin and blending interaction on sprites
|
||||
- GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops
|
||||
- GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2
|
||||
- GBA Memory: Fix bad BIOS Load16 on big endian
|
||||
- GBA Memory: Fix bad Load8 on big endian
|
||||
- ARM7: Fix instruction decoding of Thumb shifts
|
||||
Misc:
|
||||
- Qt: Window size command line options are now supported
|
||||
- Qt: Increase usability of key mapper
|
||||
- GBA Memory: Use a dynamically sized mask for ROM memory
|
||||
- Qt: Remove useless help icons in dialogs
|
||||
- ARM7: Combine shifter-immediate and shifter-register functions to reduce binary size
|
||||
- SDL: Support fullscreen in SDL 1.2
|
||||
- All: Reset next event to cycles instead of zero to interrupt
|
||||
- All: Add --version flag
|
||||
- ARM7: Force disable LTO on two files to work around a GCC bug
|
||||
- GBA: Attempting to save a screenshot-style savestate should be allowed without libpng
|
||||
- GBA: Better memory handling with PNG savestates
|
||||
- GBA: Additional savestate sanity checks
|
||||
- GBA: Check for cycle count being too high
|
||||
- GBA Audio: Allow GBAAVStream to have no video callback
|
||||
- ARM7: Force disable LTO on two files to work around a GCC bug
|
||||
- Libretro: Use anonymous memory mappers for large blocks of memory
|
||||
- Qt: Add 'Apply' button to settings window
|
||||
- GBA BIOS: Implement RegisterRamReset for SIO registers
|
||||
- Qt: Remove useless help icons in dialogs
|
||||
- Qt: Prevent savestate window from opening while in multiplayer
|
||||
- Qt: Disable menu items in multiplayer that don't make sense to have enabled
|
||||
- Qt: Dropping multiplayer windows works more cleanly now
|
||||
- GBA BIOS: Implement RegisterRamReset for SIO registers
|
||||
- All: Reset next event to cycles instead of zero to interrupt
|
||||
- GBA Video: Remove lastHblank, as it is implied
|
||||
- GBA: Check for cycle count being too high
|
||||
- GBA Config: Add "override" layer for better one-time configuration
|
||||
- SDL: Allow GBASDLAudio to be used without a thread context
|
||||
|
||||
0.3.0: (2015-08-16)
|
||||
Features:
|
||||
|
|
491
CMakeLists.txt
491
CMakeLists.txt
|
@ -10,53 +10,65 @@ set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM de
|
|||
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")
|
||||
set(USE_ZLIB ON CACHE BOOL "Whether or not to enable zlib support")
|
||||
set(USE_MINIZIP ON CACHE BOOL "Whether or not to enable external minizip support")
|
||||
set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support")
|
||||
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable ZIP support")
|
||||
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable LIBZIP support")
|
||||
set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support")
|
||||
set(USE_BLIP ON CACHE BOOL "Whether or not to enable blip_buf support")
|
||||
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
|
||||
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
|
||||
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
|
||||
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
|
||||
set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
|
||||
if(APPLE)
|
||||
set(BUILD_OPENEMU OFF CACHE BOOL "Build OpenEmu core")
|
||||
endif()
|
||||
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
||||
set(BUILD_TEST OFF CACHE BOOL "Build testing harness")
|
||||
set(BUILD_EXAMPLE OFF CACHE BOOL "Build example frontends")
|
||||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
set(SKIP_LIBRARY OFF CACHE BOOL "Skip building the library (useful for only building libretro or OpenEmu cores)")
|
||||
set(BUILD_GL ON CACHE STRING "Build with OpenGL")
|
||||
set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2")
|
||||
set(USE_EPOXY ON CACHE STRING "Build with libepoxy")
|
||||
set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies")
|
||||
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 GBA_CTX_SRC ${CMAKE_SOURCE_DIR}/src/gba/context/*.c)
|
||||
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
||||
file(GLOB GUI_SRC ${CMAKE_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_SOURCE_DIR}/src/gba/gui/*.c)
|
||||
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/*.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)
|
||||
file(GLOB ARM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/*.c)
|
||||
file(GLOB LR35902_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/*.c)
|
||||
file(GLOB GBA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/*.c)
|
||||
file(GLOB GB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/*.c)
|
||||
file(GLOB GBA_CHEATS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/cheats/*.c)
|
||||
file(GLOB GBA_RR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/rr/*.c)
|
||||
file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c)
|
||||
file(GLOB UTIL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/*.[cSs] ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c)
|
||||
file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c)
|
||||
file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c)
|
||||
file(GLOB SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c)
|
||||
file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c)
|
||||
file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||
set(CLI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/commandline.c)
|
||||
set(CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-mem.c)
|
||||
set(VFS_SRC)
|
||||
source_group("ARM core" FILES ${ARM_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC})
|
||||
source_group("GBA extra" FILES ${GBA_CHEATS_SRC} ${GBA_CTX_SRC} ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||
source_group("LR35902 core" FILES ${LR35902_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${GBA_RENDERER_SRC} ${SIO_SRC})
|
||||
source_group("GBA extra" FILES ${GBA_CHEATS_SRC} ${GBA_EXTRA_SRC} ${GBA_RR_SRC})
|
||||
source_group("GB board" FILES ${GB_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if (NOT DEFINED LIBDIR)
|
||||
set(LIBDIR "lib")
|
||||
set(LIBDIR "${CMAKE_INSTALL_LIBDIR}")
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
if (NOT DEFINED MANDIR)
|
||||
set(MANDIR ${CMAKE_INSTALL_MANDIR})
|
||||
endif()
|
||||
|
@ -94,18 +106,18 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
|||
endfunction()
|
||||
|
||||
# Version information
|
||||
add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||
add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DBINARY_NAME=${BINARY_NAME}
|
||||
-DCONFIG_FILE=${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||
-DCONFIG_FILE=${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in
|
||||
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.c
|
||||
-P ${CMAKE_SOURCE_DIR}/version.cmake
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/version.cmake)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/src/util/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||
list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c)
|
||||
source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||
list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||
source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||
|
||||
# Advanced settings
|
||||
set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization")
|
||||
|
@ -113,7 +125,7 @@ set(BUILD_PGO OFF CACHE BOOL "Build with profiling-guided optimization")
|
|||
set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated")
|
||||
set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path")
|
||||
mark_as_advanced(BUILD_LTO BUILD_PGO PGO_STAGE_2 PGO_DIR)
|
||||
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR} -fprofile-arcs")
|
||||
set(PGO_PRE_FLAGS "-fprofile-generate=${PGO_DIR} -fprofile-arcs")
|
||||
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")
|
||||
|
||||
if(BUILD_PGO AND NOT PGO_STAGE_2)
|
||||
|
@ -131,9 +143,12 @@ 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 shlwapi)
|
||||
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)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/vfs-w32.c)
|
||||
file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/*.c)
|
||||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
if(MSVC)
|
||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
add_definitions(-DUSE_PTHREADS)
|
||||
|
||||
|
@ -144,8 +159,8 @@ elseif(UNIX)
|
|||
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)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
endif()
|
||||
|
||||
|
@ -162,7 +177,6 @@ list(APPEND OS_LIB ${M_LIBRARY})
|
|||
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")
|
||||
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/arm/isa-arm.c ${CMAKE_SOURCE_DIR}/src/arm/isa-thumb.c PROPERTIES COMPILE_FLAGS -fno-lto)
|
||||
endif()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
|
||||
|
@ -187,6 +201,10 @@ if(PSP2 OR WII)
|
|||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format")
|
||||
endif()
|
||||
|
||||
if(DEFINED 3DS OR DEFINED PSP2 OR DEFINED WII)
|
||||
set(USE_GDB_STUB OFF)
|
||||
endif()
|
||||
|
||||
if(WII)
|
||||
add_definitions(-U__STRICT_ANSI__)
|
||||
endif()
|
||||
|
@ -208,7 +226,9 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic")
|
|||
check_function_exists(uselocale HAVE_USELOCALE)
|
||||
check_function_exists(setlocale HAVE_SETLOCALE)
|
||||
else()
|
||||
set(DISABLE_DEPS ON CACHE BOOL "This platform cannot build with dependencies" FORCE)
|
||||
if(NOT DEFINED 3DS AND NOT DEFINED PSP2 AND NOT DEFINED WII)
|
||||
set(DISABLE_DEPS ON CACHE BOOL "This platform cannot build with dependencies" FORCE)
|
||||
endif()
|
||||
set(DISABLE_FRONTENDS ON)
|
||||
set(MINIMAL_CORE ON)
|
||||
endif()
|
||||
|
@ -230,7 +250,7 @@ if(HAVE_LOCALTIME_R)
|
|||
list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R)
|
||||
endif()
|
||||
|
||||
if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE)
|
||||
if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE OR APPLE)
|
||||
list(APPEND FUNCTION_DEFINES HAVE_LOCALE)
|
||||
if (HAVE_STRTOF_L)
|
||||
list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L)
|
||||
|
@ -268,6 +288,9 @@ if(BUILD_GL)
|
|||
set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT BUILD_GL)
|
||||
set(OPENGLE_LIBRARY "" CACHE PATH "" FORCE)
|
||||
endif()
|
||||
if(BUILD_GLES2 AND NOT BUILD_RASPI)
|
||||
find_path(OPENGLES2_INCLUDE_DIR NAMES GLES2/gl2.h)
|
||||
find_library(OPENGLES2_LIBRARY NAMES GLESv2 GLESv2_CM)
|
||||
|
@ -275,31 +298,41 @@ if(BUILD_GLES2 AND NOT BUILD_RASPI)
|
|||
set(BUILD_GLES2 OFF CACHE BOOL "OpenGL|ES 2 not found" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT BUILD_GLES2)
|
||||
set(OPENGLES2_LIBRARY "" CACHE PATH "" FORCE)
|
||||
endif()
|
||||
set(WANT_ZLIB ${USE_ZLIB})
|
||||
set(WANT_PNG ${USE_PNG})
|
||||
set(WANT_LIBZIP ${USE_LIBZIP})
|
||||
|
||||
find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale")
|
||||
find_feature(USE_ZLIB "ZLIB")
|
||||
find_feature(USE_MINIZIP "minizip")
|
||||
find_feature(USE_PNG "PNG")
|
||||
find_feature(USE_LIBZIP "libzip")
|
||||
find_feature(USE_MAGICK "MagickWand")
|
||||
find_feature(USE_EPOXY "epoxy")
|
||||
|
||||
# Features
|
||||
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
||||
set(DEBUGGER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/debugger.c)
|
||||
set(FEATURE_SRC)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
|
||||
|
||||
if(DISABLE_DEPS)
|
||||
set(USE_LZMA OFF)
|
||||
set(USE_GDB_STUB OFF)
|
||||
endif()
|
||||
|
||||
if(USE_CLI_DEBUGGER)
|
||||
list(APPEND FEATURES CLI_DEBUGGER)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/parser.c)
|
||||
if(M_CORE_GBA)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/cli-debugger.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/cli.c)
|
||||
endif()
|
||||
if(M_CORE_GB)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/cli-debugger.c)
|
||||
endif()
|
||||
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
||||
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||
|
@ -310,9 +343,9 @@ endif()
|
|||
|
||||
if(USE_GDB_STUB)
|
||||
list(APPEND FEATURES GDB_STUB)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
||||
endif()
|
||||
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
||||
source_group("Debugger" FILES ${DEBUGGER_SRC})
|
||||
|
||||
if(USE_FFMPEG)
|
||||
list(APPEND FEATURES FFMPEG)
|
||||
|
@ -322,33 +355,32 @@ if(USE_FFMPEG)
|
|||
endif()
|
||||
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||
link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c")
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|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")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswscale${LIBSWSCALE_VERSION_MAJOR}|libswscale-ffmpeg${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
||||
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 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)
|
||||
endif()
|
||||
list(APPEND THIRD_PARTY_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
|
||||
if(USE_MAGICK)
|
||||
list(APPEND FEATURES MAGICK)
|
||||
include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS})
|
||||
link_directories(${MAGICKWAND_LIBRARY_DIRS})
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c")
|
||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/imagemagick/imagemagick-gif-encoder.c")
|
||||
list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES})
|
||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
||||
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
||||
|
@ -361,13 +393,13 @@ endif()
|
|||
|
||||
if(WANT_ZLIB AND NOT USE_ZLIB)
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/third-party/zlib zlib)
|
||||
set_property(TARGET zlibstatic PROPERTY INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/zlib;${CMAKE_SOURCE_DIR}/src/third-party/zlib)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib zlib)
|
||||
set_property(TARGET zlibstatic PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/zlib;${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib)
|
||||
set_property(TARGET zlib PROPERTY EXCLUDE_FROM_ALL ON)
|
||||
set_property(TARGET example PROPERTY EXCLUDE_FROM_ALL ON)
|
||||
set_property(TARGET minigzip PROPERTY EXCLUDE_FROM_ALL ON)
|
||||
set(ZLIB_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/third-party/zlib ${CMAKE_BINARY_DIR}/zlib)
|
||||
set(ZLIB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/third-party/zlib ${CMAKE_BINARY_DIR}/zlib)
|
||||
set(ZLIB_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
|
||||
set(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
|
||||
set(ZLIB_LIBRARY zlibstatic)
|
||||
list(APPEND DEPENDENCY_LIB zlibstatic)
|
||||
set(USE_ZLIB ON)
|
||||
|
@ -385,20 +417,18 @@ if(WANT_PNG AND USE_ZLIB AND NOT USE_PNG)
|
|||
set(PNG_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(PNG_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/third-party/libpng libpng)
|
||||
set_property(TARGET png16_static PROPERTY INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/libpng;${CMAKE_SOURCE_DIR}/src/third-party/libpng;${ZLIB_INCLUDE_DIRS})
|
||||
set(PNG_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/third-party/libpng ${CMAKE_BINARY_DIR}/libpng)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/libpng libpng)
|
||||
set_property(TARGET png16_static PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/libpng;${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/libpng;${ZLIB_INCLUDE_DIRS})
|
||||
set(PNG_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/libpng ${CMAKE_CURRENT_BINARY_DIR}/libpng)
|
||||
list(APPEND DEPENDENCY_LIB png16_static)
|
||||
set(USE_PNG ON)
|
||||
endif()
|
||||
|
||||
if(USE_PNG)
|
||||
list(APPEND FEATURES PNG)
|
||||
if(NOT PSP2)
|
||||
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0")
|
||||
endif()
|
||||
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0")
|
||||
endif()
|
||||
|
||||
if(USE_LIBZIP)
|
||||
|
@ -406,113 +436,174 @@ 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")
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-zip.c)
|
||||
string(REGEX MATCH "^[0-9]+" LIBZIP_VERSION_MAJOR ${libzip_VERSION})
|
||||
if (LIBZIP_VERSION_MAJOR LESS 1)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
||||
elseif(LIBZIP_VERSION_MAJOR EQUAL 1)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip4")
|
||||
else()
|
||||
message(AUTHOR_WARNING Unknown version of libzip detected: ${libzip_VERSION})
|
||||
endif()
|
||||
elseif(USE_MINIZIP)
|
||||
include_directories(AFTER ${MINIZIP_INCLUDE_DIRS})
|
||||
link_directories(${MINIZIP_LIBRARY_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${MINIZIP_LIBRARIES})
|
||||
list(APPEND FEATURES MINIZIP)
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-zip.c)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libminizip1")
|
||||
elseif(USE_ZLIB)
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-zip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/ioapi.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/unzip.c)
|
||||
endif()
|
||||
|
||||
if (USE_LZMA)
|
||||
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
||||
include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/third-party/lzma)
|
||||
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c)
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_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
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf2.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrc.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrcOpt.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zDec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/CpuArch.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/LzmaDec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Lzma2Dec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra86.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bcj2.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
||||
list(APPEND FEATURE_SRC ${LZMA_SRC})
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zBuf.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zBuf2.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zCrc.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zCrcOpt.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zDec.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/CpuArch.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Delta.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/LzmaDec.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Lzma2Dec.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Bra.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Bra86.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/BraIA64.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Bcj2.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Ppmd7.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
||||
list(APPEND VFS_SRC ${LZMA_SRC})
|
||||
list(APPEND FEATURES LZMA)
|
||||
endif()
|
||||
|
||||
if(USE_EPOXY)
|
||||
add_definitions(-DBUILD_GL -DBUILD_GLES2)
|
||||
list(APPEND FEATURES EPOXY)
|
||||
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
|
||||
set(OPENGLES2_LIBRARY ${EPOXY_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libepoxy0")
|
||||
endif()
|
||||
|
||||
|
||||
set(FEATURE_DEFINES)
|
||||
foreach(FEATURE IN LISTS FEATURES)
|
||||
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||
endforeach()
|
||||
|
||||
source_group("Virtual files" FILES ${VFS_SRC})
|
||||
set(CORE_SRC)
|
||||
if(M_CORE_GB)
|
||||
add_definitions(-DM_CORE_GB)
|
||||
list(APPEND CORE_SRC
|
||||
${LR35902_SRC}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/debugger.c
|
||||
${GB_SRC}
|
||||
${GB_RENDERER_SRC})
|
||||
endif()
|
||||
|
||||
if(M_CORE_GBA)
|
||||
add_definitions(-DM_CORE_GBA)
|
||||
list(APPEND CORE_SRC
|
||||
${ARM_SRC}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/debugger.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/memory-debugger.c
|
||||
${GBA_SRC}
|
||||
${GBA_CHEATS_SRC}
|
||||
${GBA_RENDERER_SRC})
|
||||
if(NOT M_CORE_GB)
|
||||
list(APPEND CORE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/audio.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group("Virtual files" FILES ${CORE_VFS_SRC} ${VFS_SRC})
|
||||
source_group("Extra features" FILES ${FEATURE_SRC})
|
||||
source_group("Third-party code" FILES ${THIRD_PARTY_SRC})
|
||||
|
||||
# Platform binaries
|
||||
if(3DS)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/3ds ${CMAKE_BINARY_DIR}/3ds)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/3ds ${CMAKE_CURRENT_BINARY_DIR}/3ds)
|
||||
endif()
|
||||
|
||||
if(WII)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/wii ${CMAKE_BINARY_DIR}/wii)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/wii ${CMAKE_CURRENT_BINARY_DIR}/wii)
|
||||
endif()
|
||||
|
||||
if(PSP2)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/psp2 ${CMAKE_BINARY_DIR}/psp2)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/psp2 ${CMAKE_CURRENT_BINARY_DIR}/psp2)
|
||||
endif()
|
||||
|
||||
# Binaries
|
||||
set(CORE_SRC
|
||||
${ARM_SRC}
|
||||
${GBA_SRC}
|
||||
${GBA_CHEATS_SRC}
|
||||
${GBA_CTX_SRC}
|
||||
${DEBUGGER_SRC}
|
||||
${RENDERER_SRC}
|
||||
list(APPEND CORE_SRC
|
||||
${UTIL_SRC}
|
||||
${VFS_SRC}
|
||||
${CORE_VFS_SRC}
|
||||
${DEBUGGER_SRC}
|
||||
${OS_SRC}
|
||||
${THIRD_PARTY_SRC})
|
||||
|
||||
set(SRC ${CORE_SRC} ${VFS_SRC})
|
||||
if(NOT MINIMAL_CORE)
|
||||
set(SRC
|
||||
${CORE_SRC}
|
||||
${GBA_RR_SRC}
|
||||
${GBA_SV_SRC}
|
||||
${SIO_SRC}
|
||||
${FEATURE_SRC})
|
||||
else()
|
||||
set(SRC ${CORE_SRC})
|
||||
if(M_CORE_GBA)
|
||||
list(APPEND SRC
|
||||
${GBA_RR_SRC}
|
||||
${GBA_EXTRA_SRC}
|
||||
${SIO_SRC})
|
||||
endif()
|
||||
list(APPEND SRC
|
||||
${FEATURE_SRC}
|
||||
${CLI_SRC})
|
||||
endif()
|
||||
|
||||
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
||||
set(BUILD_SHARED ON)
|
||||
endif()
|
||||
if(NOT SKIP_LIBRARY)
|
||||
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
||||
set(BUILD_SHARED ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED)
|
||||
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||
add_dependencies(${BINARY_NAME}-static version-info)
|
||||
if(BUILD_SHARED)
|
||||
add_library(${BINARY_NAME} SHARED ${SRC} ${VFS_SRC})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||
add_dependencies(${BINARY_NAME}-static version-info)
|
||||
endif()
|
||||
else()
|
||||
add_library(${BINARY_NAME} STATIC ${SRC})
|
||||
endif()
|
||||
|
||||
add_dependencies(${BINARY_NAME} version-info)
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
|
||||
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-48.png DESTINATION share/icons/hicolor/48x48/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-64.png DESTINATION share/icons/hicolor/64x64/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-96.png DESTINATION share/icons/hicolor/96x96/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
endif()
|
||||
else()
|
||||
add_library(${BINARY_NAME} STATIC ${SRC})
|
||||
endif()
|
||||
|
||||
add_dependencies(${BINARY_NAME} version-info)
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
|
||||
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||
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})
|
||||
set(BUILD_SHARED OFF)
|
||||
set(BUILD_STATIC OFF)
|
||||
find_library(${BINARY_NAME} ${BINARY_NAME})
|
||||
if(NOT ${BINARY_NAME}_FOUND)
|
||||
set(DISABLE_FRONTENDS ON)
|
||||
set(BUILD_PERF OFF)
|
||||
set(BUILD_TEST OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_GL)
|
||||
|
@ -529,79 +620,145 @@ if(DISABLE_FRONTENDS)
|
|||
endif()
|
||||
|
||||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES}")
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2")
|
||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP)
|
||||
endif()
|
||||
|
||||
if(BUILD_OPENEMU)
|
||||
find_library(FOUNDATION Foundation)
|
||||
find_library(OPENEMUBASE OpenEmuBase)
|
||||
file(GLOB OE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/*.m)
|
||||
add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OE_SRC})
|
||||
set_target_properties(${BINARY_NAME}-openemu PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/Info.plist.in
|
||||
BUNDLE TRUE
|
||||
BUNDLE_EXTENSION oecoreplugin
|
||||
OUTPUT_NAME ${PROJECT_NAME}
|
||||
COMPILE_DEFINITIONS "DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=1")
|
||||
target_link_libraries(${BINARY_NAME}-openemu ${OS_LIB} ${FOUNDATION} ${OPENEMUBASE})
|
||||
install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP)
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_definitions(-DBUILD_SDL)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl)
|
||||
endif()
|
||||
|
||||
if(BUILD_QT)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/qt ${CMAKE_BINARY_DIR}/qt)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/qt ${CMAKE_CURRENT_BINARY_DIR}/qt)
|
||||
endif()
|
||||
|
||||
if(BUILD_PERF)
|
||||
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/test/perf-main.c)
|
||||
set(PERF_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test/perf-main.c ${CLI_SRC})
|
||||
if(UNIX AND NOT APPLE)
|
||||
list(APPEND PERF_LIB rt)
|
||||
endif()
|
||||
|
||||
add_executable(${BINARY_NAME}-perf ${PERF_SRC})
|
||||
target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB})
|
||||
target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB} ${OS_LIB})
|
||||
set_target_properties(${BINARY_NAME}-perf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-perf DESTINATION bin COMPONENT ${BINARY_NAME}-perf)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf)
|
||||
endif()
|
||||
|
||||
if(BUILD_TEST)
|
||||
add_executable(${BINARY_NAME}-fuzz ${CMAKE_SOURCE_DIR}/src/platform/test/fuzz-main.c)
|
||||
add_executable(${BINARY_NAME}-fuzz ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test/fuzz-main.c)
|
||||
target_link_libraries(${BINARY_NAME}-fuzz ${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME}-fuzz PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test)
|
||||
endif()
|
||||
|
||||
if(BUILD_EXAMPLE)
|
||||
add_executable(${BINARY_NAME}-example-server ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/server.c)
|
||||
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME}-example-server PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_executable(${BINARY_NAME}-example-client ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/client.c)
|
||||
target_link_libraries(${BINARY_NAME}-example-client ${BINARY_NAME} ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||
set_target_properties(${BINARY_NAME}-example-client PROPERTIES
|
||||
COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}"
|
||||
INCLUDE_DIRECTORIES "${SDL_INCLUDE_DIR};${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
endif()
|
||||
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})
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_SOURCE_DIR}/README.md)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
|
||||
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "mGBA Game Boy Advance Emulator")
|
||||
set(CPACK_PACKAGE_VENDOR "Jeffrey Pfau")
|
||||
set(CPACK_PACKAGE_CONTACT "Jeffrey Pfau <jeffrey@endrift.com>")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
|
||||
SET(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
|
||||
set(CPACK_STRIP_FILES ${BINARY_NAME})
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/README.md ${CMAKE_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME})
|
||||
|
||||
include(CPack)
|
||||
|
||||
# Summaries
|
||||
message(STATUS "Feature summary:")
|
||||
message(STATUS " CLI debugger: ${USE_CLI_DEBUGGER}")
|
||||
message(STATUS " GDB stub: ${USE_GDB_STUB}")
|
||||
message(STATUS " Video recording: ${USE_FFMPEG}")
|
||||
message(STATUS " GIF recording: ${USE_MAGICK}")
|
||||
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
||||
message(STATUS " ZIP support: ${USE_LIBZIP}")
|
||||
message(STATUS " 7-Zip support: ${USE_LZMA}")
|
||||
message(STATUS " Better audio resampling: ${USE_BLIP}")
|
||||
message(STATUS "Frontend summary:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||
message(STATUS " Test harness: ${BUILD_TEST}")
|
||||
message(STATUS "Library summary:")
|
||||
message(STATUS " Static: ${BUILD_STATIC}")
|
||||
message(STATUS " Shared: ${BUILD_SHARED}")
|
||||
set(SUMMARY_GL_LIST)
|
||||
if(USE_EPOXY)
|
||||
set(SUMMARY_GL_LIST "libepoxy")
|
||||
else()
|
||||
if(BUILD_GL)
|
||||
list(APPEND SUMMARY_GL_LIST "OpenGL")
|
||||
endif()
|
||||
if(BUILD_GLES2)
|
||||
list(APPEND SUMMARY_GL_LIST "OpenGL|ES 2")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT SUMMARY_GL_LIST)
|
||||
set(SUMMARY_GL OFF)
|
||||
else()
|
||||
string(REPLACE ";" ", " SUMMARY_GL "${SUMMARY_GL_LIST}")
|
||||
endif()
|
||||
if(USE_LIBZIP)
|
||||
set(SUMMARY_ZIP libzip)
|
||||
elseif(USE_MINIZIP)
|
||||
set(SUMMARY_ZIP "minizip (external)")
|
||||
elseif(USE_ZLIB)
|
||||
set(SUMMARY_ZIP "minizip (included)")
|
||||
else()
|
||||
set(SUMMARY_ZIP OFF)
|
||||
endif()
|
||||
|
||||
if(NOT QUIET)
|
||||
message(STATUS "Platforms:")
|
||||
message(STATUS " Game Boy Advance: ${M_CORE_GBA}")
|
||||
message(STATUS " Game Boy: ${M_CORE_GB}")
|
||||
message(STATUS "Features:")
|
||||
message(STATUS " CLI debugger: ${USE_CLI_DEBUGGER}")
|
||||
message(STATUS " GDB stub: ${USE_GDB_STUB}")
|
||||
message(STATUS " Video recording: ${USE_FFMPEG}")
|
||||
message(STATUS " GIF recording: ${USE_MAGICK}")
|
||||
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
||||
message(STATUS " ZIP support: ${SUMMARY_ZIP}")
|
||||
message(STATUS " 7-Zip support: ${USE_LZMA}")
|
||||
message(STATUS " OpenGL support: ${SUMMARY_GL}")
|
||||
message(STATUS "Frontends:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||
message(STATUS " Test harness: ${BUILD_TEST}")
|
||||
message(STATUS " Examples: ${BUILD_EXAMPLE}")
|
||||
message(STATUS "Cores:")
|
||||
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||
if(APPLE)
|
||||
message(STATUS " OpenEmu core: ${BUILD_OPENEMU}")
|
||||
endif()
|
||||
message(STATUS "Libraries:")
|
||||
message(STATUS " Static: ${BUILD_STATIC}")
|
||||
message(STATUS " Shared: ${BUILD_SHARED}")
|
||||
endif()
|
||||
|
|
19
PORTING.md
19
PORTING.md
|
@ -10,15 +10,7 @@ The general porting process involves branching `master`, making the needed chang
|
|||
Port-specific TODO
|
||||
------------------
|
||||
|
||||
The ports are vaguely usable, but by no means should be considered stable.
|
||||
|
||||
### 3DS (master)
|
||||
* Add audio
|
||||
* Thread support testing
|
||||
* Make it faster
|
||||
* Threaded renderer shim
|
||||
* ARMv6 dynarec
|
||||
* Hardware acceleration
|
||||
The following ports are considered incomplete and are thus on branches still. They may work, but should not be considered stable.
|
||||
|
||||
### PSP (port/psp)
|
||||
* Add menu
|
||||
|
@ -27,12 +19,3 @@ The ports are vaguely usable, but by no means should be considered stable.
|
|||
* Make it faster
|
||||
* MIPS dynarec
|
||||
* Hardware acceleration
|
||||
|
||||
### PS Vita (master)
|
||||
* Make it faster
|
||||
* Threaded renderer shim
|
||||
* Hardware acceleration
|
||||
|
||||
### Wii (master)
|
||||
* Thread support
|
||||
* Clean up video detection
|
||||
|
|
21
README.md
21
README.md
|
@ -1,11 +1,11 @@
|
|||
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.
|
||||
mGBA is an 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 [mgba.io](http://mgba.io/).
|
||||
Up-to-date news and downloads can be found at [mgba.io](https://mgba.io/).
|
||||
|
||||
![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)
|
||||
[![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -30,6 +30,7 @@ Features
|
|||
- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro.
|
||||
- Configurable emulation rewinding.
|
||||
- Support for loading and exporting GameShark and Action Replay snapshots.
|
||||
- Cores available for RetroArch/Libretro and OpenEmu.
|
||||
|
||||
### Planned features
|
||||
|
||||
|
@ -38,7 +39,6 @@ Features
|
|||
- 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](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))
|
||||
|
||||
|
||||
|
@ -49,6 +49,9 @@ Supported Platforms
|
|||
- OS X 10.7 (Lion)[<sup>[3]</sup>](#osxver) or newer
|
||||
- Linux
|
||||
- FreeBSD
|
||||
- Nintendo 3DS
|
||||
- Wii
|
||||
- PlayStation Vita
|
||||
|
||||
Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported.
|
||||
|
||||
|
@ -125,7 +128,6 @@ Footnotes
|
|||
|
||||
- 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))
|
||||
|
||||
<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.
|
||||
|
||||
|
@ -141,7 +143,10 @@ mGBA is Copyright © 2013 – 2015 Jeffrey Pfau. It is distributed under the [Mo
|
|||
|
||||
mGBA contains the following third-party libraries:
|
||||
|
||||
- [inih](https://code.google.com/p/inih/), which is copyright © 2009 Brush Technology and used under a BSD 3-clause license.
|
||||
- [blip-buf](https://code.google.com/p/blip-buf/), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License.
|
||||
- [inih](https://github.com/benhoyt/inih), which is copyright © 2009 Ben Hoyt and used under a BSD 3-clause license.
|
||||
- [blip-buf](https://code.google.com/archive/p/blip-buf), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License.
|
||||
- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public domain.
|
||||
- [MurmurHash3](https://code.google.com/p/smhasher/wiki/MurmurHash3) implementation by Austin Appleby, which is public domain.
|
||||
- [MurmurHash3](https://github.com/aappleby/smhasher) implementation by Austin Appleby, which is public domain.
|
||||
- [getopt for MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/), which is public domain.
|
||||
|
||||
If you are a game publisher and wish to license mGBA for commercial usage, please email [licensing@mgba.io](mailto:licensing@mgba.io) for more information.
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
.Nd Game Boy Advance emulator
|
||||
.Sh SYNOPSIS
|
||||
.Nm mgba-qt
|
||||
.Op Fl 123456f
|
||||
.Op Fl 123456fg
|
||||
.Op Fl b Ar biosfile
|
||||
.Op Fl l Ar loglevel
|
||||
.Op Fl p Ar patchfile
|
||||
|
@ -42,6 +42,11 @@ will use the BIOS specified in the configuration file,
|
|||
or a high\(hylevel emulated BIOS if none is specified.
|
||||
.It Fl f
|
||||
Start the emulator full\(hyscreen.
|
||||
.It Fl g
|
||||
Start a
|
||||
.Xr gdb 1
|
||||
session.
|
||||
By default the session starts on port 2345.
|
||||
.It Fl l Ar loglevel
|
||||
Log messages during emulation.
|
||||
.Ar loglevel
|
||||
|
|
BIN
res/font.png
BIN
res/font.png
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -14,7 +14,7 @@ BEGIN
|
|||
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 "LegalCopyright", "(c) 2013 - 2016 Jeffrey Pfau"
|
||||
VALUE "OriginalFilename", "${BINARY_NAME}"
|
||||
VALUE "ProductName", "${PROJECT_NAME}"
|
||||
VALUE "ProductVersion", "${BINARY_NAME}"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
vec3 arrayX[4];
|
||||
arrayX[0] = vec3(1.0, 0.2, 0.2);
|
||||
arrayX[1] = vec3(0.2, 1.0, 0.2);
|
||||
arrayX[2] = vec3(0.2, 0.2, 1.0);
|
||||
arrayX[3] = vec3(0.4, 0.4, 0.4);
|
||||
vec3 arrayY[4];
|
||||
arrayY[0] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[1] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[2] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[3] = vec3(0.8, 0.8, 0.8);
|
||||
color.rgb = pow(color.rgb * vec3(0.8, 0.8, 0.8), vec3(1.8, 1.8, 1.8)) + vec3(0.16, 0.16, 0.16);
|
||||
color.rgb *= arrayX[int(mod(texCoord.s * texSize.x * 4.0, 4.0))];
|
||||
color.rgb *= arrayY[int(mod(texCoord.t * texSize.y * 4.0, 4.0))];
|
||||
color.a = 0.5;
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[shader]
|
||||
name=AGB-001
|
||||
author=endrift
|
||||
description=A glorious recreation of the original Game Boy Advance
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=agb001.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
|
@ -0,0 +1,27 @@
|
|||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform float reflectionBrightness;
|
||||
uniform vec2 reflectionDistance;
|
||||
uniform float lightBrightness;
|
||||
|
||||
const float speed = 2.0;
|
||||
const float decay = 2.0;
|
||||
const float coeff = 2.5;
|
||||
|
||||
void main() {
|
||||
float sp = pow(speed, lightBrightness);
|
||||
float dc = pow(decay, -lightBrightness);
|
||||
float s = (sp - dc) / (sp + dc);
|
||||
vec2 radius = (texCoord.st - vec2(0.5, 0.5)) * vec2(coeff * s);
|
||||
radius = pow(abs(radius), vec2(4.0));
|
||||
vec3 bleed = vec3(0.12, 0.14, 0.19);
|
||||
bleed += (dot(radius, radius) + vec3(0.02, 0.03, 0.05)) * vec3(0.14, 0.18, 0.2);
|
||||
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
color.rgb += pow(bleed, pow(vec3(lightBrightness), vec3(-0.5)));
|
||||
|
||||
vec4 reflection = texture2D(tex, texCoord - reflectionDistance);
|
||||
color.rgb += reflection.rgb * reflectionBrightness;
|
||||
color.a = 1.0;
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
vec3 arrayX[4];
|
||||
arrayX[0] = vec3(1.0, 0.2, 0.2);
|
||||
arrayX[1] = vec3(0.2, 1.0, 0.2);
|
||||
arrayX[2] = vec3(0.2, 0.2, 1.0);
|
||||
arrayX[3] = vec3(0.4, 0.4, 0.4);
|
||||
vec3 arrayY[4];
|
||||
arrayY[0] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[1] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[2] = vec3(1.0, 1.0, 1.0);
|
||||
arrayY[3] = vec3(0.9, 0.9, 0.9);
|
||||
color.rgb = pow(color.rgb, vec3(1.6, 1.6, 1.6));
|
||||
color.rgb *= arrayX[int(mod(texCoord.s * texSize.x * 4.0, 4.0))];
|
||||
color.rgb *= arrayY[int(mod(texCoord.t * texSize.y * 4.0, 4.0))];
|
||||
color.a = 0.8;
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
[shader]
|
||||
name=AGS-001
|
||||
author=endrift
|
||||
description=A pristine recreation of the illuminated Game Boy Advance SP
|
||||
passes=2
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=ags001.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
|
||||
[pass.1]
|
||||
fragmentShader=ags001-light.fs
|
||||
width=960
|
||||
height=640
|
||||
|
||||
[pass.1.uniform.lightBrightness]
|
||||
type=float
|
||||
default=1
|
||||
readableName=Light brightness
|
||||
|
||||
[pass.1.uniform.reflectionBrightness]
|
||||
type=float
|
||||
default=0.07
|
||||
readableName=Reflection brightness
|
||||
|
||||
[pass.1.uniform.reflectionDistance]
|
||||
type=float2
|
||||
default[0]=0
|
||||
default[1]=0.025
|
||||
readableName=Reflection distance
|
|
@ -0,0 +1,9 @@
|
|||
[shader]
|
||||
name=Pixelate
|
||||
author=endrift
|
||||
description=Only scale up the screen at an integer ratio
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
blend=1
|
||||
integerScaling=1
|
|
@ -0,0 +1,38 @@
|
|||
[shader]
|
||||
name=xBR
|
||||
author=Hyllian
|
||||
description=xBR upsampling filter
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
integerScaling=1
|
||||
vertexShader=xbr.vs
|
||||
fragmentShader=xbr.fs
|
||||
|
||||
[pass.0.uniform.XBR_Y_WEIGHT]
|
||||
type=float
|
||||
default=48
|
||||
readableName=Y Weight
|
||||
min=0
|
||||
max=100
|
||||
|
||||
[pass.0.uniform.XBR_EQ_THRESHOLD]
|
||||
type=float
|
||||
readableName=Eq Threshold
|
||||
default=10.0
|
||||
min=0.0
|
||||
max=50.0
|
||||
|
||||
[pass.0.uniform.XBR_EQ_THRESHOLD2]
|
||||
type=float
|
||||
readableName=Eq Threshold2
|
||||
default=2.0
|
||||
min=0.0
|
||||
max=4.0
|
||||
|
||||
[pass.0.uniform.XBR_LV2_COEFFICIENT]
|
||||
type=float
|
||||
readableName=Lv2 Coefficient
|
||||
default=2.0
|
||||
min=1.0
|
||||
max=3.0
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
Hyllian's xBR-lv3 Shader
|
||||
|
||||
Copyright (C) 2011-2015 Hyllian - sergiogdb@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
Incorporates some of the ideas from SABR shader. Thanks to Joshua Street.
|
||||
*/
|
||||
|
||||
uniform float XBR_Y_WEIGHT;
|
||||
uniform float XBR_EQ_THRESHOLD;
|
||||
uniform float XBR_EQ_THRESHOLD2;
|
||||
uniform float XBR_LV2_COEFFICIENT;
|
||||
|
||||
const mat3 yuv = mat3(0.299, 0.587, 0.114, -0.169, -0.331, 0.499, 0.499, -0.418, -0.0813);
|
||||
const vec4 delta = vec4(0.4, 0.4, 0.4, 0.4);
|
||||
|
||||
vec4 df(vec4 A, vec4 B)
|
||||
{
|
||||
return vec4(abs(A-B));
|
||||
}
|
||||
|
||||
float c_df(vec3 c1, vec3 c2) {
|
||||
vec3 df = abs(c1 - c2);
|
||||
return df.r + df.g + df.b;
|
||||
}
|
||||
|
||||
bvec4 eq(vec4 A, vec4 B)
|
||||
{
|
||||
return lessThan(df(A, B), vec4(XBR_EQ_THRESHOLD));
|
||||
}
|
||||
|
||||
bvec4 eq2(vec4 A, vec4 B)
|
||||
{
|
||||
return lessThan(df(A, B), vec4(XBR_EQ_THRESHOLD2));
|
||||
}
|
||||
|
||||
bvec4 and(bvec4 A, bvec4 B)
|
||||
{
|
||||
return bvec4(A.x && B.x, A.y && B.y, A.z && B.z, A.w && B.w);
|
||||
}
|
||||
|
||||
bvec4 or(bvec4 A, bvec4 B)
|
||||
{
|
||||
return bvec4(A.x || B.x, A.y || B.y, A.z || B.z, A.w || B.w);
|
||||
}
|
||||
|
||||
vec4 weighted_distance(vec4 a, vec4 b, vec4 c, vec4 d, vec4 e, vec4 f, vec4 g, vec4 h)
|
||||
{
|
||||
return (df(a,b) + df(a,c) + df(d,e) + df(d,f) + 4.0*df(g,h));
|
||||
}
|
||||
|
||||
// GLSL shader autogenerated by cg2glsl.py.
|
||||
#if __VERSION__ >= 130
|
||||
#define varying in
|
||||
#define COMPAT_TEXTURE texture
|
||||
out vec4 FragColor;
|
||||
#else
|
||||
#define FragColor gl_FragColor
|
||||
#define COMPAT_TEXTURE texture2D
|
||||
#endif
|
||||
|
||||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#define COMPAT_PRECISION mediump
|
||||
#else
|
||||
#define COMPAT_PRECISION
|
||||
#endif
|
||||
uniform sampler2D tex;
|
||||
varying vec2 texCoord;
|
||||
varying vec4 TEX1;
|
||||
varying vec4 TEX2;
|
||||
varying vec4 TEX3;
|
||||
varying vec4 TEX4;
|
||||
varying vec4 TEX5;
|
||||
varying vec4 TEX6;
|
||||
varying vec4 TEX7;
|
||||
|
||||
uniform vec2 texSize;
|
||||
|
||||
void main()
|
||||
{
|
||||
bvec4 edr, edr_left, edr_up, edr3_left, edr3_up, px; // px = pixel, edr = edge detection rule
|
||||
bvec4 interp_restriction_lv1, interp_restriction_lv2_left, interp_restriction_lv2_up;
|
||||
bvec4 interp_restriction_lv3_left, interp_restriction_lv3_up;
|
||||
bvec4 nc, nc30, nc60, nc45, nc15, nc75; // new_color
|
||||
vec4 fx, fx_left, fx_up, finalfx, fx3_left, fx3_up; // inequations of straight lines.
|
||||
vec3 res1, res2, pix1, pix2;
|
||||
float blend1, blend2;
|
||||
|
||||
vec2 fp = fract(texCoord * texSize);
|
||||
|
||||
vec3 A1 = COMPAT_TEXTURE(tex, TEX1.xw).rgb;
|
||||
vec3 B1 = COMPAT_TEXTURE(tex, TEX1.yw).rgb;
|
||||
vec3 C1 = COMPAT_TEXTURE(tex, TEX1.zw).rgb;
|
||||
|
||||
vec3 A = COMPAT_TEXTURE(tex, TEX2.xw).rgb;
|
||||
vec3 B = COMPAT_TEXTURE(tex, TEX2.yw).rgb;
|
||||
vec3 C = COMPAT_TEXTURE(tex, TEX2.zw).rgb;
|
||||
|
||||
vec3 D = COMPAT_TEXTURE(tex, TEX3.xw).rgb;
|
||||
vec3 E = COMPAT_TEXTURE(tex, TEX3.yw).rgb;
|
||||
vec3 F = COMPAT_TEXTURE(tex, TEX3.zw).rgb;
|
||||
|
||||
vec3 G = COMPAT_TEXTURE(tex, TEX4.xw).rgb;
|
||||
vec3 H = COMPAT_TEXTURE(tex, TEX4.yw).rgb;
|
||||
vec3 I = COMPAT_TEXTURE(tex, TEX4.zw).rgb;
|
||||
|
||||
vec3 G5 = COMPAT_TEXTURE(tex, TEX5.xw).rgb;
|
||||
vec3 H5 = COMPAT_TEXTURE(tex, TEX5.yw).rgb;
|
||||
vec3 I5 = COMPAT_TEXTURE(tex, TEX5.zw).rgb;
|
||||
|
||||
vec3 A0 = COMPAT_TEXTURE(tex, TEX6.xy).rgb;
|
||||
vec3 D0 = COMPAT_TEXTURE(tex, TEX6.xz).rgb;
|
||||
vec3 G0 = COMPAT_TEXTURE(tex, TEX6.xw).rgb;
|
||||
|
||||
vec3 C4 = COMPAT_TEXTURE(tex, TEX7.xy).rgb;
|
||||
vec3 F4 = COMPAT_TEXTURE(tex, TEX7.xz).rgb;
|
||||
vec3 I4 = COMPAT_TEXTURE(tex, TEX7.xw).rgb;
|
||||
|
||||
vec4 b = transpose(mat4x3(B, D, H, F)) * (XBR_Y_WEIGHT * yuv[0]);
|
||||
vec4 c = transpose(mat4x3(C, A, G, I)) * (XBR_Y_WEIGHT * yuv[0]);
|
||||
vec4 e = transpose(mat4x3(E, E, E, E)) * (XBR_Y_WEIGHT * yuv[0]);
|
||||
vec4 d = b.yzwx;
|
||||
vec4 f = b.wxyz;
|
||||
vec4 g = c.zwxy;
|
||||
vec4 h = b.zwxy;
|
||||
vec4 i = c.wxyz;
|
||||
|
||||
vec4 i4 = transpose(mat4x3(I4, C1, A0, G5)) * (XBR_Y_WEIGHT*yuv[0]);
|
||||
vec4 i5 = transpose(mat4x3(I5, C4, A1, G0)) * (XBR_Y_WEIGHT*yuv[0]);
|
||||
vec4 h5 = transpose(mat4x3(H5, F4, B1, D0)) * (XBR_Y_WEIGHT*yuv[0]);
|
||||
vec4 f4 = h5.yzwx;
|
||||
|
||||
vec4 c1 = i4.yzwx;
|
||||
vec4 g0 = i5.wxyz;
|
||||
vec4 b1 = h5.zwxy;
|
||||
vec4 d0 = h5.wxyz;
|
||||
|
||||
vec4 Ao = vec4( 1.0, -1.0, -1.0, 1.0 );
|
||||
vec4 Bo = vec4( 1.0, 1.0, -1.0,-1.0 );
|
||||
vec4 Co = vec4( 1.5, 0.5, -0.5, 0.5 );
|
||||
vec4 Ax = vec4( 1.0, -1.0, -1.0, 1.0 );
|
||||
vec4 Bx = vec4( 0.5, 2.0, -0.5,-2.0 );
|
||||
vec4 Cx = vec4( 1.0, 1.0, -0.5, 0.0 );
|
||||
vec4 Ay = vec4( 1.0, -1.0, -1.0, 1.0 );
|
||||
vec4 By = vec4( 2.0, 0.5, -2.0,-0.5 );
|
||||
vec4 Cy = vec4( 2.0, 0.0, -1.0, 0.5 );
|
||||
|
||||
vec4 Az = vec4( 6.0, -2.0, -6.0, 2.0 );
|
||||
vec4 Bz = vec4( 2.0, 6.0, -2.0, -6.0 );
|
||||
vec4 Cz = vec4( 5.0, 3.0, -3.0, -1.0 );
|
||||
vec4 Aw = vec4( 2.0, -6.0, -2.0, 6.0 );
|
||||
vec4 Bw = vec4( 6.0, 2.0, -6.0,-2.0 );
|
||||
vec4 Cw = vec4( 5.0, -1.0, -3.0, 3.0 );
|
||||
|
||||
fx = (Ao*fp.y+Bo*fp.x);
|
||||
fx_left = (Ax*fp.y+Bx*fp.x);
|
||||
fx_up = (Ay*fp.y+By*fp.x);
|
||||
fx3_left= (Az*fp.y+Bz*fp.x);
|
||||
fx3_up = (Aw*fp.y+Bw*fp.x);
|
||||
|
||||
// It uses CORNER_C if none of the others are defined.
|
||||
#ifdef CORNER_A
|
||||
interp_restriction_lv1 = and(notEqual(e, f), notEqual(e, h));
|
||||
#elif defined(CORNER_B)
|
||||
interp_restriction_lv1 = ((e!=f) && (e!=h) && ( !eq(f,b) && !eq(h,d) || eq(e,i) && !eq(f,i4) && !eq(h,i5) || eq(e,g) || eq(e,c) ) );
|
||||
#elif defined(CORNER_D)
|
||||
interp_restriction_lv1 = ((e!=f) && (e!=h) && ( !eq(f,b) && !eq(h,d) || eq(e,i) && !eq(f,i4) && !eq(h,i5) || eq(e,g) || eq(e,c) ) && (f!=f4 && f!=i || h!=h5 && h!=i || h!=g || f!=c || eq(b,c1) && eq(d,g0)));
|
||||
#else
|
||||
interp_restriction_lv1 = and(and(notEqual(e, f), notEqual(e, h)),
|
||||
or(or(and(not(eq(f,b)), not(eq(f,c))),
|
||||
and(not(eq(h,d)), not(eq(h,g)))),
|
||||
or(and(eq(e,i), or(and(not(eq(f,f4)), not(eq(f,i4))),
|
||||
and(not(eq(h,h5)), not(eq(h,i5))))),
|
||||
or(eq(e,g), eq(e,c)))));
|
||||
#endif
|
||||
|
||||
interp_restriction_lv2_left = and(notEqual(e, g), notEqual(d, g));
|
||||
interp_restriction_lv2_up = and(notEqual(e, c), notEqual(b, c));
|
||||
interp_restriction_lv3_left = and(eq2(g,g0), not(eq2(d0,g0)));
|
||||
interp_restriction_lv3_up = and(eq2(c,c1), not(eq2(b1,c1)));
|
||||
|
||||
vec4 fx45 = smoothstep(Co - delta, Co + delta, fx);
|
||||
vec4 fx30 = smoothstep(Cx - delta, Cx + delta, fx_left);
|
||||
vec4 fx60 = smoothstep(Cy - delta, Cy + delta, fx_up);
|
||||
vec4 fx15 = smoothstep(Cz - delta, Cz + delta, fx3_left);
|
||||
vec4 fx75 = smoothstep(Cw - delta, Cw + delta, fx3_up);
|
||||
|
||||
edr = and(lessThan(weighted_distance( e, c, g, i, h5, f4, h, f), weighted_distance( h, d, i5, f, i4, b, e, i)), interp_restriction_lv1);
|
||||
edr_left = and(lessThanEqual((XBR_LV2_COEFFICIENT*df(f,g)), df(h,c)), interp_restriction_lv2_left);
|
||||
edr_up = and(greaterThanEqual(df(f,g), (XBR_LV2_COEFFICIENT*df(h,c))), interp_restriction_lv2_up);
|
||||
edr3_left = interp_restriction_lv3_left;
|
||||
edr3_up = interp_restriction_lv3_up;
|
||||
|
||||
nc45 = and(edr, bvec4(fx45));
|
||||
nc30 = and(edr, and(edr_left, bvec4(fx30)));
|
||||
nc60 = and(edr, and(edr_up, bvec4(fx60)));
|
||||
nc15 = and(and(edr, edr_left), and(edr3_left, bvec4(fx15)));
|
||||
nc75 = and(and(edr, edr_up), and(edr3_up, bvec4(fx75)));
|
||||
|
||||
px = lessThanEqual(df(e, f), df(e, h));
|
||||
|
||||
nc = bvec4(nc75.x || nc15.x || nc30.x || nc60.x || nc45.x, nc75.y || nc15.y || nc30.y || nc60.y || nc45.y, nc75.z || nc15.z || nc30.z || nc60.z || nc45.z, nc75.w || nc15.w || nc30.w || nc60.w || nc45.w);
|
||||
|
||||
vec4 final45 = vec4(nc45) * fx45;
|
||||
vec4 final30 = vec4(nc30) * fx30;
|
||||
vec4 final60 = vec4(nc60) * fx60;
|
||||
vec4 final15 = vec4(nc15) * fx15;
|
||||
vec4 final75 = vec4(nc75) * fx75;
|
||||
|
||||
vec4 maximo = max(max(max(final15, final75),max(final30, final60)), final45);
|
||||
|
||||
if (nc.x) {pix1 = px.x ? F : H; blend1 = maximo.x;}
|
||||
else if (nc.y) {pix1 = px.y ? B : F; blend1 = maximo.y;}
|
||||
else if (nc.z) {pix1 = px.z ? D : B; blend1 = maximo.z;}
|
||||
else if (nc.w) {pix1 = px.w ? H : D; blend1 = maximo.w;}
|
||||
|
||||
if (nc.w) {pix2 = px.w ? H : D; blend2 = maximo.w;}
|
||||
else if (nc.z) {pix2 = px.z ? D : B; blend2 = maximo.z;}
|
||||
else if (nc.y) {pix2 = px.y ? B : F; blend2 = maximo.y;}
|
||||
else if (nc.x) {pix2 = px.x ? F : H; blend2 = maximo.x;}
|
||||
|
||||
res1 = mix(E, pix1, blend1);
|
||||
res2 = mix(E, pix2, blend2);
|
||||
vec3 res = mix(res1, res2, step(c_df(E, res1), c_df(E, res2)));
|
||||
|
||||
FragColor = vec4(res, 1.0);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Hyllian's xBR-lv3 Shader
|
||||
|
||||
Copyright (C) 2011-2015 Hyllian - sergiogdb@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Incorporates some of the ideas from SABR shader. Thanks to Joshua Street.
|
||||
*/
|
||||
|
||||
varying vec2 texCoord;
|
||||
varying vec4 TEX1;
|
||||
varying vec4 TEX2;
|
||||
varying vec4 TEX3;
|
||||
varying vec4 TEX4;
|
||||
varying vec4 TEX5;
|
||||
varying vec4 TEX6;
|
||||
varying vec4 TEX7;
|
||||
attribute vec4 position;
|
||||
|
||||
uniform vec2 texSize;
|
||||
|
||||
/* VERTEX_SHADER */
|
||||
void main()
|
||||
{
|
||||
gl_Position = position;
|
||||
|
||||
vec2 ps = vec2(1.0) / texSize;
|
||||
float dx = ps.x;
|
||||
float dy = ps.y;
|
||||
|
||||
// A1 B1 C1
|
||||
// A0 A B C C4
|
||||
// D0 D E F F4
|
||||
// G0 G H I I4
|
||||
// G5 H5 I5
|
||||
|
||||
texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5);
|
||||
TEX1 = texCoord.xxxy + vec4( -dx, 0, dx,-2.0*dy); // A1 B1 C1
|
||||
TEX2 = texCoord.xxxy + vec4( -dx, 0, dx, -dy); // A B C
|
||||
TEX3 = texCoord.xxxy + vec4( -dx, 0, dx, 0); // D E F
|
||||
TEX4 = texCoord.xxxy + vec4( -dx, 0, dx, dy); // G H I
|
||||
TEX5 = texCoord.xxxy + vec4( -dx, 0, dx, 2.0*dy); // G5 H5 I5
|
||||
TEX6 = texCoord.xyyy + vec4(-2.0*dx,-dy, 0, dy); // A0 D0 G0
|
||||
TEX7 = texCoord.xyyy + vec4( 2.0*dx,-dy, 0, dy); // C4 F4 I4
|
||||
}
|
|
@ -90,7 +90,7 @@ void ARMDeinit(struct ARMCore* cpu) {
|
|||
}
|
||||
}
|
||||
|
||||
void ARMSetComponents(struct ARMCore* cpu, struct ARMComponent* master, int extra, struct ARMComponent** extras) {
|
||||
void ARMSetComponents(struct ARMCore* cpu, struct mCPUComponent* master, int extra, struct mCPUComponent** extras) {
|
||||
cpu->master = master;
|
||||
cpu->numComponents = extra;
|
||||
cpu->components = extras;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/cpu.h"
|
||||
|
||||
enum {
|
||||
ARM_SP = 13,
|
||||
ARM_LR = 14,
|
||||
|
@ -129,12 +131,6 @@ struct ARMInterruptHandler {
|
|||
void (*hitStub)(struct ARMCore* cpu, uint32_t opcode);
|
||||
};
|
||||
|
||||
struct ARMComponent {
|
||||
uint32_t id;
|
||||
void (*init)(struct ARMCore* cpu, struct ARMComponent* component);
|
||||
void (*deinit)(struct ARMComponent* component);
|
||||
};
|
||||
|
||||
struct ARMCore {
|
||||
int32_t gprs[16];
|
||||
union PSR cpsr;
|
||||
|
@ -157,15 +153,15 @@ struct ARMCore {
|
|||
struct ARMMemory memory;
|
||||
struct ARMInterruptHandler irqh;
|
||||
|
||||
struct ARMComponent* master;
|
||||
struct mCPUComponent* master;
|
||||
|
||||
size_t numComponents;
|
||||
struct ARMComponent** components;
|
||||
struct mCPUComponent** components;
|
||||
};
|
||||
|
||||
void ARMInit(struct ARMCore* cpu);
|
||||
void ARMDeinit(struct ARMCore* cpu);
|
||||
void ARMSetComponents(struct ARMCore* cpu, struct ARMComponent* master, int extra, struct ARMComponent** extras);
|
||||
void ARMSetComponents(struct ARMCore* cpu, struct mCPUComponent* master, int extra, struct mCPUComponent** extras);
|
||||
void ARMHotplugAttach(struct ARMCore* cpu, size_t slot);
|
||||
void ARMHotplugDetach(struct ARMCore* cpu, size_t slot);
|
||||
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
/* Copyright (c) 2013-2016 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 "cli-debugger.h"
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
#include "arm/debugger/memory-debugger.h"
|
||||
#include "arm/decoder.h"
|
||||
#include "core/core.h"
|
||||
#include "debugger/cli-debugger.h"
|
||||
|
||||
static void _printStatus(struct CLIDebuggerSystem*);
|
||||
|
||||
static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _disassembleThumb(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
||||
static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode);
|
||||
static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
|
||||
static struct CLIDebuggerCommandSummary _armCommands[] = {
|
||||
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "dis/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "dis/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "disasm/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "disasm/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "disassemble/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "disassemble/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "w/r", _writeRegister, CLIDVParse, "Write a register" },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static inline void _printPSR(union PSR psr) {
|
||||
printf("%08X [%c%c%c%c%c%c%c]\n", psr.packed,
|
||||
psr.n ? 'N' : '-',
|
||||
psr.z ? 'Z' : '-',
|
||||
psr.c ? 'C' : '-',
|
||||
psr.v ? 'V' : '-',
|
||||
psr.i ? 'I' : '-',
|
||||
psr.f ? 'F' : '-',
|
||||
psr.t ? 'T' : '-');
|
||||
}
|
||||
|
||||
static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) {
|
||||
struct ARMCore* cpu = debugger->p->d.core->cpu;
|
||||
_disassembleMode(debugger->p, dv, cpu->executionMode);
|
||||
}
|
||||
|
||||
static void _disassembleArm(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
_disassembleMode(debugger, dv, MODE_ARM);
|
||||
}
|
||||
|
||||
static void _disassembleThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
_disassembleMode(debugger, dv, MODE_THUMB);
|
||||
}
|
||||
|
||||
static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv, enum ExecutionMode mode) {
|
||||
struct ARMCore* cpu = debugger->d.core->cpu;
|
||||
uint32_t address;
|
||||
int size;
|
||||
int wordSize;
|
||||
|
||||
if (mode == MODE_ARM) {
|
||||
wordSize = WORD_SIZE_ARM;
|
||||
} else {
|
||||
wordSize = WORD_SIZE_THUMB;
|
||||
}
|
||||
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
address = cpu->gprs[ARM_PC] - wordSize;
|
||||
} else {
|
||||
address = dv->intValue;
|
||||
dv = dv->next;
|
||||
}
|
||||
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
size = 1;
|
||||
} else {
|
||||
size = dv->intValue;
|
||||
dv = dv->next; // TODO: Check for excess args
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
address += _printLine(debugger, address, mode);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
char disassembly[48];
|
||||
struct ARMInstructionInfo info;
|
||||
printf("%08X: ", address);
|
||||
if (mode == MODE_ARM) {
|
||||
uint32_t instruction = debugger->d.core->busRead32(debugger->d.core, address);
|
||||
ARMDecodeARM(instruction, &info);
|
||||
ARMDisassemble(&info, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly));
|
||||
printf("%08X\t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_ARM;
|
||||
} else {
|
||||
struct ARMInstructionInfo info2;
|
||||
struct ARMInstructionInfo combined;
|
||||
uint16_t instruction = debugger->d.core->busRead16(debugger->d.core, address);
|
||||
uint16_t instruction2 = debugger->d.core->busRead16(debugger->d.core, address + WORD_SIZE_THUMB);
|
||||
ARMDecodeThumb(instruction, &info);
|
||||
ARMDecodeThumb(instruction2, &info2);
|
||||
if (ARMDecodeThumbCombine(&info, &info2, &combined)) {
|
||||
ARMDisassemble(&combined, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X %04X\t%s\n", instruction, instruction2, disassembly);
|
||||
return WORD_SIZE_THUMB * 2;
|
||||
} else {
|
||||
ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X \t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_THUMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _printStatus(struct CLIDebuggerSystem* debugger) {
|
||||
struct ARMCore* cpu = debugger->p->d.core->cpu;
|
||||
int r;
|
||||
for (r = 0; r < 4; ++r) {
|
||||
printf("%08X %08X %08X %08X\n",
|
||||
cpu->gprs[r << 2],
|
||||
cpu->gprs[(r << 2) + 1],
|
||||
cpu->gprs[(r << 2) + 2],
|
||||
cpu->gprs[(r << 2) + 3]);
|
||||
}
|
||||
_printPSR(cpu->cpsr);
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
instructionLength = WORD_SIZE_ARM;
|
||||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
_printLine(debugger->p, cpu->gprs[ARM_PC] - instructionLength, mode);
|
||||
}
|
||||
|
||||
static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
struct ARMCore* cpu = debugger->d.core->cpu;
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t regid = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
if (regid >= ARM_PC) {
|
||||
return;
|
||||
}
|
||||
cpu->gprs[regid] = value;
|
||||
}
|
||||
|
||||
static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_ARM);
|
||||
}
|
||||
|
||||
static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_THUMB);
|
||||
}
|
||||
|
||||
static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
struct ARMCore* cpu = debugger->p->d.core->cpu;
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
return cpu->gprs[ARM_SP];
|
||||
}
|
||||
if (strcmp(name, "lr") == 0) {
|
||||
return cpu->gprs[ARM_LR];
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
return cpu->gprs[ARM_PC];
|
||||
}
|
||||
if (strcmp(name, "cpsr") == 0) {
|
||||
return cpu->cpsr.packed;
|
||||
}
|
||||
// TODO: test if mode has SPSR
|
||||
if (strcmp(name, "spsr") == 0) {
|
||||
return cpu->spsr.packed;
|
||||
}
|
||||
if (name[0] == 'r' && name[1] >= '0' && name[1] <= '9') {
|
||||
int reg = atoi(&name[1]);
|
||||
if (reg < 16) {
|
||||
return cpu->gprs[reg];
|
||||
}
|
||||
}
|
||||
dv->type = CLIDV_ERROR_TYPE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ARMCLIDebuggerCreate(struct CLIDebuggerSystem* debugger) {
|
||||
debugger->printStatus = _printStatus;
|
||||
debugger->disassemble = _disassemble;
|
||||
debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier;
|
||||
debugger->platformName = "ARM";
|
||||
debugger->platformCommands = _armCommands;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,14 @@
|
|||
/* Copyright (c) 2013-2016 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 ARM_CLI_DEBUGGER_H
|
||||
#define ARM_CLI_DEBUGGER_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct CLIDebuggerSystem;
|
||||
void ARMCLIDebuggerCreate(struct CLIDebuggerSystem* debugger);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,172 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "debugger.h"
|
||||
|
||||
#include "arm/arm.h"
|
||||
#include "arm/isa-inlines.h"
|
||||
#include "arm/debugger/memory-debugger.h"
|
||||
#include "core/core.h"
|
||||
|
||||
DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint);
|
||||
DEFINE_VECTOR(ARMDebugWatchpointList, struct ARMDebugWatchpoint);
|
||||
|
||||
static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointList* breakpoints, uint32_t address) {
|
||||
size_t i;
|
||||
for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) {
|
||||
if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) {
|
||||
return ARMDebugBreakpointListGetPointer(breakpoints, i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = debugger->cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
instructionLength = WORD_SIZE_ARM;
|
||||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength);
|
||||
if (!breakpoint) {
|
||||
return;
|
||||
}
|
||||
struct mDebuggerEntryInfo info = {
|
||||
.address = breakpoint->address
|
||||
};
|
||||
mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info);
|
||||
}
|
||||
|
||||
static void ARMDebuggerInit(void* cpu, struct mDebuggerPlatform* platform);
|
||||
static void ARMDebuggerDeinit(struct mDebuggerPlatform* platform);
|
||||
|
||||
static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info);
|
||||
|
||||
static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address);
|
||||
static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address);
|
||||
static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, enum mWatchpointType type);
|
||||
static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address);
|
||||
static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*);
|
||||
static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*);
|
||||
|
||||
struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) {
|
||||
struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger));
|
||||
platform->entered = ARMDebuggerEnter;
|
||||
platform->init = ARMDebuggerInit;
|
||||
platform->deinit = ARMDebuggerDeinit;
|
||||
platform->setBreakpoint = ARMDebuggerSetBreakpoint;
|
||||
platform->clearBreakpoint = ARMDebuggerClearBreakpoint;
|
||||
platform->setWatchpoint = ARMDebuggerSetWatchpoint;
|
||||
platform->clearWatchpoint = ARMDebuggerClearWatchpoint;
|
||||
platform->checkBreakpoints = ARMDebuggerCheckBreakpoints;
|
||||
platform->hasBreakpoints = ARMDebuggerHasBreakpoints;
|
||||
return platform;
|
||||
}
|
||||
|
||||
void ARMDebuggerInit(void* cpu, struct mDebuggerPlatform* platform) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) platform;
|
||||
debugger->cpu = cpu;
|
||||
debugger->originalMemory = debugger->cpu->memory;
|
||||
ARMDebugBreakpointListInit(&debugger->breakpoints, 0);
|
||||
ARMDebugBreakpointListInit(&debugger->swBreakpoints, 0);
|
||||
ARMDebugWatchpointListInit(&debugger->watchpoints, 0);
|
||||
}
|
||||
|
||||
void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) platform;
|
||||
ARMDebugBreakpointListDeinit(&debugger->breakpoints);
|
||||
ARMDebugBreakpointListDeinit(&debugger->swBreakpoints);
|
||||
ARMDebugWatchpointListDeinit(&debugger->watchpoints);
|
||||
}
|
||||
|
||||
static void ARMDebuggerEnter(struct mDebuggerPlatform* platform, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) platform;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
cpu->nextEvent = cpu->cycles;
|
||||
if (reason == DEBUGGER_ENTER_BREAKPOINT) {
|
||||
struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->swBreakpoints, _ARMPCAddress(cpu));
|
||||
if (breakpoint && breakpoint->isSw) {
|
||||
info->address = breakpoint->address;
|
||||
if (debugger->clearSoftwareBreakpoint) {
|
||||
debugger->clearSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, breakpoint->sw.opcode);
|
||||
}
|
||||
|
||||
ARMRunFake(cpu, breakpoint->sw.opcode);
|
||||
|
||||
if (debugger->setSoftwareBreakpoint) {
|
||||
debugger->setSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, &breakpoint->sw.opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debugger->entered) {
|
||||
debugger->entered(debugger->d.p, reason, info);
|
||||
}
|
||||
}
|
||||
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t address, enum ExecutionMode mode) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
uint32_t opcode;
|
||||
if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->swBreakpoints);
|
||||
breakpoint->address = address;
|
||||
breakpoint->isSw = true;
|
||||
breakpoint->sw.opcode = opcode;
|
||||
breakpoint->sw.mode = mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints);
|
||||
breakpoint->address = address;
|
||||
breakpoint->isSw = false;
|
||||
}
|
||||
|
||||
static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints;
|
||||
size_t i;
|
||||
for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) {
|
||||
if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) {
|
||||
ARMDebugBreakpointListShift(breakpoints, i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* d) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
return ARMDebugBreakpointListSize(&debugger->breakpoints) || ARMDebugWatchpointListSize(&debugger->watchpoints);
|
||||
}
|
||||
|
||||
static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, enum mWatchpointType type) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) {
|
||||
ARMDebuggerInstallMemoryShim(debugger);
|
||||
}
|
||||
struct ARMDebugWatchpoint* watchpoint = ARMDebugWatchpointListAppend(&debugger->watchpoints);
|
||||
watchpoint->address = address;
|
||||
watchpoint->type = type;
|
||||
}
|
||||
|
||||
static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
struct ARMDebugWatchpointList* watchpoints = &debugger->watchpoints;
|
||||
size_t i;
|
||||
for (i = 0; i < ARMDebugWatchpointListSize(watchpoints); ++i) {
|
||||
if (ARMDebugWatchpointListGetPointer(watchpoints, i)->address == address) {
|
||||
ARMDebugWatchpointListShift(watchpoints, i, 1);
|
||||
}
|
||||
}
|
||||
if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) {
|
||||
ARMDebuggerRemoveMemoryShim(debugger);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
struct ARMDebugBreakpoint {
|
||||
uint32_t address;
|
||||
bool isSw;
|
||||
struct {
|
||||
uint32_t opcode;
|
||||
enum ExecutionMode mode;
|
||||
} sw;
|
||||
};
|
||||
|
||||
struct ARMDebugWatchpoint {
|
||||
uint32_t address;
|
||||
enum mWatchpointType type;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint);
|
||||
DECLARE_VECTOR(ARMDebugWatchpointList, struct ARMDebugWatchpoint);
|
||||
|
||||
struct ARMDebugger {
|
||||
struct mDebuggerPlatform d;
|
||||
struct ARMCore* cpu;
|
||||
|
||||
struct ARMDebugBreakpointList breakpoints;
|
||||
struct ARMDebugBreakpointList swBreakpoints;
|
||||
struct ARMDebugWatchpointList watchpoints;
|
||||
struct ARMMemory originalMemory;
|
||||
|
||||
void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void);
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, uint32_t address, enum ExecutionMode mode);
|
|
@ -0,0 +1,144 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "memory-debugger.h"
|
||||
|
||||
#include "arm/debugger/debugger.h"
|
||||
|
||||
#include "util/math.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width);
|
||||
|
||||
#define FIND_DEBUGGER(DEBUGGER, CPU) \
|
||||
do { \
|
||||
DEBUGGER = 0; \
|
||||
size_t i; \
|
||||
for (i = 0; i < CPU->numComponents; ++i) { \
|
||||
if (CPU->components[i]->id == DEBUGGER_ID) { \
|
||||
DEBUGGER = (struct ARMDebugger*) ((struct mDebugger*) cpu->components[i])->platform; \
|
||||
goto debuggerFound; \
|
||||
} \
|
||||
} \
|
||||
abort(); \
|
||||
debuggerFound: break; \
|
||||
} while(0)
|
||||
|
||||
#define CREATE_SHIM(NAME, RETURN, TYPES, ...) \
|
||||
static RETURN DebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define CREATE_WATCHPOINT_READ_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \
|
||||
static RETURN DebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
struct mDebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_READ, 0, WIDTH)) { \
|
||||
mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define CREATE_WATCHPOINT_WRITE_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \
|
||||
static RETURN DebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
struct mDebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_WRITE, value, WIDTH)) { \
|
||||
mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME, ACCESS_TYPE) \
|
||||
static uint32_t DebuggerShim_ ## NAME (struct ARMCore* cpu, uint32_t address, int mask, enum LSMDirection direction, int* cycleCounter) { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
uint32_t popcount = popcount32(mask); \
|
||||
int offset = 4; \
|
||||
int base = address; \
|
||||
if (direction & LSM_D) { \
|
||||
offset = -4; \
|
||||
base -= (popcount << 2) - 4; \
|
||||
} \
|
||||
if (direction & LSM_B) { \
|
||||
base += offset; \
|
||||
} \
|
||||
unsigned i; \
|
||||
for (i = 0; i < popcount; ++i) { \
|
||||
struct mDebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, base + 4 * i, &info, ACCESS_TYPE, 0, 4)) { \
|
||||
mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \
|
||||
}
|
||||
|
||||
CREATE_WATCHPOINT_READ_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_READ_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_READ_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_WRITE_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_WRITE_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_WRITE_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple, WATCHPOINT_READ)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple, WATCHPOINT_WRITE)
|
||||
CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address)
|
||||
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width) {
|
||||
--width;
|
||||
struct ARMDebugWatchpoint* watchpoint;
|
||||
size_t i;
|
||||
for (i = 0; i < ARMDebugWatchpointListSize(&debugger->watchpoints); ++i) {
|
||||
watchpoint = ARMDebugWatchpointListGetPointer(&debugger->watchpoints, i);
|
||||
if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) {
|
||||
switch (width + 1) {
|
||||
case 1:
|
||||
info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 2:
|
||||
info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 4:
|
||||
info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0);
|
||||
break;
|
||||
}
|
||||
info->newValue = newValue;
|
||||
info->address = address;
|
||||
info->watchType = watchpoint->type;
|
||||
info->accessType = type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger) {
|
||||
debugger->originalMemory = debugger->cpu->memory;
|
||||
debugger->cpu->memory.store32 = DebuggerShim_store32;
|
||||
debugger->cpu->memory.store16 = DebuggerShim_store16;
|
||||
debugger->cpu->memory.store8 = DebuggerShim_store8;
|
||||
debugger->cpu->memory.load32 = DebuggerShim_load32;
|
||||
debugger->cpu->memory.load16 = DebuggerShim_load16;
|
||||
debugger->cpu->memory.load8 = DebuggerShim_load8;
|
||||
debugger->cpu->memory.storeMultiple = DebuggerShim_storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = DebuggerShim_loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = DebuggerShim_setActiveRegion;
|
||||
}
|
||||
|
||||
void ARMDebuggerRemoveMemoryShim(struct ARMDebugger* debugger) {
|
||||
debugger->cpu->memory.store32 = debugger->originalMemory.store32;
|
||||
debugger->cpu->memory.store16 = debugger->originalMemory.store16;
|
||||
debugger->cpu->memory.store8 = debugger->originalMemory.store8;
|
||||
debugger->cpu->memory.load32 = debugger->originalMemory.load32;
|
||||
debugger->cpu->memory.load16 = debugger->originalMemory.load16;
|
||||
debugger->cpu->memory.load8 = debugger->originalMemory.load8;
|
||||
debugger->cpu->memory.storeMultiple = debugger->originalMemory.storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = debugger->originalMemory.loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = debugger->originalMemory.setActiveRegion;
|
||||
}
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "arm.h"
|
||||
|
||||
struct ARMDebugger;
|
||||
|
||||
void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger);
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
#define ADDR_MODE_1_SHIFT(OP) \
|
||||
info->op3.reg = opcode & 0x0000000F; \
|
||||
info->op3.shifterOp = ARM_SHIFT_ ## OP; \
|
||||
info->operandFormat |= ARM_OPERAND_REGISTER_3; \
|
||||
if (opcode & 0x00000010) { \
|
||||
info->op3.shifterOp = ARM_SHIFT_ ## OP; \
|
||||
info->op3.shifterReg = (opcode >> 8) & 0xF; \
|
||||
++info->iCycles; \
|
||||
info->operandFormat |= ARM_OPERAND_SHIFT_REGISTER_3; \
|
||||
|
@ -101,11 +101,13 @@
|
|||
info->affectsCPSR = S; \
|
||||
SHIFTER; \
|
||||
if (SKIPPED == 1) { \
|
||||
info->operandFormat >>= 8; \
|
||||
info->op1 = info->op2; \
|
||||
info->op2 = info->op3; \
|
||||
info->operandFormat >>= 8; \
|
||||
} else if (SKIPPED == 2) { \
|
||||
info->operandFormat &= ~ARM_OPERAND_2; \
|
||||
info->op2 = info->op3; \
|
||||
info->operandFormat |= info->operandFormat >> 8; \
|
||||
info->operandFormat &= ~ARM_OPERAND_3; \
|
||||
} \
|
||||
if (info->op1.reg == ARM_PC) { \
|
||||
info->branchType = ARM_BRANCH_INDIRECT; \
|
||||
|
@ -435,18 +437,12 @@ static const ARMDecoder _armDecoderTable[0x1000] = {
|
|||
};
|
||||
|
||||
void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info) {
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->execMode = MODE_ARM;
|
||||
info->opcode = opcode;
|
||||
info->branchType = ARM_BRANCH_NONE;
|
||||
info->traps = 0;
|
||||
info->affectsCPSR = 0;
|
||||
info->condition = opcode >> 28;
|
||||
info->sDataCycles = 0;
|
||||
info->nDataCycles = 0;
|
||||
info->sInstructionCycles = 1;
|
||||
info->nInstructionCycles = 0;
|
||||
info->iCycles = 0;
|
||||
info->cCycles = 0;
|
||||
ARMDecoder decoder = _armDecoderTable[((opcode >> 16) & 0xFF0) | ((opcode >> 4) & 0x00F)];
|
||||
decoder(opcode, info);
|
||||
}
|
||||
|
|
|
@ -27,14 +27,14 @@
|
|||
ARM_OPERAND_REGISTER_2 | \
|
||||
ARM_OPERAND_IMMEDIATE_3;)
|
||||
|
||||
#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, MNEMONIC, CYCLES, WIDTH) \
|
||||
#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, MNEMONIC, CYCLES, WIDTH, AFFECTED) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->op1.reg = opcode & 0x0007; \
|
||||
info->memory.baseReg = (opcode >> 3) & 0x0007; \
|
||||
info->memory.offset.immediate = ((opcode >> 6) & 0x001F) * WIDTH; \
|
||||
info->memory.width = (enum ARMMemoryAccessType) WIDTH; \
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | \
|
||||
ARM_OPERAND_AFFECTED_1 | \
|
||||
ARM_OPERAND_AFFECTED_ ## AFFECTED | \
|
||||
ARM_OPERAND_MEMORY_2; \
|
||||
info->memory.format = ARM_MEMORY_REGISTER_BASE | \
|
||||
ARM_MEMORY_IMMEDIATE_OFFSET; \
|
||||
|
@ -43,12 +43,12 @@
|
|||
DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSL1, LSL)
|
||||
DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSR1, LSR)
|
||||
DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(ASR1, ASR)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDR1, LDR, LOAD_CYCLES, 4)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRB1, LDR, LOAD_CYCLES, 1)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRH1, LDR, LOAD_CYCLES, 2)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STR1, STR, STORE_CYCLES, 4)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRB1, STR, STORE_CYCLES, 1)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRH1, STR, STORE_CYCLES, 2)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDR1, LDR, LOAD_CYCLES, 4, 1)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRB1, LDR, LOAD_CYCLES, 1, 1)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRH1, LDR, LOAD_CYCLES, 2, 1)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STR1, STR, STORE_CYCLES, 4, 2)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRB1, STR, STORE_CYCLES, 1, 2)
|
||||
DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRH1, STR, STORE_CYCLES, 2, 2)
|
||||
|
||||
#define DEFINE_DATA_FORM_1_DECODER_THUMB(NAME, MNEMONIC) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
|
@ -143,7 +143,7 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
|
|||
|
||||
#define DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(NAME, MNEMONIC, REG) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->op1.reg = (opcode >> 6) & 0x0007; \
|
||||
info->op1.reg = (opcode >> 8) & 0x0007; \
|
||||
info->op2.reg = REG; \
|
||||
info->op3.immediate = (opcode & 0x00FF) << 2; \
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | \
|
||||
|
@ -151,47 +151,47 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
|
|||
ARM_OPERAND_REGISTER_2 | \
|
||||
ARM_OPERAND_IMMEDIATE_3;)
|
||||
|
||||
#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \
|
||||
#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES, AFFECTED) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->op1.reg = (opcode >> 8) & 0x0007; \
|
||||
info->memory.baseReg = REG; \
|
||||
info->memory.offset.immediate = (opcode & 0x00FF) << 2; \
|
||||
info->memory.width = ARM_ACCESS_WORD; \
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | \
|
||||
ARM_OPERAND_AFFECTED_1 | \
|
||||
ARM_OPERAND_AFFECTED_ ## AFFECTED | \
|
||||
ARM_OPERAND_MEMORY_2; \
|
||||
info->memory.format = ARM_MEMORY_REGISTER_BASE | \
|
||||
ARM_MEMORY_IMMEDIATE_OFFSET; \
|
||||
CYCLES;)
|
||||
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR3, LDR, ARM_PC, LOAD_CYCLES)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR4, LDR, ARM_SP, LOAD_CYCLES)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(STR3, STR, ARM_SP, STORE_CYCLES)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR3, LDR, ARM_PC, LOAD_CYCLES, 1)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR4, LDR, ARM_SP, LOAD_CYCLES, 1)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(STR3, STR, ARM_SP, STORE_CYCLES, 2)
|
||||
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD5, ADD, ARM_PC)
|
||||
DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD6, ADD, ARM_SP)
|
||||
|
||||
#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE) \
|
||||
#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE, AFFECTED) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->memory.offset.reg = (opcode >> 6) & 0x0007; \
|
||||
info->op1.reg = opcode & 0x0007; \
|
||||
info->memory.baseReg = (opcode >> 3) & 0x0007; \
|
||||
info->memory.width = TYPE; \
|
||||
info->operandFormat = ARM_OPERAND_REGISTER_1 | \
|
||||
ARM_OPERAND_AFFECTED_1 | /* TODO: Remove this for STR */ \
|
||||
ARM_OPERAND_AFFECTED_ ## AFFECTED | \
|
||||
ARM_OPERAND_MEMORY_2; \
|
||||
info->memory.format = ARM_MEMORY_REGISTER_BASE | \
|
||||
ARM_MEMORY_REGISTER_OFFSET; \
|
||||
CYCLES;)
|
||||
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, LDR, LOAD_CYCLES, ARM_ACCESS_WORD)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, LDR, LOAD_CYCLES, ARM_ACCESS_BYTE)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, LDR, LOAD_CYCLES, ARM_ACCESS_HALFWORD)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_BYTE)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_HALFWORD)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, STR, STORE_CYCLES, ARM_ACCESS_WORD)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, STR, STORE_CYCLES, ARM_ACCESS_BYTE)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFWORD)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, LDR, LOAD_CYCLES, ARM_ACCESS_WORD, 1)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, LDR, LOAD_CYCLES, ARM_ACCESS_BYTE, 1)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, LDR, LOAD_CYCLES, ARM_ACCESS_HALFWORD, 1)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_BYTE, 1)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_HALFWORD, 1)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, STR, STORE_CYCLES, ARM_ACCESS_WORD, 2)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, STR, STORE_CYCLES, ARM_ACCESS_BYTE, 2)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFWORD, 2)
|
||||
|
||||
// TODO: Estimate memory cycles
|
||||
#define DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(NAME, RN, MNEMONIC, DIRECTION, ADDITIONAL_REG) \
|
||||
|
@ -201,7 +201,7 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFW
|
|||
if (info->op1.immediate & (1 << ARM_PC)) { \
|
||||
info->branchType = ARM_BRANCH_INDIRECT; \
|
||||
} \
|
||||
info->operandFormat = ARM_OPERAND_MEMORY_1; \
|
||||
info->operandFormat = ARM_OPERAND_MEMORY_1 | ARM_OPERAND_AFFECTED_1; \
|
||||
info->memory.format = ARM_MEMORY_REGISTER_BASE | \
|
||||
ARM_MEMORY_WRITEBACK | \
|
||||
DIRECTION;)
|
||||
|
@ -299,18 +299,12 @@ static const ThumbDecoder _thumbDecoderTable[0x400] = {
|
|||
};
|
||||
|
||||
void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info) {
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->execMode = MODE_THUMB;
|
||||
info->opcode = opcode;
|
||||
info->branchType = ARM_BRANCH_NONE;
|
||||
info->traps = 0;
|
||||
info->affectsCPSR = 0;
|
||||
info->condition = ARM_CONDITION_AL;
|
||||
info->sDataCycles = 0;
|
||||
info->nDataCycles = 0;
|
||||
info->sInstructionCycles = 1;
|
||||
info->nInstructionCycles = 0;
|
||||
info->iCycles = 0;
|
||||
info->cCycles = 0;
|
||||
ThumbDecoder decoder = _thumbDecoderTable[opcode >> 6];
|
||||
decoder(opcode, info);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#define ARM_OPERAND_SHIFT_IMMEDIATE_4 0x20000000
|
||||
#define ARM_OPERAND_4 0xFF000000
|
||||
|
||||
#define ARM_OPERAND_MEMORY (ARM_OPERAND_MEMORY_1 | ARM_OPERAND_MEMORY_2 | ARM_OPERAND_MEMORY_3 | ARM_OPERAND_MEMORY_4)
|
||||
|
||||
#define ARM_MEMORY_REGISTER_BASE 0x0001
|
||||
#define ARM_MEMORY_IMMEDIATE_OFFSET 0x0002
|
||||
|
|
|
@ -76,20 +76,20 @@
|
|||
#define DECLARE_ARM_EMITTER_BLOCK(EMITTER) \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, AND, MUL, STRH, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ANDS, MULS, LDRH, LDRSB, LDRSH), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, EOR, MLA, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, EORS, MLAS, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, EOR, MLA, STRH, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, EORS, MLAS, LDRH, LDRSB, LDRSH), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, SUB, ILL, STRHI, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, SUBS, ILL, LDRHI, LDRSBI, LDRSHI), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSB, ILL, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSBS, ILL, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSB, ILL, STRHI, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSBS, ILL, LDRHI, LDRSBI, LDRSHI), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADD, UMULL, STRHU, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADDS, UMULLS, LDRHU, LDRSBU, LDRSHU), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADC, UMLAL, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADCS, UMLALS, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADC, UMLAL, STRHU, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, ADCS, UMLALS, LDRHU, LDRSBU, LDRSHU), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, SBC, SMULL, STRHIU, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, SBCS, SMULLS, LDRHIU, LDRSBIU, LDRSHIU), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSC, SMLAL, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSCS, SMLALS, ILL, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSC, SMLAL, STRHIU, ILL, ILL), \
|
||||
DECLARE_ARM_ALU_BLOCK(EMITTER, RSCS, SMLALS, LDRHIU, LDRSBIU, LDRSHIU), \
|
||||
DECLARE_INSTRUCTION_ARM(EMITTER, MRS), \
|
||||
DECLARE_INSTRUCTION_ARM(EMITTER, ILL), \
|
||||
DECLARE_INSTRUCTION_ARM(EMITTER, ILL), \
|
||||
|
|
|
@ -184,8 +184,6 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
// Instruction definitions
|
||||
// Beware pre-processor antics
|
||||
|
||||
#define NO_EXTEND64(V) (uint64_t)(uint32_t) (V)
|
||||
|
||||
#define ARM_ADDITION_S(M, N, D) \
|
||||
if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \
|
||||
cpu->cpsr = cpu->spsr; \
|
||||
|
@ -208,6 +206,17 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
cpu->cpsr.v = ARM_V_SUBTRACTION(M, N, D); \
|
||||
}
|
||||
|
||||
#define ARM_SUBTRACTION_CARRY_S(M, N, D, C) \
|
||||
if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \
|
||||
cpu->cpsr = cpu->spsr; \
|
||||
_ARMReadCPSR(cpu); \
|
||||
} else { \
|
||||
cpu->cpsr.n = ARM_SIGN(D); \
|
||||
cpu->cpsr.z = !(D); \
|
||||
cpu->cpsr.c = ARM_BORROW_FROM_CARRY(M, N, D, C); \
|
||||
cpu->cpsr.v = ARM_V_SUBTRACTION(M, N, D); \
|
||||
}
|
||||
|
||||
#define ARM_NEUTRAL_S(M, N, D) \
|
||||
if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \
|
||||
cpu->cpsr = cpu->spsr; \
|
||||
|
@ -454,14 +463,13 @@ DEFINE_ALU_INSTRUCTION_ARM(RSB, ARM_SUBTRACTION_S(cpu->shifterOperand, n, cpu->g
|
|||
int32_t n = cpu->gprs[rn];
|
||||
cpu->gprs[rd] = cpu->shifterOperand - n;)
|
||||
|
||||
DEFINE_ALU_INSTRUCTION_ARM(RSC, ARM_SUBTRACTION_S(cpu->shifterOperand, n, cpu->gprs[rd]),
|
||||
int32_t n = cpu->gprs[rn] + !cpu->cpsr.c;
|
||||
cpu->gprs[rd] = cpu->shifterOperand - n;)
|
||||
|
||||
DEFINE_ALU_INSTRUCTION_ARM(SBC, ARM_SUBTRACTION_S(n, shifterOperand, cpu->gprs[rd]),
|
||||
DEFINE_ALU_INSTRUCTION_ARM(RSC, ARM_SUBTRACTION_CARRY_S(cpu->shifterOperand, n, cpu->gprs[rd], !cpu->cpsr.c),
|
||||
int32_t n = cpu->gprs[rn];
|
||||
int32_t shifterOperand = cpu->shifterOperand + !cpu->cpsr.c;
|
||||
cpu->gprs[rd] = n - shifterOperand;)
|
||||
cpu->gprs[rd] = cpu->shifterOperand - n - !cpu->cpsr.c;)
|
||||
|
||||
DEFINE_ALU_INSTRUCTION_ARM(SBC, ARM_SUBTRACTION_CARRY_S(n, cpu->shifterOperand, cpu->gprs[rd], !cpu->cpsr.c),
|
||||
int32_t n = cpu->gprs[rn];
|
||||
cpu->gprs[rd] = n - cpu->shifterOperand - !cpu->cpsr.c;)
|
||||
|
||||
DEFINE_ALU_INSTRUCTION_ARM(SUB, ARM_SUBTRACTION_S(n, cpu->shifterOperand, cpu->gprs[rd]),
|
||||
int32_t n = cpu->gprs[rn];
|
||||
|
@ -495,7 +503,7 @@ DEFINE_MULTIPLY_INSTRUCTION_ARM(SMULL,
|
|||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]))
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_ARM(UMLAL,
|
||||
uint64_t d = NO_EXTEND64(cpu->gprs[rm]) * NO_EXTEND64(cpu->gprs[rs]);
|
||||
uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]);
|
||||
int32_t dm = cpu->gprs[rd];
|
||||
int32_t dn = d;
|
||||
cpu->gprs[rd] = dm + dn;
|
||||
|
@ -503,7 +511,7 @@ DEFINE_MULTIPLY_INSTRUCTION_ARM(UMLAL,
|
|||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]))
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_ARM(UMULL,
|
||||
uint64_t d = NO_EXTEND64(cpu->gprs[rm]) * NO_EXTEND64(cpu->gprs[rs]);
|
||||
uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]);
|
||||
cpu->gprs[rd] = d;
|
||||
cpu->gprs[rdHi] = d >> 32;,
|
||||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]))
|
||||
|
@ -516,7 +524,7 @@ DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDR, cpu->gprs[rd] = cpu->memory.load32(cpu, a
|
|||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDRB, cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = address & 1 ? ARM_SXT_8(cpu->memory.load16(cpu, address, ¤tCycles)) : ARM_SXT_16(cpu->memory.load16(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STR, cpu->memory.store32(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STRB, cpu->memory.store8(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)
|
||||
|
@ -524,28 +532,32 @@ DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address,
|
|||
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(LDRBT,
|
||||
enum PrivilegeMode priv = cpu->privilegeMode;
|
||||
ARMSetPrivilegeMode(cpu, MODE_USER);
|
||||
cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles);
|
||||
int32_t r = cpu->memory.load8(cpu, address, ¤tCycles);
|
||||
ARMSetPrivilegeMode(cpu, priv);
|
||||
cpu->gprs[rd] = r;
|
||||
ARM_LOAD_POST_BODY;)
|
||||
|
||||
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(LDRT,
|
||||
enum PrivilegeMode priv = cpu->privilegeMode;
|
||||
ARMSetPrivilegeMode(cpu, MODE_USER);
|
||||
cpu->gprs[rd] = cpu->memory.load32(cpu, address, ¤tCycles);
|
||||
int32_t r = cpu->memory.load32(cpu, address, ¤tCycles);
|
||||
ARMSetPrivilegeMode(cpu, priv);
|
||||
cpu->gprs[rd] = r;
|
||||
ARM_LOAD_POST_BODY;)
|
||||
|
||||
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRBT,
|
||||
enum PrivilegeMode priv = cpu->privilegeMode;
|
||||
int32_t r = cpu->gprs[rd];
|
||||
ARMSetPrivilegeMode(cpu, MODE_USER);
|
||||
cpu->memory.store32(cpu, address, cpu->gprs[rd], ¤tCycles);
|
||||
cpu->memory.store8(cpu, address, r, ¤tCycles);
|
||||
ARMSetPrivilegeMode(cpu, priv);
|
||||
ARM_STORE_POST_BODY;)
|
||||
|
||||
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRT,
|
||||
enum PrivilegeMode priv = cpu->privilegeMode;
|
||||
int32_t r = cpu->gprs[rd];
|
||||
ARMSetPrivilegeMode(cpu, MODE_USER);
|
||||
cpu->memory.store8(cpu, address, cpu->gprs[rd], ¤tCycles);
|
||||
cpu->memory.store32(cpu, address, r, ¤tCycles);
|
||||
ARMSetPrivilegeMode(cpu, priv);
|
||||
ARM_STORE_POST_BODY;)
|
||||
|
||||
|
@ -625,11 +637,21 @@ DEFINE_INSTRUCTION_ARM(MSR,
|
|||
if (mask & PSR_USER_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK);
|
||||
}
|
||||
if (mask & PSR_STATE_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_STATE_MASK) | (operand & PSR_STATE_MASK);
|
||||
}
|
||||
if (cpu->privilegeMode != MODE_USER && (mask & PSR_PRIV_MASK)) {
|
||||
ARMSetPrivilegeMode(cpu, (enum PrivilegeMode) ((operand & 0x0000000F) | 0x00000010));
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_PRIV_MASK) | (operand & PSR_PRIV_MASK);
|
||||
}
|
||||
_ARMReadCPSR(cpu);)
|
||||
_ARMReadCPSR(cpu);
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
LOAD_16(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
} else {
|
||||
LOAD_32(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
})
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MSRR,
|
||||
int c = opcode & 0x00010000;
|
||||
|
@ -637,7 +659,7 @@ DEFINE_INSTRUCTION_ARM(MSRR,
|
|||
int32_t operand = cpu->gprs[opcode & 0x0000000F];
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK;
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);)
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;)
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MRS, \
|
||||
int rd = (opcode >> 12) & 0xF; \
|
||||
|
@ -656,11 +678,21 @@ DEFINE_INSTRUCTION_ARM(MSRI,
|
|||
if (mask & PSR_USER_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK);
|
||||
}
|
||||
if (mask & PSR_STATE_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_STATE_MASK) | (operand & PSR_STATE_MASK);
|
||||
}
|
||||
if (cpu->privilegeMode != MODE_USER && (mask & PSR_PRIV_MASK)) {
|
||||
ARMSetPrivilegeMode(cpu, (enum PrivilegeMode) ((operand & 0x0000000F) | 0x00000010));
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_PRIV_MASK) | (operand & PSR_PRIV_MASK);
|
||||
}
|
||||
_ARMReadCPSR(cpu);)
|
||||
_ARMReadCPSR(cpu);
|
||||
if (cpu->executionMode == MODE_THUMB) {
|
||||
LOAD_16(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
} else {
|
||||
LOAD_32(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion);
|
||||
})
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MSRRI,
|
||||
int c = opcode & 0x00010000;
|
||||
|
@ -669,7 +701,7 @@ DEFINE_INSTRUCTION_ARM(MSRRI,
|
|||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK;
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);)
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;)
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(SWI, cpu->irqh.swi32(cpu, opcode & 0xFFFFFF))
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
struct ARMCore;
|
||||
|
||||
typedef void (*ARMInstruction)(struct ARMCore*, uint32_t opcode);
|
||||
const ARMInstruction _armTable[0x1000];
|
||||
extern const ARMInstruction _armTable[0x1000];
|
||||
|
||||
#endif
|
||||
|
|
|
@ -29,9 +29,11 @@
|
|||
#define ARM_SIGN(I) ((I) >> 31)
|
||||
#define ARM_SXT_8(I) (((int8_t) (I) << 24) >> 24)
|
||||
#define ARM_SXT_16(I) (((int16_t) (I) << 16) >> 16)
|
||||
#define ARM_UXT_64(I) (uint64_t)(uint32_t) (I)
|
||||
|
||||
#define ARM_CARRY_FROM(M, N, D) (((uint32_t) (M) >> 31) + ((uint32_t) (N) >> 31) > ((uint32_t) (D) >> 31))
|
||||
#define ARM_BORROW_FROM(M, N, D) (((uint32_t) (M)) >= ((uint32_t) (N)))
|
||||
#define ARM_BORROW_FROM_CARRY(M, N, D, C) (ARM_UXT_64(M) >= (ARM_UXT_64(N)) + (uint64_t) (C))
|
||||
#define ARM_V_ADDITION(M, N, D) (!(ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D))) && (ARM_SIGN((N) ^ (D))))
|
||||
#define ARM_V_SUBTRACTION(M, N, D) ((ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D))))
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, cpu->gprs[rd] = cpu->memory.load32(c
|
|||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, rm = cpu->gprs[rn] + cpu->gprs[rm]; cpu->gprs[rd] = rm & 1 ? ARM_SXT_8(cpu->memory.load16(cpu, rm, ¤tCycles)) : ARM_SXT_16(cpu->memory.load16(cpu, rm, ¤tCycles)); THUMB_LOAD_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, cpu->memory.store32(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, cpu->memory.store8(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)
|
||||
|
@ -309,7 +309,7 @@ DEFINE_LOAD_STORE_MULTIPLE_THUMB(STMIA,
|
|||
DEFINE_INSTRUCTION_THUMB(B ## COND, \
|
||||
if (ARM_COND_ ## COND) { \
|
||||
int8_t immediate = opcode; \
|
||||
cpu->gprs[ARM_PC] += immediate << 1; \
|
||||
cpu->gprs[ARM_PC] += (int32_t) immediate << 1; \
|
||||
THUMB_WRITE_PC; \
|
||||
})
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
struct ARMCore;
|
||||
|
||||
typedef void (*ThumbInstruction)(struct ARMCore*, uint16_t opcode);
|
||||
const ThumbInstruction _thumbTable[0x400];
|
||||
extern const ThumbInstruction _thumbTable[0x400];
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#define LOAD_64 LOAD_64LE
|
||||
#define LOAD_32 LOAD_32LE
|
||||
#define LOAD_16 LOAD_16LE
|
||||
#define STORE_64 STORE_64LE
|
||||
#define STORE_32 STORE_32LE
|
||||
#define STORE_16 STORE_16LE
|
||||
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "cheats.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "util/string.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define MAX_LINE_LENGTH 128
|
||||
|
||||
const uint32_t M_CHEAT_DEVICE_ID = 0xABADC0DE;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(CHEATS, "Cheats");
|
||||
|
||||
DEFINE_VECTOR(mCheatList, struct mCheat);
|
||||
DEFINE_VECTOR(mCheatSets, struct mCheatSet*);
|
||||
DEFINE_VECTOR(StringList, char*);
|
||||
|
||||
static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return core->busRead8(core, address);
|
||||
case 2:
|
||||
return core->busRead16(core, address);
|
||||
case 4:
|
||||
return core->busRead32(core, address);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
core->busWrite8(core, address, value);
|
||||
break;
|
||||
case 2:
|
||||
core->busWrite16(core, address, value);
|
||||
break;
|
||||
case 4:
|
||||
core->busWrite32(core, address, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void mCheatDeviceInit(void*, struct mCPUComponent*);
|
||||
static void mCheatDeviceDeinit(struct mCPUComponent*);
|
||||
|
||||
void mCheatDeviceCreate(struct mCheatDevice* device) {
|
||||
device->d.id = M_CHEAT_DEVICE_ID;
|
||||
device->d.init = mCheatDeviceInit;
|
||||
device->d.deinit = mCheatDeviceDeinit;
|
||||
mCheatSetsInit(&device->cheats, 4);
|
||||
}
|
||||
|
||||
void mCheatDeviceDestroy(struct mCheatDevice* device) {
|
||||
mCheatDeviceClear(device);
|
||||
mCheatSetsDeinit(&device->cheats);
|
||||
}
|
||||
|
||||
void mCheatDeviceClear(struct mCheatDevice* device) {
|
||||
size_t i;
|
||||
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
||||
struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
|
||||
mCheatSetDeinit(set);
|
||||
}
|
||||
mCheatSetsClear(&device->cheats);
|
||||
}
|
||||
|
||||
void mCheatSetInit(struct mCheatSet* set, const char* name) {
|
||||
mCheatListInit(&set->list, 4);
|
||||
StringListInit(&set->lines, 4);
|
||||
if (name) {
|
||||
set->name = strdup(name);
|
||||
} else {
|
||||
set->name = 0;
|
||||
}
|
||||
set->enabled = true;
|
||||
}
|
||||
|
||||
void mCheatSetDeinit(struct mCheatSet* set) {
|
||||
mCheatListDeinit(&set->list);
|
||||
size_t i;
|
||||
for (i = 0; i < StringListSize(&set->lines); ++i) {
|
||||
free(*StringListGetPointer(&set->lines, i));
|
||||
}
|
||||
if (set->name) {
|
||||
free(set->name);
|
||||
}
|
||||
set->deinit(set);
|
||||
free(set);
|
||||
}
|
||||
|
||||
void mCheatSetRename(struct mCheatSet* set, const char* name) {
|
||||
if (set->name) {
|
||||
free(set->name);
|
||||
set->name = NULL;
|
||||
}
|
||||
if (name) {
|
||||
set->name = strdup(name);
|
||||
}
|
||||
}
|
||||
|
||||
bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
|
||||
if (!set->addLine(set, line, type)) {
|
||||
return false;
|
||||
}
|
||||
*StringListAppend(&set->lines) = strdup(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
||||
*mCheatSetsAppend(&device->cheats) = cheats;
|
||||
cheats->add(cheats, device);
|
||||
}
|
||||
|
||||
void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
||||
size_t i;
|
||||
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
||||
if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == mCheatSetsSize(&device->cheats)) {
|
||||
return;
|
||||
}
|
||||
mCheatSetsShift(&device->cheats, i, 1);
|
||||
cheats->remove(cheats, device);
|
||||
}
|
||||
|
||||
bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
|
||||
#warning Cheat loading is currently broken
|
||||
return false;
|
||||
#if 0
|
||||
char cheat[MAX_LINE_LENGTH];
|
||||
struct mCheatSet* set = NULL;
|
||||
struct mCheatSet* newSet;
|
||||
bool nextDisabled = false;
|
||||
void* directives = NULL;
|
||||
while (true) {
|
||||
size_t i = 0;
|
||||
ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
||||
if (bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
if (bytesRead < 0) {
|
||||
return false;
|
||||
}
|
||||
while (isspace((int) cheat[i])) {
|
||||
++i;
|
||||
}
|
||||
switch (cheat[i]) {
|
||||
case '#':
|
||||
do {
|
||||
++i;
|
||||
} while (isspace((int) cheat[i]));
|
||||
cheat[strlen(cheat) - 1] = '\0'; // Remove trailing newline
|
||||
newSet = device->createSet(device, &cheat[i]);
|
||||
newSet->enabled = !nextDisabled;
|
||||
nextDisabled = false;
|
||||
if (set) {
|
||||
mCheatAddSet(device, set);
|
||||
}
|
||||
if (set) {
|
||||
newSet->copyProperties(newSet, set);
|
||||
}
|
||||
set = newSet;
|
||||
break;
|
||||
case '!':
|
||||
do {
|
||||
++i;
|
||||
} while (isspace((int) cheat[i]));
|
||||
if (strcasecmp(&cheat[i], "disabled") == 0) {
|
||||
nextDisabled = true;
|
||||
break;
|
||||
}
|
||||
if (strcasecmp(&cheat[i], "reset") == 0) {
|
||||
directives = NULL;
|
||||
break;
|
||||
}
|
||||
directives = set->parseDirective(set, &cheat[i], directives);
|
||||
break;
|
||||
default:
|
||||
if (!set) {
|
||||
set = device->createSet(device, NULL);
|
||||
set->enabled = !nextDisabled;
|
||||
nextDisabled = false;
|
||||
}
|
||||
mCheatAddLine(set, cheat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (set) {
|
||||
mCheatAddSet(device, set);
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
|
||||
#warning Cheat saving is currently broken
|
||||
return false;
|
||||
#if 0
|
||||
static const char lineStart[3] = "# ";
|
||||
static const char lineEnd = '\n';
|
||||
void* directives = NULL;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
||||
struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
|
||||
void* directives = set->dumpDirectives(set, vf, directives);
|
||||
if (!set->enabled) {
|
||||
static const char* disabledDirective = "!disabled\n";
|
||||
vf->write(vf, disabledDirective, strlen(disabledDirective));
|
||||
}
|
||||
|
||||
vf->write(vf, lineStart, 2);
|
||||
if (set->name) {
|
||||
vf->write(vf, set->name, strlen(set->name));
|
||||
}
|
||||
vf->write(vf, &lineEnd, 1);
|
||||
size_t c;
|
||||
for (c = 0; c < StringListSize(&set->lines); ++c) {
|
||||
const char* line = *StringListGetPointer(&set->lines, c);
|
||||
vf->write(vf, line, strlen(line));
|
||||
vf->write(vf, &lineEnd, 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
||||
if (!cheats->enabled) {
|
||||
return;
|
||||
}
|
||||
bool condition = true;
|
||||
int conditionRemaining = 0;
|
||||
int negativeConditionRemaining = 0;
|
||||
cheats->refresh(cheats, device);
|
||||
|
||||
size_t nCodes = mCheatListSize(&cheats->list);
|
||||
size_t i;
|
||||
for (i = 0; i < nCodes; ++i) {
|
||||
if (conditionRemaining > 0) {
|
||||
--conditionRemaining;
|
||||
if (!condition) {
|
||||
continue;
|
||||
}
|
||||
} else if (negativeConditionRemaining > 0) {
|
||||
conditionRemaining = negativeConditionRemaining - 1;
|
||||
negativeConditionRemaining = 0;
|
||||
condition = !condition;
|
||||
if (!condition) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
condition = true;
|
||||
}
|
||||
struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
|
||||
int32_t value = 0;
|
||||
int32_t operand = cheat->operand;
|
||||
uint32_t operationsRemaining = cheat->repeat;
|
||||
uint32_t address = cheat->address;
|
||||
bool performAssignment = false;
|
||||
for (; operationsRemaining; --operationsRemaining) {
|
||||
switch (cheat->type) {
|
||||
case CHEAT_ASSIGN:
|
||||
value = operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_ASSIGN_INDIRECT:
|
||||
value = operand;
|
||||
address = _readMem(device->p, address + cheat->addressOffset, 4);
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_AND:
|
||||
value = _readMem(device->p, address, cheat->width) & operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_ADD:
|
||||
value = _readMem(device->p, address, cheat->width) + operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_OR:
|
||||
value = _readMem(device->p, address, cheat->width) | operand;
|
||||
performAssignment = true;
|
||||
break;
|
||||
case CHEAT_IF_EQ:
|
||||
condition = _readMem(device->p, address, cheat->width) == operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_NE:
|
||||
condition = _readMem(device->p, address, cheat->width) != operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_LT:
|
||||
condition = _readMem(device->p, address, cheat->width) < operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_GT:
|
||||
condition = _readMem(device->p, address, cheat->width) > operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_ULT:
|
||||
condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_UGT:
|
||||
condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_AND:
|
||||
condition = _readMem(device->p, address, cheat->width) & operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
case CHEAT_IF_LAND:
|
||||
condition = _readMem(device->p, address, cheat->width) && operand;
|
||||
conditionRemaining = cheat->repeat;
|
||||
negativeConditionRemaining = cheat->negativeRepeat;
|
||||
break;
|
||||
}
|
||||
|
||||
if (performAssignment) {
|
||||
_writeMem(device->p, address, cheat->width, value);
|
||||
}
|
||||
|
||||
address += cheat->addressOffset;
|
||||
operand += cheat->operandOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
|
||||
UNUSED(cpu);
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) component;
|
||||
size_t i;
|
||||
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
||||
struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
|
||||
cheats->add(cheats, device);
|
||||
}
|
||||
}
|
||||
|
||||
void mCheatDeviceDeinit(struct mCPUComponent* component) {
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) component;
|
||||
size_t i;
|
||||
for (i = mCheatSetsSize(&device->cheats); i--;) {
|
||||
struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
|
||||
cheats->remove(cheats, device);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/* Copyright (c) 2013-2016 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 CHEATS_H
|
||||
#define CHEATS_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/cpu.h"
|
||||
#include "core/log.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
#define MAX_ROM_PATCHES 4
|
||||
|
||||
enum mCheatType {
|
||||
CHEAT_ASSIGN,
|
||||
CHEAT_ASSIGN_INDIRECT,
|
||||
CHEAT_AND,
|
||||
CHEAT_ADD,
|
||||
CHEAT_OR,
|
||||
CHEAT_IF_EQ,
|
||||
CHEAT_IF_NE,
|
||||
CHEAT_IF_LT,
|
||||
CHEAT_IF_GT,
|
||||
CHEAT_IF_ULT,
|
||||
CHEAT_IF_UGT,
|
||||
CHEAT_IF_AND,
|
||||
CHEAT_IF_LAND
|
||||
};
|
||||
|
||||
struct mCheat {
|
||||
enum mCheatType type;
|
||||
int width;
|
||||
uint32_t address;
|
||||
uint32_t operand;
|
||||
uint32_t repeat;
|
||||
uint32_t negativeRepeat;
|
||||
|
||||
int32_t addressOffset;
|
||||
int32_t operandOffset;
|
||||
};
|
||||
|
||||
mLOG_DECLARE_CATEGORY(CHEATS);
|
||||
|
||||
DECLARE_VECTOR(mCheatList, struct mCheat);
|
||||
DECLARE_VECTOR(StringList, char*);
|
||||
|
||||
struct mCheatDevice;
|
||||
struct mCheatSet {
|
||||
struct mCheatList list;
|
||||
|
||||
void (*deinit)(struct mCheatSet* set);
|
||||
void (*add)(struct mCheatSet* set, struct mCheatDevice* device);
|
||||
void (*remove)(struct mCheatSet* set, struct mCheatDevice* device);
|
||||
|
||||
bool (*addLine)(struct mCheatSet* set, const char* cheat, int type);
|
||||
void (*copyProperties)(struct mCheatSet* set, struct mCheatSet* oldSet);
|
||||
|
||||
void (*refresh)(struct mCheatSet* set, struct mCheatDevice* device);
|
||||
|
||||
char* name;
|
||||
bool enabled;
|
||||
struct StringList lines;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(mCheatSets, struct mCheatSet*);
|
||||
|
||||
struct mCheatDevice {
|
||||
struct mCPUComponent d;
|
||||
struct mCore* p;
|
||||
|
||||
struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name);
|
||||
|
||||
struct mCheatSets cheats;
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
|
||||
void mCheatDeviceCreate(struct mCheatDevice*);
|
||||
void mCheatDeviceDestroy(struct mCheatDevice*);
|
||||
void mCheatDeviceClear(struct mCheatDevice*);
|
||||
|
||||
void mCheatSetInit(struct mCheatSet*, const char* name);
|
||||
void mCheatSetDeinit(struct mCheatSet*);
|
||||
void mCheatSetRename(struct mCheatSet*, const char* name);
|
||||
|
||||
bool mCheatAddLine(struct mCheatSet*, const char* line, int type);
|
||||
|
||||
void mCheatAddSet(struct mCheatDevice*, struct mCheatSet*);
|
||||
void mCheatRemoveSet(struct mCheatDevice*, struct mCheatSet*);
|
||||
|
||||
bool mCheatParseFile(struct mCheatDevice*, struct VFile*);
|
||||
bool mCheatSaveFile(struct mCheatDevice*, struct VFile*);
|
||||
|
||||
void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*);
|
||||
|
||||
#endif
|
|
@ -1,10 +1,11 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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 "config.h"
|
||||
|
||||
#include "core/version.h"
|
||||
#include "util/formatting.h"
|
||||
#include "util/string.h"
|
||||
#include "util/vfs.h"
|
||||
|
@ -28,7 +29,7 @@
|
|||
|
||||
#define SECTION_NAME_MAX 128
|
||||
|
||||
static const char* _lookupValue(const struct GBAConfig* config, const char* key) {
|
||||
static const char* _lookupValue(const struct mCoreConfig* config, const char* key) {
|
||||
const char* value;
|
||||
if (config->port) {
|
||||
value = ConfigurationGetValue(&config->overridesTable, config->port, key);
|
||||
|
@ -59,7 +60,7 @@ static const char* _lookupValue(const struct GBAConfig* config, const char* key)
|
|||
return ConfigurationGetValue(&config->defaultsTable, 0, key);
|
||||
}
|
||||
|
||||
static bool _lookupCharValue(const struct GBAConfig* config, const char* key, char** out) {
|
||||
static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) {
|
||||
const char* value = _lookupValue(config, key);
|
||||
if (!value) {
|
||||
return false;
|
||||
|
@ -71,7 +72,7 @@ static bool _lookupCharValue(const struct GBAConfig* config, const char* key, ch
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool _lookupIntValue(const struct GBAConfig* config, const char* key, int* out) {
|
||||
static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, int* out) {
|
||||
const char* charValue = _lookupValue(config, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
|
@ -85,7 +86,7 @@ static bool _lookupIntValue(const struct GBAConfig* config, const char* key, int
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool _lookupUIntValue(const struct GBAConfig* config, const char* key, unsigned* out) {
|
||||
static bool _lookupUIntValue(const struct mCoreConfig* config, const char* key, unsigned* out) {
|
||||
const char* charValue = _lookupValue(config, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
|
@ -99,7 +100,7 @@ static bool _lookupUIntValue(const struct GBAConfig* config, const char* key, un
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, float* out) {
|
||||
static bool _lookupFloatValue(const struct mCoreConfig* config, const char* key, float* out) {
|
||||
const char* charValue = _lookupValue(config, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
|
@ -113,7 +114,7 @@ static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, f
|
|||
return true;
|
||||
}
|
||||
|
||||
void GBAConfigInit(struct GBAConfig* config, const char* port) {
|
||||
void mCoreConfigInit(struct mCoreConfig* config, const char* port) {
|
||||
ConfigurationInit(&config->configTable);
|
||||
ConfigurationInit(&config->defaultsTable);
|
||||
ConfigurationInit(&config->overridesTable);
|
||||
|
@ -125,36 +126,37 @@ void GBAConfigInit(struct GBAConfig* config, const char* port) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBAConfigDeinit(struct GBAConfig* config) {
|
||||
void mCoreConfigDeinit(struct mCoreConfig* config) {
|
||||
ConfigurationDeinit(&config->configTable);
|
||||
ConfigurationDeinit(&config->defaultsTable);
|
||||
ConfigurationDeinit(&config->overridesTable);
|
||||
free(config->port);
|
||||
}
|
||||
|
||||
bool GBAConfigLoad(struct GBAConfig* config) {
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
bool mCoreConfigLoad(struct mCoreConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
mCoreConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return GBAConfigLoadPath(config, path);
|
||||
return mCoreConfigLoadPath(config, path);
|
||||
}
|
||||
|
||||
bool GBAConfigSave(const struct GBAConfig* config) {
|
||||
bool mCoreConfigSave(const struct mCoreConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
mCoreConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return GBAConfigSavePath(config, path);
|
||||
return mCoreConfigSavePath(config, path);
|
||||
}
|
||||
|
||||
bool GBAConfigLoadPath(struct GBAConfig* config, const char* path) {
|
||||
bool mCoreConfigLoadPath(struct mCoreConfig* config, const char* path) {
|
||||
return ConfigurationRead(&config->configTable, path);
|
||||
}
|
||||
|
||||
bool GBAConfigSavePath(const struct GBAConfig* config, const char* path) {
|
||||
bool mCoreConfigSavePath(const struct mCoreConfig* config, const char* path) {
|
||||
return ConfigurationWrite(&config->configTable, path);
|
||||
}
|
||||
|
||||
void GBAConfigMakePortable(const struct GBAConfig* config) {
|
||||
void mCoreConfigMakePortable(const struct mCoreConfig* config) {
|
||||
struct VFile* portable = 0;
|
||||
#ifdef _WIN32
|
||||
char out[MAX_PATH];
|
||||
|
@ -177,11 +179,11 @@ void GBAConfigMakePortable(const struct GBAConfig* config) {
|
|||
#endif
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
GBAConfigSave(config);
|
||||
mCoreConfigSave(config);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAConfigDirectory(char* out, size_t outLength) {
|
||||
void mCoreConfigDirectory(char* out, size_t outLength) {
|
||||
struct VFile* portable;
|
||||
#ifdef _WIN32
|
||||
wchar_t wpath[MAX_PATH];
|
||||
|
@ -205,15 +207,16 @@ void GBAConfigDirectory(char* out, size_t outLength) {
|
|||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
#elif defined(PSP2)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "cache0:/%s", projectName);
|
||||
snprintf(out, outLength, "ux0:/%s", projectName);
|
||||
sceIoMkdir(out, 0777);
|
||||
#elif defined(GEKKO)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "/%s", projectName);
|
||||
mkdir(out, 0777);
|
||||
#elif defined(_3DS)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "/%s", projectName);
|
||||
FSUSER_CreateDirectory(0, sdmcArchive, FS_makePath(PATH_CHAR, out));
|
||||
FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, out), 0);
|
||||
#else
|
||||
getcwd(out, outLength);
|
||||
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
|
||||
|
@ -231,73 +234,75 @@ void GBAConfigDirectory(char* out, size_t outLength) {
|
|||
mkdir(out, 0755);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) {
|
||||
const char* mCoreConfigGetValue(const struct mCoreConfig* config, const char* key) {
|
||||
return _lookupValue(config, key);
|
||||
}
|
||||
|
||||
bool GBAConfigGetIntValue(const struct GBAConfig* config, const char* key, int* value) {
|
||||
bool mCoreConfigGetIntValue(const struct mCoreConfig* config, const char* key, int* value) {
|
||||
return _lookupIntValue(config, key, value);
|
||||
}
|
||||
|
||||
bool GBAConfigGetUIntValue(const struct GBAConfig* config, const char* key, unsigned* value) {
|
||||
bool mCoreConfigGetUIntValue(const struct mCoreConfig* config, const char* key, unsigned* value) {
|
||||
return _lookupUIntValue(config, key, value);
|
||||
}
|
||||
|
||||
bool GBAConfigGetFloatValue(const struct GBAConfig* config, const char* key, float* value) {
|
||||
bool mCoreConfigGetFloatValue(const struct mCoreConfig* config, const char* key, float* value) {
|
||||
return _lookupFloatValue(config, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetValue(struct GBAConfig* config, const char* key, const char* value) {
|
||||
void mCoreConfigSetValue(struct mCoreConfig* config, const char* key, const char* value) {
|
||||
ConfigurationSetValue(&config->configTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetIntValue(struct GBAConfig* config, const char* key, int value) {
|
||||
void mCoreConfigSetIntValue(struct mCoreConfig* config, const char* key, int value) {
|
||||
ConfigurationSetIntValue(&config->configTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetUIntValue(struct GBAConfig* config, const char* key, unsigned value) {
|
||||
void mCoreConfigSetUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
|
||||
ConfigurationSetUIntValue(&config->configTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetFloatValue(struct GBAConfig* config, const char* key, float value) {
|
||||
void mCoreConfigSetFloatValue(struct mCoreConfig* config, const char* key, float value) {
|
||||
ConfigurationSetFloatValue(&config->configTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetDefaultValue(struct GBAConfig* config, const char* key, const char* value) {
|
||||
void mCoreConfigSetDefaultValue(struct mCoreConfig* config, const char* key, const char* value) {
|
||||
ConfigurationSetValue(&config->defaultsTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetDefaultIntValue(struct GBAConfig* config, const char* key, int value) {
|
||||
void mCoreConfigSetDefaultIntValue(struct mCoreConfig* config, const char* key, int value) {
|
||||
ConfigurationSetIntValue(&config->defaultsTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetDefaultUIntValue(struct GBAConfig* config, const char* key, unsigned value) {
|
||||
void mCoreConfigSetDefaultUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
|
||||
ConfigurationSetUIntValue(&config->defaultsTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetDefaultFloatValue(struct GBAConfig* config, const char* key, float value) {
|
||||
void mCoreConfigSetDefaultFloatValue(struct mCoreConfig* config, const char* key, float value) {
|
||||
ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideValue(struct GBAConfig* config, const char* key, const char* value) {
|
||||
void mCoreConfigSetOverrideValue(struct mCoreConfig* config, const char* key, const char* value) {
|
||||
ConfigurationSetValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideIntValue(struct GBAConfig* config, const char* key, int value) {
|
||||
void mCoreConfigSetOverrideIntValue(struct mCoreConfig* config, const char* key, int value) {
|
||||
ConfigurationSetIntValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideUIntValue(struct GBAConfig* config, const char* key, unsigned value) {
|
||||
void mCoreConfigSetOverrideUIntValue(struct mCoreConfig* config, const char* key, unsigned value) {
|
||||
ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideFloatValue(struct GBAConfig* config, const char* key, float value) {
|
||||
void mCoreConfigSetOverrideFloatValue(struct mCoreConfig* config, const char* key, float value) {
|
||||
ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
||||
void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) {
|
||||
_lookupCharValue(config, "bios", &opts->bios);
|
||||
_lookupCharValue(config, "shader", &opts->shader);
|
||||
_lookupIntValue(config, "logLevel", &opts->logLevel);
|
||||
_lookupIntValue(config, "frameskip", &opts->frameskip);
|
||||
_lookupIntValue(config, "volume", &opts->volume);
|
||||
|
@ -343,21 +348,15 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
_lookupIntValue(config, "width", &opts->width);
|
||||
_lookupIntValue(config, "height", &opts->height);
|
||||
|
||||
char* idleOptimization = 0;
|
||||
if (_lookupCharValue(config, "idleOptimization", &idleOptimization)) {
|
||||
if (strcasecmp(idleOptimization, "ignore") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_IGNORE;
|
||||
} else if (strcasecmp(idleOptimization, "remove") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
} else if (strcasecmp(idleOptimization, "detect") == 0) {
|
||||
opts->idleOptimization = IDLE_LOOP_DETECT;
|
||||
}
|
||||
free(idleOptimization);
|
||||
}
|
||||
_lookupCharValue(config, "savegamePath", &opts->savegamePath);
|
||||
_lookupCharValue(config, "savestatePath", &opts->savestatePath);
|
||||
_lookupCharValue(config, "screenshotPath", &opts->screenshotPath);
|
||||
_lookupCharValue(config, "patchPath", &opts->patchPath);
|
||||
}
|
||||
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) {
|
||||
void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "shader", opts->shader);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
|
||||
|
@ -378,30 +377,32 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
|
|||
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:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "ignore");
|
||||
break;
|
||||
case IDLE_LOOP_REMOVE:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "remove");
|
||||
break;
|
||||
case IDLE_LOOP_DETECT:
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "detect");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// These two are basically placeholders in case the internal layout changes, e.g. for loading separate files
|
||||
struct Configuration* GBAConfigGetInput(struct GBAConfig* config) {
|
||||
struct Configuration* mCoreConfigGetInput(struct mCoreConfig* config) {
|
||||
return &config->configTable;
|
||||
}
|
||||
|
||||
struct Configuration* GBAConfigGetOverrides(struct GBAConfig* config) {
|
||||
struct Configuration* mCoreConfigGetOverrides(struct mCoreConfig* config) {
|
||||
return &config->configTable;
|
||||
}
|
||||
|
||||
void GBAConfigFreeOpts(struct GBAOptions* opts) {
|
||||
const struct Configuration* mCoreConfigGetOverridesConst(const struct mCoreConfig* config) {
|
||||
return &config->configTable;
|
||||
}
|
||||
|
||||
void mCoreConfigFreeOpts(struct mCoreOptions* opts) {
|
||||
free(opts->bios);
|
||||
free(opts->shader);
|
||||
free(opts->savegamePath);
|
||||
free(opts->savestatePath);
|
||||
free(opts->screenshotPath);
|
||||
free(opts->patchPath);
|
||||
opts->bios = 0;
|
||||
opts->shader = 0;
|
||||
opts->savegamePath = 0;
|
||||
opts->savestatePath = 0;
|
||||
opts->screenshotPath = 0;
|
||||
opts->patchPath = 0;
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/* Copyright (c) 2013-2016 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 M_CORE_CONFIG_H
|
||||
#define M_CORE_CONFIG_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
|
||||
struct mCoreConfig {
|
||||
struct Configuration configTable;
|
||||
struct Configuration defaultsTable;
|
||||
struct Configuration overridesTable;
|
||||
char* port;
|
||||
};
|
||||
|
||||
struct mCoreOptions {
|
||||
char* bios;
|
||||
bool skipBios;
|
||||
bool useBios;
|
||||
int logLevel;
|
||||
int frameskip;
|
||||
bool rewindEnable;
|
||||
int rewindBufferCapacity;
|
||||
int rewindBufferInterval;
|
||||
float fpsTarget;
|
||||
size_t audioBuffers;
|
||||
unsigned sampleRate;
|
||||
|
||||
int fullscreen;
|
||||
int width;
|
||||
int height;
|
||||
bool lockAspectRatio;
|
||||
bool resampleVideo;
|
||||
bool suspendScreensaver;
|
||||
char* shader;
|
||||
|
||||
char* savegamePath;
|
||||
char* savestatePath;
|
||||
char* screenshotPath;
|
||||
char* patchPath;
|
||||
|
||||
int volume;
|
||||
bool mute;
|
||||
|
||||
bool videoSync;
|
||||
bool audioSync;
|
||||
};
|
||||
|
||||
void mCoreConfigInit(struct mCoreConfig*, const char* port);
|
||||
void mCoreConfigDeinit(struct mCoreConfig*);
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
bool mCoreConfigLoad(struct mCoreConfig*);
|
||||
bool mCoreConfigSave(const struct mCoreConfig*);
|
||||
bool mCoreConfigLoadPath(struct mCoreConfig*, const char* path);
|
||||
bool mCoreConfigSavePath(const struct mCoreConfig*, const char* path);
|
||||
|
||||
void mCoreConfigMakePortable(const struct mCoreConfig*);
|
||||
void mCoreConfigDirectory(char* out, size_t outLength);
|
||||
#endif
|
||||
|
||||
const char* mCoreConfigGetValue(const struct mCoreConfig*, const char* key);
|
||||
bool mCoreConfigGetIntValue(const struct mCoreConfig*, const char* key, int* value);
|
||||
bool mCoreConfigGetUIntValue(const struct mCoreConfig*, const char* key, unsigned* value);
|
||||
bool mCoreConfigGetFloatValue(const struct mCoreConfig*, const char* key, float* value);
|
||||
|
||||
void mCoreConfigSetValue(struct mCoreConfig*, const char* key, const char* value);
|
||||
void mCoreConfigSetIntValue(struct mCoreConfig*, const char* key, int value);
|
||||
void mCoreConfigSetUIntValue(struct mCoreConfig*, const char* key, unsigned value);
|
||||
void mCoreConfigSetFloatValue(struct mCoreConfig*, const char* key, float value);
|
||||
|
||||
void mCoreConfigSetDefaultValue(struct mCoreConfig*, const char* key, const char* value);
|
||||
void mCoreConfigSetDefaultIntValue(struct mCoreConfig*, const char* key, int value);
|
||||
void mCoreConfigSetDefaultUIntValue(struct mCoreConfig*, const char* key, unsigned value);
|
||||
void mCoreConfigSetDefaultFloatValue(struct mCoreConfig*, const char* key, float value);
|
||||
|
||||
void mCoreConfigSetOverrideValue(struct mCoreConfig*, const char* key, const char* value);
|
||||
void mCoreConfigSetOverrideIntValue(struct mCoreConfig*, const char* key, int value);
|
||||
void mCoreConfigSetOverrideUIntValue(struct mCoreConfig*, const char* key, unsigned value);
|
||||
void mCoreConfigSetOverrideFloatValue(struct mCoreConfig*, const char* key, float value);
|
||||
|
||||
void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts);
|
||||
void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts);
|
||||
|
||||
struct Configuration* mCoreConfigGetInput(struct mCoreConfig*);
|
||||
struct Configuration* mCoreConfigGetOverrides(struct mCoreConfig*);
|
||||
const struct Configuration* mCoreConfigGetOverridesConst(const struct mCoreConfig*);
|
||||
|
||||
void mCoreConfigFreeOpts(struct mCoreOptions* opts);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,198 @@
|
|||
/* Copyright (c) 2013-2016 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 "core.h"
|
||||
|
||||
#include "core/log.h"
|
||||
#include "core/serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
#include "util/png-io.h"
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
#include "gb/core.h"
|
||||
#include "gb/gb.h"
|
||||
#endif
|
||||
#ifdef M_CORE_GBA
|
||||
#include "gba/core.h"
|
||||
#include "gba/gba.h"
|
||||
#endif
|
||||
|
||||
static struct mCoreFilter {
|
||||
bool (*filter)(struct VFile*);
|
||||
struct mCore* (*open)(void);
|
||||
} _filters[] = {
|
||||
#ifdef M_CORE_GBA
|
||||
{ GBAIsROM, GBACoreCreate },
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
{ GBIsROM, GBCoreCreate },
|
||||
#endif
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
struct mCore* mCoreFind(const char* path) {
|
||||
struct VDir* archive = VDirOpenArchive(path);
|
||||
struct mCore* (*open)(void) = NULL;
|
||||
if (archive) {
|
||||
struct VDirEntry* dirent = archive->listNext(archive);
|
||||
while (dirent) {
|
||||
struct VFile* vf = archive->openFile(archive, dirent->name(dirent), O_RDONLY);
|
||||
if (!vf) {
|
||||
dirent = archive->listNext(archive);
|
||||
continue;
|
||||
}
|
||||
struct mCoreFilter* filter;
|
||||
for (filter = &_filters[0]; filter->filter; ++filter) {
|
||||
if (filter->filter(vf)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vf->close(vf);
|
||||
if (filter->open) {
|
||||
open = filter->open;
|
||||
break;
|
||||
}
|
||||
dirent = archive->listNext(archive);
|
||||
}
|
||||
archive->close(archive);
|
||||
} else {
|
||||
struct VFile* vf = VFileOpen(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return NULL;
|
||||
}
|
||||
struct mCoreFilter* filter;
|
||||
for (filter = &_filters[0]; filter->filter; ++filter) {
|
||||
if (filter->filter(vf)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vf->close(vf);
|
||||
if (filter->open) {
|
||||
open = filter->open;
|
||||
}
|
||||
}
|
||||
if (open) {
|
||||
return open();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool mCoreLoadFile(struct mCore* core, const char* path) {
|
||||
struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM);
|
||||
if (!rom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = core->loadROM(core, rom);
|
||||
if (!ret) {
|
||||
rom->close(rom);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool mCoreAutoloadSave(struct mCore* core) {
|
||||
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR));
|
||||
}
|
||||
|
||||
bool mCoreAutoloadPatch(struct mCore* core) {
|
||||
return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) ||
|
||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||
|
||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
|
||||
}
|
||||
|
||||
bool mCoreSaveState(struct mCore* core, int slot, int flags) {
|
||||
struct VFile* vf = mCoreGetState(core, slot, true);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = mCoreSaveStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
mLOG(STATUS, INFO, "State %i saved", slot);
|
||||
} else {
|
||||
mLOG(STATUS, INFO, "State %i failed to save", slot);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mCoreLoadState(struct mCore* core, int slot, int flags) {
|
||||
struct VFile* vf = mCoreGetState(core, slot, false);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = mCoreLoadStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
mLOG(STATUS, INFO, "State %i loaded", slot);
|
||||
} else {
|
||||
mLOG(STATUS, INFO, "State %i failed to loaded", slot);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) {
|
||||
char name[PATH_MAX];
|
||||
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
|
||||
return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
}
|
||||
|
||||
void mCoreDeleteState(struct mCore* core, int slot) {
|
||||
char name[PATH_MAX];
|
||||
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
|
||||
core->dirs.state->deleteFile(core->dirs.state, name);
|
||||
}
|
||||
|
||||
void mCoreTakeScreenshot(struct mCore* core) {
|
||||
#ifdef USE_PNG
|
||||
size_t stride;
|
||||
color_t* pixels = 0;
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
struct VFile* vf = VDirFindNextAvailable(core->dirs.screenshot, core->dirs.baseName, "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
bool success = false;
|
||||
if (vf) {
|
||||
core->getVideoBuffer(core, &pixels, &stride);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, width, height);
|
||||
success = PNGWritePixels(png, width, height, stride, pixels);
|
||||
PNGWriteClose(png, info);
|
||||
vf->close(vf);
|
||||
}
|
||||
if (success) {
|
||||
mLOG(STATUS, INFO, "Screenshot saved");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
UNUSED(core);
|
||||
#endif
|
||||
mLOG(STATUS, WARN, "Failed to take screenshot");
|
||||
}
|
||||
#endif
|
||||
|
||||
void mCoreInitConfig(struct mCore* core, const char* port) {
|
||||
mCoreConfigInit(&core->config, port);
|
||||
}
|
||||
|
||||
void mCoreLoadConfig(struct mCore* core) {
|
||||
#ifndef MINIMAL_CORE
|
||||
mCoreConfigLoad(&core->config);
|
||||
#endif
|
||||
mCoreLoadForeignConfig(core, &core->config);
|
||||
}
|
||||
|
||||
void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config) {
|
||||
mCoreConfigMap(config, &core->opts);
|
||||
#ifndef MINIMAL_CORE
|
||||
mDirectorySetMapOptions(&core->dirs, &core->opts);
|
||||
#endif
|
||||
if (core->opts.audioBuffers) {
|
||||
core->setAudioBufferSize(core, core->opts.audioBuffers);
|
||||
}
|
||||
core->loadConfig(core, config);
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/* Copyright (c) 2013-2016 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 M_CORE_H
|
||||
#define M_CORE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/config.h"
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
#include "core/directories.h"
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
#include "core/input.h"
|
||||
#endif
|
||||
#include "core/interface.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
enum mPlatform {
|
||||
PLATFORM_NONE = -1,
|
||||
#ifdef M_CORE_GBA
|
||||
PLATFORM_GBA,
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
PLATFORM_GB,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct mRTCSource;
|
||||
struct mCoreConfig;
|
||||
struct mCoreSync;
|
||||
struct mStateExtdata;
|
||||
struct mCore {
|
||||
void* cpu;
|
||||
void* board;
|
||||
struct mDebugger* debugger;
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
struct mDirectorySet dirs;
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
struct mInputMap inputMap;
|
||||
#endif
|
||||
struct mCoreConfig config;
|
||||
struct mCoreOptions opts;
|
||||
|
||||
bool (*init)(struct mCore*);
|
||||
void (*deinit)(struct mCore*);
|
||||
|
||||
enum mPlatform (*platform)(struct mCore*);
|
||||
|
||||
void (*setSync)(struct mCore*, struct mCoreSync*);
|
||||
void (*loadConfig)(struct mCore*, const struct mCoreConfig*);
|
||||
|
||||
void (*desiredVideoDimensions)(struct mCore*, unsigned* width, unsigned* height);
|
||||
void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride);
|
||||
void (*getVideoBuffer)(struct mCore*, color_t** buffer, size_t* stride);
|
||||
|
||||
struct blip_t* (*getAudioChannel)(struct mCore*, int ch);
|
||||
void (*setAudioBufferSize)(struct mCore*, size_t samples);
|
||||
size_t (*getAudioBufferSize)(struct mCore*);
|
||||
|
||||
void (*setAVStream)(struct mCore*, struct mAVStream*);
|
||||
|
||||
bool (*isROM)(struct VFile* vf);
|
||||
bool (*loadROM)(struct mCore*, struct VFile* vf);
|
||||
bool (*loadSave)(struct mCore*, struct VFile* vf);
|
||||
bool (*loadTemporarySave)(struct mCore*, struct VFile* vf);
|
||||
void (*unloadROM)(struct mCore*);
|
||||
|
||||
bool (*loadBIOS)(struct mCore*, struct VFile* vf, int biosID);
|
||||
bool (*selectBIOS)(struct mCore*, int biosID);
|
||||
|
||||
bool (*loadPatch)(struct mCore*, struct VFile* vf);
|
||||
|
||||
void (*reset)(struct mCore*);
|
||||
void (*runFrame)(struct mCore*);
|
||||
void (*runLoop)(struct mCore*);
|
||||
void (*step)(struct mCore*);
|
||||
|
||||
size_t (*stateSize)(struct mCore*);
|
||||
bool (*loadState)(struct mCore*, const void* state);
|
||||
bool (*saveState)(struct mCore*, void* state);
|
||||
|
||||
void (*setKeys)(struct mCore*, uint32_t keys);
|
||||
void (*addKeys)(struct mCore*, uint32_t keys);
|
||||
void (*clearKeys)(struct mCore*, uint32_t keys);
|
||||
|
||||
int32_t (*frameCounter)(struct mCore*);
|
||||
int32_t (*frameCycles)(struct mCore*);
|
||||
int32_t (*frequency)(struct mCore*);
|
||||
|
||||
void (*getGameTitle)(struct mCore*, char* title);
|
||||
void (*getGameCode)(struct mCore*, char* title);
|
||||
|
||||
void (*setRTC)(struct mCore*, struct mRTCSource*);
|
||||
void (*setRotation)(struct mCore*, struct mRotationSource*);
|
||||
void (*setRumble)(struct mCore*, struct mRumble*);
|
||||
|
||||
uint32_t (*busRead8)(struct mCore*, uint32_t address);
|
||||
uint32_t (*busRead16)(struct mCore*, uint32_t address);
|
||||
uint32_t (*busRead32)(struct mCore*, uint32_t address);
|
||||
|
||||
void (*busWrite8)(struct mCore*, uint32_t address, uint8_t);
|
||||
void (*busWrite16)(struct mCore*, uint32_t address, uint16_t);
|
||||
void (*busWrite32)(struct mCore*, uint32_t address, uint32_t);
|
||||
|
||||
uint32_t (*rawRead8)(struct mCore*, uint32_t address);
|
||||
uint32_t (*rawRead16)(struct mCore*, uint32_t address);
|
||||
uint32_t (*rawRead32)(struct mCore*, uint32_t address);
|
||||
|
||||
void (*rawWrite8)(struct mCore*, uint32_t address, uint8_t);
|
||||
void (*rawWrite16)(struct mCore*, uint32_t address, uint16_t);
|
||||
void (*rawWrite32)(struct mCore*, uint32_t address, uint32_t);
|
||||
|
||||
bool (*supportsDebuggerType)(struct mCore*, enum mDebuggerType);
|
||||
struct mDebuggerPlatform* (*debuggerPlatform)(struct mCore*);
|
||||
struct CLIDebuggerSystem* (*cliDebuggerSystem)(struct mCore*);
|
||||
void (*attachDebugger)(struct mCore*, struct mDebugger*);
|
||||
void (*detachDebugger)(struct mCore*);
|
||||
|
||||
struct mCheatDevice* (*cheatDevice)(struct mCore*);
|
||||
|
||||
size_t (*savedataClone)(struct mCore*, void** sram);
|
||||
bool (*savedataLoad)(struct mCore*, const void* sram, size_t size);
|
||||
};
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
struct mCore* mCoreFind(const char* path);
|
||||
bool mCoreLoadFile(struct mCore* core, const char* path);
|
||||
|
||||
bool mCoreAutoloadSave(struct mCore* core);
|
||||
bool mCoreAutoloadPatch(struct mCore* core);
|
||||
|
||||
bool mCoreSaveState(struct mCore* core, int slot, int flags);
|
||||
bool mCoreLoadState(struct mCore* core, int slot, int flags);
|
||||
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write);
|
||||
void mCoreDeleteState(struct mCore* core, int slot);
|
||||
|
||||
void mCoreTakeScreenshot(struct mCore* core);
|
||||
#endif
|
||||
|
||||
void mCoreInitConfig(struct mCore* core, const char* port);
|
||||
void mCoreLoadConfig(struct mCore* core);
|
||||
void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,23 @@
|
|||
/* Copyright (c) 2013-2016 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 M_CPU_H
|
||||
#define M_CPU_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
enum mCPUComponentType {
|
||||
CPU_COMPONENT_DEBUGGER,
|
||||
CPU_COMPONENT_CHEAT_DEVICE,
|
||||
CPU_COMPONENT_MAX
|
||||
};
|
||||
|
||||
struct mCPUComponent {
|
||||
uint32_t id;
|
||||
void (*init)(void* cpu, struct mCPUComponent* component);
|
||||
void (*deinit)(struct mCPUComponent* component);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,157 @@
|
|||
/* 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 "directories.h"
|
||||
|
||||
#include "core/config.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
void mDirectorySetInit(struct mDirectorySet* dirs) {
|
||||
dirs->base = 0;
|
||||
dirs->archive = 0;
|
||||
dirs->save = 0;
|
||||
dirs->patch = 0;
|
||||
dirs->state = 0;
|
||||
dirs->screenshot = 0;
|
||||
}
|
||||
|
||||
void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
||||
mDirectorySetDetachBase(dirs);
|
||||
|
||||
if (dirs->archive) {
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = 0;
|
||||
}
|
||||
|
||||
if (dirs->save) {
|
||||
dirs->save->close(dirs->save);
|
||||
dirs->save = 0;
|
||||
}
|
||||
|
||||
if (dirs->patch) {
|
||||
dirs->patch->close(dirs->patch);
|
||||
dirs->patch = 0;
|
||||
}
|
||||
|
||||
if (dirs->state) {
|
||||
dirs->state->close(dirs->state);
|
||||
dirs->state = 0;
|
||||
}
|
||||
|
||||
if (dirs->screenshot) {
|
||||
dirs->screenshot->close(dirs->screenshot);
|
||||
dirs->screenshot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
||||
dirs->base = base;
|
||||
if (!dirs->save) {
|
||||
dirs->save = dirs->base;
|
||||
}
|
||||
if (!dirs->patch) {
|
||||
dirs->patch = dirs->base;
|
||||
}
|
||||
if (!dirs->state) {
|
||||
dirs->state = dirs->base;
|
||||
}
|
||||
if (!dirs->screenshot) {
|
||||
dirs->screenshot = dirs->base;
|
||||
}
|
||||
}
|
||||
|
||||
void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
|
||||
if (dirs->save == dirs->base) {
|
||||
dirs->save = 0;
|
||||
}
|
||||
if (dirs->patch == dirs->base) {
|
||||
dirs->patch = 0;
|
||||
}
|
||||
if (dirs->state == dirs->base) {
|
||||
dirs->state = 0;
|
||||
}
|
||||
if (dirs->screenshot == dirs->base) {
|
||||
dirs->screenshot = 0;
|
||||
}
|
||||
|
||||
if (dirs->base) {
|
||||
dirs->base->close(dirs->base);
|
||||
dirs->base = 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct VFile* mDirectorySetOpenPath(struct mDirectorySet* dirs, const char* path, bool (*filter)(struct VFile*)) {
|
||||
dirs->archive = VDirOpenArchive(path);
|
||||
struct VFile* file;
|
||||
if (dirs->archive) {
|
||||
file = VDirFindFirst(dirs->archive, filter);
|
||||
if (!file) {
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = 0;
|
||||
}
|
||||
} else {
|
||||
file = VFileOpen(path, O_RDONLY);
|
||||
if (file && !filter(file)) {
|
||||
file->close(file);
|
||||
file = 0;
|
||||
}
|
||||
}
|
||||
if (file) {
|
||||
char dirname[PATH_MAX];
|
||||
separatePath(path, dirname, dirs->baseName, 0);
|
||||
mDirectorySetAttachBase(dirs, VDirOpen(dirname));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* dir, const char* suffix, int mode) {
|
||||
char name[PATH_MAX];
|
||||
snprintf(name, sizeof(name), "%s%s", dirs->baseName, suffix);
|
||||
return dir->openFile(dir, name, mode);
|
||||
}
|
||||
|
||||
void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) {
|
||||
if (opts->savegamePath) {
|
||||
struct VDir* dir = VDirOpen(opts->savegamePath);
|
||||
if (dir) {
|
||||
if (dirs->save && dirs->save != dirs->base) {
|
||||
dirs->save->close(dirs->save);
|
||||
}
|
||||
dirs->save = dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->savestatePath) {
|
||||
struct VDir* dir = VDirOpen(opts->savestatePath);
|
||||
if (dir) {
|
||||
if (dirs->state && dirs->state != dirs->base) {
|
||||
dirs->state->close(dirs->state);
|
||||
}
|
||||
dirs->state = dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->screenshotPath) {
|
||||
struct VDir* dir = VDirOpen(opts->screenshotPath);
|
||||
if (dir) {
|
||||
if (dirs->screenshot && dirs->screenshot != dirs->base) {
|
||||
dirs->screenshot->close(dirs->screenshot);
|
||||
}
|
||||
dirs->screenshot = dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->patchPath) {
|
||||
struct VDir* dir = VDirOpen(opts->patchPath);
|
||||
if (dir) {
|
||||
if (dirs->patch && dirs->patch != dirs->base) {
|
||||
dirs->patch->close(dirs->patch);
|
||||
}
|
||||
dirs->patch = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef DIRECTORIES_H
|
||||
#define DIRECTORIES_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
struct VDir;
|
||||
|
||||
struct mDirectorySet {
|
||||
char baseName[PATH_MAX];
|
||||
struct VDir* base;
|
||||
struct VDir* archive;
|
||||
struct VDir* save;
|
||||
struct VDir* patch;
|
||||
struct VDir* state;
|
||||
struct VDir* screenshot;
|
||||
};
|
||||
|
||||
void mDirectorySetInit(struct mDirectorySet* dirs);
|
||||
void mDirectorySetDeinit(struct mDirectorySet* dirs);
|
||||
|
||||
void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base);
|
||||
void mDirectorySetDetachBase(struct mDirectorySet* dirs);
|
||||
|
||||
struct VFile* mDirectorySetOpenPath(struct mDirectorySet* dirs, const char* path, bool (*filter)(struct VFile*));
|
||||
struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* dir, const char* suffix, int mode);
|
||||
|
||||
struct mCoreOptions;
|
||||
void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts);
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,513 @@
|
|||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "input.h"
|
||||
|
||||
#include "util/configuration.h"
|
||||
#include "util/table.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define SECTION_NAME_MAX 128
|
||||
#define KEY_NAME_MAX 32
|
||||
#define KEY_VALUE_MAX 16
|
||||
#define AXIS_INFO_MAX 12
|
||||
|
||||
struct mInputMapImpl {
|
||||
int* map;
|
||||
uint32_t type;
|
||||
|
||||
struct Table axes;
|
||||
};
|
||||
|
||||
struct mInputAxisSave {
|
||||
struct Configuration* config;
|
||||
const char* sectionName;
|
||||
const struct mInputPlatformInfo* info;
|
||||
};
|
||||
|
||||
struct mInputAxisEnumerate {
|
||||
void (*handler)(int axis, const struct mInputAxis* description, void* user);
|
||||
void* user;
|
||||
};
|
||||
|
||||
static void _makeSectionName(const char* platform, char* sectionName, size_t len, uint32_t type) {
|
||||
snprintf(sectionName, len, "%s.input.%c%c%c%c", platform, 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) {
|
||||
return false;
|
||||
}
|
||||
char* end;
|
||||
long intValue = strtol(strValue, &end, 10);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
*value = intValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct mInputMapImpl* _lookupMap(struct mInputMap* map, uint32_t type) {
|
||||
size_t m;
|
||||
struct mInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static const struct mInputMapImpl* _lookupMapConst(const struct mInputMap* map, uint32_t type) {
|
||||
size_t m;
|
||||
const struct mInputMapImpl* impl = 0;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type == type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static struct mInputMapImpl* _guaranteeMap(struct mInputMap* map, uint32_t type) {
|
||||
struct mInputMapImpl* impl = 0;
|
||||
if (map->numMaps == 0) {
|
||||
map->maps = malloc(sizeof(*map->maps));
|
||||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = malloc(map->info->nKeys * sizeof(int));
|
||||
size_t i;
|
||||
for (i = 0; i < map->info->nKeys; ++i) {
|
||||
impl->map[i] = -1;
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
} else {
|
||||
impl = _lookupMap(map, type);
|
||||
}
|
||||
if (!impl) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (!map->maps[m].type) {
|
||||
impl = &map->maps[m];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = malloc(map->info->nKeys * sizeof(int));
|
||||
size_t i;
|
||||
for (i = 0; i < map->info->nKeys; ++i) {
|
||||
impl->map[i] = -1;
|
||||
}
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
map->maps[m].type = 0;
|
||||
map->maps[m].map = 0;
|
||||
}
|
||||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = malloc(map->info->nKeys * sizeof(int));
|
||||
size_t i;
|
||||
for (i = 0; i < map->info->nKeys; ++i) {
|
||||
impl->map[i] = -1;
|
||||
}
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
static void _loadKey(struct mInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, int key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
int value;
|
||||
if (!_getIntValue(config, sectionName, keyKey, &value)) {
|
||||
return;
|
||||
}
|
||||
mInputBindKey(map, type, value, key);
|
||||
}
|
||||
|
||||
static void _loadAxis(struct mInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, int direction, const char* axisName) {
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
int value;
|
||||
if (!_getIntValue(config, sectionName, axisKey, &value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
int axis;
|
||||
const char* strValue = ConfigurationGetValue(config, sectionName, axisKey);
|
||||
if (!strValue || !strValue[0]) {
|
||||
return;
|
||||
}
|
||||
char* end;
|
||||
axis = strtoul(&strValue[1], &end, 10);
|
||||
if (*end) {
|
||||
return;
|
||||
}
|
||||
|
||||
const struct mInputAxis* description = mInputQueryAxis(map, type, axis);
|
||||
struct mInputAxis realDescription = { -1, -1, 0, 0 };
|
||||
if (description) {
|
||||
realDescription = *description;
|
||||
}
|
||||
if (strValue[0] == '+') {
|
||||
realDescription.deadHigh = value;
|
||||
realDescription.highDirection = direction;
|
||||
} else if (strValue[0] == '-') {
|
||||
realDescription.deadLow = value;
|
||||
realDescription.lowDirection = direction;
|
||||
}
|
||||
mInputBindAxis(map, type, axis, &realDescription);
|
||||
}
|
||||
|
||||
static void _saveKey(const struct mInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config, int key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
int value = mInputQueryBinding(map, type, key);
|
||||
char keyValue[KEY_VALUE_MAX];
|
||||
snprintf(keyValue, KEY_VALUE_MAX, "%" PRIi32, value);
|
||||
|
||||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||
}
|
||||
|
||||
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';
|
||||
ConfigurationClearValue(config, sectionName, axisKey);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationClearValue(config, sectionName, axisKey);
|
||||
}
|
||||
|
||||
static void _saveAxis(uint32_t axis, void* dp, void* up) {
|
||||
struct mInputAxisSave* user = up;
|
||||
const struct mInputAxis* description = dp;
|
||||
|
||||
const char* sectionName = user->sectionName;
|
||||
|
||||
if (description->lowDirection != -1) {
|
||||
const char* keyName = user->info->keyId[description->lowDirection];
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadLow);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisInfo[AXIS_INFO_MAX];
|
||||
snprintf(axisInfo, AXIS_INFO_MAX, "-%u", axis);
|
||||
axisInfo[AXIS_INFO_MAX - 1] = '\0';
|
||||
ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo);
|
||||
}
|
||||
if (description->highDirection != -1) {
|
||||
const char* keyName = user->info->keyId[description->highDirection];
|
||||
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadHigh);
|
||||
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
||||
char axisInfo[AXIS_INFO_MAX];
|
||||
snprintf(axisInfo, AXIS_INFO_MAX, "+%u", axis);
|
||||
axisInfo[AXIS_INFO_MAX - 1] = '\0';
|
||||
ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void _enumerateAxis(uint32_t axis, void* dp, void* ep) {
|
||||
struct mInputAxisEnumerate* enumUser = ep;
|
||||
const struct mInputAxis* description = dp;
|
||||
enumUser->handler(axis, description, enumUser->user);
|
||||
}
|
||||
|
||||
void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
||||
UNUSED(axis);
|
||||
int* key = user;
|
||||
struct mInputAxis* description = dp;
|
||||
if (description->highDirection == *key) {
|
||||
description->highDirection = -1;
|
||||
}
|
||||
if (description->lowDirection == *key) {
|
||||
description->lowDirection = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _loadAll(struct mInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||
if (!ConfigurationHasSection(config, sectionName)) {
|
||||
return false;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < map->info->nKeys; ++i) {
|
||||
_loadKey(map, type, sectionName, config, i, map->info->keyId[i]);
|
||||
_loadAxis(map, type, sectionName, config, i, map->info->keyId[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _saveAll(const struct mInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
||||
size_t i;
|
||||
for (i = 0; i < map->info->nKeys; ++i) {
|
||||
_saveKey(map, type, sectionName, config, i, map->info->keyId[i]);
|
||||
_clearAxis(sectionName, config, map->info->keyId[i]);
|
||||
}
|
||||
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct mInputAxisSave save = {
|
||||
config,
|
||||
sectionName,
|
||||
map->info
|
||||
};
|
||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
||||
}
|
||||
|
||||
void mInputMapInit(struct mInputMap* map, const struct mInputPlatformInfo* info) {
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
map->info = info;
|
||||
}
|
||||
|
||||
void mInputMapDeinit(struct mInputMap* map) {
|
||||
size_t m;
|
||||
for (m = 0; m < map->numMaps; ++m) {
|
||||
if (map->maps[m].type) {
|
||||
free(map->maps[m].map);
|
||||
TableDeinit(&map->maps[m].axes);
|
||||
}
|
||||
}
|
||||
free(map->maps);
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
}
|
||||
|
||||
int mInputMapKey(const struct mInputMap* map, uint32_t type, int key) {
|
||||
size_t m;
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl || !impl->map) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (m = 0; m < map->info->nKeys; ++m) {
|
||||
if (impl->map[m] == key) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mInputMapKeyBits(const struct mInputMap* map, uint32_t type, uint32_t bits, unsigned offset) {
|
||||
int keys = 0;
|
||||
for (; bits; bits >>= 1, ++offset) {
|
||||
if (bits & 1) {
|
||||
int key = mInputMapKey(map, type, offset);
|
||||
if (key == -1) {
|
||||
continue;
|
||||
}
|
||||
keys |= 1 << key;
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void mInputBindKey(struct mInputMap* map, uint32_t type, int key, int input) {
|
||||
struct mInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
mInputUnbindKey(map, type, input);
|
||||
impl->map[input] = key;
|
||||
}
|
||||
|
||||
void mInputUnbindKey(struct mInputMap* map, uint32_t type, int input) {
|
||||
struct mInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (input < 0 || (size_t) input >= map->info->nKeys) {
|
||||
return;
|
||||
}
|
||||
if (impl) {
|
||||
impl->map[input] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int mInputQueryBinding(const struct mInputMap* map, uint32_t type, int input) {
|
||||
if (input < 0 || (size_t) input >= map->info->nKeys) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl || !impl->map) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return impl->map[input];
|
||||
}
|
||||
|
||||
int mInputMapAxis(const struct mInputMap* map, uint32_t type, int axis, int value) {
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return -1;
|
||||
}
|
||||
struct mInputAxis* description = TableLookup(&impl->axes, axis);
|
||||
if (!description) {
|
||||
return -1;
|
||||
}
|
||||
int state = 0;
|
||||
if (value < description->deadLow) {
|
||||
state = -1;
|
||||
} else if (value > description->deadHigh) {
|
||||
state = 1;
|
||||
}
|
||||
if (state > 0) {
|
||||
return description->highDirection;
|
||||
}
|
||||
if (state < 0) {
|
||||
return description->lowDirection;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mInputClearAxis(const struct mInputMap* map, uint32_t type, int axis, int keys) {
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return keys;
|
||||
}
|
||||
struct mInputAxis* description = TableLookup(&impl->axes, axis);
|
||||
if (!description) {
|
||||
return keys;
|
||||
}
|
||||
return keys &= ~((1 << description->highDirection) | (1 << description->lowDirection));
|
||||
}
|
||||
|
||||
void mInputBindAxis(struct mInputMap* map, uint32_t type, int axis, const struct mInputAxis* description) {
|
||||
struct mInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
struct mInputAxis d2 = *description;
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection);
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection);
|
||||
struct mInputAxis* dup = malloc(sizeof(struct mInputAxis));
|
||||
*dup = *description;
|
||||
TableInsert(&impl->axes, axis, dup);
|
||||
}
|
||||
|
||||
void mInputUnbindAxis(struct mInputMap* map, uint32_t type, int axis) {
|
||||
struct mInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (impl) {
|
||||
TableRemove(&impl->axes, axis);
|
||||
}
|
||||
}
|
||||
|
||||
void mInputUnbindAllAxes(struct mInputMap* map, uint32_t type) {
|
||||
struct mInputMapImpl* impl = _lookupMap(map, type);
|
||||
if (impl) {
|
||||
TableClear(&impl->axes);
|
||||
}
|
||||
}
|
||||
|
||||
const struct mInputAxis* mInputQueryAxis(const struct mInputMap* map, uint32_t type, int axis) {
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return 0;
|
||||
}
|
||||
return TableLookup(&impl->axes, axis);
|
||||
}
|
||||
|
||||
void mInputEnumerateAxes(const struct mInputMap* map, uint32_t type, void (handler(int axis, const struct mInputAxis* description, void* user)), void* user) {
|
||||
const struct mInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct mInputAxisEnumerate enumUser = {
|
||||
handler,
|
||||
user
|
||||
};
|
||||
TableEnumerate(&impl->axes, _enumerateAxis, &enumUser);
|
||||
}
|
||||
|
||||
void mInputMapLoad(struct mInputMap* map, uint32_t type, const struct Configuration* config) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(map->info->platformName, sectionName, SECTION_NAME_MAX, type);
|
||||
_loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void mInputMapSave(const struct mInputMap* map, uint32_t type, struct Configuration* config) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(map->info->platformName, sectionName, SECTION_NAME_MAX, type);
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
bool mInputProfileLoad(struct mInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "%s.input-profile.%s", map->info->platformName, profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
return _loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void mInputProfileSave(const struct mInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "%s.input-profile.%s", map->info->platformName, profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
const char* mInputGetPreferredDevice(const struct Configuration* config, const char* platformName, uint32_t type, int playerId) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(platformName, sectionName, SECTION_NAME_MAX, type);
|
||||
|
||||
char deviceId[KEY_NAME_MAX];
|
||||
snprintf(deviceId, sizeof(deviceId), "device%i", playerId);
|
||||
return ConfigurationGetValue(config, sectionName, deviceId);
|
||||
}
|
||||
|
||||
void mInputSetPreferredDevice(struct Configuration* config, const char* platformName, uint32_t type, int playerId, const char* deviceName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(platformName, 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* mInputGetCustomValue(const struct Configuration* config, const char* platformName, uint32_t type, const char* key, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
if (profile) {
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "%s.input-profile.%s", platformName, profile);
|
||||
const char* value = ConfigurationGetValue(config, sectionName, key);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
_makeSectionName(platformName, sectionName, SECTION_NAME_MAX, type);
|
||||
return ConfigurationGetValue(config, sectionName, key);
|
||||
}
|
||||
|
||||
void mInputSetCustomValue(struct Configuration* config, const char* platformName, uint32_t type, const char* key, const char* value, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
if (profile) {
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "%s.input-profile.%s", platformName, profile);
|
||||
ConfigurationSetValue(config, sectionName, key, value);
|
||||
}
|
||||
_makeSectionName(platformName, sectionName, SECTION_NAME_MAX, type);
|
||||
ConfigurationSetValue(config, sectionName, key, value);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/* Copyright (c) 2013-2016 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 M_INPUT_H
|
||||
#define M_INPUT_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct Configuration;
|
||||
|
||||
struct mInputPlatformInfo {
|
||||
const char* platformName;
|
||||
const char** keyId;
|
||||
size_t nKeys;
|
||||
};
|
||||
|
||||
struct mInputMap {
|
||||
struct mInputMapImpl* maps;
|
||||
size_t numMaps;
|
||||
const struct mInputPlatformInfo* info;
|
||||
};
|
||||
|
||||
struct mInputAxis {
|
||||
int highDirection;
|
||||
int lowDirection;
|
||||
int32_t deadHigh;
|
||||
int32_t deadLow;
|
||||
};
|
||||
|
||||
void mInputMapInit(struct mInputMap*, const struct mInputPlatformInfo* info);
|
||||
void mInputMapDeinit(struct mInputMap*);
|
||||
|
||||
int mInputMapKey(const struct mInputMap*, uint32_t type, int key);
|
||||
int mInputMapKeyBits(const struct mInputMap* map, uint32_t type, uint32_t bits, unsigned offset);
|
||||
void mInputBindKey(struct mInputMap*, uint32_t type, int key, int input);
|
||||
int mInputQueryBinding(const struct mInputMap*, uint32_t type, int input);
|
||||
void mInputUnbindKey(struct mInputMap*, uint32_t type, int input);
|
||||
|
||||
int mInputMapAxis(const struct mInputMap*, uint32_t type, int axis, int value);
|
||||
int mInputClearAxis(const struct mInputMap*, uint32_t type, int axis, int keys);
|
||||
void mInputBindAxis(struct mInputMap*, uint32_t type, int axis, const struct mInputAxis* description);
|
||||
void mInputUnbindAxis(struct mInputMap*, uint32_t type, int axis);
|
||||
void mInputUnbindAllAxes(struct mInputMap*, uint32_t type);
|
||||
const struct mInputAxis* mInputQueryAxis(const struct mInputMap*, uint32_t type, int axis);
|
||||
void mInputEnumerateAxes(const struct mInputMap*, uint32_t type, void (handler(int axis, const struct mInputAxis* description, void* user)), void* user);
|
||||
|
||||
void mInputMapLoad(struct mInputMap*, uint32_t type, const struct Configuration*);
|
||||
void mInputMapSave(const struct mInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
bool mInputProfileLoad(struct mInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||
void mInputProfileSave(const struct mInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||
|
||||
const char* mInputGetPreferredDevice(const struct Configuration*, const char* platformName, uint32_t type, int playerId);
|
||||
void mInputSetPreferredDevice(struct Configuration*, const char* platformName, uint32_t type, int playerId, const char* deviceName);
|
||||
|
||||
const char* mInputGetCustomValue(const struct Configuration* config, const char* platformName, uint32_t type, const char* key,
|
||||
const char* profile);
|
||||
void mInputSetCustomValue(struct Configuration* config, const char* platformName, uint32_t type, const char* key, const char* value,
|
||||
const char* profile);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,30 @@
|
|||
/* 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 "interface.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include <time.h>
|
||||
|
||||
static time_t _rtcGenericCallback(struct mRTCSource* source) {
|
||||
struct mRTCGenericSource* rtc = (struct mRTCGenericSource*) source;
|
||||
switch (rtc->override) {
|
||||
case RTC_NO_OVERRIDE:
|
||||
default:
|
||||
return time(0);
|
||||
case RTC_FIXED:
|
||||
return rtc->value;
|
||||
case RTC_FAKE_EPOCH:
|
||||
return rtc->value + rtc->p->frameCounter(rtc->p) * (int64_t) rtc->p->frameCycles(rtc->p) / rtc->p->frequency(rtc->p);
|
||||
}
|
||||
}
|
||||
|
||||
void mRTCGenericSourceInit(struct mRTCGenericSource* rtc, struct mCore* core) {
|
||||
rtc->p = core;
|
||||
rtc->override = RTC_NO_OVERRIDE;
|
||||
rtc->value = 0;
|
||||
rtc->d.sample = 0;
|
||||
rtc->d.unixTime = _rtcGenericCallback;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* 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 CORE_INTERFACE_H
|
||||
#define CORE_INTERFACE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct mCore;
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
typedef uint16_t color_t;
|
||||
#define BYTES_PER_PIXEL 2
|
||||
#else
|
||||
typedef uint32_t color_t;
|
||||
#define BYTES_PER_PIXEL 4
|
||||
#endif
|
||||
|
||||
struct blip_t;
|
||||
|
||||
struct mAVStream {
|
||||
void (*videoDimensionsChanged)(struct mAVStream*, unsigned width, unsigned height);
|
||||
void (*postVideoFrame)(struct mAVStream*, const color_t* buffer, size_t stride);
|
||||
void (*postAudioFrame)(struct mAVStream*, int16_t left, int16_t right);
|
||||
void (*postAudioBuffer)(struct mAVStream*, struct blip_t* left, struct blip_t* right);
|
||||
};
|
||||
|
||||
struct mKeyCallback {
|
||||
uint16_t (*readKeys)(struct mKeyCallback*);
|
||||
};
|
||||
|
||||
struct mStopCallback {
|
||||
void (*stop)(struct mStopCallback*);
|
||||
};
|
||||
|
||||
struct mRotationSource {
|
||||
void (*sample)(struct mRotationSource*);
|
||||
|
||||
int32_t (*readTiltX)(struct mRotationSource*);
|
||||
int32_t (*readTiltY)(struct mRotationSource*);
|
||||
|
||||
int32_t (*readGyroZ)(struct mRotationSource*);
|
||||
};
|
||||
|
||||
struct mRTCSource {
|
||||
void (*sample)(struct mRTCSource*);
|
||||
|
||||
time_t (*unixTime)(struct mRTCSource*);
|
||||
};
|
||||
|
||||
enum mRTCGenericType {
|
||||
RTC_NO_OVERRIDE,
|
||||
RTC_FIXED,
|
||||
RTC_FAKE_EPOCH
|
||||
};
|
||||
|
||||
struct mRTCGenericSource {
|
||||
struct mRTCSource d;
|
||||
struct mCore* p;
|
||||
enum mRTCGenericType override;
|
||||
int64_t value;
|
||||
};
|
||||
|
||||
void mRTCGenericSourceInit(struct mRTCGenericSource* rtc, struct mCore* core);
|
||||
|
||||
struct mRumble {
|
||||
void (*setRumble)(struct mRumble*, int enable);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
/* Copyright (c) 2013-2016 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 "log.h"
|
||||
|
||||
#include "core/thread.h"
|
||||
|
||||
#define MAX_CATEGORY 64
|
||||
|
||||
static struct mLogger* _defaultLogger = NULL;
|
||||
|
||||
struct mLogger* mLogGetContext(void) {
|
||||
struct mLogger* logger = NULL;
|
||||
#ifndef DISABLE_LOGGING
|
||||
logger = mCoreThreadLogger();
|
||||
#endif
|
||||
if (logger) {
|
||||
return logger;
|
||||
}
|
||||
return _defaultLogger;
|
||||
}
|
||||
|
||||
void mLogSetDefaultLogger(struct mLogger* logger) {
|
||||
_defaultLogger = logger;
|
||||
}
|
||||
|
||||
static int _category = 0;
|
||||
static const char* _categoryNames[MAX_CATEGORY];
|
||||
|
||||
int mLogGenerateCategory(const char* name) {
|
||||
++_category;
|
||||
if (_category < MAX_CATEGORY) {
|
||||
_categoryNames[_category] = name;
|
||||
}
|
||||
return _category;
|
||||
}
|
||||
|
||||
const char* mLogCategoryName(int category) {
|
||||
if (category < MAX_CATEGORY) {
|
||||
return _categoryNames[category];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
mLOG_DEFINE_CATEGORY(STATUS, "Status")
|
|
@ -0,0 +1,61 @@
|
|||
/* Copyright (c) 2013-2016 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 M_LOG_H
|
||||
#define M_LOG_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
enum mLogLevel {
|
||||
mLOG_FATAL = 0x01,
|
||||
mLOG_ERROR = 0x02,
|
||||
mLOG_WARN = 0x04,
|
||||
mLOG_INFO = 0x08,
|
||||
mLOG_DEBUG = 0x10,
|
||||
mLOG_STUB = 0x20,
|
||||
mLOG_GAME_ERROR = 0x40,
|
||||
|
||||
mLOG_ALL = 0x7F
|
||||
};
|
||||
|
||||
struct mLogger {
|
||||
void (*log)(struct mLogger*, int category, enum mLogLevel level, const char* format, va_list args);
|
||||
};
|
||||
|
||||
struct mLogger* mLogGetContext(void);
|
||||
void mLogSetDefaultLogger(struct mLogger*);
|
||||
int mLogGenerateCategory(const char*);
|
||||
const char* mLogCategoryName(int);
|
||||
|
||||
ATTRIBUTE_FORMAT(printf, 3, 4)
|
||||
static inline void mLog(int category, enum mLogLevel level, const char* format, ...) {
|
||||
struct mLogger* context = mLogGetContext();
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (context) {
|
||||
context->log(context, category, level, format, args);
|
||||
} else {
|
||||
printf("%s: ", mLogCategoryName(category));
|
||||
vprintf(format, args);
|
||||
printf("\n");
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define mLOG(CATEGORY, LEVEL, ...) mLog(_mLOG_CAT_ ## CATEGORY (), mLOG_ ## LEVEL, __VA_ARGS__)
|
||||
|
||||
#define mLOG_DECLARE_CATEGORY(CATEGORY) int _mLOG_CAT_ ## CATEGORY (void);
|
||||
#define mLOG_DEFINE_CATEGORY(CATEGORY, NAME) \
|
||||
int _mLOG_CAT_ ## CATEGORY (void) { \
|
||||
static int category = 0; \
|
||||
if (!category) { \
|
||||
category = mLogGenerateCategory(NAME); \
|
||||
} \
|
||||
return category; \
|
||||
}
|
||||
|
||||
mLOG_DECLARE_CATEGORY(STATUS)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,436 @@
|
|||
/* Copyright (c) 2013-2016 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 "serialize.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/cheats.h"
|
||||
#include "core/sync.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include "util/png-io.h"
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate");
|
||||
|
||||
struct mBundledState {
|
||||
size_t stateSize;
|
||||
void* state;
|
||||
struct mStateExtdata* extdata;
|
||||
};
|
||||
|
||||
struct mStateExtdataHeader {
|
||||
uint32_t tag;
|
||||
int32_t size;
|
||||
int64_t offset;
|
||||
};
|
||||
|
||||
bool mStateExtdataInit(struct mStateExtdata* extdata) {
|
||||
memset(extdata->data, 0, sizeof(extdata->data));
|
||||
return true;
|
||||
}
|
||||
|
||||
void mStateExtdataDeinit(struct mStateExtdata* extdata) {
|
||||
size_t i;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data && extdata->data[i].clean) {
|
||||
extdata->data[i].clean(extdata->data[i].data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
|
||||
if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (extdata->data[tag].data && extdata->data[tag].clean) {
|
||||
extdata->data[tag].clean(extdata->data[tag].data);
|
||||
}
|
||||
extdata->data[tag] = *item;
|
||||
}
|
||||
|
||||
bool mStateExtdataGet(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
|
||||
if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*item = extdata->data[tag];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf) {
|
||||
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
|
||||
ssize_t size = sizeof(struct mStateExtdataHeader);
|
||||
size_t i = 0;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data) {
|
||||
size += sizeof(struct mStateExtdataHeader);
|
||||
}
|
||||
}
|
||||
if (size == sizeof(struct mStateExtdataHeader)) {
|
||||
return true;
|
||||
}
|
||||
struct mStateExtdataHeader* header = malloc(size);
|
||||
position += size;
|
||||
|
||||
size_t j;
|
||||
for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data) {
|
||||
STORE_32LE(i, offsetof(struct mStateExtdataHeader, tag), &header[j]);
|
||||
STORE_32LE(extdata->data[i].size, offsetof(struct mStateExtdataHeader, size), &header[j]);
|
||||
STORE_64LE(position, offsetof(struct mStateExtdataHeader, offset), &header[j]);
|
||||
position += extdata->data[i].size;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
header[j].tag = 0;
|
||||
header[j].size = 0;
|
||||
header[j].offset = 0;
|
||||
|
||||
if (vf->write(vf, header, size) != size) {
|
||||
free(header);
|
||||
return false;
|
||||
}
|
||||
free(header);
|
||||
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data) {
|
||||
if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
|
||||
while (true) {
|
||||
struct mStateExtdataHeader buffer, header;
|
||||
if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32LE(header.tag, 0, &buffer.tag);
|
||||
LOAD_32LE(header.size, 0, &buffer.size);
|
||||
LOAD_64LE(header.offset, 0, &buffer.offset);
|
||||
|
||||
if (header.tag == EXTDATA_NONE) {
|
||||
break;
|
||||
}
|
||||
if (header.tag >= EXTDATA_MAX) {
|
||||
continue;
|
||||
}
|
||||
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
|
||||
if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
struct mStateExtdataItem item = {
|
||||
.data = malloc(header.size),
|
||||
.size = header.size,
|
||||
.clean = free
|
||||
};
|
||||
if (!item.data) {
|
||||
continue;
|
||||
}
|
||||
if (vf->read(vf, item.data, header.size) != header.size) {
|
||||
free(item.data);
|
||||
continue;
|
||||
}
|
||||
mStateExtdataPut(extdata, header.tag, &item);
|
||||
vf->seek(vf, position, SEEK_SET);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef USE_PNG
|
||||
static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
size_t stride;
|
||||
color_t* pixels = 0;
|
||||
|
||||
core->getVideoBuffer(core, &pixels, &stride);
|
||||
if (!pixels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t stateSize = core->stateSize(core);
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
core->saveState(core, state);
|
||||
|
||||
uLongf len = compressBound(stateSize);
|
||||
void* buffer = malloc(len);
|
||||
if (!buffer) {
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return false;
|
||||
}
|
||||
compress(buffer, &len, (const Bytef*) state, stateSize);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, width, height);
|
||||
if (!png || !info) {
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
PNGWritePixels(png, width, height, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
if (extdata) {
|
||||
uint32_t i;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (!extdata->data[i].data) {
|
||||
continue;
|
||||
}
|
||||
uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
|
||||
uint32_t* data = malloc(len);
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
STORE_32LE(i, 0, data);
|
||||
STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
|
||||
compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
|
||||
PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||
struct mBundledState* bundle = png_get_user_chunk_ptr(png);
|
||||
if (!bundle) {
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAs")) {
|
||||
void* state = bundle->state;
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
uLongf len = bundle->stateSize;
|
||||
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAx")) {
|
||||
struct mStateExtdata* extdata = bundle->extdata;
|
||||
if (!extdata) {
|
||||
return 0;
|
||||
}
|
||||
struct mStateExtdataItem item;
|
||||
if (chunk->size < sizeof(uint32_t) * 2) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t tag;
|
||||
LOAD_32LE(tag, 0, chunk->data);
|
||||
LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
|
||||
uLongf len = item.size;
|
||||
if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return 0;
|
||||
}
|
||||
item.data = malloc(item.size);
|
||||
item.clean = free;
|
||||
if (!item.data) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t* data = chunk->data;
|
||||
data += sizeof(uint32_t) * 2;
|
||||
uncompress((Bytef*) item.data, &len, data, chunk->size);
|
||||
item.size = len;
|
||||
mStateExtdataPut(extdata, tag, &item);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
|
||||
png_infop info = png_create_info_struct(png);
|
||||
png_infop end = png_create_info_struct(png);
|
||||
if (!png || !info || !end) {
|
||||
PNGReadClose(png, info, end);
|
||||
return false;
|
||||
}
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
uint32_t* pixels = malloc(width * height * 4);
|
||||
if (!pixels) {
|
||||
PNGReadClose(png, info, end);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t stateSize = core->stateSize(core);
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
struct mBundledState bundle = {
|
||||
.stateSize = stateSize,
|
||||
.state = state,
|
||||
.extdata = extdata
|
||||
};
|
||||
|
||||
PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
|
||||
bool success = PNGReadHeader(png, info);
|
||||
success = success && PNGReadPixels(png, info, pixels, width, height, width);
|
||||
success = success && PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
|
||||
if (success) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = width * height * 4,
|
||||
.data = pixels,
|
||||
.clean = free
|
||||
};
|
||||
mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
|
||||
} else {
|
||||
free(pixels);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
size_t stateSize = core->stateSize(core);
|
||||
if (flags & SAVESTATE_SAVEDATA) {
|
||||
void* sram = NULL;
|
||||
size_t size = core->savedataClone(core, &sram);
|
||||
if (size) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = size,
|
||||
.data = sram,
|
||||
.clean = free
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
|
||||
}
|
||||
}
|
||||
struct VFile* cheatVf = 0;
|
||||
struct mCheatDevice* device;
|
||||
if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
|
||||
cheatVf = VFileMemChunk(0, 0);
|
||||
if (cheatVf) {
|
||||
mCheatSaveFile(device, cheatVf);
|
||||
struct mStateExtdataItem item = {
|
||||
.size = cheatVf->size(cheatVf),
|
||||
.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
|
||||
.clean = 0
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
|
||||
}
|
||||
}
|
||||
#ifdef USE_PNG
|
||||
if (!(flags & SAVESTATE_SCREENSHOT)) {
|
||||
#else
|
||||
UNUSED(flags);
|
||||
#endif
|
||||
vf->truncate(vf, stateSize);
|
||||
struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
|
||||
if (!state) {
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
core->saveState(core, state);
|
||||
vf->unmap(vf, state, stateSize);
|
||||
vf->seek(vf, stateSize, SEEK_SET);
|
||||
mStateExtdataSerialize(&extdata, vf);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return true;
|
||||
#ifdef USE_PNG
|
||||
}
|
||||
else {
|
||||
bool success = _savePNGState(core, vf, &extdata);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
#ifdef USE_PNG
|
||||
if (isPNG(vf)) {
|
||||
return _loadPNGState(core, vf, extdata);
|
||||
}
|
||||
#endif
|
||||
ssize_t stateSize = core->stateSize(core);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (vf->size(vf) < stateSize) {
|
||||
return false;
|
||||
}
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
if (vf->read(vf, state, stateSize) != stateSize) {
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return 0;
|
||||
}
|
||||
if (extdata) {
|
||||
mStateExtdataDeserialize(extdata, vf);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
void* state = mCoreExtractState(core, vf, &extdata);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
bool success = core->loadState(core, state);
|
||||
mappedMemoryFree(state, core->stateSize(core));
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
|
||||
struct mStateExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
|
||||
if (item.size >= (int) (width * height) * 4) {
|
||||
/*gba->video.renderer->putPixels(gba->video.renderer, width, item.data);
|
||||
mCoreSyncForceFrame(core->sync);*/
|
||||
} else {
|
||||
mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
|
||||
}
|
||||
}
|
||||
if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
|
||||
if (item.data) {
|
||||
core->savedataLoad(core, item.data, item.size);
|
||||
}
|
||||
}
|
||||
struct mCheatDevice* device;
|
||||
if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
|
||||
if (item.size) {
|
||||
struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
if (svf) {
|
||||
mCheatDeviceClear(device);
|
||||
mCheatParseFile(device, svf);
|
||||
svf->close(svf);
|
||||
}
|
||||
}
|
||||
}
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/* Copyright (c) 2013-2016 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 M_SERIALIZE_H
|
||||
#define M_SERIALIZE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
enum mStateExtdataTag {
|
||||
EXTDATA_NONE = 0,
|
||||
EXTDATA_SCREENSHOT = 1,
|
||||
EXTDATA_SAVEDATA = 2,
|
||||
EXTDATA_CHEATS = 3,
|
||||
EXTDATA_MAX
|
||||
};
|
||||
|
||||
#define SAVESTATE_SCREENSHOT 1
|
||||
#define SAVESTATE_SAVEDATA 2
|
||||
#define SAVESTATE_CHEATS 4
|
||||
|
||||
struct mStateExtdataItem {
|
||||
int32_t size;
|
||||
void* data;
|
||||
void (*clean)(void*);
|
||||
};
|
||||
|
||||
struct mStateExtdata {
|
||||
struct mStateExtdataItem data[EXTDATA_MAX];
|
||||
};
|
||||
|
||||
bool mStateExtdataInit(struct mStateExtdata*);
|
||||
void mStateExtdataDeinit(struct mStateExtdata*);
|
||||
void mStateExtdataPut(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*);
|
||||
bool mStateExtdataGet(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*);
|
||||
|
||||
struct VFile;
|
||||
bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf);
|
||||
bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf);
|
||||
|
||||
struct mCore;
|
||||
bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags);
|
||||
bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags);
|
||||
void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata);
|
||||
|
||||
#endif
|
|
@ -5,7 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "sync.h"
|
||||
|
||||
static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
|
||||
static void _changeVideoSync(struct mCoreSync* sync, bool frameOn) {
|
||||
// Make sure the video thread can process events while the GBA thread is paused
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
if (frameOn != sync->videoFrameOn) {
|
||||
|
@ -15,7 +15,7 @@ static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
|
|||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync) {
|
||||
void mCoreSyncPostFrame(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ void GBASyncPostFrame(struct GBASync* sync) {
|
|||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBASyncForceFrame(struct GBASync* sync) {
|
||||
void mCoreSyncForceFrame(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ void GBASyncForceFrame(struct GBASync* sync) {
|
|||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync) {
|
||||
bool mCoreSyncWaitFrameStart(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ bool GBASyncWaitFrameStart(struct GBASync* sync) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync) {
|
||||
void mCoreSyncWaitFrameEnd(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ void GBASyncWaitFrameEnd(struct GBASync* sync) {
|
|||
MutexUnlock(&sync->videoFrameMutex);
|
||||
}
|
||||
|
||||
void GBASyncSetVideoSync(struct GBASync* sync, bool wait) {
|
||||
void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool wait) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ void GBASyncSetVideoSync(struct GBASync* sync, bool wait) {
|
|||
_changeVideoSync(sync, wait);
|
||||
}
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
|
||||
void mCoreSyncProduceAudio(struct mCoreSync* sync, bool wait) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
|
|||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncLockAudio(struct GBASync* sync) {
|
||||
void mCoreSyncLockAudio(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ void GBASyncLockAudio(struct GBASync* sync) {
|
|||
MutexLock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncUnlockAudio(struct GBASync* sync) {
|
||||
void mCoreSyncUnlockAudio(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ void GBASyncUnlockAudio(struct GBASync* sync) {
|
|||
MutexUnlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBASyncConsumeAudio(struct GBASync* sync) {
|
||||
void mCoreSyncConsumeAudio(struct mCoreSync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright (c) 2013-2016 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 M_CORE_SYNC_H
|
||||
#define M_CORE_SYNC_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
struct mCoreSync {
|
||||
int videoFramePending;
|
||||
bool videoFrameWait;
|
||||
bool videoFrameOn;
|
||||
Mutex videoFrameMutex;
|
||||
Condition videoFrameAvailableCond;
|
||||
Condition videoFrameRequiredCond;
|
||||
|
||||
bool audioWait;
|
||||
Condition audioRequiredCond;
|
||||
Mutex audioBufferMutex;
|
||||
|
||||
float fpsTarget;
|
||||
};
|
||||
|
||||
void mCoreSyncPostFrame(struct mCoreSync* sync);
|
||||
void mCoreSyncForceFrame(struct mCoreSync* sync);
|
||||
bool mCoreSyncWaitFrameStart(struct mCoreSync* sync);
|
||||
void mCoreSyncWaitFrameEnd(struct mCoreSync* sync);
|
||||
void mCoreSyncSetVideoSync(struct mCoreSync* sync, bool wait);
|
||||
|
||||
void mCoreSyncProduceAudio(struct mCoreSync* sync, bool wait);
|
||||
void mCoreSyncLockAudio(struct mCoreSync* sync);
|
||||
void mCoreSyncUnlockAudio(struct mCoreSync* sync);
|
||||
void mCoreSyncConsumeAudio(struct mCoreSync* sync);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,456 @@
|
|||
/* Copyright (c) 2013-2016 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"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "util/patch.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#include "feature/commandline.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
|
||||
static const float _defaultFPSTarget = 60.f;
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
static pthread_key_t _contextKey;
|
||||
static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void _createTLS(void) {
|
||||
pthread_key_create(&_contextKey, 0);
|
||||
}
|
||||
#elif _WIN32
|
||||
static DWORD _contextKey;
|
||||
static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
|
||||
UNUSED(once);
|
||||
UNUSED(param);
|
||||
UNUSED(context);
|
||||
_contextKey = TlsAlloc();
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
threadContext->state = newState;
|
||||
if (broadcast) {
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
static void _waitOnInterrupt(struct mCoreThread* threadContext) {
|
||||
while (threadContext->state == THREAD_INTERRUPTED) {
|
||||
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThreadState oldState) {
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
bool videoFrameWait = threadContext->sync.videoFrameWait;
|
||||
threadContext->sync.videoFrameWait = false;
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
|
||||
while (threadContext->state == oldState) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
if (!MutexTryLock(&threadContext->sync.videoFrameMutex)) {
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
if (!MutexTryLock(&threadContext->sync.audioBufferMutex)) {
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.audioBufferMutex);
|
||||
}
|
||||
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
threadContext->sync.videoFrameWait = videoFrameWait;
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
static void _pauseThread(struct mCoreThread* threadContext, bool onThread) {
|
||||
threadContext->state = THREAD_PAUSING;
|
||||
if (!onThread) {
|
||||
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||
}
|
||||
}
|
||||
|
||||
static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||
struct mCoreThread* threadContext = context;
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_once(&_contextOnce, _createTLS);
|
||||
pthread_setspecific(_contextKey, threadContext);
|
||||
#elif _WIN32
|
||||
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||
TlsSetValue(_contextKey, threadContext);
|
||||
#endif
|
||||
|
||||
ThreadSetName("CPU Thread");
|
||||
|
||||
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
pthread_sigmask(SIG_SETMASK, &signals, 0);
|
||||
#endif
|
||||
|
||||
struct mCore* core = threadContext->core;
|
||||
core->setSync(core, &threadContext->sync);
|
||||
core->reset(core);
|
||||
|
||||
_changeState(threadContext, THREAD_RUNNING, true);
|
||||
|
||||
if (threadContext->startCallback) {
|
||||
threadContext->startCallback(threadContext);
|
||||
}
|
||||
|
||||
while (threadContext->state < THREAD_EXITING) {
|
||||
struct mDebugger* debugger = core->debugger;
|
||||
if (debugger) {
|
||||
mDebuggerRun(debugger);
|
||||
if (debugger->state == DEBUGGER_SHUTDOWN) {
|
||||
_changeState(threadContext, THREAD_EXITING, false);
|
||||
}
|
||||
} else {
|
||||
while (threadContext->state == THREAD_RUNNING) {
|
||||
core->runLoop(core);
|
||||
}
|
||||
}
|
||||
|
||||
int resetScheduled = 0;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
while (threadContext->state > THREAD_RUNNING && threadContext->state < THREAD_EXITING) {
|
||||
if (threadContext->state == THREAD_PAUSING) {
|
||||
threadContext->state = THREAD_PAUSED;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
if (threadContext->state == THREAD_INTERRUPTING) {
|
||||
threadContext->state = THREAD_INTERRUPTED;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
if (threadContext->state == THREAD_RUN_ON) {
|
||||
if (threadContext->run) {
|
||||
threadContext->run(threadContext);
|
||||
}
|
||||
threadContext->state = threadContext->savedState;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
if (threadContext->state == THREAD_RESETING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
resetScheduled = 1;
|
||||
}
|
||||
while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
|
||||
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||
}
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
if (resetScheduled) {
|
||||
core->reset(core);
|
||||
}
|
||||
}
|
||||
|
||||
while (threadContext->state < THREAD_SHUTDOWN) {
|
||||
_changeState(threadContext, THREAD_SHUTDOWN, false);
|
||||
}
|
||||
|
||||
if (threadContext->cleanCallback) {
|
||||
threadContext->cleanCallback(threadContext);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool mCoreThreadStart(struct mCoreThread* threadContext) {
|
||||
threadContext->state = THREAD_INITIALIZED;
|
||||
threadContext->logger.p = threadContext;
|
||||
threadContext->logLevel = threadContext->core->opts.logLevel;
|
||||
|
||||
if (!threadContext->sync.fpsTarget) {
|
||||
threadContext->sync.fpsTarget = _defaultFPSTarget;
|
||||
}
|
||||
|
||||
MutexInit(&threadContext->stateMutex);
|
||||
ConditionInit(&threadContext->stateCond);
|
||||
|
||||
MutexInit(&threadContext->sync.videoFrameMutex);
|
||||
ConditionInit(&threadContext->sync.videoFrameAvailableCond);
|
||||
ConditionInit(&threadContext->sync.videoFrameRequiredCond);
|
||||
MutexInit(&threadContext->sync.audioBufferMutex);
|
||||
ConditionInit(&threadContext->sync.audioRequiredCond);
|
||||
|
||||
threadContext->interruptDepth = 0;
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
sigaddset(&signals, SIGINT);
|
||||
sigaddset(&signals, SIGTRAP);
|
||||
pthread_sigmask(SIG_BLOCK, &signals, 0);
|
||||
#endif
|
||||
|
||||
threadContext->sync.audioWait = threadContext->core->opts.audioSync;
|
||||
threadContext->sync.videoFrameWait = threadContext->core->opts.videoSync;
|
||||
threadContext->sync.fpsTarget = threadContext->core->opts.fpsTarget;
|
||||
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
ThreadCreate(&threadContext->thread, _mCoreThreadRun, threadContext);
|
||||
while (threadContext->state < THREAD_RUNNING) {
|
||||
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
|
||||
bool hasStarted;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
hasStarted = threadContext->state > THREAD_INITIALIZED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return hasStarted;
|
||||
}
|
||||
|
||||
bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
|
||||
bool hasExited;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
hasExited = threadContext->state > THREAD_EXITING;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return hasExited;
|
||||
}
|
||||
|
||||
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
|
||||
bool hasExited;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
hasExited = threadContext->state == THREAD_CRASHED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return hasExited;
|
||||
}
|
||||
|
||||
void mCoreThreadEnd(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->state = THREAD_EXITING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
MutexLock(&threadContext->sync.audioBufferMutex);
|
||||
threadContext->sync.audioWait = 0;
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.audioBufferMutex);
|
||||
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
threadContext->sync.videoFrameWait = false;
|
||||
threadContext->sync.videoFrameOn = false;
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadReset(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->state = THREAD_RESETING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadJoin(struct mCoreThread* threadContext) {
|
||||
ThreadJoin(threadContext->thread);
|
||||
|
||||
MutexDeinit(&threadContext->stateMutex);
|
||||
ConditionDeinit(&threadContext->stateCond);
|
||||
|
||||
MutexDeinit(&threadContext->sync.videoFrameMutex);
|
||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||
ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
|
||||
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
ConditionDeinit(&threadContext->sync.audioRequiredCond);
|
||||
MutexDeinit(&threadContext->sync.audioBufferMutex);
|
||||
}
|
||||
|
||||
bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
|
||||
return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
|
||||
}
|
||||
|
||||
void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
|
||||
if (!threadContext) {
|
||||
return;
|
||||
}
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
++threadContext->interruptDepth;
|
||||
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return;
|
||||
}
|
||||
threadContext->savedState = threadContext->state;
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->state = THREAD_INTERRUPTING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
|
||||
if (!threadContext) {
|
||||
return;
|
||||
}
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
++threadContext->interruptDepth;
|
||||
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return;
|
||||
}
|
||||
threadContext->savedState = threadContext->state;
|
||||
threadContext->state = THREAD_INTERRUPTING;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadContinue(struct mCoreThread* threadContext) {
|
||||
if (!threadContext) {
|
||||
return;
|
||||
}
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
--threadContext->interruptDepth;
|
||||
if (threadContext->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
|
||||
threadContext->state = threadContext->savedState;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
threadContext->run = run;
|
||||
_waitOnInterrupt(threadContext);
|
||||
threadContext->savedState = threadContext->state;
|
||||
threadContext->state = THREAD_RUN_ON;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
_waitUntilNotState(threadContext, THREAD_RUN_ON);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
||||
void mCoreThreadPause(struct mCoreThread* threadContext) {
|
||||
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);
|
||||
|
||||
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
void mCoreThreadUnpause(struct mCoreThread* 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);
|
||||
|
||||
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
|
||||
bool isPaused;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
_waitOnInterrupt(threadContext);
|
||||
isPaused = threadContext->state == THREAD_PAUSED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return isPaused;
|
||||
}
|
||||
|
||||
void mCoreThreadTogglePause(struct mCoreThread* 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;
|
||||
} else if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, false);
|
||||
threadContext->frameWasOn = frameOn;
|
||||
frameOn = false;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
|
||||
bool frameOn = true;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_RUNNING) {
|
||||
_pauseThread(threadContext, true);
|
||||
frameOn = false;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||
}
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
struct mCoreThread* mCoreThreadGet(void) {
|
||||
pthread_once(&_contextOnce, _createTLS);
|
||||
return pthread_getspecific(_contextKey);
|
||||
}
|
||||
#elif _WIN32
|
||||
struct mCoreThread* mCoreThreadGet(void) {
|
||||
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||
return TlsGetValue(_contextKey);
|
||||
}
|
||||
#else
|
||||
struct mCoreThread* mCoreThreadGet(void) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
struct mCoreThread* mCoreThreadGet(void) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
|
||||
UNUSED(logger);
|
||||
struct mCoreThread* thread = mCoreThreadGet();
|
||||
if (thread && !(thread->logLevel & level)) {
|
||||
return;
|
||||
}
|
||||
printf("%s: ", mLogCategoryName(category));
|
||||
vprintf(format, args);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
struct mLogger* mCoreThreadLogger(void) {
|
||||
struct mCoreThread* thread = mCoreThreadGet();
|
||||
if (thread) {
|
||||
if (!thread->logger.d.log) {
|
||||
thread->logger.d.log = _mCoreLog;
|
||||
}
|
||||
return &thread->logger.d;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/* Copyright (c) 2013-2016 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 M_CORE_THREAD_H
|
||||
#define M_CORE_THREAD_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/log.h"
|
||||
#include "core/sync.h"
|
||||
#include "util/threading.h"
|
||||
|
||||
struct mCoreThread;
|
||||
struct mCore;
|
||||
|
||||
typedef void (*ThreadCallback)(struct mCoreThread* threadContext);
|
||||
|
||||
enum mCoreThreadState {
|
||||
THREAD_INITIALIZED = -1,
|
||||
THREAD_RUNNING = 0,
|
||||
THREAD_INTERRUPTED,
|
||||
THREAD_INTERRUPTING,
|
||||
THREAD_PAUSED,
|
||||
THREAD_PAUSING,
|
||||
THREAD_RUN_ON,
|
||||
THREAD_RESETING,
|
||||
THREAD_EXITING,
|
||||
THREAD_SHUTDOWN,
|
||||
THREAD_CRASHED
|
||||
};
|
||||
|
||||
struct mCoreThread;
|
||||
struct mThreadLogger {
|
||||
struct mLogger d;
|
||||
struct mCoreThread* p;
|
||||
};
|
||||
|
||||
struct mCoreThread {
|
||||
// Input
|
||||
struct mCore* core;
|
||||
|
||||
// Threading state
|
||||
Thread thread;
|
||||
enum mCoreThreadState state;
|
||||
|
||||
Mutex stateMutex;
|
||||
Condition stateCond;
|
||||
enum mCoreThreadState savedState;
|
||||
int interruptDepth;
|
||||
bool frameWasOn;
|
||||
|
||||
struct mThreadLogger logger;
|
||||
enum mLogLevel logLevel;
|
||||
ThreadCallback startCallback;
|
||||
ThreadCallback cleanCallback;
|
||||
ThreadCallback frameCallback;
|
||||
void* userData;
|
||||
void (*run)(struct mCoreThread*);
|
||||
|
||||
struct mCoreSync sync;
|
||||
};
|
||||
|
||||
bool mCoreThreadStart(struct mCoreThread* threadContext);
|
||||
bool mCoreThreadHasStarted(struct mCoreThread* threadContext);
|
||||
bool mCoreThreadHasExited(struct mCoreThread* threadContext);
|
||||
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext);
|
||||
void mCoreThreadEnd(struct mCoreThread* threadContext);
|
||||
void mCoreThreadReset(struct mCoreThread* threadContext);
|
||||
void mCoreThreadJoin(struct mCoreThread* threadContext);
|
||||
|
||||
bool mCoreThreadIsActive(struct mCoreThread* threadContext);
|
||||
void mCoreThreadInterrupt(struct mCoreThread* threadContext);
|
||||
void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext);
|
||||
void mCoreThreadContinue(struct mCoreThread* threadContext);
|
||||
|
||||
void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*));
|
||||
|
||||
void mCoreThreadPause(struct mCoreThread* threadContext);
|
||||
void mCoreThreadUnpause(struct mCoreThread* threadContext);
|
||||
bool mCoreThreadIsPaused(struct mCoreThread* threadContext);
|
||||
void mCoreThreadTogglePause(struct mCoreThread* threadContext);
|
||||
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext);
|
||||
|
||||
struct mCoreThread* mCoreThreadGet(void);
|
||||
struct mLogger* mCoreThreadLogger(void);
|
||||
|
||||
#endif
|
|
@ -1,9 +1,9 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/version.h"
|
||||
#include "core/version.h"
|
||||
|
||||
const char* const gitCommit = "${GIT_COMMIT}";
|
||||
const char* const gitCommitShort = "${GIT_COMMIT_SHORT}";
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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
|
|
@ -4,7 +4,11 @@
|
|||
* 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 "cli-debugger.h"
|
||||
#include "decoder.h"
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/version.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
@ -13,8 +17,8 @@
|
|||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share
|
||||
static const char* ERROR_OVERFLOW = "Arguments overflow";
|
||||
const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share
|
||||
const char* ERROR_OVERFLOW = "Arguments overflow";
|
||||
|
||||
static struct CLIDebugger* _activeDebugger;
|
||||
|
||||
|
@ -23,8 +27,6 @@ static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*);
|
|||
#endif
|
||||
static void _continue(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _disassemble(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _disassembleThumb(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _next(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _print(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _printBin(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
@ -37,42 +39,27 @@ static void _reset(struct CLIDebugger*, struct CLIDebugVector*);
|
|||
static void _readHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _readWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _clearBreakpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _setWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _dumpWord(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
||||
static void _breakIntoDefault(int signal);
|
||||
static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode);
|
||||
static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
|
||||
static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
||||
{ "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
|
||||
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
|
||||
{ "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "c", _continue, 0, "Continue execution" },
|
||||
{ "continue", _continue, 0, "Continue execution" },
|
||||
{ "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" },
|
||||
{ "delete", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" },
|
||||
{ "dis", _disassemble, CLIDVParse, "Disassemble instructions" },
|
||||
{ "dis/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "dis/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "disasm", _disassemble, CLIDVParse, "Disassemble instructions" },
|
||||
{ "disasm/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "disasm/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "disassemble", _disassemble, CLIDVParse, "Disassemble instructions" },
|
||||
{ "disassemble/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" },
|
||||
{ "disassemble/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" },
|
||||
{ "h", _printHelp, CLIDVStringParse, "Print help" },
|
||||
{ "help", _printHelp, CLIDVStringParse, "Print help" },
|
||||
{ "i", _printStatus, 0, "Print the current status" },
|
||||
|
@ -93,11 +80,10 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
|||
{ "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" },
|
||||
{ "status", _printStatus, 0, "Print the current status" },
|
||||
{ "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" },
|
||||
{ "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" },
|
||||
{ "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" },
|
||||
{ "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" },
|
||||
{ "w/4", _writeWord, CLIDVParse, "Write a word at a specified offset" },
|
||||
{ "w/r", _writeRegister, CLIDVParse, "Write a register" },
|
||||
{ "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" },
|
||||
{ "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" },
|
||||
{ "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" },
|
||||
{ "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" },
|
||||
|
@ -107,17 +93,6 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
|||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static inline void _printPSR(union PSR psr) {
|
||||
printf("%08X [%c%c%c%c%c%c%c]\n", psr.packed,
|
||||
psr.n ? 'N' : '-',
|
||||
psr.z ? 'Z' : '-',
|
||||
psr.c ? 'C' : '-',
|
||||
psr.v ? 'V' : '-',
|
||||
psr.i ? 'I' : '-',
|
||||
psr.f ? 'F' : '-',
|
||||
psr.t ? 'T' : '-');
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void _handleDeath(int sig) {
|
||||
UNUSED(sig);
|
||||
|
@ -149,57 +124,12 @@ static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
if (debugger->d.currentBreakpoint) {
|
||||
if (debugger->d.currentBreakpoint->isSw && debugger->d.setSoftwareBreakpoint) {
|
||||
debugger->d.setSoftwareBreakpoint(&debugger->d, debugger->d.currentBreakpoint->address, debugger->d.currentBreakpoint->sw.mode, &debugger->d.currentBreakpoint->sw.opcode);
|
||||
}
|
||||
debugger->d.currentBreakpoint = 0;
|
||||
}
|
||||
ARMRun(debugger->d.cpu);
|
||||
debugger->d.core->step(debugger->d.core);
|
||||
_printStatus(debugger, 0);
|
||||
}
|
||||
|
||||
static void _disassemble(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
_disassembleMode(debugger, dv, debugger->d.cpu->executionMode);
|
||||
}
|
||||
|
||||
static void _disassembleArm(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
_disassembleMode(debugger, dv, MODE_ARM);
|
||||
}
|
||||
|
||||
static void _disassembleThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
_disassembleMode(debugger, dv, MODE_THUMB);
|
||||
}
|
||||
|
||||
static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv, enum ExecutionMode mode) {
|
||||
uint32_t address;
|
||||
int size;
|
||||
int wordSize;
|
||||
|
||||
if (mode == MODE_ARM) {
|
||||
wordSize = WORD_SIZE_ARM;
|
||||
} else {
|
||||
wordSize = WORD_SIZE_THUMB;
|
||||
}
|
||||
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
address = debugger->d.cpu->gprs[ARM_PC] - wordSize;
|
||||
} else {
|
||||
address = dv->intValue;
|
||||
dv = dv->next;
|
||||
}
|
||||
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
size = 1;
|
||||
} else {
|
||||
size = dv->intValue;
|
||||
dv = dv->next; // TODO: Check for excess args
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < size; ++i) {
|
||||
address += _printLine(debugger, address, mode);;
|
||||
}
|
||||
debugger->system->disassemble(debugger->system, dv);
|
||||
}
|
||||
|
||||
static void _print(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -234,12 +164,16 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
UNUSED(debugger);
|
||||
UNUSED(dv);
|
||||
if (!dv) {
|
||||
puts("ARM commands:");
|
||||
puts("Generic commands:");
|
||||
int i;
|
||||
for (i = 0; _debuggerCommands[i].name; ++i) {
|
||||
printf("%-10s %s\n", _debuggerCommands[i].name, _debuggerCommands[i].summary);
|
||||
}
|
||||
if (debugger->system) {
|
||||
printf("%s commands:\n", debugger->system->platformName);
|
||||
for (i = 0; debugger->system->platformCommands[i].name; ++i) {
|
||||
printf("%-10s %s\n", debugger->system->platformCommands[i].name, debugger->system->platformCommands[i].summary);
|
||||
}
|
||||
printf("%s commands:\n", debugger->system->name);
|
||||
for (i = 0; debugger->system->commands[i].name; ++i) {
|
||||
printf("%-10s %s\n", debugger->system->commands[i].name, debugger->system->commands[i].summary);
|
||||
|
@ -253,7 +187,11 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
}
|
||||
}
|
||||
if (debugger->system) {
|
||||
printf("\n%s commands:\n", debugger->system->name);
|
||||
for (i = 0; debugger->system->platformCommands[i].name; ++i) {
|
||||
if (strcmp(debugger->system->platformCommands[i].name, dv->charValue) == 0) {
|
||||
printf(" %s\n", debugger->system->platformCommands[i].summary);
|
||||
}
|
||||
}
|
||||
for (i = 0; debugger->system->commands[i].name; ++i) {
|
||||
if (strcmp(debugger->system->commands[i].name, dv->charValue) == 0) {
|
||||
printf(" %s\n", debugger->system->commands[i].summary);
|
||||
|
@ -263,56 +201,6 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
}
|
||||
}
|
||||
|
||||
static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
char disassembly[48];
|
||||
struct ARMInstructionInfo info;
|
||||
printf("%08X: ", address);
|
||||
if (mode == MODE_ARM) {
|
||||
uint32_t instruction = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0);
|
||||
ARMDecodeARM(instruction, &info);
|
||||
ARMDisassemble(&info, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly));
|
||||
printf("%08X\t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_ARM;
|
||||
} else {
|
||||
struct ARMInstructionInfo info2;
|
||||
struct ARMInstructionInfo combined;
|
||||
uint16_t instruction = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0);
|
||||
uint16_t instruction2 = debugger->d.cpu->memory.load16(debugger->d.cpu, address + WORD_SIZE_THUMB, 0);
|
||||
ARMDecodeThumb(instruction, &info);
|
||||
ARMDecodeThumb(instruction2, &info2);
|
||||
if (ARMDecodeThumbCombine(&info, &info2, &combined)) {
|
||||
ARMDisassemble(&combined, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X %04X\t%s\n", instruction, instruction2, disassembly);
|
||||
return WORD_SIZE_THUMB * 2;
|
||||
} else {
|
||||
ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly));
|
||||
printf("%04X \t%s\n", instruction, disassembly);
|
||||
return WORD_SIZE_THUMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
int r;
|
||||
for (r = 0; r < 4; ++r) {
|
||||
printf("%08X %08X %08X %08X\n",
|
||||
debugger->d.cpu->gprs[r << 2],
|
||||
debugger->d.cpu->gprs[(r << 2) + 1],
|
||||
debugger->d.cpu->gprs[(r << 2) + 2],
|
||||
debugger->d.cpu->gprs[(r << 2) + 3]);
|
||||
}
|
||||
_printPSR(debugger->d.cpu->cpsr);
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = debugger->d.cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
instructionLength = WORD_SIZE_ARM;
|
||||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
_printLine(debugger, debugger->d.cpu->gprs[ARM_PC] - instructionLength, mode);
|
||||
}
|
||||
|
||||
static void _quit(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
debugger->d.state = DEBUGGER_SHUTDOWN;
|
||||
|
@ -324,13 +212,13 @@ static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint8_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0);
|
||||
uint8_t value = debugger->d.core->busRead8(debugger->d.core, address);
|
||||
printf(" 0x%02X\n", value);
|
||||
}
|
||||
|
||||
static void _reset(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
ARMReset(debugger->d.cpu);
|
||||
debugger->d.core->reset(debugger->d.core);
|
||||
_printStatus(debugger, 0);
|
||||
}
|
||||
|
||||
|
@ -340,7 +228,7 @@ static void _readHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint16_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address & ~1, 0);
|
||||
uint16_t value = debugger->d.core->busRead16(debugger->d.core, address & ~1);
|
||||
printf(" 0x%04X\n", value);
|
||||
}
|
||||
|
||||
|
@ -350,7 +238,7 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address & ~3, 0);
|
||||
uint32_t value = debugger->d.core->busRead32(debugger->d.core, address & ~3);
|
||||
printf(" 0x%08X\n", value);
|
||||
}
|
||||
|
||||
|
@ -369,7 +257,7 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
printf("%s\n", ERROR_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->memory.store8(debugger->d.cpu, address, value, 0);
|
||||
debugger->d.core->busWrite8(debugger->d.core, address, value);
|
||||
}
|
||||
|
||||
static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -387,7 +275,7 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
printf("%s\n", ERROR_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->memory.store16(debugger->d.cpu, address, value, 0);
|
||||
debugger->d.core->busWrite16(debugger->d.core, address, value);
|
||||
}
|
||||
|
||||
static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -401,24 +289,7 @@ static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
|
|||
}
|
||||
uint32_t address = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
debugger->d.cpu->memory.store32(debugger->d.cpu, address, value, 0);
|
||||
}
|
||||
|
||||
static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t regid = dv->intValue;
|
||||
uint32_t value = dv->next->intValue;
|
||||
if (regid >= ARM_PC) {
|
||||
return;
|
||||
}
|
||||
debugger->d.cpu->gprs[regid] = value;
|
||||
debugger->d.core->busWrite32(debugger->d.core, address, value);
|
||||
}
|
||||
|
||||
static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -438,7 +309,7 @@ static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, ++address, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0);
|
||||
uint32_t value = debugger->d.core->busRead8(debugger->d.core, address);
|
||||
printf(" %02X", value);
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -462,7 +333,7 @@ static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d
|
|||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, address += 2, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0);
|
||||
uint32_t value = debugger->d.core->busRead16(debugger->d.core, address);
|
||||
printf(" %04X", value);
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -486,7 +357,7 @@ static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
}
|
||||
printf("0x%08X:", address);
|
||||
for (; line > 0; --line, address += 4, --words) {
|
||||
uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0);
|
||||
uint32_t value = debugger->d.core->busRead32(debugger->d.core, address);
|
||||
printf(" %08X", value);
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -499,35 +370,7 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetBreakpoint(&debugger->d, address);
|
||||
}
|
||||
|
||||
static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_ARM);
|
||||
}
|
||||
|
||||
static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_THUMB);
|
||||
}
|
||||
|
||||
static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerClearBreakpoint(&debugger->d, address);
|
||||
ARMDebuggerClearWatchpoint(&debugger->d, address);
|
||||
debugger->d.platform->setBreakpoint(debugger->d.platform, address);
|
||||
}
|
||||
|
||||
static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -536,12 +379,27 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
ARMDebuggerSetWatchpoint(&debugger->d, address);
|
||||
debugger->d.platform->setWatchpoint(debugger->d.platform, address, WATCHPOINT_RW); // TODO: ro/wo
|
||||
}
|
||||
|
||||
static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
uint32_t address = dv->intValue;
|
||||
debugger->d.platform->clearBreakpoint(debugger->d.platform, address);
|
||||
debugger->d.platform->clearWatchpoint(debugger->d.platform, address);
|
||||
}
|
||||
|
||||
static void _breakIntoDefault(int signal) {
|
||||
UNUSED(signal);
|
||||
ARMDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
mDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
}
|
||||
|
||||
static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
debugger->system->printStatus(debugger->system);
|
||||
}
|
||||
|
||||
static uint32_t _performOperation(enum Operation operation, uint32_t current, uint32_t next, struct CLIDebugVector* dv) {
|
||||
|
@ -570,42 +428,24 @@ static uint32_t _performOperation(enum Operation operation, uint32_t current, ui
|
|||
return current;
|
||||
}
|
||||
|
||||
static uint32_t _lookupIdentifier(struct ARMDebugger* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
static uint32_t _lookupIdentifier(struct mDebugger* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
return debugger->cpu->gprs[ARM_SP];
|
||||
}
|
||||
if (strcmp(name, "lr") == 0) {
|
||||
return debugger->cpu->gprs[ARM_LR];
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
return debugger->cpu->gprs[ARM_PC];
|
||||
}
|
||||
if (strcmp(name, "cpsr") == 0) {
|
||||
return debugger->cpu->cpsr.packed;
|
||||
}
|
||||
// TODO: test if mode has SPSR
|
||||
if (strcmp(name, "spsr") == 0) {
|
||||
return debugger->cpu->spsr.packed;
|
||||
}
|
||||
if (name[0] == 'r' && name[1] >= '0' && name[1] <= '9') {
|
||||
int reg = atoi(&name[1]);
|
||||
if (reg < 16) {
|
||||
return debugger->cpu->gprs[reg];
|
||||
}
|
||||
}
|
||||
if (cliDebugger->system) {
|
||||
uint32_t value = cliDebugger->system->lookupIdentifier(cliDebugger->system, name, dv);
|
||||
uint32_t value = cliDebugger->system->lookupPlatformIdentifier(cliDebugger->system, name, dv);
|
||||
if (dv->type != CLIDV_ERROR_TYPE) {
|
||||
return value;
|
||||
}
|
||||
dv->type = CLIDV_INT_TYPE;
|
||||
value = cliDebugger->system->lookupIdentifier(cliDebugger->system, name, dv);
|
||||
if (dv->type != CLIDV_ERROR_TYPE) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
dv->type = CLIDV_ERROR_TYPE;
|
||||
}
|
||||
dv->type = CLIDV_ERROR_TYPE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t _evaluateParseTree(struct ARMDebugger* debugger, struct ParseTree* tree, struct CLIDebugVector* dv) {
|
||||
static uint32_t _evaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, struct CLIDebugVector* dv) {
|
||||
switch (tree->token.type) {
|
||||
case TOKEN_UINT_TYPE:
|
||||
return tree->token.uintValue;
|
||||
|
@ -753,6 +593,9 @@ static bool _parse(struct CLIDebugger* debugger, const char* line, size_t count)
|
|||
int result = _tryCommands(debugger, _debuggerCommands, line, cmdLength, args, count - cmdLength - 1);
|
||||
if (result < 0 && debugger->system) {
|
||||
result = _tryCommands(debugger, debugger->system->commands, line, cmdLength, args, count - cmdLength - 1);
|
||||
if (result < 0) {
|
||||
result = _tryCommands(debugger, debugger->system->platformCommands, line, cmdLength, args, count - cmdLength - 1);
|
||||
}
|
||||
}
|
||||
if (result < 0) {
|
||||
printf("Command not found\n");
|
||||
|
@ -765,7 +608,7 @@ static char* _prompt(EditLine* el) {
|
|||
return "> ";
|
||||
}
|
||||
|
||||
static void _commandLine(struct ARMDebugger* debugger) {
|
||||
static void _commandLine(struct mDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
const char* line;
|
||||
_printStatus(cliDebugger, 0);
|
||||
|
@ -774,6 +617,7 @@ static void _commandLine(struct ARMDebugger* debugger) {
|
|||
while (debugger->state == DEBUGGER_PAUSED) {
|
||||
line = el_gets(cliDebugger->elstate, &count);
|
||||
if (!line) {
|
||||
debugger->state = DEBUGGER_SHUTDOWN;
|
||||
return;
|
||||
}
|
||||
if (line[0] == '\n') {
|
||||
|
@ -787,7 +631,7 @@ static void _commandLine(struct ARMDebugger* debugger) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
|
||||
UNUSED(debugger);
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_MANUAL:
|
||||
|
@ -802,7 +646,11 @@ static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason
|
|||
break;
|
||||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
if (info) {
|
||||
printf("Hit watchpoint at 0x%08X: (old value = 0x%08X)\n", info->address, info->oldValue);
|
||||
if (info->accessType & WATCHPOINT_WRITE) {
|
||||
printf("Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->newValue, info->oldValue);
|
||||
} else {
|
||||
printf("Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->oldValue);
|
||||
}
|
||||
} else {
|
||||
printf("Hit watchpoint\n");
|
||||
}
|
||||
|
@ -870,7 +718,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
return CC_REDISPLAY;
|
||||
}
|
||||
|
||||
static void _cliDebuggerInit(struct ARMDebugger* debugger) {
|
||||
static void _cliDebuggerInit(struct mDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
// TODO: get argv[0]
|
||||
cliDebugger->elstate = el_init(binaryName, stdin, stdout, stderr);
|
||||
|
@ -888,19 +736,21 @@ static void _cliDebuggerInit(struct ARMDebugger* debugger) {
|
|||
signal(SIGINT, _breakIntoDefault);
|
||||
}
|
||||
|
||||
static void _cliDebuggerDeinit(struct ARMDebugger* debugger) {
|
||||
static void _cliDebuggerDeinit(struct mDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
history_end(cliDebugger->histate);
|
||||
el_end(cliDebugger->elstate);
|
||||
|
||||
if (cliDebugger->system) {
|
||||
cliDebugger->system->deinit(cliDebugger->system);
|
||||
if (cliDebugger->system->deinit) {
|
||||
cliDebugger->system->deinit(cliDebugger->system);
|
||||
}
|
||||
free(cliDebugger->system);
|
||||
cliDebugger->system = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _cliDebuggerCustom(struct ARMDebugger* debugger) {
|
||||
static void _cliDebuggerCustom(struct mDebugger* debugger) {
|
||||
struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger;
|
||||
bool retain = false;
|
||||
if (cliDebugger->system) {
|
||||
|
@ -912,7 +762,6 @@ static void _cliDebuggerCustom(struct ARMDebugger* debugger) {
|
|||
}
|
||||
|
||||
void CLIDebuggerCreate(struct CLIDebugger* debugger) {
|
||||
ARMDebuggerCreate(&debugger->d);
|
||||
debugger->d.init = _cliDebuggerInit;
|
||||
debugger->d.deinit = _cliDebuggerDeinit;
|
||||
debugger->d.custom = _cliDebuggerCustom;
|
||||
|
@ -924,10 +773,14 @@ void CLIDebuggerCreate(struct CLIDebugger* debugger) {
|
|||
|
||||
void CLIDebuggerAttachSystem(struct CLIDebugger* debugger, struct CLIDebuggerSystem* system) {
|
||||
if (debugger->system) {
|
||||
debugger->system->deinit(debugger->system);
|
||||
if (debugger->system->deinit) {
|
||||
debugger->system->deinit(debugger->system);
|
||||
}
|
||||
free(debugger->system);
|
||||
}
|
||||
|
||||
debugger->system = system;
|
||||
system->p = debugger;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -45,14 +45,19 @@ struct CLIDebuggerSystem {
|
|||
void (*deinit)(struct CLIDebuggerSystem*);
|
||||
bool (*custom)(struct CLIDebuggerSystem*);
|
||||
|
||||
void (*disassemble)(struct CLIDebuggerSystem*, struct CLIDebugVector* dv);
|
||||
uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||
uint32_t (*lookupPlatformIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||
void (*printStatus)(struct CLIDebuggerSystem*);
|
||||
|
||||
struct CLIDebuggerCommandSummary* commands;
|
||||
const char* name;
|
||||
struct CLIDebuggerCommandSummary* platformCommands;
|
||||
const char* platformName;
|
||||
};
|
||||
|
||||
struct CLIDebugger {
|
||||
struct ARMDebugger d;
|
||||
struct mDebugger d;
|
||||
|
||||
struct CLIDebuggerSystem* system;
|
||||
|
||||
|
|
|
@ -1,85 +1,91 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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 "debugger.h"
|
||||
|
||||
#include "arm.h"
|
||||
#include "isa-inlines.h"
|
||||
#include "core/core.h"
|
||||
|
||||
#include "memory-debugger.h"
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
#include "debugger/cli-debugger.h"
|
||||
#endif
|
||||
|
||||
const uint32_t ARM_DEBUGGER_ID = 0xDEADBEEF;
|
||||
#ifdef USE_GDB_STUB
|
||||
#include "debugger/gdb-stub.h"
|
||||
#endif
|
||||
|
||||
static struct DebugBreakpoint* _lookupBreakpoint(struct DebugBreakpoint* breakpoints, uint32_t address) {
|
||||
for (; breakpoints; breakpoints = breakpoints->next) {
|
||||
if (breakpoints->address == address) {
|
||||
return breakpoints;
|
||||
}
|
||||
const uint32_t DEBUGGER_ID = 0xDEADBEEF;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(DEBUGGER, "Debugger");
|
||||
|
||||
static void mDebuggerInit(void* cpu, struct mCPUComponent* component);
|
||||
static void mDebuggerDeinit(struct mCPUComponent* component);
|
||||
|
||||
struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) {
|
||||
if (!core->supportsDebuggerType(core, type)) {
|
||||
return NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _checkBreakpoints(struct ARMDebugger* debugger) {
|
||||
int instructionLength;
|
||||
enum ExecutionMode mode = debugger->cpu->cpsr.t;
|
||||
if (mode == MODE_ARM) {
|
||||
instructionLength = WORD_SIZE_ARM;
|
||||
} else {
|
||||
instructionLength = WORD_SIZE_THUMB;
|
||||
}
|
||||
struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength);
|
||||
if (!breakpoint) {
|
||||
return;
|
||||
}
|
||||
struct DebuggerEntryInfo info = {
|
||||
.address = breakpoint->address
|
||||
union DebugUnion {
|
||||
struct mDebugger d;
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
struct CLIDebugger cli;
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
struct GDBStub gdb;
|
||||
#endif
|
||||
};
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_BREAKPOINT, &info);
|
||||
}
|
||||
|
||||
static void ARMDebuggerInit(struct ARMCore*, struct ARMComponent*);
|
||||
static void ARMDebuggerDeinit(struct ARMComponent*);
|
||||
union DebugUnion* debugger = malloc(sizeof(union DebugUnion));
|
||||
|
||||
void ARMDebuggerCreate(struct ARMDebugger* debugger) {
|
||||
debugger->d.id = ARM_DEBUGGER_ID;
|
||||
debugger->d.init = ARMDebuggerInit;
|
||||
debugger->d.deinit = ARMDebuggerDeinit;
|
||||
}
|
||||
|
||||
void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) component;
|
||||
debugger->cpu = cpu;
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
debugger->breakpoints = 0;
|
||||
debugger->swBreakpoints = 0;
|
||||
debugger->originalMemory = cpu->memory;
|
||||
debugger->watchpoints = 0;
|
||||
debugger->currentBreakpoint = 0;
|
||||
if (debugger->init) {
|
||||
debugger->init(debugger);
|
||||
switch (type) {
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
case DEBUGGER_CLI:
|
||||
CLIDebuggerCreate(&debugger->cli);
|
||||
struct CLIDebuggerSystem* sys = core->cliDebuggerSystem(core);
|
||||
CLIDebuggerAttachSystem(&debugger->cli, sys);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
case DEBUGGER_GDB:
|
||||
GDBStubCreate(&debugger->gdb);
|
||||
GDBStubListen(&debugger->gdb, 2345, 0);
|
||||
break;
|
||||
#endif
|
||||
case DEBUGGER_NONE:
|
||||
case DEBUGGER_MAX:
|
||||
free(debugger);
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return &debugger->d;
|
||||
}
|
||||
|
||||
void ARMDebuggerDeinit(struct ARMComponent* component) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) component;
|
||||
debugger->deinit(debugger);
|
||||
void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) {
|
||||
debugger->d.id = DEBUGGER_ID;
|
||||
debugger->d.init = mDebuggerInit;
|
||||
debugger->d.deinit = mDebuggerDeinit;
|
||||
debugger->core = core;
|
||||
debugger->platform = core->debuggerPlatform(core);
|
||||
debugger->platform->p = debugger;
|
||||
core->attachDebugger(core, debugger);
|
||||
}
|
||||
|
||||
void ARMDebuggerRun(struct ARMDebugger* debugger) {
|
||||
void mDebuggerRun(struct mDebugger* debugger) {
|
||||
switch (debugger->state) {
|
||||
case DEBUGGER_RUNNING:
|
||||
if (!debugger->breakpoints && !debugger->watchpoints) {
|
||||
ARMRunLoop(debugger->cpu);
|
||||
if (!debugger->platform->hasBreakpoints(debugger->platform)) {
|
||||
debugger->core->runLoop(debugger->core);
|
||||
} else {
|
||||
ARMRun(debugger->cpu);
|
||||
_checkBreakpoints(debugger);
|
||||
debugger->core->step(debugger->core);
|
||||
debugger->platform->checkBreakpoints(debugger->platform);
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_CUSTOM:
|
||||
ARMRun(debugger->cpu);
|
||||
_checkBreakpoints(debugger);
|
||||
debugger->core->step(debugger->core);
|
||||
debugger->platform->checkBreakpoints(debugger->platform);
|
||||
debugger->custom(debugger);
|
||||
break;
|
||||
case DEBUGGER_PAUSED:
|
||||
|
@ -88,103 +94,32 @@ void ARMDebuggerRun(struct ARMDebugger* debugger) {
|
|||
} else {
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
}
|
||||
if (debugger->state != DEBUGGER_PAUSED && debugger->currentBreakpoint) {
|
||||
if (debugger->currentBreakpoint->isSw && debugger->setSoftwareBreakpoint) {
|
||||
debugger->setSoftwareBreakpoint(debugger, debugger->currentBreakpoint->address, debugger->currentBreakpoint->sw.mode, &debugger->currentBreakpoint->sw.opcode);
|
||||
}
|
||||
debugger->currentBreakpoint = 0;
|
||||
}
|
||||
break;
|
||||
case DEBUGGER_SHUTDOWN:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
|
||||
debugger->state = DEBUGGER_PAUSED;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
cpu->nextEvent = cpu->cycles;
|
||||
if (reason == DEBUGGER_ENTER_BREAKPOINT) {
|
||||
struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu));
|
||||
debugger->currentBreakpoint = breakpoint;
|
||||
if (breakpoint && breakpoint->isSw) {
|
||||
info->address = breakpoint->address;
|
||||
if (debugger->clearSoftwareBreakpoint) {
|
||||
debugger->clearSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, breakpoint->sw.opcode);
|
||||
}
|
||||
|
||||
ARMRunFake(cpu, breakpoint->sw.opcode);
|
||||
}
|
||||
}
|
||||
if (debugger->entered) {
|
||||
debugger->entered(debugger, reason, info);
|
||||
}
|
||||
}
|
||||
|
||||
void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint));
|
||||
breakpoint->address = address;
|
||||
breakpoint->next = debugger->breakpoints;
|
||||
breakpoint->isSw = false;
|
||||
debugger->breakpoints = breakpoint;
|
||||
}
|
||||
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode) {
|
||||
uint32_t opcode;
|
||||
if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint));
|
||||
breakpoint->address = address;
|
||||
breakpoint->next = debugger->swBreakpoints;
|
||||
breakpoint->isSw = true;
|
||||
breakpoint->sw.opcode = opcode;
|
||||
breakpoint->sw.mode = mode;
|
||||
debugger->swBreakpoints = breakpoint;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint** previous = &debugger->breakpoints;
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
struct DebugBreakpoint** next;
|
||||
while ((breakpoint = *previous)) {
|
||||
next = &breakpoint->next;
|
||||
if (breakpoint->address == address) {
|
||||
*previous = *next;
|
||||
free(breakpoint);
|
||||
continue;
|
||||
}
|
||||
previous = next;
|
||||
static void mDebuggerInit(void* cpu, struct mCPUComponent* component) {
|
||||
struct mDebugger* debugger = (struct mDebugger*) component;
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
debugger->platform->init(cpu, debugger->platform);
|
||||
if (debugger->init) {
|
||||
debugger->init(debugger);
|
||||
}
|
||||
}
|
||||
|
||||
void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
if (!debugger->watchpoints) {
|
||||
ARMDebuggerInstallMemoryShim(debugger);
|
||||
}
|
||||
struct DebugWatchpoint* watchpoint = malloc(sizeof(struct DebugWatchpoint));
|
||||
watchpoint->address = address;
|
||||
watchpoint->next = debugger->watchpoints;
|
||||
debugger->watchpoints = watchpoint;
|
||||
}
|
||||
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugWatchpoint** previous = &debugger->watchpoints;
|
||||
struct DebugWatchpoint* watchpoint;
|
||||
struct DebugWatchpoint** next;
|
||||
while ((watchpoint = *previous)) {
|
||||
next = &watchpoint->next;
|
||||
if (watchpoint->address == address) {
|
||||
*previous = *next;
|
||||
free(watchpoint);
|
||||
continue;
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
if (!debugger->watchpoints) {
|
||||
ARMDebuggerRemoveMemoryShim(debugger);
|
||||
static void mDebuggerDeinit(struct mCPUComponent* component) {
|
||||
struct mDebugger* debugger = (struct mDebugger*) component;
|
||||
if (debugger->deinit) {
|
||||
debugger->deinit(debugger);
|
||||
}
|
||||
debugger->platform->deinit(debugger->platform);
|
||||
}
|
||||
|
|
|
@ -8,40 +8,39 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "arm.h"
|
||||
#include "arm/arm.h"
|
||||
#include "core/log.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
extern const uint32_t ARM_DEBUGGER_ID;
|
||||
mLOG_DECLARE_CATEGORY(DEBUGGER);
|
||||
|
||||
enum DebuggerState {
|
||||
extern const uint32_t DEBUGGER_ID;
|
||||
|
||||
enum mDebuggerType {
|
||||
DEBUGGER_NONE = 0,
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
DEBUGGER_CLI,
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
DEBUGGER_GDB,
|
||||
#endif
|
||||
DEBUGGER_MAX
|
||||
};
|
||||
|
||||
enum mDebuggerState {
|
||||
DEBUGGER_PAUSED,
|
||||
DEBUGGER_RUNNING,
|
||||
DEBUGGER_CUSTOM,
|
||||
DEBUGGER_SHUTDOWN
|
||||
};
|
||||
|
||||
struct DebugBreakpoint {
|
||||
struct DebugBreakpoint* next;
|
||||
uint32_t address;
|
||||
bool isSw;
|
||||
struct {
|
||||
uint32_t opcode;
|
||||
enum ExecutionMode mode;
|
||||
} sw;
|
||||
};
|
||||
|
||||
enum WatchpointType {
|
||||
enum mWatchpointType {
|
||||
WATCHPOINT_WRITE = 1,
|
||||
WATCHPOINT_READ = 2,
|
||||
WATCHPOINT_RW = 3
|
||||
WATCHPOINT_RW = WATCHPOINT_WRITE | WATCHPOINT_READ
|
||||
};
|
||||
|
||||
struct DebugWatchpoint {
|
||||
struct DebugWatchpoint* next;
|
||||
uint32_t address;
|
||||
enum WatchpointType type;
|
||||
};
|
||||
|
||||
enum DebuggerEntryReason {
|
||||
enum mDebuggerEntryReason {
|
||||
DEBUGGER_ENTER_MANUAL,
|
||||
DEBUGGER_ENTER_ATTACHED,
|
||||
DEBUGGER_ENTER_BREAKPOINT,
|
||||
|
@ -49,12 +48,17 @@ enum DebuggerEntryReason {
|
|||
DEBUGGER_ENTER_ILLEGAL_OP
|
||||
};
|
||||
|
||||
struct DebuggerEntryInfo {
|
||||
extern const char* ERROR_MISSING_ARGS;
|
||||
extern const char* ERROR_OVERFLOW;
|
||||
|
||||
struct mDebuggerEntryInfo {
|
||||
uint32_t address;
|
||||
union {
|
||||
struct {
|
||||
uint32_t oldValue;
|
||||
enum WatchpointType watchType;
|
||||
uint32_t newValue;
|
||||
enum mWatchpointType watchType;
|
||||
enum mWatchpointType accessType;
|
||||
};
|
||||
|
||||
struct {
|
||||
|
@ -63,45 +67,40 @@ struct DebuggerEntryInfo {
|
|||
};
|
||||
};
|
||||
|
||||
enum DebuggerLogLevel {
|
||||
DEBUGGER_LOG_DEBUG = 0x01,
|
||||
DEBUGGER_LOG_INFO = 0x02,
|
||||
DEBUGGER_LOG_WARN = 0x04,
|
||||
DEBUGGER_LOG_ERROR = 0x08
|
||||
struct mDebugger;
|
||||
struct mDebuggerPlatform {
|
||||
struct mDebugger* p;
|
||||
|
||||
void (*init)(void* cpu, struct mDebuggerPlatform*);
|
||||
void (*deinit)(struct mDebuggerPlatform*);
|
||||
void (*entered)(struct mDebuggerPlatform*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
|
||||
|
||||
bool (*hasBreakpoints)(struct mDebuggerPlatform*);
|
||||
void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address);
|
||||
void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address);
|
||||
void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, enum mWatchpointType type);
|
||||
void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address);
|
||||
void (*checkBreakpoints)(struct mDebuggerPlatform*);
|
||||
};
|
||||
|
||||
struct ARMDebugger {
|
||||
struct ARMComponent d;
|
||||
enum DebuggerState state;
|
||||
struct ARMCore* cpu;
|
||||
struct mDebugger {
|
||||
struct mCPUComponent d;
|
||||
struct mDebuggerPlatform* platform;
|
||||
enum mDebuggerState state;
|
||||
struct mCore* core;
|
||||
|
||||
struct DebugBreakpoint* breakpoints;
|
||||
struct DebugBreakpoint* swBreakpoints;
|
||||
struct DebugWatchpoint* watchpoints;
|
||||
struct ARMMemory originalMemory;
|
||||
void (*init)(struct mDebugger*);
|
||||
void (*deinit)(struct mDebugger*);
|
||||
|
||||
struct DebugBreakpoint* currentBreakpoint;
|
||||
|
||||
void (*init)(struct ARMDebugger*);
|
||||
void (*deinit)(struct ARMDebugger*);
|
||||
void (*paused)(struct ARMDebugger*);
|
||||
void (*entered)(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*);
|
||||
void (*custom)(struct ARMDebugger*);
|
||||
|
||||
bool (*setSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);
|
||||
bool (*clearSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
|
||||
|
||||
ATTRIBUTE_FORMAT(printf, 3, 4)
|
||||
void (*log)(struct ARMDebugger*, enum DebuggerLogLevel, const char* format, ...);
|
||||
void (*paused)(struct mDebugger*);
|
||||
void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
|
||||
void (*custom)(struct mDebugger*);
|
||||
};
|
||||
|
||||
void ARMDebuggerCreate(struct ARMDebugger*);
|
||||
void ARMDebuggerRun(struct ARMDebugger*);
|
||||
void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*);
|
||||
void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode);
|
||||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address);
|
||||
struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore*);
|
||||
void mDebuggerAttach(struct mDebugger*, struct mCore*);
|
||||
void mDebuggerRun(struct mDebugger*);
|
||||
void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gdb-stub.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gba/memory.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#ifndef SIGTRAP
|
||||
|
@ -26,27 +29,33 @@ enum {
|
|||
|
||||
static void _sendMessage(struct GDBStub* stub);
|
||||
|
||||
static void _gdbStubDeinit(struct ARMDebugger* debugger) {
|
||||
static void _gdbStubDeinit(struct mDebugger* debugger) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
if (!SOCKET_FAILED(stub->socket)) {
|
||||
GDBStubShutdown(stub);
|
||||
}
|
||||
}
|
||||
|
||||
static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
switch (reason) {
|
||||
case DEBUGGER_ENTER_MANUAL:
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
|
||||
break;
|
||||
case DEBUGGER_ENTER_BREAKPOINT:
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); // TODO: Use hwbreak/swbreak if gdb supports it
|
||||
break;
|
||||
case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address
|
||||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
if (info) {
|
||||
const char* type = 0;
|
||||
switch (info->watchType) {
|
||||
case WATCHPOINT_WRITE:
|
||||
if (info->newValue == info->oldValue) {
|
||||
if (stub->d.state == DEBUGGER_PAUSED) {
|
||||
stub->d.state = DEBUGGER_RUNNING;
|
||||
}
|
||||
return;
|
||||
}
|
||||
type = "watch";
|
||||
break;
|
||||
case WATCHPOINT_READ:
|
||||
|
@ -56,7 +65,7 @@ static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReas
|
|||
type = "awatch";
|
||||
break;
|
||||
}
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08X", SIGTRAP, type, info->address);
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08x;", SIGTRAP, type, info->address);
|
||||
} else {
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
|
||||
}
|
||||
|
@ -70,7 +79,7 @@ static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReas
|
|||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _gdbStubPoll(struct ARMDebugger* debugger) {
|
||||
static void _gdbStubPoll(struct mDebugger* debugger) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
--stub->untilPoll;
|
||||
if (stub->untilPoll > 0) {
|
||||
|
@ -81,7 +90,7 @@ static void _gdbStubPoll(struct ARMDebugger* debugger) {
|
|||
GDBStubUpdate(stub);
|
||||
}
|
||||
|
||||
static void _gdbStubWait(struct ARMDebugger* debugger) {
|
||||
static void _gdbStubWait(struct mDebugger* debugger) {
|
||||
struct GDBStub* stub = (struct GDBStub*) debugger;
|
||||
stub->shouldBlock = true;
|
||||
GDBStubUpdate(stub);
|
||||
|
@ -94,9 +103,7 @@ static void _ack(struct GDBStub* stub) {
|
|||
|
||||
static void _nak(struct GDBStub* stub) {
|
||||
char nak = '-';
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Packet error");
|
||||
}
|
||||
mLOG(DEBUGGER, WARN, "Packet error");
|
||||
SocketSend(stub->connection, &nak, 1);
|
||||
}
|
||||
|
||||
|
@ -143,7 +150,7 @@ static void _int2hex32(uint32_t value, char* out) {
|
|||
static uint32_t _readHex(const char* in, unsigned* out) {
|
||||
unsigned i;
|
||||
for (i = 0; i < 8; ++i) {
|
||||
if (in[i] == ',') {
|
||||
if (in[i] == ',' || in[i] == ':' || in[i] == '=') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -175,9 +182,7 @@ static void _sendMessage(struct GDBStub* stub) {
|
|||
stub->outgoing[i] = '#';
|
||||
_int2hex8(checksum, &stub->outgoing[i + 1]);
|
||||
stub->outgoing[i + 3] = 0;
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "> %s", stub->outgoing);
|
||||
}
|
||||
mLOG(DEBUGGER, DEBUG, "> %s", stub->outgoing);
|
||||
SocketSend(stub->connection, stub->outgoing, i + 3);
|
||||
}
|
||||
|
||||
|
@ -199,13 +204,72 @@ static void _continue(struct GDBStub* stub, const char* message) {
|
|||
}
|
||||
|
||||
static void _step(struct GDBStub* stub, const char* message) {
|
||||
ARMRun(stub->d.cpu);
|
||||
stub->d.core->step(stub->d.core);
|
||||
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
|
||||
_sendMessage(stub);
|
||||
// TODO: parse message
|
||||
UNUSED(message);
|
||||
}
|
||||
|
||||
static void _writeMemoryBinary(struct GDBStub* stub, const char* message) {
|
||||
const char* readAddress = message;
|
||||
unsigned i = 0;
|
||||
uint32_t address = _readHex(readAddress, &i);
|
||||
readAddress += i + 1;
|
||||
|
||||
i = 0;
|
||||
uint32_t size = _readHex(readAddress, &i);
|
||||
readAddress += i + 1;
|
||||
|
||||
if (size > 512) {
|
||||
_error(stub, GDB_BAD_ARGUMENTS);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
for (i = 0; i < size; i++) {
|
||||
uint8_t byte = *readAddress;
|
||||
++readAddress;
|
||||
|
||||
// Parse escape char
|
||||
if (byte == 0x7D) {
|
||||
byte = *readAddress ^ 0x20;
|
||||
++readAddress;
|
||||
}
|
||||
|
||||
GBAPatch8(cpu, address + i, byte, 0);
|
||||
}
|
||||
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
|
||||
static void _writeMemory(struct GDBStub* stub, const char* message) {
|
||||
const char* readAddress = message;
|
||||
unsigned i = 0;
|
||||
uint32_t address = _readHex(readAddress, &i);
|
||||
readAddress += i + 1;
|
||||
|
||||
i = 0;
|
||||
uint32_t size = _readHex(readAddress, &i);
|
||||
readAddress += i + 1;
|
||||
|
||||
if (size > 512) {
|
||||
_error(stub, GDB_BAD_ARGUMENTS);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
for (i = 0; i < size; ++i, readAddress += 2) {
|
||||
uint8_t byte = _hex2int(readAddress, 2);
|
||||
GBAPatch8(cpu, address + i, byte, 0);
|
||||
}
|
||||
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _readMemory(struct GDBStub* stub, const char* message) {
|
||||
const char* readAddress = message;
|
||||
unsigned i = 0;
|
||||
|
@ -216,7 +280,7 @@ static void _readMemory(struct GDBStub* stub, const char* message) {
|
|||
_error(stub, GDB_BAD_ARGUMENTS);
|
||||
return;
|
||||
}
|
||||
struct ARMCore* cpu = stub->d.cpu;
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
int writeAddress = 0;
|
||||
for (i = 0; i < size; ++i, writeAddress += 2) {
|
||||
uint8_t byte = cpu->memory.load8(cpu, address + i, 0);
|
||||
|
@ -226,27 +290,73 @@ static void _readMemory(struct GDBStub* stub, const char* message) {
|
|||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _writeGPRs(struct GDBStub* stub, const char* message) {
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
const char* readAddress = message;
|
||||
|
||||
int r;
|
||||
for (r = 0; r < 16; ++r) {
|
||||
cpu->gprs[r] = _hex2int(readAddress, 8);
|
||||
readAddress += 8;
|
||||
}
|
||||
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _readGPRs(struct GDBStub* stub, const char* message) {
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
UNUSED(message);
|
||||
int r;
|
||||
int i = 0;
|
||||
for (r = 0; r < 16; ++r) {
|
||||
_int2hex32(stub->d.cpu->gprs[r], &stub->outgoing[i]);
|
||||
_int2hex32(cpu->gprs[r], &stub->outgoing[i]);
|
||||
i += 8;
|
||||
}
|
||||
stub->outgoing[i] = 0;
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _writeRegister(struct GDBStub* stub, const char* message) {
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
const char* readAddress = message;
|
||||
|
||||
unsigned i = 0;
|
||||
uint32_t reg = _readHex(readAddress, &i);
|
||||
readAddress += i + 1;
|
||||
|
||||
uint32_t value = _readHex(readAddress, &i);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
value = _byteswap_ulong(value);
|
||||
#else
|
||||
value = __builtin_bswap32(value);
|
||||
#endif
|
||||
|
||||
if (reg < 0x10) {
|
||||
cpu->gprs[reg] = value;
|
||||
} else if (reg == 0x19) {
|
||||
cpu->cpsr.packed = value;
|
||||
} else {
|
||||
stub->outgoing[0] = '\0';
|
||||
_sendMessage(stub);
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
||||
static void _readRegister(struct GDBStub* stub, const char* message) {
|
||||
struct ARMCore* cpu = stub->d.core->cpu;
|
||||
const char* readAddress = message;
|
||||
unsigned i = 0;
|
||||
uint32_t reg = _readHex(readAddress, &i);
|
||||
uint32_t value;
|
||||
if (reg < 0x10) {
|
||||
value = stub->d.cpu->gprs[reg];
|
||||
value = cpu->gprs[reg];
|
||||
} else if (reg == 0x19) {
|
||||
value = stub->d.cpu->cpsr.packed;
|
||||
value = cpu->cpsr.packed;
|
||||
} else {
|
||||
stub->outgoing[0] = '\0';
|
||||
_sendMessage(stub);
|
||||
|
@ -296,7 +406,7 @@ static void _processVReadCommand(struct GDBStub* stub, const char* message) {
|
|||
stub->outgoing[0] = '\0';
|
||||
if (!strncmp("Attach", message, 6)) {
|
||||
strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
}
|
||||
_sendMessage(stub);
|
||||
}
|
||||
|
@ -312,14 +422,22 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
|
|||
switch (message[0]) {
|
||||
case '0': // Memory breakpoints are not currently supported
|
||||
case '1':
|
||||
ARMDebuggerSetBreakpoint(&stub->d, address);
|
||||
stub->d.platform->setBreakpoint(stub->d.platform, address);
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
break;
|
||||
case '2':
|
||||
stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_WRITE);
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
break;
|
||||
case '3':
|
||||
stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_READ);
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
break;
|
||||
case '4':
|
||||
ARMDebuggerSetWatchpoint(&stub->d, address);
|
||||
stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_RW);
|
||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
break;
|
||||
|
@ -337,12 +455,12 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
|
|||
switch (message[0]) {
|
||||
case '0': // Memory breakpoints are not currently supported
|
||||
case '1':
|
||||
ARMDebuggerClearBreakpoint(&stub->d, address);
|
||||
stub->d.platform->clearBreakpoint(stub->d.platform, address);
|
||||
break;
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
ARMDebuggerClearWatchpoint(&stub->d, address);
|
||||
stub->d.platform->clearWatchpoint(stub->d.platform, address);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -365,7 +483,7 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
++message;
|
||||
break;
|
||||
case '\x03':
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
return parsed;
|
||||
default:
|
||||
_nak(stub);
|
||||
|
@ -374,7 +492,7 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
|
||||
int i;
|
||||
char messageType = message[0];
|
||||
for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) {
|
||||
for (i = 0; message[i] != '#'; ++i, ++parsed) {
|
||||
checksum += message[i];
|
||||
}
|
||||
if (!message[i]) {
|
||||
|
@ -394,9 +512,7 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
parsed += 2;
|
||||
int networkChecksum = _hex2int(&message[i], 2);
|
||||
if (networkChecksum != checksum) {
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum);
|
||||
}
|
||||
mLOG(DEBUGGER, WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum);
|
||||
_nak(stub);
|
||||
return parsed;
|
||||
}
|
||||
|
@ -411,6 +527,9 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
case 'c':
|
||||
_continue(stub, message);
|
||||
break;
|
||||
case 'G':
|
||||
_writeGPRs(stub, message);
|
||||
break;
|
||||
case 'g':
|
||||
_readGPRs(stub, message);
|
||||
break;
|
||||
|
@ -419,9 +538,15 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||
_sendMessage(stub);
|
||||
break;
|
||||
case 'M':
|
||||
_writeMemory(stub, message);
|
||||
break;
|
||||
case 'm':
|
||||
_readMemory(stub, message);
|
||||
break;
|
||||
case 'P':
|
||||
_writeRegister(stub, message);
|
||||
break;
|
||||
case 'p':
|
||||
_readRegister(stub, message);
|
||||
break;
|
||||
|
@ -440,6 +565,9 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
case 'v':
|
||||
_processVReadCommand(stub, message);
|
||||
break;
|
||||
case 'X':
|
||||
_writeMemoryBinary(stub, message);
|
||||
break;
|
||||
case 'Z':
|
||||
_setBreakpoint(stub, message);
|
||||
break;
|
||||
|
@ -454,7 +582,6 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
|
|||
}
|
||||
|
||||
void GDBStubCreate(struct GDBStub* stub) {
|
||||
ARMDebuggerCreate(&stub->d);
|
||||
stub->socket = INVALID_SOCKET;
|
||||
stub->connection = INVALID_SOCKET;
|
||||
stub->d.init = 0;
|
||||
|
@ -462,7 +589,6 @@ void GDBStubCreate(struct GDBStub* stub) {
|
|||
stub->d.paused = _gdbStubWait;
|
||||
stub->d.entered = _gdbStubEntered;
|
||||
stub->d.custom = _gdbStubPoll;
|
||||
stub->d.log = 0;
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
stub->lineAck = GDB_ACK_PENDING;
|
||||
stub->shouldBlock = false;
|
||||
|
@ -474,9 +600,7 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd
|
|||
}
|
||||
stub->socket = SocketOpenTCP(port, bindAddress);
|
||||
if (SOCKET_FAILED(stub->socket)) {
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket");
|
||||
}
|
||||
mLOG(DEBUGGER, ERROR, "Couldn't open socket");
|
||||
return false;
|
||||
}
|
||||
if (!SocketSetBlocking(stub->socket, false)) {
|
||||
|
@ -490,9 +614,7 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd
|
|||
return true;
|
||||
|
||||
cleanup:
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port");
|
||||
}
|
||||
mLOG(DEBUGGER, ERROR, "Couldn't listen on port");
|
||||
SocketClose(stub->socket);
|
||||
stub->socket = INVALID_SOCKET;
|
||||
return false;
|
||||
|
@ -533,7 +655,7 @@ void GDBStubUpdate(struct GDBStub* stub) {
|
|||
if (!SocketSetBlocking(stub->connection, false)) {
|
||||
goto connectionLost;
|
||||
}
|
||||
ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
mDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
} else if (SocketWouldBlock()) {
|
||||
return;
|
||||
} else {
|
||||
|
@ -556,9 +678,7 @@ void GDBStubUpdate(struct GDBStub* stub) {
|
|||
goto connectionLost;
|
||||
}
|
||||
stub->line[messageLen] = '\0';
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_DEBUG, "< %s", stub->line);
|
||||
}
|
||||
mLOG(DEBUGGER, DEBUG, "< %s", stub->line);
|
||||
ssize_t position = 0;
|
||||
while (position < messageLen) {
|
||||
position += _parseGDBMessage(stub, &stub->line[position]);
|
||||
|
@ -566,8 +686,6 @@ void GDBStubUpdate(struct GDBStub* stub) {
|
|||
}
|
||||
|
||||
connectionLost:
|
||||
if (stub->d.log) {
|
||||
stub->d.log(&stub->d, DEBUGGER_LOG_INFO, "Connection lost");
|
||||
}
|
||||
mLOG(DEBUGGER, WARN, "Connection lost");
|
||||
GDBStubHangup(stub);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ enum GDBStubAckState {
|
|||
};
|
||||
|
||||
struct GDBStub {
|
||||
struct ARMDebugger d;
|
||||
struct mDebugger d;
|
||||
|
||||
char line[GDB_STUB_MAX_LINE];
|
||||
char outgoing[GDB_STUB_MAX_LINE];
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "memory-debugger.h"
|
||||
|
||||
#include "debugger.h"
|
||||
|
||||
#include "util/math.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width);
|
||||
|
||||
#define FIND_DEBUGGER(DEBUGGER, CPU) \
|
||||
{ \
|
||||
DEBUGGER = 0; \
|
||||
size_t i; \
|
||||
for (i = 0; i < CPU->numComponents; ++i) { \
|
||||
if (CPU->components[i]->id == ARM_DEBUGGER_ID) { \
|
||||
DEBUGGER = (struct ARMDebugger*) cpu->components[i]; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CREATE_SHIM(NAME, RETURN, TYPES, ...) \
|
||||
static RETURN ARMDebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define CREATE_WATCHPOINT_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \
|
||||
static RETURN ARMDebuggerShim_ ## NAME TYPES { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
struct DebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, address, &info, WIDTH)) { \
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME) \
|
||||
static uint32_t ARMDebuggerShim_ ## NAME (struct ARMCore* cpu, uint32_t address, int mask, enum LSMDirection direction, int* cycleCounter) { \
|
||||
struct ARMDebugger* debugger; \
|
||||
FIND_DEBUGGER(debugger, cpu); \
|
||||
uint32_t popcount = popcount32(mask); \
|
||||
int offset = 4; \
|
||||
int base = address; \
|
||||
if (direction & LSM_D) { \
|
||||
offset = -4; \
|
||||
base -= (popcount << 2) - 4; \
|
||||
} \
|
||||
if (direction & LSM_B) { \
|
||||
base += offset; \
|
||||
} \
|
||||
unsigned i; \
|
||||
for (i = 0; i < popcount; ++i) { \
|
||||
struct DebuggerEntryInfo info; \
|
||||
if (_checkWatchpoints(debugger, base + 4 * i, &info, 4)) { \
|
||||
ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \
|
||||
} \
|
||||
} \
|
||||
return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \
|
||||
}
|
||||
|
||||
CREATE_WATCHPOINT_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_WATCHPOINT_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple)
|
||||
CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple)
|
||||
CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address)
|
||||
|
||||
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width) {
|
||||
--width;
|
||||
struct DebugWatchpoint* watchpoints;
|
||||
for (watchpoints = debugger->watchpoints; watchpoints; watchpoints = watchpoints->next) {
|
||||
if (!((watchpoints->address ^ address) & ~width)) {
|
||||
switch (width + 1) {
|
||||
case 1:
|
||||
info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 2:
|
||||
info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0);
|
||||
break;
|
||||
case 4:
|
||||
info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0);
|
||||
break;
|
||||
}
|
||||
info->address = address;
|
||||
info->watchType = watchpoints->type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger) {
|
||||
debugger->originalMemory = debugger->cpu->memory;
|
||||
debugger->cpu->memory.store32 = ARMDebuggerShim_store32;
|
||||
debugger->cpu->memory.store16 = ARMDebuggerShim_store16;
|
||||
debugger->cpu->memory.store8 = ARMDebuggerShim_store8;
|
||||
debugger->cpu->memory.load32 = ARMDebuggerShim_load32;
|
||||
debugger->cpu->memory.load16 = ARMDebuggerShim_load16;
|
||||
debugger->cpu->memory.load8 = ARMDebuggerShim_load8;
|
||||
debugger->cpu->memory.storeMultiple = ARMDebuggerShim_storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = ARMDebuggerShim_loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = ARMDebuggerShim_setActiveRegion;
|
||||
}
|
||||
|
||||
void ARMDebuggerRemoveMemoryShim(struct ARMDebugger* debugger) {
|
||||
debugger->cpu->memory.store32 = debugger->originalMemory.store32;
|
||||
debugger->cpu->memory.store16 = debugger->originalMemory.store16;
|
||||
debugger->cpu->memory.store8 = debugger->originalMemory.store8;
|
||||
debugger->cpu->memory.load32 = debugger->originalMemory.load32;
|
||||
debugger->cpu->memory.load16 = debugger->originalMemory.load16;
|
||||
debugger->cpu->memory.load8 = debugger->originalMemory.load8;
|
||||
debugger->cpu->memory.storeMultiple = debugger->originalMemory.storeMultiple;
|
||||
debugger->cpu->memory.loadMultiple = debugger->originalMemory.loadMultiple;
|
||||
debugger->cpu->memory.setActiveRegion = debugger->originalMemory.setActiveRegion;
|
||||
}
|
|
@ -5,22 +5,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "commandline.h"
|
||||
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
#include "debugger/cli-debugger.h"
|
||||
#include "gba/supervisor/cli.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_GDB_STUB
|
||||
#include "debugger/gdb-stub.h"
|
||||
#endif
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "core/config.h"
|
||||
#include "core/version.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#ifdef _MSC_VER
|
||||
#include "platform/windows/getopt.h"
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#define GRAPHICS_OPTIONS "123456f"
|
||||
#define GRAPHICS_USAGE \
|
||||
|
@ -36,7 +30,6 @@
|
|||
static const struct option _options[] = {
|
||||
{ "bios", required_argument, 0, 'b' },
|
||||
{ "cheats", required_argument, 0, 'c' },
|
||||
{ "dirmode", required_argument, 0, 'D' },
|
||||
{ "frameskip", required_argument, 0, 's' },
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
{ "debug", no_argument, 0, 'd' },
|
||||
|
@ -47,15 +40,17 @@ static const struct option _options[] = {
|
|||
{ "help", no_argument, 0, 'h' },
|
||||
{ "movie", required_argument, 0, 'v' },
|
||||
{ "patch", required_argument, 0, 'p' },
|
||||
{ "version", no_argument, 0, '\0' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
|
||||
static bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg);
|
||||
static void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config);
|
||||
|
||||
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
|
||||
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) {
|
||||
int ch;
|
||||
char options[64] =
|
||||
"b:c:Dhl:p:s:v:"
|
||||
"b:c:hl:p:s:v:"
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
"d"
|
||||
#endif
|
||||
|
@ -63,56 +58,64 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
"g"
|
||||
#endif
|
||||
;
|
||||
memset(opts, 0, sizeof(*opts));
|
||||
memset(args, 0, sizeof(*args));
|
||||
args->frameskip = -1;
|
||||
args->logLevel = INT_MIN;
|
||||
if (subparser && subparser->extraOptions) {
|
||||
// TODO: modularize options to subparsers
|
||||
strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);
|
||||
}
|
||||
while ((ch = getopt_long(argc, argv, options, _options, 0)) != -1) {
|
||||
int index = 0;
|
||||
while ((ch = getopt_long(argc, argv, options, _options, &index)) != -1) {
|
||||
const struct option* opt = &_options[index];
|
||||
switch (ch) {
|
||||
case '\0':
|
||||
if (strcmp(opt->name, "version") == 0) {
|
||||
args->showVersion = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
GBAConfigSetOverrideValue(config, "bios", optarg);
|
||||
args->bios = strdup(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
opts->cheatsFile = strdup(optarg);
|
||||
break;
|
||||
case 'D':
|
||||
opts->dirmode = true;
|
||||
args->cheatsFile = strdup(optarg);
|
||||
break;
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
case 'd':
|
||||
if (opts->debuggerType != DEBUGGER_NONE) {
|
||||
if (args->debuggerType != DEBUGGER_NONE) {
|
||||
return false;
|
||||
}
|
||||
opts->debuggerType = DEBUGGER_CLI;
|
||||
args->debuggerType = DEBUGGER_CLI;
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
case 'g':
|
||||
if (opts->debuggerType != DEBUGGER_NONE) {
|
||||
if (args->debuggerType != DEBUGGER_NONE) {
|
||||
return false;
|
||||
}
|
||||
opts->debuggerType = DEBUGGER_GDB;
|
||||
args->debuggerType = DEBUGGER_GDB;
|
||||
break;
|
||||
#endif
|
||||
case 'h':
|
||||
opts->showHelp = true;
|
||||
args->showHelp = true;
|
||||
break;
|
||||
case 'l':
|
||||
GBAConfigSetOverrideValue(config, "logLevel", optarg);
|
||||
args->logLevel = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
opts->patch = strdup(optarg);
|
||||
args->patch = strdup(optarg);
|
||||
break;
|
||||
case 's':
|
||||
GBAConfigSetOverrideValue(config, "frameskip", optarg);
|
||||
args->frameskip = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
opts->movie = strdup(optarg);
|
||||
args->movie = strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
if (subparser) {
|
||||
if (!subparser->parse(subparser, config, ch, optarg)) {
|
||||
if (!subparser->parse(subparser, ch, optarg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -121,40 +124,64 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc != 1) {
|
||||
return opts->showHelp;
|
||||
if (argc > 1) {
|
||||
return false;
|
||||
} else if (argc == 1) {
|
||||
args->fname = strdup(argv[0]);
|
||||
} else {
|
||||
args->fname = NULL;
|
||||
}
|
||||
opts->fname = strdup(argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void freeArguments(struct GBAArguments* opts) {
|
||||
free(opts->fname);
|
||||
opts->fname = 0;
|
||||
|
||||
free(opts->patch);
|
||||
opts->patch = 0;
|
||||
|
||||
free(opts->movie);
|
||||
opts->movie = 0;
|
||||
void applyArguments(const struct mArguments* args, struct mSubParser* subparser, struct mCoreConfig* config) {
|
||||
if (args->frameskip >= 0) {
|
||||
mCoreConfigSetOverrideIntValue(config, "frameskip", args->frameskip);
|
||||
}
|
||||
if (args->logLevel > INT_MIN) {
|
||||
mCoreConfigSetOverrideIntValue(config, "logLevel", args->logLevel);
|
||||
}
|
||||
if (args->bios) {
|
||||
mCoreConfigSetOverrideValue(config, "bios", args->bios);
|
||||
}
|
||||
if (subparser) {
|
||||
subparser->apply(subparser, config);
|
||||
}
|
||||
}
|
||||
|
||||
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
|
||||
void freeArguments(struct mArguments* args) {
|
||||
free(args->fname);
|
||||
args->fname = 0;
|
||||
|
||||
free(args->patch);
|
||||
args->patch = 0;
|
||||
|
||||
free(args->movie);
|
||||
args->movie = 0;
|
||||
|
||||
free(args->cheatsFile);
|
||||
args->cheatsFile = 0;
|
||||
|
||||
free(args->bios);
|
||||
args->bios = 0;
|
||||
}
|
||||
|
||||
void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts) {
|
||||
parser->usage = GRAPHICS_USAGE;
|
||||
parser->opts = opts;
|
||||
parser->parse = _parseGraphicsArg;
|
||||
parser->apply = _applyGraphicsArgs;
|
||||
parser->extraOptions = GRAPHICS_OPTIONS;
|
||||
opts->multiplier = 0;
|
||||
opts->fullscreen = false;
|
||||
}
|
||||
|
||||
bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
|
||||
bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg) {
|
||||
UNUSED(arg);
|
||||
struct GraphicsOpts* graphicsOpts = parser->opts;
|
||||
struct mGraphicsOpts* graphicsOpts = parser->opts;
|
||||
switch (option) {
|
||||
case 'f':
|
||||
graphicsOpts->fullscreen = true;
|
||||
GBAConfigSetOverrideIntValue(config, "fullscreen", 1);
|
||||
return true;
|
||||
case '1':
|
||||
case '2':
|
||||
|
@ -166,52 +193,15 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
|||
return false;
|
||||
}
|
||||
graphicsOpts->multiplier = option - '0';
|
||||
GBAConfigSetOverrideIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier);
|
||||
GBAConfigSetOverrideIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct ARMDebugger* createDebugger(struct GBAArguments* opts, struct GBAThread* context) {
|
||||
#ifndef USE_CLI_DEBUGGER
|
||||
UNUSED(context);
|
||||
#endif
|
||||
union DebugUnion {
|
||||
struct ARMDebugger d;
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
struct CLIDebugger cli;
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
struct GDBStub gdb;
|
||||
#endif
|
||||
};
|
||||
|
||||
union DebugUnion* debugger = malloc(sizeof(union DebugUnion));
|
||||
|
||||
switch (opts->debuggerType) {
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
case DEBUGGER_CLI:
|
||||
CLIDebuggerCreate(&debugger->cli);
|
||||
struct GBACLIDebugger* gbaDebugger = GBACLIDebuggerCreate(context);
|
||||
CLIDebuggerAttachSystem(&debugger->cli, &gbaDebugger->d);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_GDB_STUB
|
||||
case DEBUGGER_GDB:
|
||||
GDBStubCreate(&debugger->gdb);
|
||||
GDBStubListen(&debugger->gdb, 2345, 0);
|
||||
break;
|
||||
#endif
|
||||
case DEBUGGER_NONE:
|
||||
case DEBUGGER_MAX:
|
||||
free(debugger);
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return &debugger->d;
|
||||
void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config) {
|
||||
struct mGraphicsOpts* graphicsOpts = parser->opts;
|
||||
mCoreConfigSetOverrideIntValue(config, "fullscreen", graphicsOpts->fullscreen);
|
||||
}
|
||||
|
||||
void usage(const char* arg0, const char* extraOptions) {
|
||||
|
@ -228,7 +218,12 @@ void usage(const char* arg0, const char* extraOptions) {
|
|||
puts(" -v, --movie FILE Play back a movie of recorded input");
|
||||
puts(" -p, --patch FILE Apply a specified patch file when running");
|
||||
puts(" -s, --frameskip N Skip every N frames");
|
||||
puts(" --version Print version and exit");
|
||||
if (extraOptions) {
|
||||
puts(extraOptions);
|
||||
}
|
||||
}
|
||||
|
||||
void version(const char* arg0) {
|
||||
printf("%s %s (%s)\n", arg0, projectVersion, gitCommit);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef COMMAND_LINE_H
|
||||
#define COMMAND_LINE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
struct mArguments {
|
||||
char* fname;
|
||||
char* patch;
|
||||
char* cheatsFile;
|
||||
char* movie;
|
||||
char* bios;
|
||||
int logLevel;
|
||||
int frameskip;
|
||||
|
||||
enum mDebuggerType debuggerType;
|
||||
bool debugAtStart;
|
||||
bool showHelp;
|
||||
bool showVersion;
|
||||
};
|
||||
|
||||
struct mCoreConfig;
|
||||
struct mSubParser {
|
||||
const char* usage;
|
||||
bool (*parse)(struct mSubParser* parser, int option, const char* arg);
|
||||
void (*apply)(struct mSubParser* parser, struct mCoreConfig* config);
|
||||
const char* extraOptions;
|
||||
void* opts;
|
||||
};
|
||||
|
||||
struct mGraphicsOpts {
|
||||
int multiplier;
|
||||
bool fullscreen;
|
||||
};
|
||||
|
||||
bool parseArguments(struct mArguments* args, int argc, char* const* argv,
|
||||
struct mSubParser* subparser);
|
||||
void applyArguments(const struct mArguments* args, struct mSubParser* subparser, struct mCoreConfig* config);
|
||||
void freeArguments(struct mArguments* args);
|
||||
|
||||
void usage(const char* arg0, const char* extraOptions);
|
||||
void version(const char* arg0);
|
||||
|
||||
void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts);
|
||||
|
||||
#endif
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ffmpeg-encoder.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gba/video.h"
|
||||
|
||||
#include <libavcodec/version.h>
|
||||
|
@ -21,8 +22,9 @@
|
|||
#include <libavresample/avresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
static void _ffmpegPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride);
|
||||
static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right);
|
||||
static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height);
|
||||
|
||||
enum {
|
||||
PREFERRED_SAMPLE_RATE = 0x8000
|
||||
|
@ -31,6 +33,7 @@ enum {
|
|||
void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
||||
av_register_all();
|
||||
|
||||
encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions;
|
||||
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
||||
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
@ -42,9 +45,12 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
|||
FFmpegEncoderSetVideo(encoder, "png", 0);
|
||||
FFmpegEncoderSetContainer(encoder, "matroska");
|
||||
FFmpegEncoderSetDimensions(encoder, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
encoder->iwidth = VIDEO_HORIZONTAL_PIXELS;
|
||||
encoder->iheight = VIDEO_VERTICAL_PIXELS;
|
||||
encoder->resampleContext = 0;
|
||||
encoder->absf = 0;
|
||||
encoder->context = 0;
|
||||
encoder->scaleContext = NULL;
|
||||
}
|
||||
|
||||
bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, unsigned abr) {
|
||||
|
@ -289,22 +295,7 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->videoFrame->width = encoder->video->width;
|
||||
encoder->videoFrame->height = encoder->video->height;
|
||||
encoder->videoFrame->pts = 0;
|
||||
encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS,
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
AV_PIX_FMT_RGB565,
|
||||
#else
|
||||
AV_PIX_FMT_BGR555,
|
||||
#endif
|
||||
#else
|
||||
#ifndef USE_LIBAV
|
||||
AV_PIX_FMT_0BGR32,
|
||||
#else
|
||||
AV_PIX_FMT_BGR32,
|
||||
#endif
|
||||
#endif
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||
SWS_POINT, 0, 0, 0);
|
||||
_ffmpegSetVideoDimensions(&encoder->d, encoder->iwidth, encoder->iheight);
|
||||
av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32);
|
||||
|
||||
avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE);
|
||||
|
@ -350,6 +341,7 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
avcodec_close(encoder->video);
|
||||
|
||||
sws_freeContext(encoder->scaleContext);
|
||||
encoder->scaleContext = NULL;
|
||||
|
||||
avformat_free_context(encoder->context);
|
||||
encoder->context = 0;
|
||||
|
@ -359,7 +351,7 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
|
|||
return !!encoder->context;
|
||||
}
|
||||
|
||||
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||
void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right) {
|
||||
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
||||
if (!encoder->context || !encoder->audioCodec) {
|
||||
return;
|
||||
|
@ -418,14 +410,11 @@ void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t rig
|
|||
av_free_packet(&packet);
|
||||
}
|
||||
|
||||
void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
|
||||
void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) {
|
||||
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
||||
if (!encoder->context) {
|
||||
return;
|
||||
}
|
||||
const uint8_t* pixels;
|
||||
unsigned stride;
|
||||
renderer->getPixels(renderer, &stride, (const void**) &pixels);
|
||||
stride *= BYTES_PER_PIXEL;
|
||||
|
||||
AVPacket packet;
|
||||
|
@ -439,7 +428,7 @@ void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base);
|
||||
++encoder->currentVideoFrame;
|
||||
|
||||
sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, VIDEO_VERTICAL_PIXELS, encoder->videoFrame->data, encoder->videoFrame->linesize);
|
||||
sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize);
|
||||
|
||||
int gotData;
|
||||
avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData);
|
||||
|
@ -452,3 +441,28 @@ void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
}
|
||||
av_free_packet(&packet);
|
||||
}
|
||||
|
||||
static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
||||
encoder->iwidth = width;
|
||||
encoder->iheight = height;
|
||||
if (encoder->scaleContext) {
|
||||
sws_freeContext(encoder->scaleContext);
|
||||
}
|
||||
encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight,
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
AV_PIX_FMT_RGB565,
|
||||
#else
|
||||
AV_PIX_FMT_BGR555,
|
||||
#endif
|
||||
#else
|
||||
#ifndef USE_LIBAV
|
||||
AV_PIX_FMT_0BGR32,
|
||||
#else
|
||||
AV_PIX_FMT_BGR32,
|
||||
#endif
|
||||
#endif
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||
SWS_POINT, 0, 0, 0);
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
#include <libavformat/avformat.h>
|
||||
|
||||
struct FFmpegEncoder {
|
||||
struct GBAAVStream d;
|
||||
struct mAVStream d;
|
||||
struct AVFormatContext* context;
|
||||
|
||||
unsigned audioBitrate;
|
||||
|
@ -42,6 +42,8 @@ struct FFmpegEncoder {
|
|||
struct AVFrame* videoFrame;
|
||||
int width;
|
||||
int height;
|
||||
int iwidth;
|
||||
int iheight;
|
||||
int64_t currentVideoFrame;
|
||||
struct SwsContext* scaleContext;
|
||||
struct AVStream* videoStream;
|
|
@ -1,15 +1,23 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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 "gui-config.h"
|
||||
|
||||
#include "gba/gui/gui-runner.h"
|
||||
#include "core/config.h"
|
||||
#include "core/core.h"
|
||||
#include "feature/gui/gui-runner.h"
|
||||
#include "feature/gui/remap.h"
|
||||
#include "gba/gba.h"
|
||||
#include "util/gui/file-select.h"
|
||||
#include "util/gui/menu.h"
|
||||
|
||||
void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra) {
|
||||
#ifndef GUI_MAX_INPUTS
|
||||
#define GUI_MAX_INPUTS 7
|
||||
#endif
|
||||
|
||||
void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra) {
|
||||
struct GUIMenu menu = {
|
||||
.title = "Configure",
|
||||
.index = 0,
|
||||
|
@ -22,8 +30,9 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
.submenu = 0,
|
||||
.state = 0,
|
||||
.validStates = (const char*[]) {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 0
|
||||
}
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
},
|
||||
.nStates = 10
|
||||
};
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Show framerate",
|
||||
|
@ -31,8 +40,9 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
.submenu = 0,
|
||||
.state = false,
|
||||
.validStates = (const char*[]) {
|
||||
"Off", "On", 0
|
||||
}
|
||||
"Off", "On"
|
||||
},
|
||||
.nStates = 2
|
||||
};
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Use BIOS if found",
|
||||
|
@ -40,20 +50,38 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
.submenu = 0,
|
||||
.state = true,
|
||||
.validStates = (const char*[]) {
|
||||
"Off", "On", 0
|
||||
}
|
||||
"Off", "On"
|
||||
},
|
||||
.nStates = 2
|
||||
};
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Select BIOS path",
|
||||
.data = "bios",
|
||||
};
|
||||
size_t i;
|
||||
const char* mapNames[GUI_MAX_INPUTS + 1];
|
||||
if (runner->keySources) {
|
||||
for (i = 0; runner->keySources[i].id && i < GUI_MAX_INPUTS; ++i) {
|
||||
mapNames[i] = runner->keySources[i].name;
|
||||
}
|
||||
if (i == 1) {
|
||||
// Don't display a name if there's only one input source
|
||||
i = 0;
|
||||
}
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Remap controls",
|
||||
.data = "*REMAP",
|
||||
.state = 0,
|
||||
.validStates = i ? mapNames : 0,
|
||||
.nStates = i
|
||||
};
|
||||
}
|
||||
for (i = 0; i < nExtra; ++i) {
|
||||
*GUIMenuItemListAppend(&menu.items) = extra[i];
|
||||
}
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Save",
|
||||
.data = "[SAVE]",
|
||||
.data = "*SAVE",
|
||||
};
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Cancel",
|
||||
|
@ -68,7 +96,7 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
if (!item->validStates || !item->data) {
|
||||
continue;
|
||||
}
|
||||
GBAConfigGetUIntValue(&runner->context.config, item->data, &item->state);
|
||||
mCoreConfigGetUIntValue(&runner->core->config, item->data, &item->state);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
@ -76,20 +104,24 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
if (reason != GUI_MENU_EXIT_ACCEPT || !item->data) {
|
||||
break;
|
||||
}
|
||||
if (!strcmp(item->data, "[SAVE]")) {
|
||||
if (!strcmp(item->data, "*SAVE")) {
|
||||
if (biosPath[0]) {
|
||||
GBAConfigSetValue(&runner->context.config, "bios", biosPath);
|
||||
mCoreConfigSetValue(&runner->core->config, "bios", biosPath);
|
||||
}
|
||||
for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) {
|
||||
item = GUIMenuItemListGetPointer(&menu.items, i);
|
||||
if (!item->validStates || !item->data) {
|
||||
continue;
|
||||
}
|
||||
GBAConfigSetUIntValue(&runner->context.config, item->data, item->state);
|
||||
mCoreConfigSetUIntValue(&runner->core->config, item->data, item->state);
|
||||
}
|
||||
GBAConfigSave(&runner->context.config);
|
||||
mCoreConfigSave(&runner->core->config);
|
||||
break;
|
||||
}
|
||||
if (!strcmp(item->data, "*REMAP")) {
|
||||
mGUIRemapKeys(&runner->params, &runner->core->inputMap, &runner->keySources[item->state]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(item->data, "bios")) {
|
||||
// TODO: show box if failed
|
||||
if (!GUISelectFile(&runner->params, biosPath, sizeof(biosPath), GBAIsBIOS)) {
|
||||
|
@ -99,7 +131,7 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
}
|
||||
if (item->validStates) {
|
||||
++item->state;
|
||||
if (!item->validStates[item->state]) {
|
||||
if (item->state >= item->nStates) {
|
||||
item->state = 0;
|
||||
}
|
||||
}
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GBAGUIRunner;
|
||||
struct mGUIRunner;
|
||||
struct GUIMenuItem;
|
||||
void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra);
|
||||
void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra);
|
||||
|
||||
#endif
|
|
@ -1,13 +1,16 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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 "gui-runner.h"
|
||||
|
||||
#include "gba/gui/gui-config.h"
|
||||
#include "core/core.h"
|
||||
#include "core/serialize.h"
|
||||
#include "feature/gui/gui-config.h"
|
||||
#include "gba/gba.h"
|
||||
#include "gba/input.h"
|
||||
#include "gba/interface.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "util/gui/file-select.h"
|
||||
#include "util/gui/font.h"
|
||||
#include "util/gui/menu.h"
|
||||
|
@ -15,6 +18,10 @@
|
|||
#include "util/png-io.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef _3DS
|
||||
#include <3ds.h>
|
||||
#endif
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#define FPS_GRANULARITY 120
|
||||
|
@ -27,6 +34,7 @@ enum {
|
|||
RUNNER_LOAD_STATE,
|
||||
RUNNER_SCREENSHOT,
|
||||
RUNNER_CONFIG,
|
||||
RUNNER_RESET,
|
||||
RUNNER_COMMAND_MASK = 0xFFFF,
|
||||
|
||||
RUNNER_STATE_1 = 0x10000,
|
||||
|
@ -42,24 +50,26 @@ enum {
|
|||
|
||||
static void _drawBackground(struct GUIBackground* background, void* context) {
|
||||
UNUSED(context);
|
||||
struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
|
||||
struct mGUIBackground* gbaBackground = (struct mGUIBackground*) background;
|
||||
if (gbaBackground->p->drawFrame) {
|
||||
gbaBackground->p->drawFrame(gbaBackground->p, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void _drawState(struct GUIBackground* background, void* id) {
|
||||
struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
|
||||
struct mGUIBackground* gbaBackground = (struct mGUIBackground*) background;
|
||||
int stateId = ((int) id) >> 16;
|
||||
if (gbaBackground->p->drawScreenshot) {
|
||||
if (gbaBackground->screenshot && gbaBackground->screenshotId == (int) id) {
|
||||
gbaBackground->p->drawScreenshot(gbaBackground->p, gbaBackground->screenshot, true);
|
||||
return;
|
||||
}
|
||||
struct VFile* vf = GBAGetState(gbaBackground->p->context.gba, 0, stateId, false);
|
||||
struct VFile* vf = mCoreGetState(gbaBackground->p->core, stateId, false);
|
||||
unsigned w, h;
|
||||
gbaBackground->p->core->desiredVideoDimensions(gbaBackground->p->core, &w, &h);
|
||||
uint32_t* pixels = gbaBackground->screenshot;
|
||||
if (!pixels) {
|
||||
pixels = anonymousMemoryMap(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
pixels = anonymousMemoryMap(w * h * 4);
|
||||
gbaBackground->screenshot = pixels;
|
||||
}
|
||||
bool success = false;
|
||||
|
@ -69,7 +79,7 @@ static void _drawState(struct GUIBackground* background, void* id) {
|
|||
png_infop end = png_create_info_struct(png);
|
||||
if (png && info && end) {
|
||||
success = PNGReadHeader(png, info);
|
||||
success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
|
||||
success = success && PNGReadPixels(png, info, pixels, w, h, w);
|
||||
success = success && PNGReadFooter(png, end);
|
||||
}
|
||||
PNGReadClose(png, info, end);
|
||||
|
@ -91,7 +101,7 @@ static void _updateLux(struct GBALuminanceSource* lux) {
|
|||
}
|
||||
|
||||
static uint8_t _readLux(struct GBALuminanceSource* lux) {
|
||||
struct GBAGUIRunnerLux* runnerLux = (struct GBAGUIRunnerLux*) lux;
|
||||
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
|
||||
int value = 0x16;
|
||||
if (runnerLux->luxLevel > 0) {
|
||||
value += GBA_LUX_LEVELS[runnerLux->luxLevel - 1];
|
||||
|
@ -99,37 +109,30 @@ static uint8_t _readLux(struct GBALuminanceSource* lux) {
|
|||
return 0xFF - value;
|
||||
}
|
||||
|
||||
void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) {
|
||||
void mGUIInit(struct mGUIRunner* runner, const char* port) {
|
||||
GUIInit(&runner->params);
|
||||
GBAContextInit(&runner->context, port);
|
||||
runner->port = port;
|
||||
runner->core = NULL;
|
||||
runner->luminanceSource.d.readLuminance = _readLux;
|
||||
runner->luminanceSource.d.sample = _updateLux;
|
||||
runner->luminanceSource.luxLevel = 0;
|
||||
runner->context.gba->luminanceSource = &runner->luminanceSource.d;
|
||||
runner->background.d.draw = _drawBackground;
|
||||
runner->background.p = runner;
|
||||
runner->fps = 0;
|
||||
runner->lastFpsCheck = 0;
|
||||
runner->totalDelta = 0;
|
||||
CircleBufferInit(&runner->fpsBuffer, FPS_BUFFER_SIZE * sizeof(uint32_t));
|
||||
if (runner->setup) {
|
||||
runner->setup(runner);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAGUIDeinit(struct GBAGUIRunner* runner) {
|
||||
void mGUIDeinit(struct mGUIRunner* runner) {
|
||||
if (runner->teardown) {
|
||||
runner->teardown(runner);
|
||||
}
|
||||
if (runner->context.config.port) {
|
||||
GBAConfigSave(&runner->context.config);
|
||||
}
|
||||
CircleBufferDeinit(&runner->fpsBuffer);
|
||||
GBAContextDeinit(&runner->context);
|
||||
}
|
||||
|
||||
void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
||||
struct GBAGUIBackground drawState = {
|
||||
void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
||||
struct mGUIBackground drawState = {
|
||||
.d = {
|
||||
.draw = _drawState
|
||||
},
|
||||
|
@ -156,8 +159,6 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
|||
GUIMenuItemListInit(&stateSaveMenu.items, 9);
|
||||
GUIMenuItemListInit(&stateLoadMenu.items, 9);
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };
|
||||
#if !(defined(__POWERPC__) || defined(__PPC__))
|
||||
// PPC doesn't have working savestates yet
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu };
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu };
|
||||
|
||||
|
@ -180,182 +181,231 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
|||
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };
|
||||
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) };
|
||||
*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) };
|
||||
#endif
|
||||
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT };
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG };
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Reset game", .data = (void*) RUNNER_RESET };
|
||||
*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT };
|
||||
|
||||
while (true) {
|
||||
char path[256];
|
||||
if (!GUISelectFile(&runner->params, path, sizeof(path), GBAIsROM)) {
|
||||
break;
|
||||
}
|
||||
// TODO: Message box API
|
||||
runner->params.drawStart();
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
}
|
||||
GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Loading...");
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
runner->params.drawEnd();
|
||||
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
bool found = false;
|
||||
runner->core = mCoreFind(path);
|
||||
if (runner->core) {
|
||||
runner->core->init(runner->core);
|
||||
mInputMapInit(&runner->core->inputMap, &GBAInputInfo);
|
||||
mCoreInitConfig(runner->core, runner->port);
|
||||
found = mCoreLoadFile(runner->core, path);
|
||||
if (!found) {
|
||||
runner->core->deinit(runner->core);
|
||||
}
|
||||
// TODO: Message box API
|
||||
runner->params.drawStart();
|
||||
GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
|
||||
runner->params.drawEnd();
|
||||
runner->params.drawStart();
|
||||
GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
|
||||
runner->params.drawEnd();
|
||||
}
|
||||
|
||||
if (!GBAContextLoadROM(&runner->context, path, true)) {
|
||||
int i;
|
||||
for (i = 0; i < 300; ++i) {
|
||||
runner->params.drawStart();
|
||||
GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Load failed!");
|
||||
runner->params.drawEnd();
|
||||
if (!found) {
|
||||
int i;
|
||||
for (i = 0; i < 240; ++i) {
|
||||
runner->params.drawStart();
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
}
|
||||
continue;
|
||||
GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Load failed!");
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
runner->params.drawEnd();
|
||||
}
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
GBAContextStart(&runner->context);
|
||||
if (runner->gameLoaded) {
|
||||
runner->gameLoaded(runner);
|
||||
return;
|
||||
}
|
||||
if (runner->core->platform(runner->core) == PLATFORM_GBA) {
|
||||
((struct GBA*) runner->core->board)->luminanceSource = &runner->luminanceSource.d;
|
||||
}
|
||||
if (runner->core->config.port && runner->keySources) {
|
||||
size_t i;
|
||||
for (i = 0; runner->keySources[i].id; ++i) {
|
||||
mInputMapLoad(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->core->config));
|
||||
}
|
||||
}
|
||||
// TODO: Do we need to load more defaults?
|
||||
mCoreConfigSetDefaultIntValue(&runner->core->config, "volume", 0x100);
|
||||
mCoreConfigSetDefaultValue(&runner->core->config, "idleOptimization", "detect");
|
||||
mCoreLoadConfig(runner->core);
|
||||
mCoreAutoloadSave(runner->core);
|
||||
if (runner->setup) {
|
||||
runner->setup(runner);
|
||||
}
|
||||
runner->core->reset(runner->core);
|
||||
bool running = true;
|
||||
if (runner->gameLoaded) {
|
||||
runner->gameLoaded(runner);
|
||||
}
|
||||
while (running) {
|
||||
CircleBufferClear(&runner->fpsBuffer);
|
||||
runner->totalDelta = 0;
|
||||
runner->fps = 0;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
|
||||
|
||||
bool running = true;
|
||||
while (running) {
|
||||
CircleBufferClear(&runner->fpsBuffer);
|
||||
runner->totalDelta = 0;
|
||||
runner->fps = 0;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
|
||||
|
||||
while (true) {
|
||||
uint32_t guiKeys;
|
||||
GUIPollInput(&runner->params, &guiKeys, 0);
|
||||
if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
|
||||
break;
|
||||
while (true) {
|
||||
#ifdef _3DS
|
||||
running = aptMainLoop();
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
uint32_t guiKeys;
|
||||
GUIPollInput(&runner->params, &guiKeys, 0);
|
||||
if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
|
||||
break;
|
||||
}
|
||||
if (guiKeys & (1 << mGUI_INPUT_INCREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel < 10) {
|
||||
++runner->luminanceSource.luxLevel;
|
||||
}
|
||||
if (guiKeys & (1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel < 10) {
|
||||
++runner->luminanceSource.luxLevel;
|
||||
}
|
||||
if (guiKeys & (1 << mGUI_INPUT_DECREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel > 0) {
|
||||
--runner->luminanceSource.luxLevel;
|
||||
}
|
||||
}
|
||||
if (guiKeys & (1 << mGUI_INPUT_SCREEN_MODE) && runner->incrementScreenMode) {
|
||||
runner->incrementScreenMode(runner);
|
||||
}
|
||||
uint16_t keys = runner->pollGameInput(runner);
|
||||
if (runner->prepareForFrame) {
|
||||
runner->prepareForFrame(runner);
|
||||
}
|
||||
runner->core->setKeys(runner->core, keys);
|
||||
runner->core->runFrame(runner->core);
|
||||
if (runner->drawFrame) {
|
||||
int drawFps = false;
|
||||
mCoreConfigGetIntValue(&runner->core->config, "fpsCounter", &drawFps);
|
||||
|
||||
runner->params.drawStart();
|
||||
runner->drawFrame(runner, false);
|
||||
if (drawFps) {
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
}
|
||||
GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_ALIGN_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps);
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
}
|
||||
if (guiKeys & (1 << GBA_GUI_INPUT_DECREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel > 0) {
|
||||
--runner->luminanceSource.luxLevel;
|
||||
}
|
||||
}
|
||||
if (guiKeys & (1 << GBA_GUI_INPUT_SCREEN_MODE) && runner->incrementScreenMode) {
|
||||
runner->incrementScreenMode(runner);
|
||||
}
|
||||
uint16_t keys = runner->pollGameInput(runner);
|
||||
if (runner->prepareForFrame) {
|
||||
runner->prepareForFrame(runner);
|
||||
}
|
||||
GBAContextFrame(&runner->context, keys);
|
||||
if (runner->drawFrame) {
|
||||
int drawFps = false;
|
||||
GBAConfigGetIntValue(&runner->context.config, "fpsCounter", &drawFps);
|
||||
runner->params.drawEnd();
|
||||
|
||||
runner->params.drawStart();
|
||||
runner->drawFrame(runner, false);
|
||||
if (runner->core->frameCounter(runner->core) % FPS_GRANULARITY == 0) {
|
||||
if (drawFps) {
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec;
|
||||
uint64_t delta = t - runner->lastFpsCheck;
|
||||
runner->lastFpsCheck = t;
|
||||
if (delta > 0x7FFFFFFFLL) {
|
||||
CircleBufferClear(&runner->fpsBuffer);
|
||||
runner->fps = 0;
|
||||
}
|
||||
GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_TEXT_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps);
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
}
|
||||
runner->params.drawEnd();
|
||||
|
||||
if (runner->context.gba->video.frameCounter % FPS_GRANULARITY == 0) {
|
||||
if (drawFps) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec;
|
||||
uint64_t delta = t - runner->lastFpsCheck;
|
||||
runner->lastFpsCheck = t;
|
||||
if (delta > 0x7FFFFFFFLL) {
|
||||
CircleBufferClear(&runner->fpsBuffer);
|
||||
runner->fps = 0;
|
||||
}
|
||||
if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) {
|
||||
int32_t last;
|
||||
CircleBufferRead32(&runner->fpsBuffer, &last);
|
||||
runner->totalDelta -= last;
|
||||
}
|
||||
CircleBufferWrite32(&runner->fpsBuffer, delta);
|
||||
runner->totalDelta += delta;
|
||||
runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t));
|
||||
if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) {
|
||||
int32_t last;
|
||||
CircleBufferRead32(&runner->fpsBuffer, &last);
|
||||
runner->totalDelta -= last;
|
||||
}
|
||||
CircleBufferWrite32(&runner->fpsBuffer, delta);
|
||||
runner->totalDelta += delta;
|
||||
runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runner->paused) {
|
||||
runner->paused(runner);
|
||||
if (runner->paused) {
|
||||
runner->paused(runner);
|
||||
}
|
||||
GUIInvalidateKeys(&runner->params);
|
||||
uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable!
|
||||
struct GUIMenuItem* item;
|
||||
enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item);
|
||||
if (reason == GUI_MENU_EXIT_ACCEPT) {
|
||||
switch (((int) item->data) & RUNNER_COMMAND_MASK) {
|
||||
case RUNNER_EXIT:
|
||||
running = false;
|
||||
keys = 0;
|
||||
break;
|
||||
case RUNNER_RESET:
|
||||
runner->core->reset(runner->core);
|
||||
break;
|
||||
case RUNNER_SAVE_STATE:
|
||||
mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT);
|
||||
break;
|
||||
case RUNNER_LOAD_STATE:
|
||||
mCoreLoadState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT);
|
||||
break;
|
||||
case RUNNER_SCREENSHOT:
|
||||
mCoreTakeScreenshot(runner->core);
|
||||
break;
|
||||
case RUNNER_CONFIG:
|
||||
mGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra);
|
||||
mCoreLoadConfig(runner->core);
|
||||
break;
|
||||
case RUNNER_CONTINUE:
|
||||
break;
|
||||
}
|
||||
GUIInvalidateKeys(&runner->params);
|
||||
uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable!
|
||||
struct GUIMenuItem* item;
|
||||
enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item);
|
||||
if (reason == GUI_MENU_EXIT_ACCEPT) {
|
||||
struct VFile* vf;
|
||||
switch (((int) item->data) & RUNNER_COMMAND_MASK) {
|
||||
case RUNNER_EXIT:
|
||||
running = false;
|
||||
keys = 0;
|
||||
break;
|
||||
case RUNNER_SAVE_STATE:
|
||||
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true);
|
||||
if (vf) {
|
||||
GBASaveStateNamed(runner->context.gba, vf, true);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
case RUNNER_LOAD_STATE:
|
||||
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false);
|
||||
if (vf) {
|
||||
GBALoadStateNamed(runner->context.gba, vf);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
case RUNNER_SCREENSHOT:
|
||||
GBATakeScreenshot(runner->context.gba, 0);
|
||||
break;
|
||||
case RUNNER_CONFIG:
|
||||
GBAGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra);
|
||||
GBAConfigGetIntValue(&runner->context.config, "frameskip", &runner->context.gba->video.frameskip);
|
||||
break;
|
||||
case RUNNER_CONTINUE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
int frames = 0;
|
||||
}
|
||||
int frames = 0;
|
||||
GUIPollInput(&runner->params, 0, &keys);
|
||||
while (keys && frames < 30) {
|
||||
++frames;
|
||||
runner->params.drawStart();
|
||||
runner->drawFrame(runner, true);
|
||||
runner->params.drawEnd();
|
||||
GUIPollInput(&runner->params, 0, &keys);
|
||||
while (keys && frames < 30) {
|
||||
++frames;
|
||||
runner->params.drawStart();
|
||||
runner->drawFrame(runner, true);
|
||||
runner->params.drawEnd();
|
||||
GUIPollInput(&runner->params, 0, &keys);
|
||||
}
|
||||
if (runner->unpaused) {
|
||||
runner->unpaused(runner);
|
||||
}
|
||||
}
|
||||
GBAContextStop(&runner->context);
|
||||
if (runner->gameUnloaded) {
|
||||
runner->gameUnloaded(runner);
|
||||
if (runner->unpaused) {
|
||||
runner->unpaused(runner);
|
||||
}
|
||||
GBAContextUnloadROM(&runner->context);
|
||||
drawState.screenshotId = 0;
|
||||
}
|
||||
if (runner->gameUnloaded) {
|
||||
runner->gameUnloaded(runner);
|
||||
}
|
||||
runner->core->unloadROM(runner->core);
|
||||
drawState.screenshotId = 0;
|
||||
if (drawState.screenshot) {
|
||||
mappedMemoryFree(drawState.screenshot, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
unsigned w, h;
|
||||
runner->core->desiredVideoDimensions(runner->core, &w, &h);
|
||||
mappedMemoryFree(drawState.screenshot, w * h * 4);
|
||||
}
|
||||
|
||||
if (runner->core->config.port) {
|
||||
if (runner->keySources) {
|
||||
size_t i;
|
||||
for (i = 0; runner->keySources[i].id; ++i) {
|
||||
mInputMapSave(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->core->config));
|
||||
}
|
||||
}
|
||||
mCoreConfigSave(&runner->core->config);
|
||||
}
|
||||
runner->core->deinit(runner->core);
|
||||
|
||||
GUIMenuItemListDeinit(&pauseMenu.items);
|
||||
GUIMenuItemListDeinit(&stateSaveMenu.items);
|
||||
GUIMenuItemListDeinit(&stateLoadMenu.items);
|
||||
}
|
||||
|
||||
void mGUIRunloop(struct mGUIRunner* runner) {
|
||||
while (true) {
|
||||
char path[PATH_MAX];
|
||||
if (!GUISelectFile(&runner->params, path, sizeof(path), 0)) {
|
||||
break;
|
||||
}
|
||||
mGUIRun(runner, path);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* Copyright (c) 2013-2016 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 GUI_RUNNER_H
|
||||
#define GUI_RUNNER_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "feature/gui/remap.h"
|
||||
#include "gba/hardware.h"
|
||||
#include "util/circle-buffer.h"
|
||||
#include "util/gui.h"
|
||||
|
||||
enum mGUIInput {
|
||||
mGUI_INPUT_INCREASE_BRIGHTNESS = GUI_INPUT_USER_START,
|
||||
mGUI_INPUT_DECREASE_BRIGHTNESS,
|
||||
mGUI_INPUT_SCREEN_MODE,
|
||||
};
|
||||
|
||||
struct mGUIBackground {
|
||||
struct GUIBackground d;
|
||||
struct mGUIRunner* p;
|
||||
|
||||
uint32_t* screenshot;
|
||||
int screenshotId;
|
||||
};
|
||||
|
||||
struct mCore;
|
||||
struct mGUIRunnerLux {
|
||||
struct GBALuminanceSource d;
|
||||
int luxLevel;
|
||||
};
|
||||
|
||||
struct mGUIRunner {
|
||||
struct mCore* core;
|
||||
struct GUIParams params;
|
||||
|
||||
struct mGUIBackground background;
|
||||
struct mGUIRunnerLux luminanceSource;
|
||||
|
||||
struct GUIMenuItem* configExtra;
|
||||
size_t nConfigExtra;
|
||||
|
||||
struct GUIInputKeys* keySources;
|
||||
|
||||
const char* port;
|
||||
float fps;
|
||||
int64_t lastFpsCheck;
|
||||
int32_t totalDelta;
|
||||
struct CircleBuffer fpsBuffer;
|
||||
|
||||
void (*setup)(struct mGUIRunner*);
|
||||
void (*teardown)(struct mGUIRunner*);
|
||||
void (*gameLoaded)(struct mGUIRunner*);
|
||||
void (*gameUnloaded)(struct mGUIRunner*);
|
||||
void (*prepareForFrame)(struct mGUIRunner*);
|
||||
void (*drawFrame)(struct mGUIRunner*, bool faded);
|
||||
void (*drawScreenshot)(struct mGUIRunner*, const uint32_t* pixels, bool faded);
|
||||
void (*paused)(struct mGUIRunner*);
|
||||
void (*unpaused)(struct mGUIRunner*);
|
||||
void (*incrementScreenMode)(struct mGUIRunner*);
|
||||
uint16_t (*pollGameInput)(struct mGUIRunner*);
|
||||
};
|
||||
|
||||
void mGUIInit(struct mGUIRunner*, const char* port);
|
||||
void mGUIDeinit(struct mGUIRunner*);
|
||||
void mGUIRun(struct mGUIRunner*, const char* path);
|
||||
void mGUIRunloop(struct mGUIRunner*);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,62 @@
|
|||
/* Copyright (c) 2013-2016 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 "remap.h"
|
||||
|
||||
#include "gba/input.h"
|
||||
#include "util/gui.h"
|
||||
#include "util/gui/menu.h"
|
||||
|
||||
void mGUIRemapKeys(struct GUIParams* params, struct mInputMap* map, const struct GUIInputKeys* keys) {
|
||||
struct GUIMenu menu = {
|
||||
.title = "Remap keys",
|
||||
.index = 0,
|
||||
.background = 0
|
||||
};
|
||||
GUIMenuItemListInit(&menu.items, 0);
|
||||
const char* keyNames[keys->nKeys + 1];
|
||||
memcpy(&keyNames[1], keys->keyNames, keys->nKeys * sizeof(keyNames[0]));
|
||||
keyNames[0] = "Unmapped";
|
||||
size_t i;
|
||||
for (i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = GBAInputInfo.keyId[i],
|
||||
.data = (void*) (GUI_INPUT_MAX + i),
|
||||
.submenu = 0,
|
||||
.state = mInputQueryBinding(map, keys->id, i) + 1,
|
||||
.validStates = keyNames,
|
||||
.nStates = keys->nKeys + 1
|
||||
};
|
||||
}
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Save",
|
||||
.data = (void*) (GUI_INPUT_MAX + GBA_KEY_MAX + 2),
|
||||
};
|
||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||
.title = "Cancel",
|
||||
.data = 0,
|
||||
};
|
||||
|
||||
struct GUIMenuItem* item;
|
||||
while (true) {
|
||||
enum GUIMenuExitReason reason;
|
||||
reason = GUIShowMenu(params, &menu, &item);
|
||||
if (reason != GUI_MENU_EXIT_ACCEPT || !item->data) {
|
||||
break;
|
||||
}
|
||||
if (item->data == (void*) (GUI_INPUT_MAX + GBA_KEY_MAX + 2)) {
|
||||
for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) {
|
||||
item = GUIMenuItemListGetPointer(&menu.items, i);
|
||||
if (i < GBA_KEY_MAX) {
|
||||
mInputBindKey(map, keys->id, item->state - 1, i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (item->validStates) {
|
||||
// TODO: Open remap menu
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* Copyright (c) 2013-2016 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 GUI_REMAP_H
|
||||
#define GUI_REMAP_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GUIInputKeys {
|
||||
const char* name;
|
||||
uint32_t id;
|
||||
const char* const* keyNames;
|
||||
size_t nKeys;
|
||||
};
|
||||
|
||||
struct GUIParams;
|
||||
struct mInputMap;
|
||||
|
||||
void mGUIRemapKeys(struct GUIParams*, struct mInputMap*, const struct GUIInputKeys*);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,102 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "imagemagick-gif-encoder.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static void _magickPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride);
|
||||
static void _magickVideoDimensionsChanged(struct mAVStream*, unsigned width, unsigned height);
|
||||
|
||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
||||
encoder->wand = 0;
|
||||
|
||||
encoder->d.videoDimensionsChanged = _magickVideoDimensionsChanged;
|
||||
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
||||
encoder->d.postAudioFrame = 0;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->frameskip = 2;
|
||||
encoder->delayMs = -1;
|
||||
|
||||
encoder->iwidth = VIDEO_HORIZONTAL_PIXELS;
|
||||
encoder->iheight = VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
|
||||
void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs) {
|
||||
if (ImageMagickGIFEncoderIsOpen(encoder)) {
|
||||
return;
|
||||
}
|
||||
encoder->frameskip = frameskip;
|
||||
encoder->delayMs = delayMs;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder* encoder, const char* outfile) {
|
||||
MagickWandGenesis();
|
||||
encoder->wand = NewMagickWand();
|
||||
MagickSetImageFormat(encoder->wand, "GIF");
|
||||
MagickSetImageDispose(encoder->wand, PreviousDispose);
|
||||
encoder->outfile = strdup(outfile);
|
||||
encoder->frame = malloc(encoder->iwidth * encoder->iheight * 4);
|
||||
encoder->currentFrame = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) {
|
||||
if (!encoder->wand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MagickBooleanType success = MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue);
|
||||
DestroyMagickWand(encoder->wand);
|
||||
encoder->wand = 0;
|
||||
free(encoder->outfile);
|
||||
free(encoder->frame);
|
||||
MagickWandTerminus();
|
||||
return success == MagickTrue;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder* encoder) {
|
||||
return !!encoder->wand;
|
||||
}
|
||||
|
||||
static void _magickPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
|
||||
if (encoder->currentFrame % (encoder->frameskip + 1)) {
|
||||
++encoder->currentFrame;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* p8 = (const uint8_t*) pixels;
|
||||
size_t row;
|
||||
for (row = 0; row < encoder->iheight; ++row) {
|
||||
memcpy(&encoder->frame[row * encoder->iwidth], &p8[row * 4 * stride], encoder->iwidth * 4);
|
||||
}
|
||||
|
||||
MagickConstituteImage(encoder->wand, encoder->iwidth, encoder->iheight, "RGBP", CharPixel, encoder->frame);
|
||||
uint64_t ts = encoder->currentFrame;
|
||||
uint64_t nts = encoder->currentFrame + encoder->frameskip + 1;
|
||||
if (encoder->delayMs >= 0) {
|
||||
ts *= encoder->delayMs;
|
||||
nts *= encoder->delayMs;
|
||||
ts /= 10;
|
||||
nts /= 10;
|
||||
} else {
|
||||
ts *= VIDEO_TOTAL_LENGTH * 100;
|
||||
nts *= VIDEO_TOTAL_LENGTH * 100;
|
||||
ts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
nts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
}
|
||||
MagickSetImageDelay(encoder->wand, nts - ts);
|
||||
++encoder->currentFrame;
|
||||
}
|
||||
|
||||
static void _magickVideoDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
encoder->iwidth = width;
|
||||
encoder->iheight = height;
|
||||
}
|
|
@ -14,18 +14,23 @@
|
|||
#include <wand/MagickWand.h>
|
||||
|
||||
struct ImageMagickGIFEncoder {
|
||||
struct GBAAVStream d;
|
||||
struct mAVStream d;
|
||||
MagickWand* wand;
|
||||
char* outfile;
|
||||
uint32_t* frame;
|
||||
|
||||
unsigned currentFrame;
|
||||
int frameskip;
|
||||
int delayMs;
|
||||
|
||||
unsigned iwidth;
|
||||
unsigned iheight;
|
||||
};
|
||||
|
||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder*);
|
||||
void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs);
|
||||
bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder*, const char* outfile);
|
||||
void ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*);
|
||||
bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*);
|
||||
bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder*);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,964 @@
|
|||
/* Copyright (c) 2013-2016 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 "audio.h"
|
||||
|
||||
#include "core/interface.h"
|
||||
#include "core/sync.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/serialize.h"
|
||||
#include "gb/io.h"
|
||||
|
||||
#ifdef _3DS
|
||||
#define blip_add_delta blip_add_delta_fast
|
||||
#endif
|
||||
|
||||
#define FRAME_CYCLES (DMG_LR35902_FREQUENCY >> 9)
|
||||
|
||||
const uint32_t DMG_LR35902_FREQUENCY = 0x400000;
|
||||
static const int CLOCKS_PER_BLIP_FRAME = 0x1000;
|
||||
static const unsigned BLIP_BUFFER_SIZE = 0x4000;
|
||||
const int GB_AUDIO_VOLUME_MAX = 0x100;
|
||||
|
||||
static void _writeDuty(struct GBAudioEnvelope* envelope, uint8_t value);
|
||||
static bool _writeSweep(struct GBAudioEnvelope* envelope, uint8_t value);
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareControl* envelope, int duty);
|
||||
static void _updateEnvelope(struct GBAudioEnvelope* envelope);
|
||||
static bool _updateSweep(struct GBAudioChannel1* ch, bool initial);
|
||||
static int32_t _updateChannel1(struct GBAudioChannel1* ch);
|
||||
static int32_t _updateChannel2(struct GBAudioChannel2* ch);
|
||||
static int32_t _updateChannel3(struct GBAudioChannel3* ch, enum GBAudioStyle style);
|
||||
static int32_t _updateChannel4(struct GBAudioChannel4* ch);
|
||||
static void _sample(struct GBAudio* audio, int32_t cycles);
|
||||
static void _scheduleEvent(struct GBAudio* audio);
|
||||
|
||||
void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style) {
|
||||
audio->samples = samples;
|
||||
audio->left = blip_new(BLIP_BUFFER_SIZE);
|
||||
audio->right = blip_new(BLIP_BUFFER_SIZE);
|
||||
audio->clockRate = DMG_LR35902_FREQUENCY;
|
||||
// Guess too large; we hang producing extra samples if we guess too low
|
||||
blip_set_rates(audio->left, DMG_LR35902_FREQUENCY, 96000);
|
||||
blip_set_rates(audio->right, DMG_LR35902_FREQUENCY, 96000);
|
||||
audio->forceDisableCh[0] = false;
|
||||
audio->forceDisableCh[1] = false;
|
||||
audio->forceDisableCh[2] = false;
|
||||
audio->forceDisableCh[3] = false;
|
||||
audio->masterVolume = GB_AUDIO_VOLUME_MAX;
|
||||
audio->nr52 = nr52;
|
||||
audio->style = style;
|
||||
}
|
||||
|
||||
void GBAudioDeinit(struct GBAudio* audio) {
|
||||
blip_delete(audio->left);
|
||||
blip_delete(audio->right);
|
||||
}
|
||||
|
||||
void GBAudioReset(struct GBAudio* audio) {
|
||||
audio->nextEvent = 0;
|
||||
audio->nextCh1 = 0;
|
||||
audio->nextCh2 = 0;
|
||||
audio->nextCh3 = 0;
|
||||
audio->fadeCh3 = 0;
|
||||
audio->nextCh4 = 0;
|
||||
audio->ch1 = (struct GBAudioChannel1) { .envelope = { .dead = 2 } };
|
||||
audio->ch2 = (struct GBAudioChannel2) { .envelope = { .dead = 2 } };
|
||||
audio->ch3 = (struct GBAudioChannel3) { .bank = 0 };
|
||||
audio->ch4 = (struct GBAudioChannel4) { .envelope = { .dead = 2 } };
|
||||
audio->eventDiff = 0;
|
||||
audio->nextFrame = 0;
|
||||
audio->frame = 0;
|
||||
audio->nextSample = 0;
|
||||
audio->sampleInterval = 128;
|
||||
audio->lastLeft = 0;
|
||||
audio->lastRight = 0;
|
||||
audio->clock = 0;
|
||||
audio->volumeRight = 0;
|
||||
audio->volumeLeft = 0;
|
||||
audio->ch1Right = false;
|
||||
audio->ch2Right = false;
|
||||
audio->ch3Right = false;
|
||||
audio->ch4Right = false;
|
||||
audio->ch1Left = false;
|
||||
audio->ch2Left = false;
|
||||
audio->ch3Left = false;
|
||||
audio->ch4Left = false;
|
||||
audio->playingCh1 = false;
|
||||
audio->playingCh2 = false;
|
||||
audio->playingCh3 = false;
|
||||
audio->playingCh4 = false;
|
||||
}
|
||||
|
||||
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
|
||||
mCoreSyncLockAudio(audio->p->sync);
|
||||
audio->samples = samples;
|
||||
blip_clear(audio->left);
|
||||
blip_clear(audio->right);
|
||||
audio->clock = 0;
|
||||
mCoreSyncConsumeAudio(audio->p->sync);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch1.shift = GBAudioRegisterSquareSweepGetShift(value);
|
||||
bool oldDirection = audio->ch1.direction;
|
||||
audio->ch1.direction = GBAudioRegisterSquareSweepGetDirection(value);
|
||||
if (audio->ch1.sweepOccurred && oldDirection && !audio->ch1.direction) {
|
||||
audio->playingCh1 = false;
|
||||
*audio->nr52 &= ~0x0001;
|
||||
}
|
||||
audio->ch1.sweepOccurred = false;
|
||||
audio->ch1.time = GBAudioRegisterSquareSweepGetTime(value);
|
||||
if (!audio->ch1.time) {
|
||||
audio->ch1.time = 8;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR11(struct GBAudio* audio, uint8_t value) {
|
||||
_writeDuty(&audio->ch1.envelope, value);
|
||||
audio->ch1.control.length = 64 - audio->ch1.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) {
|
||||
if (!_writeSweep(&audio->ch1.envelope, value)) {
|
||||
audio->playingCh1 = false;
|
||||
*audio->nr52 &= ~0x0001;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR13(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch1.control.frequency &= 0x700;
|
||||
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch1.control.frequency &= 0xFF;
|
||||
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
|
||||
bool wasStop = audio->ch1.control.stop;
|
||||
audio->ch1.control.stop = GBAudioRegisterControlGetStop(value << 8);
|
||||
if (!wasStop && audio->ch1.control.stop && audio->ch1.control.length && !(audio->frame & 1)) {
|
||||
--audio->ch1.control.length;
|
||||
if (audio->ch1.control.length == 0) {
|
||||
audio->playingCh1 = false;
|
||||
}
|
||||
}
|
||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||
audio->playingCh1 = audio->ch1.envelope.initialVolume || audio->ch1.envelope.direction;
|
||||
audio->ch1.envelope.currentVolume = audio->ch1.envelope.initialVolume;
|
||||
if (audio->ch1.envelope.currentVolume > 0) {
|
||||
audio->ch1.envelope.dead = audio->ch1.envelope.stepTime ? 0 : 1;
|
||||
} else {
|
||||
audio->ch1.envelope.dead = audio->ch1.envelope.stepTime ? 0 : 2;
|
||||
}
|
||||
if (audio->nextEvent == INT_MAX) {
|
||||
audio->eventDiff = 0;
|
||||
}
|
||||
if (audio->playingCh1) {
|
||||
audio->ch1.control.hi = !audio->ch1.control.hi;
|
||||
}
|
||||
audio->nextCh1 = audio->eventDiff;
|
||||
|
||||
audio->ch1.realFrequency = audio->ch1.control.frequency;
|
||||
audio->ch1.sweepStep = audio->ch1.time;
|
||||
audio->ch1.sweepEnable = (audio->ch1.sweepStep != 8) || audio->ch1.shift;
|
||||
audio->ch1.sweepOccurred = false;
|
||||
if (audio->playingCh1 && audio->ch1.shift) {
|
||||
audio->playingCh1 = _updateSweep(&audio->ch1, true);
|
||||
}
|
||||
if (!audio->ch1.control.length) {
|
||||
audio->ch1.control.length = 64;
|
||||
if (audio->ch1.control.stop && !(audio->frame & 1)) {
|
||||
--audio->ch1.control.length;
|
||||
}
|
||||
}
|
||||
_scheduleEvent(audio);
|
||||
}
|
||||
*audio->nr52 &= ~0x0001;
|
||||
*audio->nr52 |= audio->playingCh1;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR21(struct GBAudio* audio, uint8_t value) {
|
||||
_writeDuty(&audio->ch2.envelope, value);
|
||||
audio->ch2.control.length = 64 - audio->ch2.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) {
|
||||
if (!_writeSweep(&audio->ch2.envelope, value)) {
|
||||
audio->playingCh2 = false;
|
||||
*audio->nr52 &= ~0x0002;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR23(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch2.control.frequency &= 0x700;
|
||||
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch2.control.frequency &= 0xFF;
|
||||
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
|
||||
bool wasStop = audio->ch2.control.stop;
|
||||
audio->ch2.control.stop = GBAudioRegisterControlGetStop(value << 8);
|
||||
if (!wasStop && audio->ch2.control.stop && audio->ch2.control.length && !(audio->frame & 1)) {
|
||||
--audio->ch2.control.length;
|
||||
if (audio->ch2.control.length == 0) {
|
||||
audio->playingCh2 = false;
|
||||
}
|
||||
}
|
||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||
audio->playingCh2 = audio->ch2.envelope.initialVolume || audio->ch2.envelope.direction;
|
||||
audio->ch2.envelope.currentVolume = audio->ch2.envelope.initialVolume;
|
||||
if (audio->ch2.envelope.currentVolume > 0) {
|
||||
audio->ch2.envelope.dead = audio->ch2.envelope.stepTime ? 0 : 1;
|
||||
} else {
|
||||
audio->ch2.envelope.dead = audio->ch2.envelope.stepTime ? 0 : 2;
|
||||
}
|
||||
if (audio->nextEvent == INT_MAX) {
|
||||
audio->eventDiff = 0;
|
||||
}
|
||||
if (audio->playingCh2) {
|
||||
audio->ch2.control.hi = !audio->ch2.control.hi;
|
||||
}
|
||||
audio->nextCh2 = audio->eventDiff;
|
||||
if (!audio->ch2.control.length) {
|
||||
audio->ch2.control.length = 64;
|
||||
if (audio->ch2.control.stop && !(audio->frame & 1)) {
|
||||
--audio->ch2.control.length;
|
||||
}
|
||||
}
|
||||
_scheduleEvent(audio);
|
||||
}
|
||||
*audio->nr52 &= ~0x0002;
|
||||
*audio->nr52 |= audio->playingCh2 << 1;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch3.enable = GBAudioRegisterBankGetEnable(value);
|
||||
if (!audio->ch3.enable) {
|
||||
audio->playingCh3 = false;
|
||||
*audio->nr52 &= ~0x0004;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch3.length = 256 - value;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch3.rate &= 0x700;
|
||||
audio->ch3.rate |= GBAudioRegisterControlGetRate(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch3.rate &= 0xFF;
|
||||
audio->ch3.rate |= GBAudioRegisterControlGetRate(value << 8);
|
||||
bool wasStop = audio->ch3.stop;
|
||||
audio->ch3.stop = GBAudioRegisterControlGetStop(value << 8);
|
||||
if (!wasStop && audio->ch3.stop && audio->ch3.length && !(audio->frame & 1)) {
|
||||
--audio->ch3.length;
|
||||
if (audio->ch3.length == 0) {
|
||||
audio->playingCh3 = false;
|
||||
}
|
||||
}
|
||||
bool wasEnable = audio->playingCh3;
|
||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||
audio->playingCh3 = audio->ch3.enable;
|
||||
if (!audio->ch3.length) {
|
||||
audio->ch3.length = 256;
|
||||
if (audio->ch3.stop && !(audio->frame & 1)) {
|
||||
--audio->ch3.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->style == GB_AUDIO_DMG && wasEnable && audio->playingCh3 && audio->ch3.readable) {
|
||||
if (audio->ch3.window < 8) {
|
||||
audio->ch3.wavedata8[0] = audio->ch3.wavedata8[audio->ch3.window >> 1];
|
||||
} else {
|
||||
audio->ch3.wavedata8[0] = audio->ch3.wavedata8[((audio->ch3.window >> 1) & ~3)];
|
||||
audio->ch3.wavedata8[1] = audio->ch3.wavedata8[((audio->ch3.window >> 1) & ~3) + 1];
|
||||
audio->ch3.wavedata8[2] = audio->ch3.wavedata8[((audio->ch3.window >> 1) & ~3) + 2];
|
||||
audio->ch3.wavedata8[3] = audio->ch3.wavedata8[((audio->ch3.window >> 1) & ~3) + 3];
|
||||
}
|
||||
}
|
||||
audio->ch3.window = 0;
|
||||
}
|
||||
if (audio->playingCh3) {
|
||||
if (audio->nextEvent == INT_MAX) {
|
||||
audio->eventDiff = 0;
|
||||
}
|
||||
audio->ch3.readable = audio->style != GB_AUDIO_DMG;
|
||||
_scheduleEvent(audio);
|
||||
// TODO: Where does this cycle delay come from?
|
||||
audio->nextCh3 = audio->eventDiff + audio->nextEvent + 4 + 2 * (2048 - audio->ch3.rate);
|
||||
}
|
||||
*audio->nr52 &= ~0x0004;
|
||||
*audio->nr52 |= audio->playingCh3 << 2;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) {
|
||||
_writeDuty(&audio->ch4.envelope, value);
|
||||
audio->ch4.length = 64 - audio->ch4.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
|
||||
if (!_writeSweep(&audio->ch4.envelope, value)) {
|
||||
audio->playingCh4 = false;
|
||||
*audio->nr52 &= ~0x0008;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value);
|
||||
audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value);
|
||||
audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||
bool wasStop = audio->ch4.stop;
|
||||
audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value);
|
||||
if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) {
|
||||
--audio->ch4.length;
|
||||
if (audio->ch4.length == 0) {
|
||||
audio->playingCh4 = false;
|
||||
}
|
||||
}
|
||||
if (GBAudioRegisterNoiseControlIsRestart(value)) {
|
||||
audio->playingCh4 = audio->ch4.envelope.initialVolume || audio->ch4.envelope.direction;
|
||||
audio->ch4.envelope.currentVolume = audio->ch4.envelope.initialVolume;
|
||||
if (audio->ch4.envelope.currentVolume > 0) {
|
||||
audio->ch4.envelope.dead = audio->ch4.envelope.stepTime ? 0 : 1;
|
||||
} else {
|
||||
audio->ch4.envelope.dead = audio->ch4.envelope.stepTime ? 0 : 2;
|
||||
}
|
||||
if (audio->ch4.power) {
|
||||
audio->ch4.lfsr = 0x40;
|
||||
} else {
|
||||
audio->ch4.lfsr = 0x4000;
|
||||
}
|
||||
if (audio->nextEvent == INT_MAX) {
|
||||
audio->eventDiff = 0;
|
||||
}
|
||||
audio->nextCh4 = audio->eventDiff;
|
||||
if (!audio->ch4.length) {
|
||||
audio->ch4.length = 64;
|
||||
if (audio->ch4.stop && !(audio->frame & 1)) {
|
||||
--audio->ch4.length;
|
||||
}
|
||||
}
|
||||
_scheduleEvent(audio);
|
||||
}
|
||||
*audio->nr52 &= ~0x0008;
|
||||
*audio->nr52 |= audio->playingCh4 << 3;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) {
|
||||
audio->volumeRight = GBRegisterNR50GetVolumeRight(value);
|
||||
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) {
|
||||
audio->ch1Right = GBRegisterNR51GetCh1Right(value);
|
||||
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
|
||||
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
|
||||
audio->ch4Right = GBRegisterNR51GetCh4Right(value);
|
||||
audio->ch1Left = GBRegisterNR51GetCh1Left(value);
|
||||
audio->ch2Left = GBRegisterNR51GetCh2Left(value);
|
||||
audio->ch3Left = GBRegisterNR51GetCh3Left(value);
|
||||
audio->ch4Left = GBRegisterNR51GetCh4Left(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR52(struct GBAudio* audio, uint8_t value) {
|
||||
bool wasEnable = audio->enable;
|
||||
audio->enable = GBAudioEnableGetEnable(value);
|
||||
if (!audio->enable) {
|
||||
audio->playingCh1 = 0;
|
||||
audio->playingCh2 = 0;
|
||||
audio->playingCh3 = 0;
|
||||
audio->playingCh4 = 0;
|
||||
GBAudioWriteNR10(audio, 0);
|
||||
GBAudioWriteNR12(audio, 0);
|
||||
GBAudioWriteNR13(audio, 0);
|
||||
GBAudioWriteNR14(audio, 0);
|
||||
GBAudioWriteNR22(audio, 0);
|
||||
GBAudioWriteNR23(audio, 0);
|
||||
GBAudioWriteNR24(audio, 0);
|
||||
GBAudioWriteNR30(audio, 0);
|
||||
GBAudioWriteNR32(audio, 0);
|
||||
GBAudioWriteNR33(audio, 0);
|
||||
GBAudioWriteNR34(audio, 0);
|
||||
GBAudioWriteNR42(audio, 0);
|
||||
GBAudioWriteNR43(audio, 0);
|
||||
GBAudioWriteNR44(audio, 0);
|
||||
GBAudioWriteNR50(audio, 0);
|
||||
GBAudioWriteNR51(audio, 0);
|
||||
if (audio->style != GB_AUDIO_DMG) {
|
||||
GBAudioWriteNR11(audio, 0);
|
||||
GBAudioWriteNR21(audio, 0);
|
||||
GBAudioWriteNR31(audio, 0);
|
||||
GBAudioWriteNR41(audio, 0);
|
||||
}
|
||||
|
||||
if (audio->p) {
|
||||
audio->p->memory.io[REG_NR10] = 0;
|
||||
audio->p->memory.io[REG_NR11] = 0;
|
||||
audio->p->memory.io[REG_NR12] = 0;
|
||||
audio->p->memory.io[REG_NR13] = 0;
|
||||
audio->p->memory.io[REG_NR14] = 0;
|
||||
audio->p->memory.io[REG_NR21] = 0;
|
||||
audio->p->memory.io[REG_NR22] = 0;
|
||||
audio->p->memory.io[REG_NR23] = 0;
|
||||
audio->p->memory.io[REG_NR24] = 0;
|
||||
audio->p->memory.io[REG_NR30] = 0;
|
||||
audio->p->memory.io[REG_NR31] = 0;
|
||||
audio->p->memory.io[REG_NR32] = 0;
|
||||
audio->p->memory.io[REG_NR33] = 0;
|
||||
audio->p->memory.io[REG_NR34] = 0;
|
||||
audio->p->memory.io[REG_NR42] = 0;
|
||||
audio->p->memory.io[REG_NR43] = 0;
|
||||
audio->p->memory.io[REG_NR44] = 0;
|
||||
audio->p->memory.io[REG_NR50] = 0;
|
||||
audio->p->memory.io[REG_NR51] = 0;
|
||||
if (audio->style != GB_AUDIO_DMG) {
|
||||
audio->p->memory.io[REG_NR11] = 0;
|
||||
audio->p->memory.io[REG_NR21] = 0;
|
||||
audio->p->memory.io[REG_NR31] = 0;
|
||||
audio->p->memory.io[REG_NR41] = 0;
|
||||
}
|
||||
}
|
||||
*audio->nr52 &= ~0x000F;
|
||||
} else if (!wasEnable) {
|
||||
audio->frame = 7;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles) {
|
||||
if (audio->nextEvent == INT_MAX) {
|
||||
return INT_MAX;
|
||||
}
|
||||
audio->nextEvent -= cycles;
|
||||
audio->eventDiff += cycles;
|
||||
while (audio->nextEvent <= 0) {
|
||||
audio->nextEvent = INT_MAX;
|
||||
if (audio->enable) {
|
||||
audio->nextFrame -= audio->eventDiff;
|
||||
int frame = -1;
|
||||
if (audio->nextFrame <= 0) {
|
||||
frame = (audio->frame + 1) & 7;
|
||||
audio->frame = frame;
|
||||
audio->nextFrame += FRAME_CYCLES;
|
||||
if (audio->nextFrame < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextFrame;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh1) {
|
||||
audio->nextCh1 -= audio->eventDiff;
|
||||
if (!audio->ch1.envelope.dead && frame == 7) {
|
||||
--audio->ch1.envelope.nextStep;
|
||||
if (audio->ch1.envelope.nextStep == 0) {
|
||||
int8_t sample = audio->ch1.control.hi * 0x10 - 0x8;
|
||||
_updateEnvelope(&audio->ch1.envelope);
|
||||
audio->ch1.sample = sample * audio->ch1.envelope.currentVolume;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch1.sweepEnable && (frame & 3) == 2) {
|
||||
--audio->ch1.sweepStep;
|
||||
if (audio->ch1.sweepStep == 0) {
|
||||
audio->playingCh1 = _updateSweep(&audio->ch1, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch1.envelope.dead != 2) {
|
||||
if (audio->nextCh1 <= 0) {
|
||||
audio->nextCh1 += _updateChannel1(&audio->ch1);
|
||||
}
|
||||
if (audio->nextCh1 < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextCh1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch1.control.length && audio->ch1.control.stop && !(frame & 1)) {
|
||||
--audio->ch1.control.length;
|
||||
if (audio->ch1.control.length == 0) {
|
||||
audio->playingCh1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh2) {
|
||||
audio->nextCh2 -= audio->eventDiff;
|
||||
if (!audio->ch2.envelope.dead && frame == 7) {
|
||||
--audio->ch2.envelope.nextStep;
|
||||
if (audio->ch2.envelope.nextStep == 0) {
|
||||
int8_t sample = audio->ch2.control.hi * 0x10 - 0x8;
|
||||
_updateEnvelope(&audio->ch2.envelope);
|
||||
audio->ch2.sample = sample * audio->ch2.envelope.currentVolume;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch2.envelope.dead != 2) {
|
||||
if (audio->nextCh2 <= 0) {
|
||||
audio->nextCh2 += _updateChannel2(&audio->ch2);
|
||||
}
|
||||
if (audio->nextCh2 < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextCh2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch2.control.length && audio->ch2.control.stop && !(frame & 1)) {
|
||||
--audio->ch2.control.length;
|
||||
if (audio->ch2.control.length == 0) {
|
||||
audio->playingCh2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh3) {
|
||||
audio->nextCh3 -= audio->eventDiff;
|
||||
audio->fadeCh3 -= audio->eventDiff;
|
||||
if (audio->fadeCh3 <= 0) {
|
||||
audio->ch3.readable = false;
|
||||
audio->fadeCh3 = INT_MAX;
|
||||
}
|
||||
if (audio->nextCh3 <= 0) {
|
||||
if (audio->style == GB_AUDIO_DMG) {
|
||||
audio->fadeCh3 = audio->nextCh3 + 2;
|
||||
}
|
||||
audio->nextCh3 += _updateChannel3(&audio->ch3, audio->style);
|
||||
audio->ch3.readable = true;
|
||||
}
|
||||
if (audio->fadeCh3 < audio->nextEvent) {
|
||||
audio->nextEvent = audio->fadeCh3;
|
||||
}
|
||||
if (audio->nextCh3 < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextCh3;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch3.length && audio->ch3.stop && !(frame & 1)) {
|
||||
--audio->ch3.length;
|
||||
if (audio->ch3.length == 0) {
|
||||
audio->playingCh3 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh4) {
|
||||
audio->nextCh4 -= audio->eventDiff;
|
||||
if (!audio->ch4.envelope.dead && frame == 7) {
|
||||
--audio->ch4.envelope.nextStep;
|
||||
if (audio->ch4.envelope.nextStep == 0) {
|
||||
int8_t sample = (audio->ch4.sample >> 31) * 0x8;
|
||||
_updateEnvelope(&audio->ch4.envelope);
|
||||
audio->ch4.sample = sample * audio->ch4.envelope.currentVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->ch4.length && audio->ch4.stop && !(frame & 1)) {
|
||||
--audio->ch4.length;
|
||||
if (audio->ch4.length == 0) {
|
||||
audio->playingCh4 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*audio->nr52 &= ~0x000F;
|
||||
*audio->nr52 |= audio->playingCh1;
|
||||
*audio->nr52 |= audio->playingCh2 << 1;
|
||||
*audio->nr52 |= audio->playingCh3 << 2;
|
||||
*audio->nr52 |= audio->playingCh4 << 3;
|
||||
|
||||
if (audio->p) {
|
||||
audio->nextSample -= audio->eventDiff;
|
||||
if (audio->nextSample <= 0) {
|
||||
_sample(audio, audio->sampleInterval);
|
||||
audio->nextSample += audio->sampleInterval;
|
||||
}
|
||||
|
||||
if (audio->nextSample < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextSample;
|
||||
}
|
||||
}
|
||||
audio->eventDiff = 0;
|
||||
}
|
||||
return audio->nextEvent;
|
||||
}
|
||||
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
||||
int sampleLeft = 0;
|
||||
int sampleRight = 0;
|
||||
|
||||
if (audio->ch4.envelope.dead != 2) {
|
||||
while (audio->nextCh4 <= 0) {
|
||||
audio->nextCh4 += _updateChannel4(&audio->ch4);
|
||||
}
|
||||
if (audio->nextCh4 < audio->nextEvent) {
|
||||
audio->nextEvent = audio->nextCh4;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh1 && !audio->forceDisableCh[0]) {
|
||||
if (audio->ch1Left) {
|
||||
sampleLeft += 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->playingCh3 && !audio->forceDisableCh[2]) {
|
||||
if (audio->ch3Left) {
|
||||
sampleLeft += audio->ch3.sample;
|
||||
}
|
||||
|
||||
if (audio->ch3Right) {
|
||||
sampleRight += audio->ch3.sample;
|
||||
}
|
||||
}
|
||||
|
||||
if (audio->playingCh4 && !audio->forceDisableCh[3]) {
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += audio->ch4.sample;
|
||||
}
|
||||
|
||||
if (audio->ch4Right) {
|
||||
sampleRight += audio->ch4.sample;
|
||||
}
|
||||
}
|
||||
|
||||
*left = sampleLeft * (1 + audio->volumeLeft);
|
||||
*right = sampleRight * (1 + audio->volumeRight);
|
||||
}
|
||||
|
||||
void _sample(struct GBAudio* audio, int32_t cycles) {
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||
sampleLeft = (sampleLeft * audio->masterVolume) >> 6;
|
||||
sampleRight = (sampleRight * audio->masterVolume) >> 6;
|
||||
|
||||
mCoreSyncLockAudio(audio->p->sync);
|
||||
unsigned produced;
|
||||
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
|
||||
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += cycles;
|
||||
if (audio->clock >= CLOCKS_PER_BLIP_FRAME) {
|
||||
blip_end_frame(audio->left, audio->clock);
|
||||
blip_end_frame(audio->right, audio->clock);
|
||||
audio->clock -= CLOCKS_PER_BLIP_FRAME;
|
||||
}
|
||||
}
|
||||
produced = blip_samples_avail(audio->left);
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
bool wait = produced >= audio->samples;
|
||||
mCoreSyncProduceAudio(audio->p->sync, wait);
|
||||
|
||||
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
|
||||
audio->p->stream->postAudioBuffer(audio->p->stream, audio->left, audio->right);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeDuty(struct GBAudioEnvelope* envelope, uint8_t value) {
|
||||
envelope->length = GBAudioRegisterDutyGetLength(value);
|
||||
envelope->duty = GBAudioRegisterDutyGetDuty(value);
|
||||
}
|
||||
|
||||
bool _writeSweep(struct GBAudioEnvelope* envelope, uint8_t value) {
|
||||
envelope->stepTime = GBAudioRegisterSweepGetStepTime(value);
|
||||
envelope->direction = GBAudioRegisterSweepGetDirection(value);
|
||||
envelope->initialVolume = GBAudioRegisterSweepGetInitialVolume(value);
|
||||
if (envelope->stepTime == 0) {
|
||||
envelope->dead = envelope->currentVolume ? 1 : 2;
|
||||
} else if (!envelope->direction && !envelope->currentVolume) {
|
||||
envelope->dead = 2;
|
||||
} else if (envelope->direction && envelope->currentVolume == 0xF) {
|
||||
envelope->dead = 1;
|
||||
} else {
|
||||
envelope->dead = 0;
|
||||
}
|
||||
envelope->nextStep = envelope->stepTime;
|
||||
return envelope->initialVolume || envelope->direction;
|
||||
}
|
||||
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareControl* control, int duty) {
|
||||
control->hi = !control->hi;
|
||||
int period = 4 * (2048 - control->frequency);
|
||||
switch (duty) {
|
||||
case 0:
|
||||
return control->hi ? period : period * 7;
|
||||
case 1:
|
||||
return control->hi ? period * 2 : period * 6;
|
||||
case 2:
|
||||
return period * 4;
|
||||
case 3:
|
||||
return control->hi ? period * 6 : period * 2;
|
||||
default:
|
||||
// This should never be hit
|
||||
return period * 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void _updateEnvelope(struct GBAudioEnvelope* envelope) {
|
||||
if (envelope->direction) {
|
||||
++envelope->currentVolume;
|
||||
} else {
|
||||
--envelope->currentVolume;
|
||||
}
|
||||
if (envelope->currentVolume >= 15) {
|
||||
envelope->currentVolume = 15;
|
||||
envelope->dead = 1;
|
||||
} else if (envelope->currentVolume <= 0) {
|
||||
envelope->currentVolume = 0;
|
||||
envelope->dead = 2;
|
||||
} else {
|
||||
envelope->nextStep = envelope->stepTime;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _updateSweep(struct GBAudioChannel1* ch, bool initial) {
|
||||
if (initial || ch->time != 8) {
|
||||
int frequency = ch->realFrequency;
|
||||
if (ch->direction) {
|
||||
frequency -= frequency >> ch->shift;
|
||||
if (!initial && frequency >= 0) {
|
||||
ch->control.frequency = frequency;
|
||||
ch->realFrequency = frequency;
|
||||
}
|
||||
} else {
|
||||
frequency += frequency >> ch->shift;
|
||||
if (frequency < 2048) {
|
||||
if (!initial && ch->shift) {
|
||||
ch->control.frequency = frequency;
|
||||
ch->realFrequency = frequency;
|
||||
if (!_updateSweep(ch, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ch->sweepOccurred = true;
|
||||
}
|
||||
ch->sweepStep = ch->time;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t _updateChannel1(struct GBAudioChannel1* ch) {
|
||||
int timing = _updateSquareChannel(&ch->control, ch->envelope.duty);
|
||||
ch->sample = ch->control.hi * 0x10 - 0x8;
|
||||
ch->sample *= ch->envelope.currentVolume;
|
||||
return timing;
|
||||
}
|
||||
|
||||
static int32_t _updateChannel2(struct GBAudioChannel2* ch) {
|
||||
int timing = _updateSquareChannel(&ch->control, ch->envelope.duty);
|
||||
ch->sample = ch->control.hi * 0x10 - 0x8;
|
||||
ch->sample *= ch->envelope.currentVolume;
|
||||
return timing;
|
||||
}
|
||||
|
||||
static int32_t _updateChannel3(struct GBAudioChannel3* ch, enum GBAudioStyle style) {
|
||||
int i;
|
||||
int volume;
|
||||
switch (ch->volume) {
|
||||
case 0:
|
||||
volume = 0;
|
||||
break;
|
||||
case 1:
|
||||
volume = 4;
|
||||
break;
|
||||
case 2:
|
||||
volume = 2;
|
||||
break;
|
||||
case 3:
|
||||
volume = 1;
|
||||
break;
|
||||
default:
|
||||
volume = 3;
|
||||
break;
|
||||
}
|
||||
switch (style) {
|
||||
int start;
|
||||
int end;
|
||||
case GB_AUDIO_DMG:
|
||||
default:
|
||||
++ch->window;
|
||||
ch->window &= 0x1F;
|
||||
ch->sample = ch->wavedata8[ch->window >> 1];
|
||||
if (!(ch->window & 1)) {
|
||||
ch->sample >>= 4;
|
||||
}
|
||||
ch->sample &= 0xF;
|
||||
break;
|
||||
case GB_AUDIO_GBA:
|
||||
if (ch->size) {
|
||||
start = 7;
|
||||
end = 0;
|
||||
} else if (ch->bank) {
|
||||
start = 7;
|
||||
end = 4;
|
||||
} else {
|
||||
start = 3;
|
||||
end = 0;
|
||||
}
|
||||
uint32_t bitsCarry = ch->wavedata32[end] & 0x000000F0;
|
||||
uint32_t bits;
|
||||
for (i = start; i >= end; --i) {
|
||||
bits = ch->wavedata32[i] & 0x000000F0;
|
||||
ch->wavedata32[i] = ((ch->wavedata32[i] & 0x0F0F0F0F) << 4) | ((ch->wavedata32[i] & 0xF0F0F000) >> 12);
|
||||
ch->wavedata32[i] |= bitsCarry << 20;
|
||||
bitsCarry = bits;
|
||||
}
|
||||
ch->sample = bitsCarry >> 4;
|
||||
break;
|
||||
}
|
||||
ch->sample -= 8;
|
||||
ch->sample *= volume * 4;
|
||||
return 2 * (2048 - ch->rate);
|
||||
}
|
||||
|
||||
static int32_t _updateChannel4(struct GBAudioChannel4* ch) {
|
||||
int lsb = ch->lfsr & 1;
|
||||
ch->sample = lsb * 0x10 - 0x8;
|
||||
ch->sample *= ch->envelope.currentVolume;
|
||||
ch->lfsr >>= 1;
|
||||
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
|
||||
int timing = ch->ratio ? 2 * ch->ratio : 1;
|
||||
timing <<= ch->frequency;
|
||||
timing *= 8;
|
||||
return timing;
|
||||
}
|
||||
|
||||
void _scheduleEvent(struct GBAudio* audio) {
|
||||
// TODO: Don't need p
|
||||
if (audio->p) {
|
||||
audio->nextEvent = audio->p->cpu->cycles >> audio->p->doubleSpeed;
|
||||
audio->p->cpu->nextEvent = audio->p->cpu->cycles;
|
||||
} else {
|
||||
audio->nextEvent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) {
|
||||
uint32_t flags = 0;
|
||||
uint32_t ch1Flags = 0;
|
||||
uint32_t ch2Flags = 0;
|
||||
uint32_t ch4Flags = 0;
|
||||
|
||||
flags = GBSerializedAudioFlagsSetFrame(flags, audio->frame);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead);
|
||||
flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi);
|
||||
flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweepEnable);
|
||||
flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweepOccurred);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.realFrequency);
|
||||
STORE_32LE(ch1Flags, 0, &state->ch1.envelope);
|
||||
STORE_32LE(audio->nextFrame, 0, &state->ch1.nextFrame);
|
||||
STORE_32LE(audio->nextCh1, 0, &state->ch1.nextEvent);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead);
|
||||
flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi);
|
||||
ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length);
|
||||
ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep);
|
||||
STORE_32LE(ch2Flags, 0, &state->ch2.envelope);
|
||||
STORE_32LE(audio->nextCh2, 0, &state->ch2.nextEvent);
|
||||
|
||||
memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks));
|
||||
STORE_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||
STORE_32LE(audio->nextCh3, 0, &state->ch3.nextEvent);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead);
|
||||
STORE_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr);
|
||||
ch4Flags = GBSerializedAudioEnvelopeSetLength(ch4Flags, audio->ch4.length);
|
||||
ch4Flags = GBSerializedAudioEnvelopeSetNextStep(ch4Flags, audio->ch4.envelope.nextStep);
|
||||
STORE_32LE(ch4Flags, 0, &state->ch4.envelope);
|
||||
STORE_32LE(audio->nextCh4, 0, &state->ch4.nextEvent);
|
||||
|
||||
STORE_32LE(flags, 0, flagsOut);
|
||||
}
|
||||
|
||||
void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn) {
|
||||
uint32_t flags;
|
||||
uint32_t ch1Flags = 0;
|
||||
uint32_t ch2Flags = 0;
|
||||
uint32_t ch4Flags = 0;
|
||||
|
||||
LOAD_32LE(flags, 0, flagsIn);
|
||||
LOAD_32LE(ch1Flags, 0, &state->ch1.envelope);
|
||||
audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags);
|
||||
audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags);
|
||||
audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags);
|
||||
audio->ch1.sweepEnable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags);
|
||||
audio->ch1.sweepOccurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags);
|
||||
audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags);
|
||||
audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags);
|
||||
audio->ch1.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags);
|
||||
LOAD_32LE(audio->nextFrame, 0, &state->ch1.nextFrame);
|
||||
LOAD_32LE(audio->nextCh1, 0, &state->ch1.nextEvent);
|
||||
|
||||
LOAD_32LE(ch2Flags, 0, &state->ch1.envelope);
|
||||
audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags);
|
||||
audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags);
|
||||
audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags);
|
||||
audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags);
|
||||
audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags);
|
||||
LOAD_32LE(audio->nextCh2, 0, &state->ch2.nextEvent);
|
||||
|
||||
// TODO: Big endian?
|
||||
memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32));
|
||||
LOAD_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||
LOAD_32LE(audio->nextCh3, 0, &state->ch3.nextEvent);
|
||||
|
||||
LOAD_32LE(ch4Flags, 0, &state->ch1.envelope);
|
||||
audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags);
|
||||
audio->ch4.envelope.dead = GBSerializedAudioFlagsGetCh4Dead(flags);
|
||||
audio->ch4.length = GBSerializedAudioEnvelopeGetLength(ch4Flags);
|
||||
audio->ch4.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch4Flags);
|
||||
LOAD_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr);
|
||||
LOAD_32LE(audio->nextCh4, 0, &state->ch4.nextEvent);
|
||||
}
|
||||
|
||||
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {
|
||||
GBAudioPSGSerialize(audio, &state->audio.psg, &state->audio.flags);
|
||||
|
||||
STORE_32LE(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||
STORE_32LE(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||
STORE_32LE(audio->nextSample, 0, &state->audio.nextSample);
|
||||
}
|
||||
|
||||
void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state) {
|
||||
GBAudioPSGDeserialize(audio, &state->audio.psg, &state->audio.flags);
|
||||
|
||||
LOAD_32LE(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||
LOAD_32LE(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||
LOAD_32LE(audio->nextSample, 0, &state->audio.nextSample);
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_AUDIO_H
|
||||
#define GB_AUDIO_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "third-party/blip_buf/blip_buf.h"
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterDuty, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterDuty, Length, 0, 6);
|
||||
DECL_BITS(GBAudioRegisterDuty, Duty, 6, 2);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterSweep, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterSweep, StepTime, 0, 3);
|
||||
DECL_BIT(GBAudioRegisterSweep, Direction, 3);
|
||||
DECL_BITS(GBAudioRegisterSweep, InitialVolume, 4, 4);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterControl, uint16_t);
|
||||
DECL_BITS(GBAudioRegisterControl, Rate, 0, 11);
|
||||
DECL_BITS(GBAudioRegisterControl, Frequency, 0, 11);
|
||||
DECL_BIT(GBAudioRegisterControl, Stop, 14);
|
||||
DECL_BIT(GBAudioRegisterControl, Restart, 15);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterSquareSweep, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterSquareSweep, Shift, 0, 3);
|
||||
DECL_BIT(GBAudioRegisterSquareSweep, Direction, 3);
|
||||
DECL_BITS(GBAudioRegisterSquareSweep, Time, 4, 3);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterBank, uint8_t);
|
||||
DECL_BIT(GBAudioRegisterBank, Size, 5);
|
||||
DECL_BIT(GBAudioRegisterBank, Bank, 6);
|
||||
DECL_BIT(GBAudioRegisterBank, Enable, 7);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterBankVolume, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterBankVolume, VolumeGB, 5, 2);
|
||||
DECL_BITS(GBAudioRegisterBankVolume, VolumeGBA, 5, 3);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterNoiseFeedback, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterNoiseFeedback, Ratio, 0, 3);
|
||||
DECL_BIT(GBAudioRegisterNoiseFeedback, Power, 3);
|
||||
DECL_BITS(GBAudioRegisterNoiseFeedback, Frequency, 4, 4);
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterNoiseControl, uint8_t);
|
||||
DECL_BIT(GBAudioRegisterNoiseControl, Stop, 6);
|
||||
DECL_BIT(GBAudioRegisterNoiseControl, Restart, 7);
|
||||
|
||||
DECL_BITFIELD(GBRegisterNR50, uint8_t);
|
||||
DECL_BITS(GBRegisterNR50, VolumeRight, 0, 3);
|
||||
DECL_BITS(GBRegisterNR50, VolumeLeft, 4, 3);
|
||||
|
||||
DECL_BITFIELD(GBRegisterNR51, uint8_t);
|
||||
DECL_BIT(GBRegisterNR51, Ch1Right, 0);
|
||||
DECL_BIT(GBRegisterNR51, Ch2Right, 1);
|
||||
DECL_BIT(GBRegisterNR51, Ch3Right, 2);
|
||||
DECL_BIT(GBRegisterNR51, Ch4Right, 3);
|
||||
DECL_BIT(GBRegisterNR51, Ch1Left, 4);
|
||||
DECL_BIT(GBRegisterNR51, Ch2Left, 5);
|
||||
DECL_BIT(GBRegisterNR51, Ch3Left, 6);
|
||||
DECL_BIT(GBRegisterNR51, Ch4Left, 7);
|
||||
|
||||
DECL_BITFIELD(GBAudioEnable, uint8_t);
|
||||
DECL_BIT(GBAudioEnable, PlayingCh1, 0);
|
||||
DECL_BIT(GBAudioEnable, PlayingCh2, 1);
|
||||
DECL_BIT(GBAudioEnable, PlayingCh3, 2);
|
||||
DECL_BIT(GBAudioEnable, PlayingCh4, 3);
|
||||
DECL_BIT(GBAudioEnable, Enable, 7);
|
||||
|
||||
struct GB;
|
||||
struct GBAudioEnvelope {
|
||||
int length;
|
||||
int duty;
|
||||
int stepTime;
|
||||
int initialVolume;
|
||||
int currentVolume;
|
||||
bool direction;
|
||||
int dead;
|
||||
int nextStep;
|
||||
};
|
||||
|
||||
struct GBAudioSquareControl {
|
||||
int frequency;
|
||||
int length;
|
||||
bool stop;
|
||||
int hi;
|
||||
};
|
||||
|
||||
struct GBAudioChannel1 {
|
||||
int shift;
|
||||
int time;
|
||||
int sweepStep;
|
||||
bool direction;
|
||||
bool sweepEnable;
|
||||
bool sweepOccurred;
|
||||
int realFrequency;
|
||||
|
||||
struct GBAudioEnvelope envelope;
|
||||
struct GBAudioSquareControl control;
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
struct GBAudioChannel2 {
|
||||
struct GBAudioEnvelope envelope;
|
||||
struct GBAudioSquareControl control;
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
struct GBAudioChannel3 {
|
||||
bool size;
|
||||
bool bank;
|
||||
bool enable;
|
||||
|
||||
unsigned length;
|
||||
int volume;
|
||||
|
||||
int rate;
|
||||
bool stop;
|
||||
|
||||
int window;
|
||||
bool readable;
|
||||
union {
|
||||
uint32_t wavedata32[8];
|
||||
uint8_t wavedata8[16];
|
||||
};
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
struct GBAudioChannel4 {
|
||||
struct GBAudioEnvelope envelope;
|
||||
|
||||
int ratio;
|
||||
int frequency;
|
||||
bool power;
|
||||
bool stop;
|
||||
int length;
|
||||
|
||||
uint32_t lfsr;
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
enum GBAudioStyle {
|
||||
GB_AUDIO_DMG,
|
||||
GB_AUDIO_CGB,
|
||||
GB_AUDIO_AGB, // GB in GBA
|
||||
GB_AUDIO_GBA, // GBA PSG
|
||||
};
|
||||
|
||||
struct GBAudio {
|
||||
struct GB* p;
|
||||
struct GBAudioChannel1 ch1;
|
||||
struct GBAudioChannel2 ch2;
|
||||
struct GBAudioChannel3 ch3;
|
||||
struct GBAudioChannel4 ch4;
|
||||
|
||||
blip_t* left;
|
||||
blip_t* right;
|
||||
int16_t lastLeft;
|
||||
int16_t lastRight;
|
||||
int clock;
|
||||
int32_t clockRate;
|
||||
|
||||
uint8_t volumeRight;
|
||||
uint8_t volumeLeft;
|
||||
bool ch1Right;
|
||||
bool ch2Right;
|
||||
bool ch3Right;
|
||||
bool ch4Right;
|
||||
bool ch1Left;
|
||||
bool ch2Left;
|
||||
bool ch3Left;
|
||||
bool ch4Left;
|
||||
|
||||
bool playingCh1;
|
||||
bool playingCh2;
|
||||
bool playingCh3;
|
||||
bool playingCh4;
|
||||
uint8_t* nr52;
|
||||
|
||||
int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
int32_t nextFrame;
|
||||
int frame;
|
||||
int32_t nextSample;
|
||||
|
||||
int32_t sampleInterval;
|
||||
enum GBAudioStyle style;
|
||||
|
||||
int32_t nextCh1;
|
||||
int32_t nextCh2;
|
||||
int32_t nextCh3;
|
||||
int32_t fadeCh3;
|
||||
int32_t nextCh4;
|
||||
bool enable;
|
||||
|
||||
size_t samples;
|
||||
bool forceDisableCh[4];
|
||||
int masterVolume;
|
||||
};
|
||||
|
||||
void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style);
|
||||
void GBAudioDeinit(struct GBAudio* audio);
|
||||
void GBAudioReset(struct GBAudio* audio);
|
||||
|
||||
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples);
|
||||
|
||||
void GBAudioWriteNR10(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR11(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR12(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR13(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR14(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioWriteNR21(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR22(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR23(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR24(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioWriteNR30(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR31(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR34(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioWriteNR41(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR42(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR43(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR44(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
|
||||
|
||||
int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles);
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);
|
||||
|
||||
struct GBSerializedPSGState;
|
||||
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut);
|
||||
void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn);
|
||||
|
||||
struct GBSerializedState;
|
||||
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state);
|
||||
void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,214 @@
|
|||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "cheats.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gb/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
DEFINE_VECTOR(GBCheatPatchList, struct GBCheatPatch);
|
||||
|
||||
static void _patchROM(struct mCheatDevice* device, struct GBCheatSet* cheats) {
|
||||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < GBCheatPatchListSize(&cheats->romPatches); ++i) {
|
||||
struct GBCheatPatch* patch = GBCheatPatchListGetPointer(&cheats->romPatches, i);
|
||||
if (patch->applied) {
|
||||
continue;
|
||||
}
|
||||
// TODO: Byte check
|
||||
GBPatch8(device->p->cpu, patch->address, patch->newValue, &patch->oldValue);
|
||||
patch->applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void _unpatchROM(struct mCheatDevice* device, struct GBCheatSet* cheats) {
|
||||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < GBCheatPatchListSize(&cheats->romPatches); ++i) {
|
||||
struct GBCheatPatch* patch = GBCheatPatchListGetPointer(&cheats->romPatches, i);
|
||||
if (!patch->applied) {
|
||||
continue;
|
||||
}
|
||||
GBPatch8(device->p->cpu, patch->address, patch->oldValue, NULL);
|
||||
patch->applied = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void GBCheatSetDeinit(struct mCheatSet* set);
|
||||
static void GBCheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device);
|
||||
static void GBCheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device);
|
||||
static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device);
|
||||
static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet);
|
||||
static bool GBCheatAddLine(struct mCheatSet*, const char* line, int type);
|
||||
|
||||
static struct mCheatSet* GBCheatSetCreate(struct mCheatDevice* device, const char* name) {
|
||||
UNUSED(device);
|
||||
struct GBCheatSet* set = malloc(sizeof(*set));
|
||||
mCheatSetInit(&set->d, name);
|
||||
|
||||
GBCheatPatchListInit(&set->romPatches, 0);
|
||||
|
||||
set->d.deinit = GBCheatSetDeinit;
|
||||
set->d.add = GBCheatAddSet;
|
||||
set->d.remove = GBCheatRemoveSet;
|
||||
|
||||
set->d.addLine = GBCheatAddLine;
|
||||
set->d.copyProperties = GBCheatSetCopyProperties;
|
||||
|
||||
set->d.refresh = GBCheatRefresh;
|
||||
return &set->d;
|
||||
}
|
||||
|
||||
struct mCheatDevice* GBCheatDeviceCreate(void) {
|
||||
struct mCheatDevice* device = malloc(sizeof(*device));
|
||||
mCheatDeviceCreate(device);
|
||||
device->createSet = GBCheatSetCreate;
|
||||
return device;
|
||||
}
|
||||
|
||||
static void GBCheatSetDeinit(struct mCheatSet* set) {
|
||||
struct GBCheatSet* gbset = (struct GBCheatSet*) set;
|
||||
GBCheatPatchListDeinit(&gbset->romPatches);
|
||||
}
|
||||
|
||||
static void GBCheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device) {
|
||||
struct GBCheatSet* gbset = (struct GBCheatSet*) cheats;
|
||||
_patchROM(device, gbset);
|
||||
}
|
||||
|
||||
static void GBCheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device) {
|
||||
struct GBCheatSet* gbset = (struct GBCheatSet*) cheats;
|
||||
_unpatchROM(device, gbset);
|
||||
}
|
||||
|
||||
static bool GBCheatAddCodebreaker(struct GBCheatSet* cheats, uint16_t address, uint8_t data) {
|
||||
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
cheat->address = address;
|
||||
cheat->operand = data;
|
||||
cheat->repeat = 1;
|
||||
cheat->negativeRepeat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool GBCheatAddGameShark(struct GBCheatSet* cheats, uint32_t op) {
|
||||
return GBCheatAddCodebreaker(cheats, ((op & 0xFF) << 8) | ((op >> 8) & 0xFF), (op >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
static bool GBCheatAddGameSharkLine(struct GBCheatSet* cheats, const char* line) {
|
||||
uint32_t op;
|
||||
if (!hex32(line, &op)) {
|
||||
return false;
|
||||
}
|
||||
return GBCheatAddGameShark(cheats, op);
|
||||
}
|
||||
|
||||
static bool GBCheatAddGameGenieLine(struct GBCheatSet* cheats, const char* line) {
|
||||
uint16_t op1;
|
||||
uint16_t op2;
|
||||
const char* lineNext = hex12(line, &op1);
|
||||
if (!lineNext || lineNext[0] != '-') {
|
||||
return false;
|
||||
}
|
||||
++lineNext;
|
||||
lineNext = hex12(lineNext, &op2);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
uint16_t address = (op1 & 0xF) << 8;
|
||||
address |= (op2 >> 4) & 0xFF;
|
||||
address |= ((op2 & 0xF) ^ 0xF) << 12;
|
||||
struct GBCheatPatch* patch = GBCheatPatchListAppend(&cheats->romPatches);
|
||||
patch->address = address;
|
||||
patch->newValue = op1 >> 4;
|
||||
patch->applied = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool GBCheatAddVBALine(struct GBCheatSet* cheats, const char* line) {
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
const char* lineNext = hex16(line, &address);
|
||||
if (!lineNext && lineNext[0] != ':') {
|
||||
return false;
|
||||
}
|
||||
if (!hex8(line, &value)) {
|
||||
return false;
|
||||
}
|
||||
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = 1;
|
||||
cheat->address = address;
|
||||
cheat->operand = value;
|
||||
cheat->repeat = 1;
|
||||
cheat->negativeRepeat = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBCheatAddLine(struct mCheatSet* set, const char* line, int type) {
|
||||
struct GBCheatSet* cheats = (struct GBCheatSet*) set;
|
||||
switch (type) {
|
||||
case GB_CHEAT_AUTODETECT:
|
||||
break;
|
||||
case GB_CHEAT_GAME_GENIE:
|
||||
return GBCheatAddGameGenieLine(cheats, line);
|
||||
case GB_CHEAT_GAMESHARK:
|
||||
return GBCheatAddGameSharkLine(cheats, line);
|
||||
case GB_CHEAT_VBA:
|
||||
return GBCheatAddVBALine(cheats, line);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t op1;
|
||||
uint8_t op2;
|
||||
uint8_t op3;
|
||||
bool codebreaker = false;
|
||||
const char* lineNext = hex16(line, &op1);
|
||||
if (!lineNext) {
|
||||
return GBCheatAddGameGenieLine(cheats, line);
|
||||
}
|
||||
if (lineNext[0] == ':') {
|
||||
return GBCheatAddVBALine(cheats, line);
|
||||
}
|
||||
lineNext = hex8(lineNext, &op2);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
if (lineNext[0] == '-') {
|
||||
codebreaker = true;
|
||||
++lineNext;
|
||||
}
|
||||
lineNext = hex8(lineNext, &op3);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
if (codebreaker) {
|
||||
uint16_t address = (op1 << 8) | op2;
|
||||
return GBCheatAddCodebreaker(cheats, address, op3);
|
||||
} else {
|
||||
uint32_t realOp = op1 << 16;
|
||||
realOp |= op2 << 8;
|
||||
realOp |= op3;
|
||||
return GBCheatAddGameShark(cheats, realOp);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) {
|
||||
struct GBCheatSet* gbset = (struct GBCheatSet*) cheats;
|
||||
_patchROM(device, gbset);
|
||||
}
|
||||
|
||||
static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {
|
||||
UNUSED(set);
|
||||
UNUSED(oldSet);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_CHEATS_H
|
||||
#define GB_CHEATS_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/cheats.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
enum GBACheatType {
|
||||
GB_CHEAT_AUTODETECT,
|
||||
GB_CHEAT_GAMESHARK,
|
||||
GB_CHEAT_GAME_GENIE,
|
||||
GB_CHEAT_VBA
|
||||
};
|
||||
|
||||
struct GBCheatPatch {
|
||||
uint16_t address;
|
||||
int8_t newValue;
|
||||
int8_t oldValue;
|
||||
bool applied;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(GBCheatPatchList, struct GBCheatPatch);
|
||||
|
||||
struct GBCheatSet {
|
||||
struct mCheatSet d;
|
||||
struct GBCheatPatchList romPatches;
|
||||
};
|
||||
|
||||
struct mCheatDevice* GBCheatDeviceCreate(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,123 @@
|
|||
/* Copyright (c) 2013-2016 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 "cli.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/serialize.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/io.h"
|
||||
#include "gb/video.h"
|
||||
#include "lr35902/debugger/cli-debugger.h"
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
|
||||
static void _GBCLIDebuggerInit(struct CLIDebuggerSystem*);
|
||||
static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*);
|
||||
static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||
|
||||
static void _frame(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _load(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
static void _save(struct CLIDebugger*, struct CLIDebugVector*);
|
||||
|
||||
struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = {
|
||||
{ "frame", _frame, 0, "Frame advance" },
|
||||
{ "load", _load, CLIDVParse, "Load a savestate" },
|
||||
{ "save", _save, CLIDVParse, "Save a savestate" },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
struct GBCLIDebugger* debugger = malloc(sizeof(struct GBCLIDebugger));
|
||||
LR35902CLIDebuggerCreate(&debugger->d);
|
||||
debugger->d.init = _GBCLIDebuggerInit;
|
||||
debugger->d.deinit = NULL;
|
||||
debugger->d.custom = _GBCLIDebuggerCustom;
|
||||
debugger->d.lookupIdentifier = _GBCLIDebuggerLookupIdentifier;
|
||||
|
||||
debugger->d.name = "Game Boy";
|
||||
debugger->d.commands = _GBCLIDebuggerCommands;
|
||||
|
||||
debugger->core = core;
|
||||
|
||||
return &debugger->d;
|
||||
}
|
||||
|
||||
static void _GBCLIDebuggerInit(struct CLIDebuggerSystem* debugger) {
|
||||
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger;
|
||||
|
||||
gbDebugger->frameAdvance = false;
|
||||
}
|
||||
|
||||
static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) {
|
||||
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger;
|
||||
|
||||
if (gbDebugger->frameAdvance) {
|
||||
if (!gbDebugger->inVblank && GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1) {
|
||||
mDebuggerEnter(&gbDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0);
|
||||
gbDebugger->frameAdvance = false;
|
||||
return false;
|
||||
}
|
||||
gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) {
|
||||
UNUSED(debugger);
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; ++i) {
|
||||
const char* reg = GBIORegisterNames[i];
|
||||
if (reg && strcasecmp(reg, name) == 0) {
|
||||
return GB_BASE_IO | i;
|
||||
}
|
||||
}
|
||||
dv->type = CLIDV_ERROR_TYPE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
UNUSED(dv);
|
||||
debugger->d.state = DEBUGGER_CUSTOM;
|
||||
|
||||
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system;
|
||||
gbDebugger->frameAdvance = true;
|
||||
gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1;
|
||||
}
|
||||
|
||||
static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
|
||||
int state = dv->intValue;
|
||||
if (state < 1 || state > 9) {
|
||||
printf("State %u out of range", state);
|
||||
}
|
||||
|
||||
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system;
|
||||
|
||||
mCoreLoadState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||
}
|
||||
|
||||
static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||
printf("%s\n", ERROR_MISSING_ARGS);
|
||||
return;
|
||||
}
|
||||
|
||||
int state = dv->intValue;
|
||||
if (state < 1 || state > 9) {
|
||||
printf("State %u out of range", state);
|
||||
}
|
||||
|
||||
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system;
|
||||
|
||||
mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_CLI_H
|
||||
#define GB_CLI_H
|
||||
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
#include "debugger/cli-debugger.h"
|
||||
|
||||
struct GBCLIDebugger {
|
||||
struct CLIDebuggerSystem d;
|
||||
|
||||
struct mCore* core;
|
||||
|
||||
bool frameAdvance;
|
||||
bool inVblank;
|
||||
};
|
||||
|
||||
struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore*);
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,503 @@
|
|||
/* Copyright (c) 2013-2016 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 "core.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gb/cheats.h"
|
||||
#include "gb/cli.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/renderers/software.h"
|
||||
#include "gb/serialize.h"
|
||||
#include "lr35902/debugger/debugger.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/patch.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
struct GBCore {
|
||||
struct mCore d;
|
||||
struct GBVideoSoftwareRenderer renderer;
|
||||
uint8_t keys;
|
||||
struct mCPUComponent* components[CPU_COMPONENT_MAX];
|
||||
struct mDebuggerPlatform* debuggerPlatform;
|
||||
struct mCheatDevice* cheatDevice;
|
||||
};
|
||||
|
||||
static bool _GBCoreInit(struct mCore* core) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
|
||||
struct LR35902Core* cpu = anonymousMemoryMap(sizeof(struct LR35902Core));
|
||||
struct GB* gb = anonymousMemoryMap(sizeof(struct GB));
|
||||
if (!cpu || !gb) {
|
||||
free(cpu);
|
||||
free(gb);
|
||||
return false;
|
||||
}
|
||||
core->cpu = cpu;
|
||||
core->board = gb;
|
||||
gbcore->debuggerPlatform = NULL;
|
||||
gbcore->cheatDevice = NULL;
|
||||
|
||||
GBCreate(gb);
|
||||
memset(gbcore->components, 0, sizeof(gbcore->components));
|
||||
LR35902SetComponents(cpu, &gb->d, CPU_COMPONENT_MAX, gbcore->components);
|
||||
LR35902Init(cpu);
|
||||
|
||||
GBVideoSoftwareRendererCreate(&gbcore->renderer);
|
||||
gbcore->renderer.outputBuffer = NULL;
|
||||
|
||||
gbcore->keys = 0;
|
||||
gb->keySource = &gbcore->keys;
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
mDirectorySetInit(&core->dirs);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _GBCoreDeinit(struct mCore* core) {
|
||||
LR35902Deinit(core->cpu);
|
||||
GBDestroy(core->board);
|
||||
mappedMemoryFree(core->cpu, sizeof(struct LR35902Core));
|
||||
mappedMemoryFree(core->board, sizeof(struct GB));
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
mDirectorySetDeinit(&core->dirs);
|
||||
#endif
|
||||
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
free(gbcore->debuggerPlatform);
|
||||
if (gbcore->cheatDevice) {
|
||||
mCheatDeviceDestroy(gbcore->cheatDevice);
|
||||
}
|
||||
free(gbcore->cheatDevice);
|
||||
free(core);
|
||||
}
|
||||
|
||||
static enum mPlatform _GBCorePlatform(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
return PLATFORM_GB;
|
||||
}
|
||||
|
||||
static void _GBCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
|
||||
struct GB* gb = core->board;
|
||||
gb->sync = sync;
|
||||
}
|
||||
|
||||
static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* config) {
|
||||
UNUSED(config);
|
||||
|
||||
struct GB* gb = core->board;
|
||||
if (core->opts.mute) {
|
||||
gb->audio.masterVolume = 0;
|
||||
} else {
|
||||
gb->audio.masterVolume = core->opts.volume;
|
||||
}
|
||||
gb->video.frameskip = core->opts.frameskip;
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
struct VFile* bios = 0;
|
||||
if (core->opts.useBios && core->opts.bios) {
|
||||
bios = VFileOpen(core->opts.bios, O_RDONLY);
|
||||
}
|
||||
if (bios) {
|
||||
GBLoadBIOS(gb, bios);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
|
||||
UNUSED(core);
|
||||
*width = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = GB_VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
|
||||
static void _GBCoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
gbcore->renderer.outputBuffer = buffer;
|
||||
gbcore->renderer.outputBufferStride = stride;
|
||||
}
|
||||
|
||||
static void _GBCoreGetVideoBuffer(struct mCore* core, color_t** buffer, size_t* stride) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
*buffer = gbcore->renderer.outputBuffer;
|
||||
*stride = gbcore->renderer.outputBufferStride;
|
||||
}
|
||||
|
||||
static struct blip_t* _GBCoreGetAudioChannel(struct mCore* core, int ch) {
|
||||
struct GB* gb = core->board;
|
||||
switch (ch) {
|
||||
case 0:
|
||||
return gb->audio.left;
|
||||
case 1:
|
||||
return gb->audio.right;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void _GBCoreSetAudioBufferSize(struct mCore* core, size_t samples) {
|
||||
struct GB* gb = core->board;
|
||||
GBAudioResizeBuffer(&gb->audio, samples);
|
||||
}
|
||||
|
||||
static size_t _GBCoreGetAudioBufferSize(struct mCore* core) {
|
||||
struct GB* gb = core->board;
|
||||
return gb->audio.samples;
|
||||
}
|
||||
|
||||
static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
|
||||
struct GB* gb = core->board;
|
||||
gb->stream = stream;
|
||||
if (stream && stream->videoDimensionsChanged) {
|
||||
stream->videoDimensionsChanged(stream, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
|
||||
}
|
||||
}
|
||||
|
||||
static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {
|
||||
return GBLoadROM(core->board, vf);
|
||||
}
|
||||
|
||||
static bool _GBCoreLoadBIOS(struct mCore* core, struct VFile* vf, int type) {
|
||||
UNUSED(type);
|
||||
GBLoadBIOS(core->board, vf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _GBCoreLoadSave(struct mCore* core, struct VFile* vf) {
|
||||
return GBLoadSave(core->board, vf);
|
||||
}
|
||||
|
||||
static bool _GBCoreLoadPatch(struct mCore* core, struct VFile* vf) {
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
struct Patch patch;
|
||||
if (!loadPatch(vf, &patch)) {
|
||||
return false;
|
||||
}
|
||||
GBApplyPatch(core->board, &patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _GBCoreUnloadROM(struct mCore* core) {
|
||||
return GBUnloadROM(core->board);
|
||||
}
|
||||
|
||||
static void _GBCoreReset(struct mCore* core) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
struct GB* gb = (struct GB*) core->board;
|
||||
if (gbcore->renderer.outputBuffer) {
|
||||
GBVideoAssociateRenderer(&gb->video, &gbcore->renderer.d);
|
||||
}
|
||||
LR35902Reset(core->cpu);
|
||||
}
|
||||
|
||||
static void _GBCoreRunFrame(struct mCore* core) {
|
||||
struct GB* gb = core->board;
|
||||
int32_t frameCounter = gb->video.frameCounter;
|
||||
while (gb->video.frameCounter == frameCounter) {
|
||||
LR35902Run(core->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
static void _GBCoreRunLoop(struct mCore* core) {
|
||||
LR35902Run(core->cpu);
|
||||
}
|
||||
|
||||
static void _GBCoreStep(struct mCore* core) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
do {
|
||||
LR35902Tick(cpu);
|
||||
} while (cpu->executionState != LR35902_CORE_FETCH);
|
||||
}
|
||||
|
||||
static size_t _GBCoreStateSize(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
return sizeof(struct GBSerializedState);
|
||||
}
|
||||
|
||||
static bool _GBCoreLoadState(struct mCore* core, const void* state) {
|
||||
return GBDeserialize(core->board, state);
|
||||
}
|
||||
|
||||
static bool _GBCoreSaveState(struct mCore* core, void* state) {
|
||||
GBSerialize(core->board, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
gbcore->keys = keys;
|
||||
}
|
||||
|
||||
static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
gbcore->keys |= keys;
|
||||
}
|
||||
|
||||
static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
gbcore->keys &= ~keys;
|
||||
}
|
||||
|
||||
static int32_t _GBCoreFrameCounter(struct mCore* core) {
|
||||
struct GB* gb = core->board;
|
||||
return gb->video.frameCounter;
|
||||
}
|
||||
|
||||
static int32_t _GBCoreFrameCycles(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
return GB_VIDEO_TOTAL_LENGTH;
|
||||
}
|
||||
|
||||
static int32_t _GBCoreFrequency(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
// TODO: GB differences
|
||||
return DMG_LR35902_FREQUENCY;
|
||||
}
|
||||
|
||||
static void _GBCoreGetGameTitle(struct mCore* core, char* title) {
|
||||
GBGetGameTitle(core->board, title);
|
||||
}
|
||||
|
||||
static void _GBCoreGetGameCode(struct mCore* core, char* title) {
|
||||
GBGetGameCode(core->board, title);
|
||||
}
|
||||
|
||||
static void _GBCoreSetRTC(struct mCore* core, struct mRTCSource* rtc) {
|
||||
struct GB* gb = core->board;
|
||||
gb->memory.rtc = rtc;
|
||||
}
|
||||
|
||||
static void _GBCoreSetRotation(struct mCore* core, struct mRotationSource* rotation) {
|
||||
struct GB* gb = core->board;
|
||||
gb->memory.rotation = rotation;
|
||||
}
|
||||
|
||||
static void _GBCoreSetRumble(struct mCore* core, struct mRumble* rumble) {
|
||||
struct GB* gb = core->board;
|
||||
gb->memory.rumble = rumble;
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return cpu->memory.load8(cpu, address);
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreBusRead16(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8);
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreBusRead32(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8) |
|
||||
(cpu->memory.load8(cpu, address + 2) << 16) | (cpu->memory.load8(cpu, address + 3) << 24);
|
||||
}
|
||||
|
||||
static void _GBCoreBusWrite8(struct mCore* core, uint32_t address, uint8_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
cpu->memory.store8(cpu, address, value);
|
||||
}
|
||||
|
||||
static void _GBCoreBusWrite16(struct mCore* core, uint32_t address, uint16_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
cpu->memory.store8(cpu, address, value);
|
||||
cpu->memory.store8(cpu, address + 1, value >> 8);
|
||||
}
|
||||
|
||||
static void _GBCoreBusWrite32(struct mCore* core, uint32_t address, uint32_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
cpu->memory.store8(cpu, address, value);
|
||||
cpu->memory.store8(cpu, address + 1, value >> 8);
|
||||
cpu->memory.store8(cpu, address + 2, value >> 16);
|
||||
cpu->memory.store8(cpu, address + 3, value >> 24);
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreRawRead8(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return GBLoad8(cpu, address);
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreRawRead16(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return GBLoad8(cpu, address) | (GBLoad8(cpu, address + 1) << 8);
|
||||
}
|
||||
|
||||
static uint32_t _GBCoreRawRead32(struct mCore* core, uint32_t address) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
return GBLoad8(cpu, address) | (GBLoad8(cpu, address + 1) << 8) |
|
||||
(GBLoad8(cpu, address + 2) << 16) | (GBLoad8(cpu, address + 3) << 24);
|
||||
}
|
||||
|
||||
static void _GBCoreRawWrite8(struct mCore* core, uint32_t address, uint8_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
GBPatch8(cpu, address, value, NULL);
|
||||
}
|
||||
|
||||
static void _GBCoreRawWrite16(struct mCore* core, uint32_t address, uint16_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
GBPatch8(cpu, address, value, NULL);
|
||||
GBPatch8(cpu, address + 1, value >> 8, NULL);
|
||||
}
|
||||
|
||||
static void _GBCoreRawWrite32(struct mCore* core, uint32_t address, uint32_t value) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
GBPatch8(cpu, address, value, NULL);
|
||||
GBPatch8(cpu, address + 1, value >> 8, NULL);
|
||||
GBPatch8(cpu, address + 2, value >> 16, NULL);
|
||||
GBPatch8(cpu, address + 3, value >> 24, NULL);
|
||||
}
|
||||
|
||||
static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
|
||||
UNUSED(core);
|
||||
switch (type) {
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
case DEBUGGER_CLI:
|
||||
return true;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct mDebuggerPlatform* _GBCoreDebuggerPlatform(struct mCore* core) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
if (!gbcore->debuggerPlatform) {
|
||||
gbcore->debuggerPlatform = LR35902DebuggerPlatformCreate();
|
||||
}
|
||||
return gbcore->debuggerPlatform;
|
||||
}
|
||||
|
||||
static struct CLIDebuggerSystem* _GBCoreCliDebuggerSystem(struct mCore* core) {
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
return GBCLIDebuggerCreate(core);
|
||||
#else
|
||||
UNUSED(core);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _GBCoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
if (core->debugger) {
|
||||
LR35902HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
|
||||
}
|
||||
cpu->components[CPU_COMPONENT_DEBUGGER] = &debugger->d;
|
||||
LR35902HotplugAttach(cpu, CPU_COMPONENT_DEBUGGER);
|
||||
core->debugger = debugger;
|
||||
}
|
||||
|
||||
static void _GBCoreDetachDebugger(struct mCore* core) {
|
||||
struct LR35902Core* cpu = core->cpu;
|
||||
LR35902HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
|
||||
cpu->components[CPU_COMPONENT_DEBUGGER] = NULL;
|
||||
core->debugger = NULL;
|
||||
}
|
||||
|
||||
static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
if (!gbcore->cheatDevice) {
|
||||
gbcore->cheatDevice = GBCheatDeviceCreate();
|
||||
((struct LR35902Core*) core->cpu)->components[CPU_COMPONENT_CHEAT_DEVICE] = &gbcore->cheatDevice->d;
|
||||
LR35902HotplugAttach(core->cpu, CPU_COMPONENT_CHEAT_DEVICE);
|
||||
gbcore->cheatDevice->p = core;
|
||||
}
|
||||
return gbcore->cheatDevice;
|
||||
}
|
||||
|
||||
static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) {
|
||||
struct GB* gb = core->board;
|
||||
struct VFile* vf = gb->sramVf;
|
||||
if (vf) {
|
||||
*sram = malloc(vf->size(vf));
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
return vf->read(vf, *sram, vf->size(vf));
|
||||
}
|
||||
*sram = malloc(0x20000);
|
||||
memcpy(*sram, gb->memory.sram, 0x20000);
|
||||
return 0x20000;
|
||||
}
|
||||
|
||||
static bool _GBCoreSavedataLoad(struct mCore* core, const void* sram, size_t size) {
|
||||
struct GB* gb = core->board;
|
||||
struct VFile* vf = gb->sramVf;
|
||||
if (vf) {
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
return vf->write(vf, sram, size) > 0;
|
||||
}
|
||||
if (size > 0x20000) {
|
||||
size = 0x20000;
|
||||
}
|
||||
memcpy(gb->memory.sram, sram, 0x20000);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct mCore* GBCoreCreate(void) {
|
||||
struct GBCore* gbcore = malloc(sizeof(*gbcore));
|
||||
struct mCore* core = &gbcore->d;
|
||||
memset(&core->opts, 0, sizeof(core->opts));
|
||||
core->cpu = NULL;
|
||||
core->board = NULL;
|
||||
core->debugger = NULL;
|
||||
core->init = _GBCoreInit;
|
||||
core->deinit = _GBCoreDeinit;
|
||||
core->platform = _GBCorePlatform;
|
||||
core->setSync = _GBCoreSetSync;
|
||||
core->loadConfig = _GBCoreLoadConfig;
|
||||
core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
|
||||
core->setVideoBuffer = _GBCoreSetVideoBuffer;
|
||||
core->getVideoBuffer = _GBCoreGetVideoBuffer;
|
||||
core->getAudioChannel = _GBCoreGetAudioChannel;
|
||||
core->setAudioBufferSize = _GBCoreSetAudioBufferSize;
|
||||
core->getAudioBufferSize = _GBCoreGetAudioBufferSize;
|
||||
core->setAVStream = _GBCoreSetAVStream;
|
||||
core->isROM = GBIsROM;
|
||||
core->loadROM = _GBCoreLoadROM;
|
||||
core->loadBIOS = _GBCoreLoadBIOS;
|
||||
core->loadSave = _GBCoreLoadSave;
|
||||
core->loadPatch = _GBCoreLoadPatch;
|
||||
core->unloadROM = _GBCoreUnloadROM;
|
||||
core->reset = _GBCoreReset;
|
||||
core->runFrame = _GBCoreRunFrame;
|
||||
core->runLoop = _GBCoreRunLoop;
|
||||
core->step = _GBCoreStep;
|
||||
core->stateSize = _GBCoreStateSize;
|
||||
core->loadState = _GBCoreLoadState;
|
||||
core->saveState = _GBCoreSaveState;
|
||||
core->setKeys = _GBCoreSetKeys;
|
||||
core->addKeys = _GBCoreAddKeys;
|
||||
core->clearKeys = _GBCoreClearKeys;
|
||||
core->frameCounter = _GBCoreFrameCounter;
|
||||
core->frameCycles = _GBCoreFrameCycles;
|
||||
core->frequency = _GBCoreFrequency;
|
||||
core->getGameTitle = _GBCoreGetGameTitle;
|
||||
core->getGameCode = _GBCoreGetGameCode;
|
||||
core->setRTC = _GBCoreSetRTC;
|
||||
core->setRotation = _GBCoreSetRotation;
|
||||
core->setRumble = _GBCoreSetRumble;
|
||||
core->busRead8 = _GBCoreBusRead8;
|
||||
core->busRead16 = _GBCoreBusRead16;
|
||||
core->busRead32 = _GBCoreBusRead32;
|
||||
core->busWrite8 = _GBCoreBusWrite8;
|
||||
core->busWrite16 = _GBCoreBusWrite16;
|
||||
core->busWrite32 = _GBCoreBusWrite32;
|
||||
core->rawRead8 = _GBCoreRawRead8;
|
||||
core->rawRead16 = _GBCoreRawRead16;
|
||||
core->rawRead32 = _GBCoreRawRead32;
|
||||
core->rawWrite8 = _GBCoreRawWrite8;
|
||||
core->rawWrite16 = _GBCoreRawWrite16;
|
||||
core->rawWrite32 = _GBCoreRawWrite32;
|
||||
core->supportsDebuggerType = _GBCoreSupportsDebuggerType;
|
||||
core->debuggerPlatform = _GBCoreDebuggerPlatform;
|
||||
core->cliDebuggerSystem = _GBCoreCliDebuggerSystem;
|
||||
core->attachDebugger = _GBCoreAttachDebugger;
|
||||
core->detachDebugger = _GBCoreDetachDebugger;
|
||||
core->cheatDevice = _GBCoreCheatDevice;
|
||||
core->savedataClone = _GBCoreSavedataClone;
|
||||
core->savedataLoad = _GBCoreSavedataLoad;
|
||||
return core;
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2016 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
|
||||
#ifndef GB_CORE_H
|
||||
#define GB_CORE_H
|
||||
|
||||
#include "gba/cheats.h"
|
||||
|
||||
void GBACheatRegisterLine(struct GBACheatSet* set, const char* line);
|
||||
struct mCore;
|
||||
struct mCore* GBCoreCreate(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,446 @@
|
|||
/* Copyright (c) 2013-2016 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 "gb.h"
|
||||
|
||||
#include "gb/io.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/cheats.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "util/patch.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
const uint32_t CGB_LR35902_FREQUENCY = 0x800000;
|
||||
const uint32_t SGB_LR35902_FREQUENCY = 0x418B1E;
|
||||
|
||||
const uint32_t GB_COMPONENT_MAGIC = 0x400000;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB, "GB");
|
||||
|
||||
static void GBInit(void* cpu, struct mCPUComponent* component);
|
||||
static void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh);
|
||||
static void GBProcessEvents(struct LR35902Core* cpu);
|
||||
static void GBSetInterrupts(struct LR35902Core* cpu, bool enable);
|
||||
static void GBIllegal(struct LR35902Core* cpu);
|
||||
static void GBStop(struct LR35902Core* cpu);
|
||||
|
||||
#ifdef _3DS
|
||||
extern uint32_t* romBuffer;
|
||||
extern size_t romBufferSize;
|
||||
#endif
|
||||
|
||||
void GBCreate(struct GB* gb) {
|
||||
gb->d.id = GB_COMPONENT_MAGIC;
|
||||
gb->d.init = GBInit;
|
||||
gb->d.deinit = 0;
|
||||
}
|
||||
|
||||
static void GBInit(void* cpu, struct mCPUComponent* component) {
|
||||
struct GB* gb = (struct GB*) component;
|
||||
gb->cpu = cpu;
|
||||
gb->sync = NULL;
|
||||
|
||||
GBInterruptHandlerInit(&gb->cpu->irqh);
|
||||
GBMemoryInit(gb);
|
||||
|
||||
gb->video.p = gb;
|
||||
GBVideoInit(&gb->video);
|
||||
|
||||
gb->audio.p = gb;
|
||||
GBAudioInit(&gb->audio, 2048, &gb->memory.io[REG_NR52], GB_AUDIO_DMG); // TODO: Remove magic constant
|
||||
|
||||
gb->timer.p = gb;
|
||||
|
||||
gb->biosVf = 0;
|
||||
gb->romVf = 0;
|
||||
gb->sramVf = 0;
|
||||
|
||||
gb->pristineRom = 0;
|
||||
gb->pristineRomSize = 0;
|
||||
gb->yankedRomSize = 0;
|
||||
|
||||
gb->stream = NULL;
|
||||
|
||||
gb->eiPending = INT_MAX;
|
||||
gb->doubleSpeed = 0;
|
||||
}
|
||||
|
||||
bool GBLoadROM(struct GB* gb, struct VFile* vf) {
|
||||
GBUnloadROM(gb);
|
||||
gb->romVf = vf;
|
||||
gb->pristineRomSize = vf->size(vf);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
#ifdef _3DS
|
||||
gb->pristineRom = 0;
|
||||
if (gb->pristineRomSize <= romBufferSize) {
|
||||
gb->pristineRom = romBuffer;
|
||||
vf->read(vf, romBuffer, gb->pristineRomSize);
|
||||
}
|
||||
#else
|
||||
gb->pristineRom = vf->map(vf, gb->pristineRomSize, MAP_READ);
|
||||
#endif
|
||||
if (!gb->pristineRom) {
|
||||
return false;
|
||||
}
|
||||
gb->yankedRomSize = 0;
|
||||
gb->memory.rom = gb->pristineRom;
|
||||
gb->memory.romBase = gb->memory.rom;
|
||||
gb->memory.romSize = gb->pristineRomSize;
|
||||
gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
|
||||
|
||||
// TODO: error check
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBLoadSave(struct GB* gb, struct VFile* vf) {
|
||||
gb->sramVf = vf;
|
||||
if (vf) {
|
||||
// TODO: Do this in bank-switching code
|
||||
if (vf->size(vf) < 0x20000) {
|
||||
vf->truncate(vf, 0x20000);
|
||||
}
|
||||
gb->memory.sram = vf->map(vf, 0x20000, MAP_WRITE);
|
||||
}
|
||||
return gb->memory.sram;
|
||||
}
|
||||
|
||||
void GBUnloadROM(struct GB* gb) {
|
||||
// TODO: Share with GBAUnloadROM
|
||||
if (gb->memory.rom && gb->memory.romBase != gb->memory.rom) {
|
||||
free(gb->memory.romBase);
|
||||
}
|
||||
if (gb->memory.rom && gb->pristineRom != gb->memory.rom) {
|
||||
if (gb->yankedRomSize) {
|
||||
gb->yankedRomSize = 0;
|
||||
}
|
||||
mappedMemoryFree(gb->memory.rom, GB_SIZE_CART_MAX);
|
||||
}
|
||||
gb->memory.rom = 0;
|
||||
|
||||
if (gb->romVf) {
|
||||
#ifndef _3DS
|
||||
gb->romVf->unmap(gb->romVf, gb->pristineRom, gb->pristineRomSize);
|
||||
#endif
|
||||
gb->romVf->close(gb->romVf);
|
||||
gb->pristineRom = 0;
|
||||
gb->romVf = 0;
|
||||
}
|
||||
|
||||
if (gb->sramVf) {
|
||||
gb->sramVf->unmap(gb->sramVf, gb->memory.sram, 0x8000);
|
||||
gb->sramVf = 0;
|
||||
} else if (gb->memory.sram) {
|
||||
mappedMemoryFree(gb->memory.sram, 0x8000);
|
||||
}
|
||||
gb->memory.sram = 0;
|
||||
}
|
||||
|
||||
void GBLoadBIOS(struct GB* gb, struct VFile* vf) {
|
||||
gb->biosVf = vf;
|
||||
}
|
||||
|
||||
void GBApplyPatch(struct GB* gb, struct Patch* patch) {
|
||||
size_t patchedSize = patch->outputSize(patch, gb->memory.romSize);
|
||||
if (!patchedSize) {
|
||||
return;
|
||||
}
|
||||
if (patchedSize > GB_SIZE_CART_MAX) {
|
||||
patchedSize = GB_SIZE_CART_MAX;
|
||||
}
|
||||
gb->memory.rom = anonymousMemoryMap(GB_SIZE_CART_MAX);
|
||||
if (!patch->applyPatch(patch, gb->pristineRom, gb->pristineRomSize, gb->memory.rom, patchedSize)) {
|
||||
mappedMemoryFree(gb->memory.rom, patchedSize);
|
||||
gb->memory.rom = gb->pristineRom;
|
||||
return;
|
||||
}
|
||||
gb->memory.romSize = patchedSize;
|
||||
gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
|
||||
}
|
||||
|
||||
void GBDestroy(struct GB* gb) {
|
||||
GBUnloadROM(gb);
|
||||
|
||||
GBMemoryDeinit(gb);
|
||||
}
|
||||
|
||||
void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh) {
|
||||
irqh->reset = GBReset;
|
||||
irqh->processEvents = GBProcessEvents;
|
||||
irqh->setInterrupts = GBSetInterrupts;
|
||||
irqh->hitIllegal = GBIllegal;
|
||||
irqh->stop = GBStop;
|
||||
irqh->halt = GBHalt;
|
||||
}
|
||||
|
||||
void GBReset(struct LR35902Core* cpu) {
|
||||
struct GB* gb = (struct GB*) cpu->master;
|
||||
|
||||
if (gb->biosVf) {
|
||||
gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
|
||||
gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
|
||||
ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
|
||||
uint32_t biosCrc = doCrc32(gb->memory.romBase, size);
|
||||
switch (biosCrc) {
|
||||
case 0x59C8598E:
|
||||
gb->model = GB_MODEL_DMG;
|
||||
gb->audio.style = GB_AUDIO_DMG;
|
||||
break;
|
||||
case 0x41884E46:
|
||||
gb->model = GB_MODEL_CGB;
|
||||
gb->audio.style = GB_AUDIO_CGB;
|
||||
break;
|
||||
default:
|
||||
free(gb->memory.romBase);
|
||||
gb->memory.romBase = gb->memory.rom;
|
||||
gb->biosVf = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
|
||||
if (size > 0x100) {
|
||||
memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
|
||||
}
|
||||
|
||||
cpu->a = 0;
|
||||
cpu->f.packed = 0;
|
||||
cpu->c = 0;
|
||||
cpu->e = 0;
|
||||
cpu->h = 0;
|
||||
cpu->l = 0;
|
||||
cpu->sp = 0;
|
||||
cpu->pc = 0;
|
||||
}
|
||||
if (!gb->biosVf) {
|
||||
const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
|
||||
if (cart->cgb & 0x80) {
|
||||
gb->model = GB_MODEL_CGB;
|
||||
gb->audio.style = GB_AUDIO_CGB;
|
||||
cpu->a = 0x11;
|
||||
cpu->f.packed = 0x80;
|
||||
cpu->c = 0;
|
||||
cpu->e = 0x08;
|
||||
cpu->h = 0;
|
||||
cpu->l = 0x7C;
|
||||
} else {
|
||||
// TODO: SGB
|
||||
gb->model = GB_MODEL_DMG;
|
||||
gb->audio.style = GB_AUDIO_DMG;
|
||||
cpu->a = 1;
|
||||
cpu->f.packed = 0xB0;
|
||||
cpu->c = 0x13;
|
||||
cpu->e = 0xD8;
|
||||
cpu->h = 1;
|
||||
cpu->l = 0x4D;
|
||||
}
|
||||
|
||||
cpu->sp = 0xFFFE;
|
||||
cpu->pc = 0x100;
|
||||
}
|
||||
|
||||
cpu->b = 0;
|
||||
cpu->d = 0;
|
||||
|
||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||
|
||||
if (gb->yankedRomSize) {
|
||||
gb->memory.romSize = gb->yankedRomSize;
|
||||
gb->yankedRomSize = 0;
|
||||
}
|
||||
GBMemoryReset(gb);
|
||||
GBVideoReset(&gb->video);
|
||||
GBTimerReset(&gb->timer);
|
||||
GBIOReset(gb);
|
||||
GBAudioReset(&gb->audio);
|
||||
}
|
||||
|
||||
void GBUpdateIRQs(struct GB* gb) {
|
||||
int irqs = gb->memory.ie & gb->memory.io[REG_IF];
|
||||
if (!irqs) {
|
||||
return;
|
||||
}
|
||||
gb->cpu->halted = false;
|
||||
|
||||
if (!gb->memory.ime || gb->cpu->irqPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (irqs & (1 << GB_IRQ_VBLANK)) {
|
||||
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_VBLANK);
|
||||
gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_VBLANK);
|
||||
return;
|
||||
}
|
||||
if (irqs & (1 << GB_IRQ_LCDSTAT)) {
|
||||
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_LCDSTAT);
|
||||
gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_LCDSTAT);
|
||||
return;
|
||||
}
|
||||
if (irqs & (1 << GB_IRQ_TIMER)) {
|
||||
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_TIMER);
|
||||
gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_TIMER);
|
||||
return;
|
||||
}
|
||||
if (irqs & (1 << GB_IRQ_SIO)) {
|
||||
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_SIO);
|
||||
gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_SIO);
|
||||
return;
|
||||
}
|
||||
if (irqs & (1 << GB_IRQ_KEYPAD)) {
|
||||
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_KEYPAD);
|
||||
gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_KEYPAD);
|
||||
}
|
||||
}
|
||||
|
||||
void GBProcessEvents(struct LR35902Core* cpu) {
|
||||
struct GB* gb = (struct GB*) cpu->master;
|
||||
do {
|
||||
int32_t cycles = cpu->nextEvent;
|
||||
int32_t nextEvent = INT_MAX;
|
||||
int32_t testEvent;
|
||||
|
||||
if (gb->eiPending != INT_MAX) {
|
||||
gb->eiPending -= cycles;
|
||||
if (gb->eiPending <= 0) {
|
||||
gb->memory.ime = true;
|
||||
GBUpdateIRQs(gb);
|
||||
gb->eiPending = INT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
testEvent = GBVideoProcessEvents(&gb->video, cycles >> gb->doubleSpeed);
|
||||
if (testEvent != INT_MAX) {
|
||||
testEvent <<= gb->doubleSpeed;
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
}
|
||||
|
||||
testEvent = GBAudioProcessEvents(&gb->audio, cycles >> gb->doubleSpeed);
|
||||
if (testEvent != INT_MAX) {
|
||||
testEvent <<= gb->doubleSpeed;
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
}
|
||||
|
||||
testEvent = GBTimerProcessEvents(&gb->timer, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
testEvent = GBMemoryProcessEvents(gb, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
cpu->cycles -= cycles;
|
||||
cpu->nextEvent = nextEvent;
|
||||
|
||||
if (cpu->halted) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
}
|
||||
} while (cpu->cycles >= cpu->nextEvent);
|
||||
}
|
||||
|
||||
void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {
|
||||
struct GB* gb = (struct GB*) cpu->master;
|
||||
if (!enable) {
|
||||
gb->memory.ime = enable;
|
||||
gb->eiPending = INT_MAX;
|
||||
GBUpdateIRQs(gb);
|
||||
} else {
|
||||
if (cpu->nextEvent > cpu->cycles + 4) {
|
||||
cpu->nextEvent = cpu->cycles + 4;
|
||||
}
|
||||
gb->eiPending = cpu->cycles + 4;
|
||||
}
|
||||
}
|
||||
|
||||
void GBHalt(struct LR35902Core* cpu) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
cpu->halted = true;
|
||||
}
|
||||
|
||||
void GBStop(struct LR35902Core* cpu) {
|
||||
struct GB* gb = (struct GB*) cpu->master;
|
||||
if (gb->memory.io[REG_KEY1] & 1) {
|
||||
gb->doubleSpeed ^= 1;
|
||||
gb->memory.io[REG_KEY1] &= 1;
|
||||
gb->memory.io[REG_KEY1] |= gb->doubleSpeed << 7;
|
||||
}
|
||||
// TODO: Actually stop
|
||||
}
|
||||
|
||||
void GBIllegal(struct LR35902Core* cpu) {
|
||||
// TODO
|
||||
mLOG(GB, GAME_ERROR, "Hit illegal opcode at address %04X:%02X\n", cpu->pc, cpu->bus);
|
||||
}
|
||||
|
||||
bool GBIsROM(struct VFile* vf) {
|
||||
vf->seek(vf, 0x104, SEEK_SET);
|
||||
uint8_t header[4];
|
||||
static const uint8_t knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66};
|
||||
|
||||
if (vf->read(vf, &header, sizeof(header)) < (ssize_t) sizeof(header)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(header, knownHeader, sizeof(header))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBGetGameTitle(struct GB* gb, char* out) {
|
||||
const struct GBCartridge* cart = NULL;
|
||||
if (gb->memory.rom) {
|
||||
cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
|
||||
}
|
||||
if (gb->pristineRom) {
|
||||
cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
|
||||
}
|
||||
if (!cart) {
|
||||
return;
|
||||
}
|
||||
if (cart->oldLicensee != 0x33) {
|
||||
memcpy(out, cart->titleLong, 16);
|
||||
} else {
|
||||
memcpy(out, cart->titleShort, 11);
|
||||
}
|
||||
}
|
||||
|
||||
void GBGetGameCode(struct GB* gb, char* out) {
|
||||
memset(out, 0, 8);
|
||||
const struct GBCartridge* cart = NULL;
|
||||
if (gb->memory.rom) {
|
||||
cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
|
||||
}
|
||||
if (gb->pristineRom) {
|
||||
cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
|
||||
}
|
||||
if (!cart) {
|
||||
return;
|
||||
}
|
||||
if (cart->cgb == 0xC0) {
|
||||
memcpy(out, "CGB-????", 8);
|
||||
} else {
|
||||
memcpy(out, "DMG-????", 8);
|
||||
}
|
||||
if (cart->oldLicensee == 0x33) {
|
||||
memcpy(&out[4], cart->maker, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void GBFrameEnded(struct GB* gb) {
|
||||
if (gb->cpu->components && gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
|
||||
size_t i;
|
||||
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
||||
struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
|
||||
mCheatRefresh(device, cheats);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_H
|
||||
#define GB_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/log.h"
|
||||
|
||||
#include "lr35902/lr35902.h"
|
||||
|
||||
#include "gb/audio.h"
|
||||
#include "gb/interface.h"
|
||||
#include "gb/memory.h"
|
||||
#include "gb/timer.h"
|
||||
#include "gb/video.h"
|
||||
|
||||
extern const uint32_t DMG_LR35902_FREQUENCY;
|
||||
extern const uint32_t CGB_LR35902_FREQUENCY;
|
||||
extern const uint32_t SGB_LR35902_FREQUENCY;
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GB);
|
||||
|
||||
// TODO: Prefix GBAIRQ
|
||||
enum GBIRQ {
|
||||
GB_IRQ_VBLANK = 0x0,
|
||||
GB_IRQ_LCDSTAT = 0x1,
|
||||
GB_IRQ_TIMER = 0x2,
|
||||
GB_IRQ_SIO = 0x3,
|
||||
GB_IRQ_KEYPAD = 0x4,
|
||||
};
|
||||
|
||||
enum GBIRQVector {
|
||||
GB_VECTOR_VBLANK = 0x40,
|
||||
GB_VECTOR_LCDSTAT = 0x48,
|
||||
GB_VECTOR_TIMER = 0x50,
|
||||
GB_VECTOR_SIO = 0x58,
|
||||
GB_VECTOR_KEYPAD = 0x60,
|
||||
};
|
||||
|
||||
struct mCoreSync;
|
||||
struct mAVStream;
|
||||
struct GB {
|
||||
struct mCPUComponent d;
|
||||
|
||||
struct LR35902Core* cpu;
|
||||
struct GBMemory memory;
|
||||
struct GBVideo video;
|
||||
struct GBTimer timer;
|
||||
struct GBAudio audio;
|
||||
enum GBModel model;
|
||||
|
||||
struct mCoreSync* sync;
|
||||
|
||||
uint8_t* keySource;
|
||||
|
||||
void* pristineRom;
|
||||
size_t pristineRomSize;
|
||||
size_t yankedRomSize;
|
||||
uint32_t romCrc32;
|
||||
struct VFile* romVf;
|
||||
struct VFile* biosVf;
|
||||
struct VFile* sramVf;
|
||||
|
||||
struct mAVStream* stream;
|
||||
|
||||
int32_t eiPending;
|
||||
unsigned doubleSpeed;
|
||||
};
|
||||
|
||||
struct GBCartridge {
|
||||
uint8_t entry[4];
|
||||
uint8_t logo[48];
|
||||
union {
|
||||
char titleLong[16];
|
||||
struct {
|
||||
char titleShort[11];
|
||||
char maker[4];
|
||||
uint8_t cgb;
|
||||
};
|
||||
};
|
||||
char licensee[2];
|
||||
uint8_t sgb;
|
||||
uint8_t type;
|
||||
uint8_t romSize;
|
||||
uint8_t ramSize;
|
||||
uint8_t region;
|
||||
uint8_t oldLicensee;
|
||||
uint8_t version;
|
||||
uint8_t headerChecksum;
|
||||
uint16_t globalChecksum;
|
||||
// And ROM data...
|
||||
};
|
||||
|
||||
void GBCreate(struct GB* gb);
|
||||
void GBDestroy(struct GB* gb);
|
||||
|
||||
void GBReset(struct LR35902Core* cpu);
|
||||
|
||||
void GBUpdateIRQs(struct GB* gb);
|
||||
void GBHalt(struct LR35902Core* cpu);
|
||||
|
||||
struct VFile;
|
||||
bool GBLoadROM(struct GB* gb, struct VFile* vf);
|
||||
bool GBLoadSave(struct GB* gb, struct VFile* vf);
|
||||
void GBYankROM(struct GB* gb);
|
||||
void GBUnloadROM(struct GB* gb);
|
||||
|
||||
void GBLoadBIOS(struct GB* gb, struct VFile* vf);
|
||||
|
||||
struct Patch;
|
||||
void GBApplyPatch(struct GB* gb, struct Patch* patch);
|
||||
|
||||
bool GBIsROM(struct VFile* vf);
|
||||
void GBGetGameTitle(struct GB* gba, char* out);
|
||||
void GBGetGameCode(struct GB* gba, char* out);
|
||||
|
||||
void GBFrameStarted(struct GB* gb);
|
||||
void GBFrameEnded(struct GB* gb);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,18 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_INTERFACE_H
|
||||
#define GB_INTERFACE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
enum GBModel {
|
||||
GB_MODEL_DMG = 0x00,
|
||||
GB_MODEL_SGB = 0x40,
|
||||
GB_MODEL_CGB = 0x80,
|
||||
GB_MODEL_AGB = 0xC0
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,573 @@
|
|||
/* Copyright (c) 2013-2016 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 "io.h"
|
||||
|
||||
#include "gb/gb.h"
|
||||
#include "gb/serialize.h"
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O");
|
||||
|
||||
const char* const GBIORegisterNames[] = {
|
||||
[REG_JOYP] = "JOYP",
|
||||
[REG_SB] = "SB",
|
||||
[REG_SC] = "SC",
|
||||
[REG_DIV] = "DIV",
|
||||
[REG_TIMA] = "TIMA",
|
||||
[REG_TMA] = "TMA",
|
||||
[REG_TAC] = "TAC",
|
||||
[REG_IF] = "IF",
|
||||
[REG_NR10] = "NR10",
|
||||
[REG_NR11] = "NR11",
|
||||
[REG_NR12] = "NR12",
|
||||
[REG_NR13] = "NR13",
|
||||
[REG_NR14] = "NR14",
|
||||
[REG_NR21] = "NR21",
|
||||
[REG_NR22] = "NR22",
|
||||
[REG_NR23] = "NR23",
|
||||
[REG_NR24] = "NR24",
|
||||
[REG_NR30] = "NR30",
|
||||
[REG_NR31] = "NR31",
|
||||
[REG_NR32] = "NR32",
|
||||
[REG_NR33] = "NR33",
|
||||
[REG_NR34] = "NR34",
|
||||
[REG_NR41] = "NR41",
|
||||
[REG_NR42] = "NR42",
|
||||
[REG_NR43] = "NR43",
|
||||
[REG_NR44] = "NR44",
|
||||
[REG_NR50] = "NR50",
|
||||
[REG_NR51] = "NR51",
|
||||
[REG_NR52] = "NR52",
|
||||
[REG_LCDC] = "LCDC",
|
||||
[REG_STAT] = "STAT",
|
||||
[REG_SCY] = "SCY",
|
||||
[REG_SCX] = "SCX",
|
||||
[REG_LY] = "LY",
|
||||
[REG_LYC] = "LYC",
|
||||
[REG_DMA] = "DMA",
|
||||
[REG_BGP] = "BGP",
|
||||
[REG_OBP0] = "OBP0",
|
||||
[REG_OBP1] = "OBP1",
|
||||
[REG_WY] = "WY",
|
||||
[REG_WX] = "WX",
|
||||
[REG_KEY1] = "KEY1",
|
||||
[REG_VBK] = "VBK",
|
||||
[REG_HDMA1] = "HDMA1",
|
||||
[REG_HDMA2] = "HDMA2",
|
||||
[REG_HDMA3] = "HDMA3",
|
||||
[REG_HDMA4] = "HDMA4",
|
||||
[REG_HDMA5] = "HDMA5",
|
||||
[REG_RP] = "RP",
|
||||
[REG_BCPS] = "BCPS",
|
||||
[REG_BCPD] = "BCPD",
|
||||
[REG_OCPS] = "OCPS",
|
||||
[REG_OCPD] = "OCPD",
|
||||
[REG_SVBK] = "SVBK",
|
||||
[REG_IE] = "IE",
|
||||
};
|
||||
|
||||
static const uint8_t _registerMask[] = {
|
||||
[REG_SC] = 0x7E, // TODO: GBC differences
|
||||
[REG_IF] = 0xE0,
|
||||
[REG_TAC] = 0xF8,
|
||||
[REG_NR10] = 0x80,
|
||||
[REG_NR11] = 0x3F,
|
||||
[REG_NR12] = 0x00,
|
||||
[REG_NR13] = 0xFF,
|
||||
[REG_NR14] = 0xBF,
|
||||
[REG_NR21] = 0x3F,
|
||||
[REG_NR22] = 0x00,
|
||||
[REG_NR23] = 0xFF,
|
||||
[REG_NR24] = 0xBF,
|
||||
[REG_NR30] = 0x7F,
|
||||
[REG_NR31] = 0xFF,
|
||||
[REG_NR32] = 0x9F,
|
||||
[REG_NR33] = 0xFF,
|
||||
[REG_NR34] = 0xBF,
|
||||
[REG_NR41] = 0xFF,
|
||||
[REG_NR42] = 0x00,
|
||||
[REG_NR43] = 0x00,
|
||||
[REG_NR44] = 0xBF,
|
||||
[REG_NR50] = 0x00,
|
||||
[REG_NR51] = 0x00,
|
||||
[REG_NR52] = 0x70,
|
||||
[REG_STAT] = 0x80,
|
||||
[REG_KEY1] = 0x7E,
|
||||
[REG_VBK] = 0xFE,
|
||||
[REG_OCPS] = 0x40,
|
||||
[REG_BCPS] = 0x40,
|
||||
[REG_UNK6C] = 0xFE,
|
||||
[REG_SVBK] = 0xF8,
|
||||
[REG_UNK75] = 0x8F,
|
||||
[REG_IE] = 0xE0,
|
||||
};
|
||||
|
||||
void GBIOInit(struct GB* gb) {
|
||||
memset(gb->memory.io, 0, sizeof(gb->memory.io));
|
||||
}
|
||||
|
||||
void GBIOReset(struct GB* gb) {
|
||||
memset(gb->memory.io, 0, sizeof(gb->memory.io));
|
||||
|
||||
GBIOWrite(gb, REG_TIMA, 0);
|
||||
GBIOWrite(gb, REG_TMA, 0);
|
||||
GBIOWrite(gb, REG_TAC, 0);
|
||||
GBIOWrite(gb, REG_IF, 1);
|
||||
GBIOWrite(gb, REG_NR52, 0xF1);
|
||||
GBIOWrite(gb, REG_NR10, 0x80);
|
||||
GBIOWrite(gb, REG_NR11, 0xBF);
|
||||
GBIOWrite(gb, REG_NR12, 0xF3);
|
||||
GBIOWrite(gb, REG_NR13, 0xF3);
|
||||
GBIOWrite(gb, REG_NR14, 0xBF);
|
||||
GBIOWrite(gb, REG_NR21, 0x3F);
|
||||
GBIOWrite(gb, REG_NR22, 0x00);
|
||||
GBIOWrite(gb, REG_NR24, 0xBF);
|
||||
GBIOWrite(gb, REG_NR30, 0x7F);
|
||||
GBIOWrite(gb, REG_NR31, 0xFF);
|
||||
GBIOWrite(gb, REG_NR32, 0x9F);
|
||||
GBIOWrite(gb, REG_NR34, 0xBF);
|
||||
GBIOWrite(gb, REG_NR41, 0xFF);
|
||||
GBIOWrite(gb, REG_NR42, 0x00);
|
||||
GBIOWrite(gb, REG_NR43, 0x00);
|
||||
GBIOWrite(gb, REG_NR44, 0xBF);
|
||||
GBIOWrite(gb, REG_NR50, 0x77);
|
||||
GBIOWrite(gb, REG_NR51, 0xF3);
|
||||
GBIOWrite(gb, REG_LCDC, 0x91);
|
||||
GBIOWrite(gb, REG_SCY, 0x00);
|
||||
GBIOWrite(gb, REG_SCX, 0x00);
|
||||
GBIOWrite(gb, REG_LYC, 0x00);
|
||||
GBIOWrite(gb, REG_BGP, 0xFC);
|
||||
GBIOWrite(gb, REG_OBP0, 0xFF);
|
||||
GBIOWrite(gb, REG_OBP1, 0xFF);
|
||||
GBIOWrite(gb, REG_WY, 0x00);
|
||||
GBIOWrite(gb, REG_WX, 0x00);
|
||||
GBIOWrite(gb, REG_VBK, 0);
|
||||
GBIOWrite(gb, REG_BCPS, 0);
|
||||
GBIOWrite(gb, REG_OCPS, 0);
|
||||
GBIOWrite(gb, REG_SVBK, 1);
|
||||
GBIOWrite(gb, REG_HDMA1, 0xFF);
|
||||
GBIOWrite(gb, REG_HDMA2, 0xFF);
|
||||
GBIOWrite(gb, REG_HDMA3, 0xFF);
|
||||
GBIOWrite(gb, REG_HDMA4, 0xFF);
|
||||
gb->memory.io[REG_HDMA5] = 0xFF;
|
||||
GBIOWrite(gb, REG_IE, 0x00);
|
||||
}
|
||||
|
||||
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
||||
switch (address) {
|
||||
case REG_DIV:
|
||||
GBTimerDivReset(&gb->timer);
|
||||
return;
|
||||
case REG_NR10:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR10(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR11:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR11(&gb->audio, value);
|
||||
} else {
|
||||
if (gb->audio.style == GB_AUDIO_DMG) {
|
||||
GBAudioWriteNR11(&gb->audio, value & _registerMask[REG_NR11]);
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR12:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR12(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR13:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR13(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR14:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR14(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR21:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR21(&gb->audio, value);
|
||||
} else {
|
||||
if (gb->audio.style == GB_AUDIO_DMG) {
|
||||
GBAudioWriteNR21(&gb->audio, value & _registerMask[REG_NR21]);
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR22:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR22(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR23:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR23(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR24:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR24(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR30:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR30(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR31:
|
||||
if (gb->audio.enable || gb->audio.style == GB_AUDIO_DMG) {
|
||||
GBAudioWriteNR31(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR32:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR32(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR33:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR33(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR34:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR34(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR41:
|
||||
if (gb->audio.enable || gb->audio.style == GB_AUDIO_DMG) {
|
||||
GBAudioWriteNR41(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR42:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR42(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR43:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR43(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR44:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR44(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR50:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR50(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR51:
|
||||
if (gb->audio.enable) {
|
||||
GBAudioWriteNR51(&gb->audio, value);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_NR52:
|
||||
GBAudioWriteNR52(&gb->audio, value);
|
||||
value &= 0x80;
|
||||
value |= gb->memory.io[REG_NR52] & 0x0F;
|
||||
break;
|
||||
case REG_WAVE_0:
|
||||
case REG_WAVE_1:
|
||||
case REG_WAVE_2:
|
||||
case REG_WAVE_3:
|
||||
case REG_WAVE_4:
|
||||
case REG_WAVE_5:
|
||||
case REG_WAVE_6:
|
||||
case REG_WAVE_7:
|
||||
case REG_WAVE_8:
|
||||
case REG_WAVE_9:
|
||||
case REG_WAVE_A:
|
||||
case REG_WAVE_B:
|
||||
case REG_WAVE_C:
|
||||
case REG_WAVE_D:
|
||||
case REG_WAVE_E:
|
||||
case REG_WAVE_F:
|
||||
if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) {
|
||||
gb->audio.ch3.wavedata8[address - REG_WAVE_0] = value;
|
||||
} else if(gb->audio.ch3.readable) {
|
||||
gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1] = value;
|
||||
}
|
||||
break;
|
||||
case REG_JOYP:
|
||||
case REG_TIMA:
|
||||
case REG_TMA:
|
||||
case REG_LYC:
|
||||
// Handled transparently by the registers
|
||||
break;
|
||||
case REG_TAC:
|
||||
value = GBTimerUpdateTAC(&gb->timer, value);
|
||||
break;
|
||||
case REG_IF:
|
||||
gb->memory.io[REG_IF] = value | 0xE0;
|
||||
GBUpdateIRQs(gb);
|
||||
return;
|
||||
case REG_LCDC:
|
||||
// TODO: handle GBC differences
|
||||
value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value);
|
||||
GBVideoWriteLCDC(&gb->video, value);
|
||||
break;
|
||||
case REG_DMA:
|
||||
GBMemoryDMA(gb, value << 8);
|
||||
break;
|
||||
case REG_SCY:
|
||||
case REG_SCX:
|
||||
case REG_WY:
|
||||
case REG_WX:
|
||||
GBVideoProcessDots(&gb->video);
|
||||
value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value);
|
||||
break;
|
||||
case REG_BGP:
|
||||
case REG_OBP0:
|
||||
case REG_OBP1:
|
||||
GBVideoProcessDots(&gb->video);
|
||||
GBVideoWritePalette(&gb->video, address, value);
|
||||
break;
|
||||
case REG_STAT:
|
||||
GBVideoWriteSTAT(&gb->video, value);
|
||||
value = gb->video.stat;
|
||||
break;
|
||||
case 0x50:
|
||||
if (gb->memory.romBase != gb->memory.rom) {
|
||||
free(gb->memory.romBase);
|
||||
gb->memory.romBase = gb->memory.rom;
|
||||
}
|
||||
break;
|
||||
case REG_IE:
|
||||
gb->memory.ie = value;
|
||||
GBUpdateIRQs(gb);
|
||||
return;
|
||||
default:
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
switch (address) {
|
||||
case REG_KEY1:
|
||||
value &= 0x1;
|
||||
value |= gb->memory.io[address] & 0x80;
|
||||
break;
|
||||
case REG_VBK:
|
||||
GBVideoSwitchBank(&gb->video, value);
|
||||
break;
|
||||
case REG_HDMA1:
|
||||
case REG_HDMA2:
|
||||
case REG_HDMA3:
|
||||
case REG_HDMA4:
|
||||
// Handled transparently by the registers
|
||||
break;
|
||||
case REG_HDMA5:
|
||||
GBMemoryWriteHDMA5(gb, value);
|
||||
value &= 0x7F;
|
||||
break;
|
||||
case REG_BCPS:
|
||||
gb->video.bcpIndex = value & 0x3F;
|
||||
gb->video.bcpIncrement = value & 0x80;
|
||||
gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1));
|
||||
break;
|
||||
case REG_BCPD:
|
||||
GBVideoProcessDots(&gb->video);
|
||||
GBVideoWritePalette(&gb->video, address, value);
|
||||
return;
|
||||
case REG_OCPS:
|
||||
gb->video.ocpIndex = value & 0x3F;
|
||||
gb->video.ocpIncrement = value & 0x80;
|
||||
gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1));
|
||||
break;
|
||||
case REG_OCPD:
|
||||
GBVideoProcessDots(&gb->video);
|
||||
GBVideoWritePalette(&gb->video, address, value);
|
||||
return;
|
||||
case REG_SVBK:
|
||||
GBMemorySwitchWramBank(&gb->memory, value);
|
||||
value = gb->memory.wramCurrentBank;
|
||||
break;
|
||||
default:
|
||||
goto failed;
|
||||
}
|
||||
goto success;
|
||||
}
|
||||
failed:
|
||||
mLOG(GB_IO, STUB, "Writing to unknown register FF%02X:%02X", address, value);
|
||||
if (address >= GB_SIZE_IO) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
success:
|
||||
gb->memory.io[address] = value;
|
||||
}
|
||||
|
||||
static uint8_t _readKeys(struct GB* gb) {
|
||||
uint8_t keys = *gb->keySource;
|
||||
switch (gb->memory.io[REG_JOYP] & 0x30) {
|
||||
case 0x30:
|
||||
keys = 0;
|
||||
break;
|
||||
case 0x20:
|
||||
keys >>= 4;
|
||||
break;
|
||||
case 0x10:
|
||||
break;
|
||||
case 0x00:
|
||||
keys |= keys >> 4;
|
||||
break;
|
||||
}
|
||||
return (0xC0 | (gb->memory.io[REG_JOYP] | 0xF)) ^ (keys & 0xF);
|
||||
}
|
||||
|
||||
uint8_t GBIORead(struct GB* gb, unsigned address) {
|
||||
switch (address) {
|
||||
case REG_JOYP:
|
||||
return _readKeys(gb);
|
||||
case REG_SB:
|
||||
case REG_SC:
|
||||
// TODO
|
||||
break;
|
||||
case REG_IE:
|
||||
return gb->memory.ie;
|
||||
case REG_WAVE_0:
|
||||
case REG_WAVE_1:
|
||||
case REG_WAVE_2:
|
||||
case REG_WAVE_3:
|
||||
case REG_WAVE_4:
|
||||
case REG_WAVE_5:
|
||||
case REG_WAVE_6:
|
||||
case REG_WAVE_7:
|
||||
case REG_WAVE_8:
|
||||
case REG_WAVE_9:
|
||||
case REG_WAVE_A:
|
||||
case REG_WAVE_B:
|
||||
case REG_WAVE_C:
|
||||
case REG_WAVE_D:
|
||||
case REG_WAVE_E:
|
||||
case REG_WAVE_F:
|
||||
if (gb->audio.playingCh3) {
|
||||
if (gb->audio.ch3.readable || gb->audio.style != GB_AUDIO_DMG) {
|
||||
return gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1];
|
||||
} else {
|
||||
return 0xFF;
|
||||
}
|
||||
} else {
|
||||
return gb->audio.ch3.wavedata8[address - REG_WAVE_0];
|
||||
}
|
||||
break;
|
||||
case REG_IF:
|
||||
case REG_NR10:
|
||||
case REG_NR11:
|
||||
case REG_NR12:
|
||||
case REG_NR14:
|
||||
case REG_NR21:
|
||||
case REG_NR22:
|
||||
case REG_NR24:
|
||||
case REG_NR30:
|
||||
case REG_NR32:
|
||||
case REG_NR34:
|
||||
case REG_NR41:
|
||||
case REG_NR42:
|
||||
case REG_NR43:
|
||||
case REG_NR44:
|
||||
case REG_NR50:
|
||||
case REG_NR51:
|
||||
case REG_NR52:
|
||||
case REG_DIV:
|
||||
case REG_TIMA:
|
||||
case REG_TMA:
|
||||
case REG_TAC:
|
||||
case REG_STAT:
|
||||
case REG_LCDC:
|
||||
case REG_SCY:
|
||||
case REG_SCX:
|
||||
case REG_LY:
|
||||
case REG_LYC:
|
||||
case REG_BGP:
|
||||
case REG_OBP0:
|
||||
case REG_OBP1:
|
||||
case REG_WY:
|
||||
case REG_WX:
|
||||
// Handled transparently by the registers
|
||||
break;
|
||||
default:
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
switch (address) {
|
||||
case REG_KEY1:
|
||||
case REG_VBK:
|
||||
case REG_HDMA1:
|
||||
case REG_HDMA2:
|
||||
case REG_HDMA3:
|
||||
case REG_HDMA4:
|
||||
case REG_HDMA5:
|
||||
case REG_BCPS:
|
||||
case REG_BCPD:
|
||||
case REG_OCPS:
|
||||
case REG_OCPD:
|
||||
case REG_SVBK:
|
||||
// Handled transparently by the registers
|
||||
goto success;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
mLOG(GB_IO, STUB, "Reading from unknown register FF%02X", address);
|
||||
return 0xFF;
|
||||
}
|
||||
success:
|
||||
return gb->memory.io[address] | _registerMask[address];
|
||||
}
|
||||
|
||||
struct GBSerializedState;
|
||||
void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state) {
|
||||
memcpy(state->io, gb->memory.io, GB_SIZE_IO);
|
||||
state->ie = gb->memory.ie;
|
||||
}
|
||||
|
||||
void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||
memcpy(gb->memory.io, state->io, GB_SIZE_IO);
|
||||
gb->memory.ie = state->ie;
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_LCDC, state->io[REG_LCDC]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCY, state->io[REG_SCY]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WY, state->io[REG_WY]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WX, state->io[REG_WX]);
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_IO_H
|
||||
#define GB_IO_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/log.h"
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GB_IO);
|
||||
|
||||
enum GBIORegisters {
|
||||
REG_JOYP = 0x00,
|
||||
REG_SB = 0x01,
|
||||
REG_SC = 0x02,
|
||||
|
||||
// Timing
|
||||
REG_DIV = 0x04,
|
||||
REG_TIMA = 0x05,
|
||||
REG_TMA = 0x06,
|
||||
REG_TAC = 0x07,
|
||||
|
||||
// Interrupts
|
||||
REG_IF = 0x0F,
|
||||
REG_IE = 0xFF,
|
||||
|
||||
// Audio
|
||||
REG_NR10 = 0x10,
|
||||
REG_NR11 = 0x11,
|
||||
REG_NR12 = 0x12,
|
||||
REG_NR13 = 0x13,
|
||||
REG_NR14 = 0x14,
|
||||
REG_NR21 = 0x16,
|
||||
REG_NR22 = 0x17,
|
||||
REG_NR23 = 0x18,
|
||||
REG_NR24 = 0x19,
|
||||
REG_NR30 = 0x1A,
|
||||
REG_NR31 = 0x1B,
|
||||
REG_NR32 = 0x1C,
|
||||
REG_NR33 = 0x1D,
|
||||
REG_NR34 = 0x1E,
|
||||
REG_NR41 = 0x20,
|
||||
REG_NR42 = 0x21,
|
||||
REG_NR43 = 0x22,
|
||||
REG_NR44 = 0x23,
|
||||
REG_NR50 = 0x24,
|
||||
REG_NR51 = 0x25,
|
||||
REG_NR52 = 0x26,
|
||||
|
||||
REG_WAVE_0 = 0x30,
|
||||
REG_WAVE_1 = 0x31,
|
||||
REG_WAVE_2 = 0x32,
|
||||
REG_WAVE_3 = 0x33,
|
||||
REG_WAVE_4 = 0x34,
|
||||
REG_WAVE_5 = 0x35,
|
||||
REG_WAVE_6 = 0x36,
|
||||
REG_WAVE_7 = 0x37,
|
||||
REG_WAVE_8 = 0x38,
|
||||
REG_WAVE_9 = 0x39,
|
||||
REG_WAVE_A = 0x3A,
|
||||
REG_WAVE_B = 0x3B,
|
||||
REG_WAVE_C = 0x3C,
|
||||
REG_WAVE_D = 0x3D,
|
||||
REG_WAVE_E = 0x3E,
|
||||
REG_WAVE_F = 0x3F,
|
||||
|
||||
// Video
|
||||
REG_LCDC = 0x40,
|
||||
REG_STAT = 0x41,
|
||||
REG_SCY = 0x42,
|
||||
REG_SCX = 0x43,
|
||||
REG_LY = 0x44,
|
||||
REG_LYC = 0x45,
|
||||
REG_DMA = 0x46,
|
||||
REG_BGP = 0x47,
|
||||
REG_OBP0 = 0x48,
|
||||
REG_OBP1 = 0x49,
|
||||
REG_WY = 0x4A,
|
||||
REG_WX = 0x4B,
|
||||
|
||||
// CGB
|
||||
REG_KEY1 = 0x4D,
|
||||
REG_VBK = 0x4F,
|
||||
REG_HDMA1 = 0x51,
|
||||
REG_HDMA2 = 0x52,
|
||||
REG_HDMA3 = 0x53,
|
||||
REG_HDMA4 = 0x54,
|
||||
REG_HDMA5 = 0x55,
|
||||
REG_RP = 0x56,
|
||||
REG_BCPS = 0x68,
|
||||
REG_BCPD = 0x69,
|
||||
REG_OCPS = 0x6A,
|
||||
REG_OCPD = 0x6B,
|
||||
REG_UNK6C = 0x6C,
|
||||
REG_SVBK = 0x70,
|
||||
REG_UNK72 = 0x72,
|
||||
REG_UNK73 = 0x73,
|
||||
REG_UNK74 = 0x74,
|
||||
REG_UNK75 = 0x75,
|
||||
REG_UNK76 = 0x76,
|
||||
REG_UNK77 = 0x77,
|
||||
REG_MAX = 0x100
|
||||
};
|
||||
|
||||
extern const char* const GBIORegisterNames[];
|
||||
|
||||
struct GB;
|
||||
void GBIOInit(struct GB* gb);
|
||||
void GBIOReset(struct GB* gb);
|
||||
|
||||
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value);
|
||||
uint8_t GBIORead(struct GB* gb, unsigned address);
|
||||
|
||||
struct GBSerializedState;
|
||||
void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state);
|
||||
void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state);
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,181 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_MEMORY_H
|
||||
#define GB_MEMORY_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/log.h"
|
||||
|
||||
#include "lr35902/lr35902.h"
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GB_MBC);
|
||||
mLOG_DECLARE_CATEGORY(GB_MEM);
|
||||
|
||||
struct GB;
|
||||
|
||||
enum {
|
||||
GB_BASE_CART_BANK0 = 0x0000,
|
||||
GB_BASE_CART_BANK1 = 0x4000,
|
||||
GB_BASE_VRAM = 0x8000,
|
||||
GB_BASE_EXTERNAL_RAM = 0xA000,
|
||||
GB_BASE_WORKING_RAM_BANK0 = 0xC000,
|
||||
GB_BASE_WORKING_RAM_BANK1 = 0xD000,
|
||||
GB_BASE_OAM = 0xFE00,
|
||||
GB_BASE_UNUSABLE = 0xFEA0,
|
||||
GB_BASE_IO = 0xFF00,
|
||||
GB_BASE_HRAM = 0xFF80,
|
||||
GB_BASE_IE = 0xFFFF
|
||||
};
|
||||
|
||||
enum {
|
||||
GB_REGION_CART_BANK0 = 0x0,
|
||||
GB_REGION_CART_BANK1 = 0x4,
|
||||
GB_REGION_VRAM = 0x8,
|
||||
GB_REGION_EXTERNAL_RAM = 0xA,
|
||||
GB_REGION_WORKING_RAM_BANK0 = 0xC,
|
||||
GB_REGION_WORKING_RAM_BANK1 = 0xD,
|
||||
GB_REGION_WORKING_RAM_BANK1_MIRROR = 0xE,
|
||||
GB_REGION_OTHER = 0xF,
|
||||
};
|
||||
|
||||
enum {
|
||||
GB_SIZE_CART_BANK0 = 0x4000,
|
||||
GB_SIZE_CART_MAX = 0x800000,
|
||||
GB_SIZE_VRAM = 0x4000,
|
||||
GB_SIZE_VRAM_BANK0 = 0x2000,
|
||||
GB_SIZE_EXTERNAL_RAM = 0x2000,
|
||||
GB_SIZE_WORKING_RAM = 0x8000,
|
||||
GB_SIZE_WORKING_RAM_BANK0 = 0x1000,
|
||||
GB_SIZE_OAM = 0xA0,
|
||||
GB_SIZE_IO = 0x80,
|
||||
GB_SIZE_HRAM = 0x7F,
|
||||
};
|
||||
|
||||
enum GBMemoryBankControllerType {
|
||||
GB_MBC_NONE = 0,
|
||||
GB_MBC1 = 1,
|
||||
GB_MBC2 = 2,
|
||||
GB_MBC3 = 3,
|
||||
GB_MBC5 = 5,
|
||||
GB_MBC6 = 6,
|
||||
GB_MBC7 = 7,
|
||||
GB_MMM01 = 0x10,
|
||||
GB_HuC1 = 0x11,
|
||||
GB_HuC3 = 0x12,
|
||||
GB_MBC5_RUMBLE = 0x105
|
||||
};
|
||||
|
||||
struct GBMemory;
|
||||
typedef void (*GBMemoryBankController)(struct GBMemory*, uint16_t address, uint8_t value);
|
||||
|
||||
DECL_BITFIELD(GBMBC7Field, uint8_t);
|
||||
DECL_BIT(GBMBC7Field, SK, 6);
|
||||
DECL_BIT(GBMBC7Field, CS, 7);
|
||||
DECL_BIT(GBMBC7Field, IO, 1);
|
||||
|
||||
enum GBMBC7MachineState {
|
||||
GBMBC7_STATE_NULL = -1,
|
||||
GBMBC7_STATE_IDLE = 0,
|
||||
GBMBC7_STATE_READ_COMMAND = 1,
|
||||
GBMBC7_STATE_READ_ADDRESS = 2,
|
||||
GBMBC7_STATE_COMMAND_0 = 3,
|
||||
GBMBC7_STATE_COMMAND_SR_WRITE = 4,
|
||||
GBMBC7_STATE_COMMAND_SR_READ = 5,
|
||||
GBMBC7_STATE_COMMAND_SR_FILL = 6,
|
||||
GBMBC7_STATE_READ = 7,
|
||||
GBMBC7_STATE_WRITE = 8,
|
||||
};
|
||||
|
||||
struct GBMBC1State {
|
||||
int mode;
|
||||
};
|
||||
|
||||
struct GBMBC7State {
|
||||
enum GBMBC7MachineState state;
|
||||
uint32_t sr;
|
||||
uint8_t address;
|
||||
bool writable;
|
||||
int srBits;
|
||||
int command;
|
||||
GBMBC7Field field;
|
||||
};
|
||||
|
||||
union GBMBCState {
|
||||
struct GBMBC1State mbc1;
|
||||
struct GBMBC7State mbc7;
|
||||
};
|
||||
|
||||
struct mRotationSource;
|
||||
struct GBMemory {
|
||||
uint8_t* rom;
|
||||
uint8_t* romBase;
|
||||
uint8_t* romBank;
|
||||
enum GBMemoryBankControllerType mbcType;
|
||||
GBMemoryBankController mbc;
|
||||
union GBMBCState mbcState;
|
||||
int currentBank;
|
||||
|
||||
uint8_t* wram;
|
||||
uint8_t* wramBank;
|
||||
int wramCurrentBank;
|
||||
|
||||
bool sramAccess;
|
||||
uint8_t* sram;
|
||||
uint8_t* sramBank;
|
||||
int sramCurrentBank;
|
||||
|
||||
uint8_t io[GB_SIZE_IO];
|
||||
bool ime;
|
||||
uint8_t ie;
|
||||
|
||||
uint8_t hram[GB_SIZE_HRAM];
|
||||
|
||||
int32_t dmaNext;
|
||||
uint16_t dmaSource;
|
||||
uint16_t dmaDest;
|
||||
int dmaRemaining;
|
||||
|
||||
int32_t hdmaNext;
|
||||
uint16_t hdmaSource;
|
||||
uint16_t hdmaDest;
|
||||
int hdmaRemaining;
|
||||
bool isHdma;
|
||||
|
||||
size_t romSize;
|
||||
|
||||
bool rtcAccess;
|
||||
int activeRtcReg;
|
||||
bool rtcLatched;
|
||||
uint8_t rtcRegs[5];
|
||||
struct mRTCSource* rtc;
|
||||
struct mRotationSource* rotation;
|
||||
struct mRumble* rumble;
|
||||
};
|
||||
|
||||
void GBMemoryInit(struct GB* gb);
|
||||
void GBMemoryDeinit(struct GB* gb);
|
||||
|
||||
void GBMemoryReset(struct GB* gb);
|
||||
void GBMemorySwitchWramBank(struct GBMemory* memory, int bank);
|
||||
|
||||
uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address);
|
||||
void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value);
|
||||
|
||||
int32_t GBMemoryProcessEvents(struct GB* gb, int32_t cycles);
|
||||
void GBMemoryDMA(struct GB* gb, uint16_t base);
|
||||
void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value);
|
||||
|
||||
uint8_t GBDMALoad8(struct LR35902Core* cpu, uint16_t address);
|
||||
void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value);
|
||||
|
||||
void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old);
|
||||
|
||||
struct GBSerializedState;
|
||||
void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state);
|
||||
void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,440 @@
|
|||
/* Copyright (c) 2013-2016 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 "software.h"
|
||||
|
||||
#include "gb/io.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model);
|
||||
static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer);
|
||||
static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
|
||||
static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value);
|
||||
static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax);
|
||||
static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y);
|
||||
static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);
|
||||
static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
|
||||
static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, unsigned stride, void* pixels);
|
||||
|
||||
static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int y, int sx, int sy);
|
||||
static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y);
|
||||
|
||||
void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) {
|
||||
renderer->d.init = GBVideoSoftwareRendererInit;
|
||||
renderer->d.deinit = GBVideoSoftwareRendererDeinit;
|
||||
renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister;
|
||||
renderer->d.writePalette = GBVideoSoftwareRendererWritePalette,
|
||||
renderer->d.drawRange = GBVideoSoftwareRendererDrawRange;
|
||||
renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline;
|
||||
renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame;
|
||||
renderer->d.getPixels = GBVideoSoftwareRendererGetPixels;
|
||||
renderer->d.putPixels = 0;
|
||||
|
||||
renderer->temporaryBuffer = 0;
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
softwareRenderer->scy = 0;
|
||||
softwareRenderer->scx = 0;
|
||||
softwareRenderer->wy = 0;
|
||||
softwareRenderer->currentWy = 0;
|
||||
softwareRenderer->wx = 0;
|
||||
softwareRenderer->model = model;
|
||||
|
||||
int y;
|
||||
for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
|
||||
int x;
|
||||
for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
row[x] = softwareRenderer->palette[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
UNUSED(softwareRenderer);
|
||||
}
|
||||
|
||||
static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
switch (address) {
|
||||
case REG_LCDC:
|
||||
softwareRenderer->lcdc = value;
|
||||
break;
|
||||
case REG_SCY:
|
||||
softwareRenderer->scy = value;
|
||||
break;
|
||||
case REG_SCX:
|
||||
softwareRenderer->scx = value;
|
||||
break;
|
||||
case REG_WY:
|
||||
softwareRenderer->wy = value;
|
||||
break;
|
||||
case REG_WX:
|
||||
softwareRenderer->wx = value;
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
color_t color = 0;
|
||||
color |= (value & 0x001F) << 11;
|
||||
color |= (value & 0x03E0) << 1;
|
||||
color |= (value & 0x7C00) >> 10;
|
||||
#else
|
||||
color_t color = value;
|
||||
#endif
|
||||
#else
|
||||
color_t color = 0;
|
||||
color |= (value << 3) & 0xF8;
|
||||
color |= (value << 6) & 0xF800;
|
||||
color |= (value << 9) & 0xF80000;
|
||||
#endif
|
||||
softwareRenderer->palette[index] = color;
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
|
||||
if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
|
||||
maps += GB_SIZE_MAP;
|
||||
}
|
||||
if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
|
||||
if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) {
|
||||
if (softwareRenderer->wx - 7 > 0) {
|
||||
GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, y, softwareRenderer->scx, softwareRenderer->scy);
|
||||
}
|
||||
|
||||
maps = &softwareRenderer->d.vram[GB_BASE_MAP];
|
||||
if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
|
||||
maps += GB_SIZE_MAP;
|
||||
}
|
||||
GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, y, 7 - softwareRenderer->wx, (softwareRenderer->currentWy - y) - softwareRenderer->wy);
|
||||
} else {
|
||||
GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, y, softwareRenderer->scx, softwareRenderer->scy);
|
||||
}
|
||||
} else {
|
||||
memset(&softwareRenderer->row[startX], 0, endX - startX);
|
||||
}
|
||||
|
||||
if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) {
|
||||
size_t i;
|
||||
for (i = 0; i < oamMax; ++i) {
|
||||
GBVideoSoftwareRendererDrawObj(softwareRenderer, obj[i], startX, endX, y);
|
||||
}
|
||||
}
|
||||
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
|
||||
int x;
|
||||
for (x = startX; x < (endX & ~7); x += 8) {
|
||||
row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
|
||||
row[x + 1] = softwareRenderer->palette[softwareRenderer->row[x + 1] & 0x7F];
|
||||
row[x + 2] = softwareRenderer->palette[softwareRenderer->row[x + 2] & 0x7F];
|
||||
row[x + 3] = softwareRenderer->palette[softwareRenderer->row[x + 3] & 0x7F];
|
||||
row[x + 4] = softwareRenderer->palette[softwareRenderer->row[x + 4] & 0x7F];
|
||||
row[x + 5] = softwareRenderer->palette[softwareRenderer->row[x + 5] & 0x7F];
|
||||
row[x + 6] = softwareRenderer->palette[softwareRenderer->row[x + 6] & 0x7F];
|
||||
row[x + 7] = softwareRenderer->palette[softwareRenderer->row[x + 7] & 0x7F];
|
||||
}
|
||||
for (; x < endX; ++x) {
|
||||
row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
|
||||
}
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) && GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) {
|
||||
++softwareRenderer->currentWy;
|
||||
}
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
|
||||
if (softwareRenderer->temporaryBuffer) {
|
||||
mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
|
||||
softwareRenderer->temporaryBuffer = 0;
|
||||
}
|
||||
softwareRenderer->currentWy = softwareRenderer->wy;
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int y, int sx, int sy) {
|
||||
uint8_t* data = renderer->d.vram;
|
||||
uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
|
||||
if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
|
||||
data += 0x1000;
|
||||
}
|
||||
int topY = (((y + sy) >> 3) & 0x1F) * 0x20;
|
||||
int bottomY = (y + sy) & 7;
|
||||
if (startX < 0) {
|
||||
startX = 0;
|
||||
}
|
||||
int x;
|
||||
if ((startX + sx) & 7) {
|
||||
int startX2 = startX + 8 - ((startX + sx) & 7);
|
||||
for (x = startX; x < startX2; ++x) {
|
||||
uint8_t* localData = data;
|
||||
int localY = bottomY;
|
||||
int topX = ((x + sx) >> 3) & 0x1F;
|
||||
int bottomX = 7 - ((x + sx) & 7);
|
||||
int bgTile;
|
||||
if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
|
||||
bgTile = maps[topX + topY];
|
||||
} else {
|
||||
bgTile = ((int8_t*) maps)[topX + topY];
|
||||
}
|
||||
int p = 0;
|
||||
if (renderer->model >= GB_MODEL_CGB) {
|
||||
GBObjAttributes attrs = attr[topX + topY];
|
||||
p = GBObjAttributesGetCGBPalette(attrs) * 4;
|
||||
if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
|
||||
p |= 0x80;
|
||||
}
|
||||
if (GBObjAttributesIsBank(attrs)) {
|
||||
localData += GB_SIZE_VRAM_BANK0;
|
||||
}
|
||||
if (GBObjAttributesIsYFlip(attrs)) {
|
||||
localY = 7 - bottomY;
|
||||
}
|
||||
if (GBObjAttributesIsXFlip(attrs)) {
|
||||
bottomX = 7 - bottomX;
|
||||
}
|
||||
}
|
||||
uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
|
||||
uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
|
||||
tileDataUpper >>= bottomX;
|
||||
tileDataLower >>= bottomX;
|
||||
renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
}
|
||||
startX = startX2;
|
||||
}
|
||||
for (x = startX; x < endX; x += 8) {
|
||||
uint8_t* localData = data;
|
||||
int localY = bottomY;
|
||||
int topX = ((x + sx) >> 3) & 0x1F;
|
||||
int bgTile;
|
||||
if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
|
||||
bgTile = maps[topX + topY];
|
||||
} else {
|
||||
bgTile = ((int8_t*) maps)[topX + topY];
|
||||
}
|
||||
int p = 0;
|
||||
if (renderer->model >= GB_MODEL_CGB) {
|
||||
GBObjAttributes attrs = attr[topX + topY];
|
||||
p = GBObjAttributesGetCGBPalette(attrs) * 4;
|
||||
if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
|
||||
p |= 0x80;
|
||||
}
|
||||
if (GBObjAttributesIsBank(attrs)) {
|
||||
localData += GB_SIZE_VRAM_BANK0;
|
||||
}
|
||||
if (GBObjAttributesIsYFlip(attrs)) {
|
||||
localY = 7 - bottomY;
|
||||
}
|
||||
if (GBObjAttributesIsXFlip(attrs)) {
|
||||
uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
|
||||
uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
|
||||
renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
|
||||
renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
|
||||
renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
|
||||
renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
|
||||
renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
|
||||
renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
|
||||
renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
|
||||
uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
|
||||
renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
|
||||
renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
|
||||
renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
|
||||
renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
|
||||
renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
|
||||
renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
|
||||
renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
|
||||
int ix = obj->x - 8;
|
||||
if (endX < ix || startX >= ix + 8) {
|
||||
return;
|
||||
}
|
||||
if (obj->x < endX) {
|
||||
endX = obj->x;
|
||||
}
|
||||
if (obj->x - 8 > startX) {
|
||||
startX = obj->x - 8;
|
||||
}
|
||||
if (startX < 0) {
|
||||
startX = 0;
|
||||
}
|
||||
uint8_t* data = renderer->d.vram;
|
||||
int tileOffset = 0;
|
||||
int bottomY;
|
||||
if (GBObjAttributesIsYFlip(obj->attr)) {
|
||||
bottomY = 7 - ((y - obj->y - 16) & 7);
|
||||
if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
|
||||
++tileOffset;
|
||||
}
|
||||
} else {
|
||||
bottomY = (y - obj->y - 16) & 7;
|
||||
if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
|
||||
++tileOffset;
|
||||
}
|
||||
}
|
||||
if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
|
||||
--tileOffset;
|
||||
}
|
||||
uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
|
||||
uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
|
||||
int p;
|
||||
if (renderer->model >= GB_MODEL_CGB) {
|
||||
p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
|
||||
if (GBObjAttributesIsBank(obj->attr)) {
|
||||
data += GB_SIZE_VRAM_BANK0;
|
||||
}
|
||||
if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
|
||||
mask = 0x60;
|
||||
mask2 = 0x83;
|
||||
}
|
||||
} else {
|
||||
p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
|
||||
}
|
||||
int bottomX;
|
||||
int x = startX;
|
||||
if ((x - obj->x) & 7) {
|
||||
for (; x < endX; ++x) {
|
||||
if (GBObjAttributesIsXFlip(obj->attr)) {
|
||||
bottomX = (x - obj->x) & 7;
|
||||
} else {
|
||||
bottomX = 7 - ((x - obj->x) & 7);
|
||||
}
|
||||
int objTile = obj->tile + tileOffset;
|
||||
uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
|
||||
uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
|
||||
tileDataUpper >>= bottomX;
|
||||
tileDataLower >>= bottomX;
|
||||
color_t current = renderer->row[x];
|
||||
if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
}
|
||||
}
|
||||
} else if (GBObjAttributesIsXFlip(obj->attr)) {
|
||||
int objTile = obj->tile + tileOffset;
|
||||
uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
|
||||
uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
|
||||
color_t current;
|
||||
current = renderer->row[x];
|
||||
if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
}
|
||||
current = renderer->row[x + 1];
|
||||
if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
|
||||
}
|
||||
current = renderer->row[x + 2];
|
||||
if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
|
||||
}
|
||||
current = renderer->row[x + 3];
|
||||
if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
|
||||
}
|
||||
current = renderer->row[x + 4];
|
||||
if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
|
||||
}
|
||||
current = renderer->row[x + 5];
|
||||
if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
|
||||
}
|
||||
current = renderer->row[x + 6];
|
||||
if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
|
||||
}
|
||||
current = renderer->row[x + 7];
|
||||
if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
|
||||
}
|
||||
} else {
|
||||
int objTile = obj->tile + tileOffset;
|
||||
uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
|
||||
uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
|
||||
color_t current;
|
||||
current = renderer->row[x + 7];
|
||||
if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
}
|
||||
current = renderer->row[x + 6];
|
||||
if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
|
||||
}
|
||||
current = renderer->row[x + 5];
|
||||
if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
|
||||
}
|
||||
current = renderer->row[x + 4];
|
||||
if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
|
||||
}
|
||||
current = renderer->row[x + 3];
|
||||
if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
|
||||
}
|
||||
current = renderer->row[x + 2];
|
||||
if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
|
||||
}
|
||||
current = renderer->row[x + 1];
|
||||
if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
|
||||
}
|
||||
current = renderer->row[x];
|
||||
if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
|
||||
renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
// TODO: Share with GBAVideoSoftwareRendererGetPixels
|
||||
#ifdef COLOR_16_BIT
|
||||
*stride = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||
if (!softwareRenderer->temporaryBuffer) {
|
||||
softwareRenderer->temporaryBuffer = anonymousMemoryMap(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
|
||||
}
|
||||
*pixels = softwareRenderer->temporaryBuffer;
|
||||
unsigned y, x;
|
||||
for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
color_t inColor = softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + x];
|
||||
uint32_t outColor;
|
||||
#ifdef COLOR_5_6_5
|
||||
outColor = (inColor & 0x1F) << 19;
|
||||
outColor |= (inColor & 0x7C0) << 5;
|
||||
outColor |= (inColor & 0xF800) >> 8;
|
||||
#else
|
||||
outColor = (inColor & 0x1F) << 3;
|
||||
outColor |= (inColor & 0x3E0) << 6;
|
||||
outColor |= (inColor & 0x7C00) << 9;
|
||||
#endif
|
||||
softwareRenderer->temporaryBuffer[GB_VIDEO_HORIZONTAL_PIXELS * y + x] = outColor;
|
||||
}
|
||||
}
|
||||
#else
|
||||
*stride = softwareRenderer->outputBufferStride;
|
||||
*pixels = softwareRenderer->outputBuffer;
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright (c) 2013-2016 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 GB_RENDERER_SOFTWARE_H
|
||||
#define GB_RENDERER_SOFTWARE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/video.h"
|
||||
|
||||
struct GBVideoSoftwareRenderer {
|
||||
struct GBVideoRenderer d;
|
||||
|
||||
color_t* outputBuffer;
|
||||
int outputBufferStride;
|
||||
|
||||
uint8_t row[GB_VIDEO_HORIZONTAL_PIXELS + 8];
|
||||
|
||||
color_t palette[128];
|
||||
|
||||
uint32_t* temporaryBuffer;
|
||||
|
||||
uint8_t scy;
|
||||
uint8_t scx;
|
||||
uint8_t wy;
|
||||
uint8_t wx;
|
||||
uint8_t currentWy;
|
||||
|
||||
GBRegisterLCDC lcdc;
|
||||
enum GBModel model;
|
||||
};
|
||||
|
||||
void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,172 @@
|
|||
/* Copyright (c) 2013-2016 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 "serialize.h"
|
||||
|
||||
#include "gb/io.h"
|
||||
#include "gb/timer.h"
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate");
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <time.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
const uint32_t GB_SAVESTATE_MAGIC = 0x00400000;
|
||||
const uint32_t GB_SAVESTATE_VERSION = 0x00000000;
|
||||
|
||||
void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
|
||||
STORE_32LE(GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, 0, &state->versionMagic);
|
||||
STORE_32LE(gb->romCrc32, 0, &state->romCrc32);
|
||||
|
||||
if (gb->memory.rom) {
|
||||
memcpy(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title));
|
||||
} else {
|
||||
memset(state->title, 0, sizeof(state->title));
|
||||
}
|
||||
|
||||
state->model = gb->model;
|
||||
|
||||
state->cpu.a = gb->cpu->a;
|
||||
state->cpu.f = gb->cpu->f.packed;
|
||||
state->cpu.b = gb->cpu->b;
|
||||
state->cpu.c = gb->cpu->c;
|
||||
state->cpu.d = gb->cpu->d;
|
||||
state->cpu.e = gb->cpu->e;
|
||||
state->cpu.h = gb->cpu->h;
|
||||
state->cpu.l = gb->cpu->l;
|
||||
STORE_16LE(gb->cpu->sp, 0, &state->cpu.sp);
|
||||
STORE_16LE(gb->cpu->pc, 0, &state->cpu.pc);
|
||||
|
||||
STORE_32LE(gb->cpu->cycles, 0, &state->cpu.cycles);
|
||||
STORE_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||
|
||||
STORE_16LE(gb->cpu->index, 0, &state->cpu.index);
|
||||
state->cpu.bus = gb->cpu->bus;
|
||||
state->cpu.executionState = gb->cpu->executionState;
|
||||
STORE_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector);
|
||||
|
||||
STORE_32LE(gb->eiPending, 0, &state->cpu.eiPending);
|
||||
|
||||
GBSerializedCpuFlags flags = 0;
|
||||
flags = GBSerializedCpuFlagsSetCondition(flags, gb->cpu->condition);
|
||||
flags = GBSerializedCpuFlagsSetIrqPending(flags, gb->cpu->irqPending);
|
||||
flags = GBSerializedCpuFlagsSetDoubleSpeed(flags, gb->doubleSpeed);
|
||||
STORE_32LE(flags, 0, &state->cpu.flags);
|
||||
|
||||
GBMemorySerialize(&gb->memory, state);
|
||||
GBIOSerialize(gb, state);
|
||||
GBVideoSerialize(&gb->video, state);
|
||||
GBTimerSerialize(&gb->timer, state);
|
||||
GBAudioSerialize(&gb->audio, state);
|
||||
|
||||
#ifndef _MSC_VER
|
||||
struct timeval tv;
|
||||
if (!gettimeofday(&tv, 0)) {
|
||||
uint64_t usec = tv.tv_usec;
|
||||
usec += tv.tv_sec * 1000000LL;
|
||||
STORE_64LE(usec, 0, &state->creationUsec);
|
||||
}
|
||||
#else
|
||||
struct timespec ts;
|
||||
if (timespec_get(&ts, TIME_UTC)) {
|
||||
uint64_t usec = ts.tv_nsec / 1000;
|
||||
usec += ts.tv_sec * 1000000LL;
|
||||
STORE_64LE(usec, 0, &state->creationUsec);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
state->creationUsec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||
bool error = false;
|
||||
int32_t check;
|
||||
uint32_t ucheck;
|
||||
LOAD_32LE(ucheck, 0, &state->versionMagic);
|
||||
if (ucheck > GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||
mLOG(GB_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GB_SAVESTATE_MAGIC) {
|
||||
mLOG(GB_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
error = true;
|
||||
} else if (ucheck < GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||
mLOG(GB_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||
}
|
||||
|
||||
if (gb->memory.rom && memcmp(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title))) {
|
||||
mLOG(GB_STATE, WARN, "Savestate is for a different game");
|
||||
error = true;
|
||||
}
|
||||
LOAD_32LE(ucheck, 0, &state->romCrc32);
|
||||
if (ucheck != gb->romCrc32) {
|
||||
mLOG(GB_STATE, WARN, "Savestate is for a different version of the game");
|
||||
}
|
||||
LOAD_32LE(check, 0, &state->cpu.cycles);
|
||||
if (check < 0) {
|
||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are negative");
|
||||
error = true;
|
||||
}
|
||||
if (check >= (int32_t) DMG_LR35902_FREQUENCY) {
|
||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are too high");
|
||||
error = true;
|
||||
}
|
||||
LOAD_32LE(check, 0, &state->video.eventDiff);
|
||||
if (check < 0) {
|
||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: video eventDiff is negative");
|
||||
error = true;
|
||||
}
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gb->cpu->a = state->cpu.a;
|
||||
gb->cpu->f.packed = state->cpu.f;
|
||||
gb->cpu->b = state->cpu.b;
|
||||
gb->cpu->c = state->cpu.c;
|
||||
gb->cpu->d = state->cpu.d;
|
||||
gb->cpu->e = state->cpu.e;
|
||||
gb->cpu->h = state->cpu.h;
|
||||
gb->cpu->l = state->cpu.l;
|
||||
LOAD_16LE(gb->cpu->sp, 0, &state->cpu.sp);
|
||||
LOAD_16LE(gb->cpu->pc, 0, &state->cpu.pc);
|
||||
|
||||
LOAD_16LE(gb->cpu->index, 0, &state->cpu.index);
|
||||
gb->cpu->bus = state->cpu.bus;
|
||||
gb->cpu->executionState = state->cpu.executionState;
|
||||
LOAD_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector);
|
||||
|
||||
LOAD_32LE(gb->eiPending, 0, &state->cpu.eiPending);
|
||||
|
||||
GBSerializedCpuFlags flags;
|
||||
LOAD_32LE(flags, 0, &state->cpu.flags);
|
||||
gb->cpu->condition = GBSerializedCpuFlagsGetCondition(flags);
|
||||
gb->cpu->irqPending = GBSerializedCpuFlagsGetIrqPending(flags);
|
||||
gb->doubleSpeed = GBSerializedCpuFlagsGetDoubleSpeed(flags);
|
||||
|
||||
LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles);
|
||||
LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||
|
||||
gb->model = state->model;
|
||||
|
||||
if (gb->model < GB_MODEL_CGB) {
|
||||
gb->audio.style = GB_AUDIO_DMG;
|
||||
} else {
|
||||
gb->audio.style = GB_AUDIO_CGB;
|
||||
}
|
||||
|
||||
GBMemoryDeserialize(&gb->memory, state);
|
||||
GBIODeserialize(gb, state);
|
||||
GBVideoDeserialize(&gb->video, state);
|
||||
GBTimerDeserialize(&gb->timer, state);
|
||||
GBAudioDeserialize(&gb->audio, state);
|
||||
|
||||
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
|
||||
|
||||
return true;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue