mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/sio-dolphin
This commit is contained in:
commit
5c082b86dc
|
@ -3,17 +3,12 @@ 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 \
|
||||
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_MODULE_PATH=/usr/local/opt/qt
|
||||
|
||||
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
|
||||
|
|
129
CHANGES
129
CHANGES
|
@ -1,36 +1,127 @@
|
|||
0.4.0: (Future)
|
||||
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
|
||||
Bugfixes:
|
||||
- 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
|
||||
- VFS: Fix VFileReadline and remove _vfdReadline
|
||||
- GBA Memory: Fix DMA register writing behavior
|
||||
- GBA BIOS: Fix misaligned CpuSet
|
||||
- ARM7: Fix sign of unaligned LDRSH
|
||||
- GBA: Fix warnings when creating and loading savestates
|
||||
- GBA Memory: Fix DMAs triggering two cycles early
|
||||
- GBA Hardware: Fix GPIO on big endian
|
||||
- Util: Fix excessive memory allocation when decoding a PNG
|
||||
- GBA: Fix Iridion II savetype
|
||||
- Libretro: Fix aspect ratio
|
||||
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
|
||||
- GBA: Attempting to save a screenshot-style savestate should be allowed without libpng
|
||||
- GBA: Better memory handling with PNG savestates
|
||||
- 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 Video: Remove lastHblank, as it is implied
|
||||
- GBA Config: Add "override" layer for better one-time configuration
|
||||
- SDL: Allow GBASDLAudio to be used without a thread context
|
||||
- All: Improved PowerPC support
|
||||
- All: Fix some undefined behavior warnings
|
||||
- Util: Use VFile for configuration
|
||||
- GBA Memory: Implement several unimplemented memory access types
|
||||
- GBA: Implement bad I/O register loading
|
||||
- GBA Memory: Add GBAView* functions for viewing memory directly without bus issues
|
||||
- Util: Add MutexTryLock
|
||||
- Qt: Gray out "Skip BIOS intro" while "Use BIOS file" is unchecked
|
||||
- Qt: Allow use of modifier keys as input
|
||||
- Qt: Optimize log viewer
|
||||
- GBA RR: Starting from savestate now embeds the savegame
|
||||
- Libretro: Add install target for libretro core
|
||||
- 3DS: Update to new ctrulib API
|
||||
- GBA RR: Add preliminary SRAM support for VBM loading
|
||||
- GBA RR: Add support for resets in movies
|
||||
- GBA Input: Consolidate GBA_KEY_NONE and GBA_NO_MAPPING
|
||||
|
||||
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
|
||||
- VFS: Fix return values of VFileFILE.read and .write
|
||||
Misc:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
0.3.0: (2015-08-16)
|
||||
Features:
|
||||
|
|
192
CMakeLists.txt
192
CMakeLists.txt
|
@ -11,19 +11,24 @@ set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugg
|
|||
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_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(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_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)
|
||||
|
@ -36,8 +41,9 @@ file(GLOB GUI_SRC ${CMAKE_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_SOURCE_DIR}/src/g
|
|||
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/*.c)
|
||||
file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/*.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)
|
||||
list(APPEND GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||
set(CORE_VFS_SRC ${CMAKE_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})
|
||||
|
@ -113,7 +119,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,7 +137,7 @@ 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)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
|
||||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
elseif(UNIX)
|
||||
|
@ -144,7 +150,7 @@ 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)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
endif()
|
||||
|
@ -187,6 +193,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 +218,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()
|
||||
|
@ -268,6 +280,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,6 +290,9 @@ 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})
|
||||
|
@ -284,6 +302,7 @@ find_feature(USE_ZLIB "ZLIB")
|
|||
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)
|
||||
|
@ -291,7 +310,6 @@ set(FEATURE_SRC)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
|
||||
|
||||
if(DISABLE_DEPS)
|
||||
set(USE_LZMA OFF)
|
||||
set(USE_GDB_STUB OFF)
|
||||
endif()
|
||||
|
||||
|
@ -394,11 +412,9 @@ 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)
|
||||
|
@ -408,6 +424,10 @@ if(USE_LIBZIP)
|
|||
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")
|
||||
elseif(USE_ZLIB)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-zip.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/ioapi.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/unzip.c)
|
||||
endif()
|
||||
|
||||
if (USE_LZMA)
|
||||
|
@ -423,25 +443,36 @@ if (USE_LZMA)
|
|||
${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/Delta.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/BraIA64.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bcj2.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
||||
list(APPEND FEATURE_SRC ${LZMA_SRC})
|
||||
list(APPEND 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})
|
||||
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})
|
||||
|
||||
|
@ -467,7 +498,7 @@ set(CORE_SRC
|
|||
${DEBUGGER_SRC}
|
||||
${RENDERER_SRC}
|
||||
${UTIL_SRC}
|
||||
${VFS_SRC}
|
||||
${CORE_VFS_SRC}
|
||||
${OS_SRC}
|
||||
${THIRD_PARTY_SRC})
|
||||
|
||||
|
@ -477,42 +508,54 @@ if(NOT MINIMAL_CORE)
|
|||
${GBA_RR_SRC}
|
||||
${GBA_SV_SRC}
|
||||
${SIO_SRC}
|
||||
${FEATURE_SRC})
|
||||
${FEATURE_SRC}
|
||||
${VFS_SRC})
|
||||
else()
|
||||
set(SRC ${CORE_SRC})
|
||||
set(SRC ${CORE_SRC} ${VFS_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_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-48.png DESTINATION share/icons/hicolor/48x48/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-64.png DESTINATION share/icons/hicolor/64x64/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-96.png DESTINATION share/icons/hicolor/96x96/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
endif()
|
||||
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)
|
||||
|
@ -531,8 +574,24 @@ endif()
|
|||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING")
|
||||
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_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_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)
|
||||
|
@ -587,21 +646,50 @@ install(FILES ${CMAKE_SOURCE_DIR}/README.md ${CMAKE_SOURCE_DIR}/CHANGES DESTINAT
|
|||
include(CPack)
|
||||
|
||||
# Summaries
|
||||
message(STATUS "Feature summary:")
|
||||
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_ZLIB)
|
||||
set(SUMMARY_ZIP minizip)
|
||||
else()
|
||||
set(SUMMARY_ZIP OFF)
|
||||
endif()
|
||||
|
||||
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: ${USE_LIBZIP}")
|
||||
message(STATUS " ZIP support: ${SUMMARY_ZIP}")
|
||||
message(STATUS " 7-Zip support: ${USE_LZMA}")
|
||||
message(STATUS " Better audio resampling: ${USE_BLIP}")
|
||||
message(STATUS "Frontend summary:")
|
||||
message(STATUS " OpenGL support: ${SUMMARY_GL}")
|
||||
message(STATUS "Frontends:")
|
||||
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 "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}")
|
||||
|
|
|
@ -5,7 +5,7 @@ mGBA is a new emulator for running Game Boy Advance games. It aims to be faster
|
|||
|
||||
Up-to-date news and downloads can be found at [mgba.io](http://mgba.io/).
|
||||
|
||||
![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)
|
||||
[![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
@ -141,7 +141,7 @@ 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.
|
||||
- [inih](https://code.google.com/p/inih/), which is copyright © 2009 Ben Hoyt and used under a BSD 3-clause license.
|
||||
- [blip-buf](https://code.google.com/p/blip-buf/), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License.
|
||||
- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public domain.
|
||||
- [MurmurHash3](https://code.google.com/p/smhasher/wiki/MurmurHash3) implementation by Austin Appleby, which is public domain.
|
||||
|
|
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 |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
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 * 960.0, 4.0))];
|
||||
color.rgb *= arrayY[int(mod(texCoord.t * 640.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(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,21 @@
|
|||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
|
||||
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 * 960.0, 4.0))];
|
||||
color.rgb *= arrayY[int(mod(texCoord.t * 640.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
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
#define DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(NAME, MNEMONIC) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->op3.immediate = (opcode >> 6) & 0x0007; \
|
||||
info->op3.immediate = (opcode >> 6) & 0x001F; \
|
||||
info->op1.reg = opcode & 0x0007; \
|
||||
info->op2.reg = (opcode >> 3) & 0x0007; \
|
||||
info->affectsCPSR = 1; \
|
||||
|
|
|
@ -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), \
|
||||
|
|
|
@ -516,7 +516,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 +524,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;)
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ static inline void _ARMSetMode(struct ARMCore* cpu, enum ExecutionMode execution
|
|||
case MODE_THUMB:
|
||||
cpu->cpsr.t = 1;
|
||||
}
|
||||
cpu->nextEvent = 0;
|
||||
cpu->nextEvent = cpu->cycles;
|
||||
}
|
||||
|
||||
static inline void _ARMReadCPSR(struct ARMCore* cpu) {
|
||||
|
|
|
@ -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; \
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ void ARMDebuggerRun(struct ARMDebugger* debugger) {
|
|||
void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) {
|
||||
debugger->state = DEBUGGER_PAUSED;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
cpu->nextEvent = 0;
|
||||
cpu->nextEvent = cpu->cycles;
|
||||
if (reason == DEBUGGER_ENTER_BREAKPOINT) {
|
||||
struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu));
|
||||
debugger->currentBreakpoint = breakpoint;
|
||||
|
|
125
src/gba/audio.c
125
src/gba/audio.c
|
@ -283,6 +283,12 @@ int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
audio->p->memory.io[REG_SOUNDCNT_X >> 1] &= ~0x000F;
|
||||
audio->p->memory.io[REG_SOUNDCNT_X >> 1] |= audio->playingCh1;
|
||||
audio->p->memory.io[REG_SOUNDCNT_X >> 1] |= audio->playingCh2 << 1;
|
||||
audio->p->memory.io[REG_SOUNDCNT_X >> 1] |= audio->playingCh3 << 2;
|
||||
audio->p->memory.io[REG_SOUNDCNT_X >> 1] |= audio->playingCh4 << 3;
|
||||
}
|
||||
|
||||
audio->nextSample -= audio->eventDiff;
|
||||
|
@ -523,7 +529,7 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
|||
channel->dmaSource = 0;
|
||||
}
|
||||
}
|
||||
CircleBufferRead8(&channel->fifo, &channel->sample);
|
||||
CircleBufferRead8(&channel->fifo, (int8_t*) &channel->sample);
|
||||
}
|
||||
|
||||
#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
|
||||
|
@ -848,75 +854,84 @@ static void _sample(struct GBAAudio* audio) {
|
|||
}
|
||||
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||
state->audio.ch1Volume = audio->ch1.envelope.currentVolume;
|
||||
state->audio.ch1Dead = audio->ch1.envelope.dead;
|
||||
state->audio.ch1Hi = audio->ch1.control.hi;
|
||||
state->audio.ch1.envelopeNextStep = audio->ch1.envelope.nextStep;
|
||||
state->audio.ch1.waveNextStep = audio->ch1.control.nextStep;
|
||||
state->audio.ch1.sweepNextStep = audio->ch1.nextSweep;
|
||||
state->audio.ch1.endTime = audio->ch1.control.endTime;
|
||||
state->audio.ch1.nextEvent = audio->nextCh1;
|
||||
uint32_t flags = 0;
|
||||
|
||||
state->audio.ch2Volume = audio->ch2.envelope.currentVolume;
|
||||
state->audio.ch2Dead = audio->ch2.envelope.dead;
|
||||
state->audio.ch2Hi = audio->ch2.control.hi;
|
||||
state->audio.ch2.envelopeNextStep = audio->ch2.envelope.nextStep;
|
||||
state->audio.ch2.waveNextStep = audio->ch2.control.nextStep;
|
||||
state->audio.ch2.endTime = audio->ch2.control.endTime;
|
||||
state->audio.ch2.nextEvent = audio->nextCh2;
|
||||
flags = GBASerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume);
|
||||
flags = GBASerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead);
|
||||
flags = GBASerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi);
|
||||
STORE_32(audio->ch1.envelope.nextStep, 0, &state->audio.ch1.envelopeNextStep);
|
||||
STORE_32(audio->ch1.control.nextStep, 0, &state->audio.ch1.waveNextStep);
|
||||
STORE_32(audio->ch1.nextSweep, 0, &state->audio.ch1.sweepNextStep);
|
||||
STORE_32(audio->ch1.control.endTime, 0, &state->audio.ch1.endTime);
|
||||
STORE_32(audio->nextCh1, 0, &state->audio.ch1.nextEvent);
|
||||
|
||||
flags = GBASerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume);
|
||||
flags = GBASerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead);
|
||||
flags = GBASerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi);
|
||||
STORE_32(audio->ch2.envelope.nextStep, 0, &state->audio.ch2.envelopeNextStep);
|
||||
STORE_32(audio->ch2.control.nextStep, 0, &state->audio.ch2.waveNextStep);
|
||||
STORE_32(audio->ch2.control.endTime, 0, &state->audio.ch2.endTime);
|
||||
STORE_32(audio->nextCh2, 0, &state->audio.ch2.nextEvent);
|
||||
|
||||
memcpy(state->audio.ch3.wavebanks, audio->ch3.wavedata, sizeof(state->audio.ch3.wavebanks));
|
||||
state->audio.ch3.endTime = audio->ch3.control.endTime;
|
||||
state->audio.ch3.nextEvent = audio->nextCh3;
|
||||
STORE_32(audio->ch3.control.endTime, 0, &state->audio.ch3.endTime);
|
||||
STORE_32(audio->nextCh3, 0, &state->audio.ch3.nextEvent);
|
||||
|
||||
state->audio.ch4Volume = audio->ch4.envelope.currentVolume;
|
||||
state->audio.ch4Dead = audio->ch4.envelope.dead;
|
||||
state->audio.ch4.envelopeNextStep = audio->ch4.envelope.nextStep;
|
||||
state->audio.ch4.lfsr = audio->ch4.lfsr;
|
||||
state->audio.ch4.endTime = audio->ch4.control.endTime;
|
||||
state->audio.ch4.nextEvent = audio->nextCh4;
|
||||
state->audio.flags = GBASerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume);
|
||||
state->audio.flags = GBASerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead);
|
||||
STORE_32(audio->ch4.envelope.nextStep, 0, &state->audio.ch4.envelopeNextStep);
|
||||
STORE_32(audio->ch4.lfsr, 0, &state->audio.ch4.lfsr);
|
||||
STORE_32(audio->ch4.control.endTime, 0, &state->audio.ch4.endTime);
|
||||
STORE_32(audio->nextCh4, 0, &state->audio.ch4.nextEvent);
|
||||
|
||||
STORE_32(flags, 0, &state->audio.flags);
|
||||
|
||||
CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA));
|
||||
CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB));
|
||||
state->audio.fifoSize = CircleBufferSize(&audio->chA.fifo);
|
||||
uint32_t fifoSize = CircleBufferSize(&audio->chA.fifo);
|
||||
STORE_32(fifoSize, 0, &state->audio.fifoSize);
|
||||
|
||||
state->audio.nextEvent = audio->nextEvent;
|
||||
state->audio.eventDiff = audio->eventDiff;
|
||||
state->audio.nextSample = audio->nextSample;
|
||||
STORE_32(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||
STORE_32(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||
STORE_32(audio->nextSample, 0, &state->audio.nextSample);
|
||||
}
|
||||
|
||||
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
|
||||
audio->ch1.envelope.currentVolume = state->audio.ch1Volume;
|
||||
audio->ch1.envelope.dead = state->audio.ch1Dead;
|
||||
audio->ch1.control.hi = state->audio.ch1Hi;
|
||||
audio->ch1.envelope.nextStep = state->audio.ch1.envelopeNextStep;
|
||||
audio->ch1.control.nextStep = state->audio.ch1.waveNextStep;
|
||||
audio->ch1.nextSweep = state->audio.ch1.sweepNextStep;
|
||||
audio->ch1.control.endTime = state->audio.ch1.endTime;
|
||||
audio->nextCh1 = state->audio.ch1.nextEvent;
|
||||
uint32_t flags;
|
||||
LOAD_32(flags, 0, &state->audio.flags);
|
||||
audio->ch1.envelope.currentVolume = GBASerializedAudioFlagsGetCh1Volume(flags);
|
||||
audio->ch1.envelope.dead = GBASerializedAudioFlagsGetCh1Dead(flags);
|
||||
audio->ch1.control.hi = GBASerializedAudioFlagsGetCh1Hi(flags);
|
||||
LOAD_32(audio->ch1.envelope.nextStep, 0, &state->audio.ch1.envelopeNextStep);
|
||||
LOAD_32(audio->ch1.control.nextStep, 0, &state->audio.ch1.waveNextStep);
|
||||
LOAD_32(audio->ch1.nextSweep, 0, &state->audio.ch1.sweepNextStep);
|
||||
LOAD_32(audio->ch1.control.endTime, 0, &state->audio.ch1.endTime);
|
||||
LOAD_32(audio->nextCh1, 0, &state->audio.ch1.nextEvent);
|
||||
|
||||
audio->ch2.envelope.currentVolume = state->audio.ch2Volume;
|
||||
audio->ch2.envelope.dead = state->audio.ch2Dead;
|
||||
audio->ch2.control.hi = state->audio.ch2Hi;
|
||||
audio->ch2.envelope.nextStep = state->audio.ch2.envelopeNextStep;
|
||||
audio->ch2.control.nextStep = state->audio.ch2.waveNextStep;
|
||||
audio->ch2.control.endTime = state->audio.ch2.endTime;
|
||||
audio->nextCh2 = state->audio.ch2.nextEvent;
|
||||
audio->ch2.envelope.currentVolume = GBASerializedAudioFlagsGetCh2Volume(flags);
|
||||
audio->ch2.envelope.dead = GBASerializedAudioFlagsGetCh2Dead(flags);
|
||||
audio->ch2.control.hi = GBASerializedAudioFlagsGetCh2Hi(flags);
|
||||
LOAD_32(audio->ch2.envelope.nextStep, 0, &state->audio.ch2.envelopeNextStep);
|
||||
LOAD_32(audio->ch2.control.nextStep, 0, &state->audio.ch2.waveNextStep);
|
||||
LOAD_32(audio->ch2.control.endTime, 0, &state->audio.ch2.endTime);
|
||||
LOAD_32(audio->nextCh2, 0, &state->audio.ch2.nextEvent);
|
||||
|
||||
// TODO: Big endian?
|
||||
memcpy(audio->ch3.wavedata, state->audio.ch3.wavebanks, sizeof(audio->ch3.wavedata));
|
||||
audio->ch3.control.endTime = state->audio.ch3.endTime;
|
||||
audio->nextCh3 = state->audio.ch3.nextEvent;
|
||||
LOAD_32(audio->ch3.control.endTime, 0, &state->audio.ch3.endTime);
|
||||
LOAD_32(audio->nextCh3, 0, &state->audio.ch3.nextEvent);
|
||||
|
||||
audio->ch4.envelope.currentVolume = state->audio.ch4Volume;
|
||||
audio->ch4.envelope.dead = state->audio.ch4Dead;
|
||||
audio->ch4.envelope.nextStep = state->audio.ch4.envelopeNextStep;
|
||||
audio->ch4.lfsr = state->audio.ch4.lfsr;
|
||||
audio->ch4.control.endTime = state->audio.ch4.endTime;
|
||||
audio->nextCh4 = state->audio.ch4.nextEvent;
|
||||
audio->ch4.envelope.currentVolume = GBASerializedAudioFlagsGetCh4Volume(flags);
|
||||
audio->ch4.envelope.dead = GBASerializedAudioFlagsGetCh4Dead(flags);
|
||||
LOAD_32(audio->ch4.envelope.nextStep, 0, &state->audio.ch4.envelopeNextStep);
|
||||
LOAD_32(audio->ch4.lfsr, 0, &state->audio.ch4.lfsr);
|
||||
LOAD_32(audio->ch4.control.endTime, 0, &state->audio.ch4.endTime);
|
||||
LOAD_32(audio->nextCh4, 0, &state->audio.ch4.nextEvent);
|
||||
|
||||
CircleBufferClear(&audio->chA.fifo);
|
||||
CircleBufferClear(&audio->chB.fifo);
|
||||
size_t fifoSize = state->audio.fifoSize;
|
||||
uint32_t fifoSize;
|
||||
LOAD_32(fifoSize, 0, &state->audio.fifoSize);
|
||||
if (state->audio.fifoSize > CircleBufferCapacity(&audio->chA.fifo)) {
|
||||
fifoSize = CircleBufferCapacity(&audio->chA.fifo);
|
||||
}
|
||||
|
@ -926,9 +941,9 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
|
|||
CircleBufferWrite8(&audio->chB.fifo, state->audio.fifoB[i]);
|
||||
}
|
||||
|
||||
audio->nextEvent = state->audio.nextEvent;
|
||||
audio->eventDiff = state->audio.eventDiff;
|
||||
audio->nextSample = state->audio.nextSample;
|
||||
LOAD_32(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||
LOAD_32(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||
LOAD_32(audio->nextSample, 0, &state->audio.nextSample);
|
||||
}
|
||||
|
||||
float GBAAudioCalculateRatio(float inputSampleRate, float desiredFPS, float desiredSampleRate) {
|
||||
|
|
|
@ -128,7 +128,7 @@ struct GBAAudioChannel4 {
|
|||
int32_t endTime;
|
||||
} control;
|
||||
|
||||
unsigned lfsr;
|
||||
uint32_t lfsr;
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
|
|
|
@ -213,17 +213,23 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
_Div(gba, cpu->gprs[1], cpu->gprs[0]);
|
||||
break;
|
||||
case 0x8:
|
||||
cpu->gprs[0] = sqrt(cpu->gprs[0]);
|
||||
cpu->gprs[0] = sqrt((uint32_t) cpu->gprs[0]);
|
||||
break;
|
||||
case 0xA:
|
||||
cpu->gprs[0] = atan2f(cpu->gprs[1] / 16384.f, cpu->gprs[0] / 16384.f) / (2 * M_PI) * 0x10000;
|
||||
break;
|
||||
case 0xB:
|
||||
case 0xC:
|
||||
if (cpu->gprs[0] >> BASE_OFFSET == REGION_BIOS) {
|
||||
if (cpu->gprs[0] >> BASE_OFFSET < REGION_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot CpuSet from BIOS");
|
||||
return;
|
||||
}
|
||||
if (cpu->gprs[0] & (cpu->gprs[2] & (1 << 26) ? 3 : 1)) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Misaligned CpuSet source");
|
||||
}
|
||||
if (cpu->gprs[1] & (cpu->gprs[2] & (1 << 26) ? 3 : 1)) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Misaligned CpuSet destination");
|
||||
}
|
||||
ARMRaiseSWI(cpu);
|
||||
break;
|
||||
case 0xD:
|
||||
|
@ -476,8 +482,8 @@ static void _unHuffman(struct GBA* gba) {
|
|||
|
||||
static void _unRl(struct GBA* gba, int width) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
|
||||
int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8;
|
||||
uint32_t source = cpu->gprs[0];
|
||||
int remaining = (cpu->memory.load32(cpu, source & 0xFFFFFFFC, 0) & 0xFFFFFF00) >> 8;
|
||||
int padding = (4 - remaining) & 0x3;
|
||||
// We assume the signature byte (0x30) is correct
|
||||
int blockheader;
|
||||
|
|
|
@ -359,26 +359,67 @@ bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t address;
|
||||
uint8_t op;
|
||||
uint32_t value = 0;
|
||||
int width = 0;
|
||||
const char* lineNext = hex32(line, &address);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
if (lineNext[0] != ':') {
|
||||
return false;
|
||||
}
|
||||
++lineNext;
|
||||
while (width < 4) {
|
||||
lineNext = hex8(lineNext, &op);
|
||||
if (!lineNext) {
|
||||
break;
|
||||
}
|
||||
value <<= 8;
|
||||
value |= op;
|
||||
++width;
|
||||
}
|
||||
if (width == 0 || width == 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
|
||||
cheat->address = address;
|
||||
cheat->operandOffset = 0;
|
||||
cheat->addressOffset = 0;
|
||||
cheat->repeat = 1;
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = width;
|
||||
cheat->operand = value;
|
||||
GBACheatRegisterLine(cheats, line);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
|
||||
uint32_t op1;
|
||||
uint16_t op2;
|
||||
uint16_t op3;
|
||||
line = hex32(line, &op1);
|
||||
if (!line) {
|
||||
const char* lineNext = hex32(line, &op1);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
while (isspace((int) line[0])) {
|
||||
++line;
|
||||
if (lineNext[0] == ':') {
|
||||
return GBACheatAddVBALine(cheats, line);
|
||||
}
|
||||
line = hex16(line, &op2);
|
||||
if (!line) {
|
||||
while (isspace((int) lineNext[0])) {
|
||||
++lineNext;
|
||||
}
|
||||
lineNext = hex16(lineNext, &op2);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
if (!line[0] || isspace((int) line[0])) {
|
||||
if (!lineNext[0] || isspace((int) lineNext[0])) {
|
||||
return GBACheatAddCodeBreaker(cheats, op1, op2);
|
||||
}
|
||||
line = hex16(line, &op3);
|
||||
if (!line) {
|
||||
lineNext = hex16(lineNext, &op3);
|
||||
if (!lineNext) {
|
||||
return false;
|
||||
}
|
||||
uint32_t realOp2 = op2;
|
||||
|
|
|
@ -211,8 +211,9 @@ bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line);
|
|||
bool GBACheatAddProActionReplay(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddProActionReplayLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatAddVBALine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2);
|
||||
bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line);
|
||||
|
||||
bool GBACheatParseFile(struct GBACheatDevice*, struct VFile*);
|
||||
bool GBACheatSaveFile(struct GBACheatDevice*, struct VFile*);
|
||||
|
|
|
@ -30,6 +30,16 @@
|
|||
|
||||
static const char* _lookupValue(const struct GBAConfig* config, const char* key) {
|
||||
const char* value;
|
||||
if (config->port) {
|
||||
value = ConfigurationGetValue(&config->overridesTable, config->port, key);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
value = ConfigurationGetValue(&config->overridesTable, 0, key);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
if (config->port) {
|
||||
value = ConfigurationGetValue(&config->configTable, config->port, key);
|
||||
if (value) {
|
||||
|
@ -106,6 +116,7 @@ static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, f
|
|||
void GBAConfigInit(struct GBAConfig* config, const char* port) {
|
||||
ConfigurationInit(&config->configTable);
|
||||
ConfigurationInit(&config->defaultsTable);
|
||||
ConfigurationInit(&config->overridesTable);
|
||||
if (port) {
|
||||
config->port = malloc(strlen("ports.") + strlen(port) + 1);
|
||||
snprintf(config->port, strlen("ports.") + strlen(port) + 1, "ports.%s", port);
|
||||
|
@ -117,9 +128,11 @@ void GBAConfigInit(struct GBAConfig* config, const char* port) {
|
|||
void GBAConfigDeinit(struct GBAConfig* config) {
|
||||
ConfigurationDeinit(&config->configTable);
|
||||
ConfigurationDeinit(&config->defaultsTable);
|
||||
ConfigurationDeinit(&config->overridesTable);
|
||||
free(config->port);
|
||||
}
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
bool GBAConfigLoad(struct GBAConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
|
@ -200,8 +213,9 @@ void GBAConfigDirectory(char* out, size_t outLength) {
|
|||
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));
|
||||
|
@ -219,6 +233,7 @@ void GBAConfigDirectory(char* out, size_t outLength) {
|
|||
mkdir(out, 0755);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) {
|
||||
return _lookupValue(config, key);
|
||||
|
@ -268,8 +283,25 @@ void GBAConfigSetDefaultFloatValue(struct GBAConfig* config, const char* key, fl
|
|||
ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideValue(struct GBAConfig* config, const char* key, const char* value) {
|
||||
ConfigurationSetValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideIntValue(struct GBAConfig* config, const char* key, int value) {
|
||||
ConfigurationSetIntValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideUIntValue(struct GBAConfig* config, const char* key, unsigned value) {
|
||||
ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigSetOverrideFloatValue(struct GBAConfig* config, const char* key, float value) {
|
||||
ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value);
|
||||
}
|
||||
|
||||
void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* 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);
|
||||
|
@ -330,6 +362,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
|||
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) {
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
|
||||
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);
|
||||
|
@ -375,5 +408,7 @@ struct Configuration* GBAConfigGetOverrides(struct GBAConfig* config) {
|
|||
|
||||
void GBAConfigFreeOpts(struct GBAOptions* opts) {
|
||||
free(opts->bios);
|
||||
free(opts->shader);
|
||||
opts->bios = 0;
|
||||
opts->shader = 0;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
struct GBAConfig {
|
||||
struct Configuration configTable;
|
||||
struct Configuration defaultsTable;
|
||||
struct Configuration overridesTable;
|
||||
char* port;
|
||||
};
|
||||
|
||||
|
@ -37,6 +38,7 @@ struct GBAOptions {
|
|||
bool lockAspectRatio;
|
||||
bool resampleVideo;
|
||||
bool suspendScreensaver;
|
||||
char* shader;
|
||||
|
||||
int volume;
|
||||
bool mute;
|
||||
|
@ -50,6 +52,7 @@ struct GBAOptions {
|
|||
void GBAConfigInit(struct GBAConfig*, const char* port);
|
||||
void GBAConfigDeinit(struct GBAConfig*);
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
bool GBAConfigLoad(struct GBAConfig*);
|
||||
bool GBAConfigSave(const struct GBAConfig*);
|
||||
bool GBAConfigLoadPath(struct GBAConfig*, const char* path);
|
||||
|
@ -57,6 +60,7 @@ bool GBAConfigSavePath(const struct GBAConfig*, const char* path);
|
|||
|
||||
void GBAConfigMakePortable(const struct GBAConfig*);
|
||||
void GBAConfigDirectory(char* out, size_t outLength);
|
||||
#endif
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig*, const char* key);
|
||||
bool GBAConfigGetIntValue(const struct GBAConfig*, const char* key, int* value);
|
||||
|
@ -73,6 +77,11 @@ void GBAConfigSetDefaultIntValue(struct GBAConfig*, const char* key, int value);
|
|||
void GBAConfigSetDefaultUIntValue(struct GBAConfig*, const char* key, unsigned value);
|
||||
void GBAConfigSetDefaultFloatValue(struct GBAConfig*, const char* key, float value);
|
||||
|
||||
void GBAConfigSetOverrideValue(struct GBAConfig*, const char* key, const char* value);
|
||||
void GBAConfigSetOverrideIntValue(struct GBAConfig*, const char* key, int value);
|
||||
void GBAConfigSetOverrideUIntValue(struct GBAConfig*, const char* key, unsigned value);
|
||||
void GBAConfigSetOverrideFloatValue(struct GBAConfig*, const char* key, float value);
|
||||
|
||||
void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts);
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ bool GBAContextInit(struct GBAContext* context, const char* port) {
|
|||
context->fname = 0;
|
||||
context->save = 0;
|
||||
context->renderer = 0;
|
||||
GBADirectorySetInit(&context->dirs);
|
||||
memset(context->components, 0, sizeof(context->components));
|
||||
|
||||
if (!context->gba || !context->cpu) {
|
||||
|
@ -33,10 +34,11 @@ bool GBAContextInit(struct GBAContext* context, const char* port) {
|
|||
return false;
|
||||
}
|
||||
GBACreate(context->gba);
|
||||
ARMSetComponents(context->cpu, &context->gba->d, 0, context->components);
|
||||
ARMSetComponents(context->cpu, &context->gba->d, GBA_COMPONENT_MAX, context->components);
|
||||
ARMInit(context->cpu);
|
||||
|
||||
GBAConfigInit(&context->config, port);
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
if (port) {
|
||||
if (!_logFile) {
|
||||
char logPath[PATH_MAX];
|
||||
|
@ -59,6 +61,9 @@ bool GBAContextInit(struct GBAContext* context, const char* port) {
|
|||
GBAConfigLoad(&context->config);
|
||||
GBAConfigLoadDefaults(&context->config, &opts);
|
||||
}
|
||||
#else
|
||||
UNUSED(port);
|
||||
#endif
|
||||
|
||||
context->gba->sync = 0;
|
||||
return true;
|
||||
|
@ -67,54 +72,41 @@ bool GBAContextInit(struct GBAContext* context, const char* port) {
|
|||
void GBAContextDeinit(struct GBAContext* context) {
|
||||
ARMDeinit(context->cpu);
|
||||
GBADestroy(context->gba);
|
||||
if (context->bios) {
|
||||
context->bios->close(context->bios);
|
||||
context->bios = 0;
|
||||
}
|
||||
mappedMemoryFree(context->gba, 0);
|
||||
mappedMemoryFree(context->cpu, 0);
|
||||
GBAConfigDeinit(&context->config);
|
||||
GBADirectorySetDeinit(&context->dirs);
|
||||
}
|
||||
|
||||
bool GBAContextLoadROM(struct GBAContext* context, const char* path, bool autoloadSave) {
|
||||
struct VDir* dir = VDirOpenArchive(path);
|
||||
if (dir) {
|
||||
struct VDirEntry* de;
|
||||
while ((de = dir->listNext(dir))) {
|
||||
struct VFile* vf = dir->openFile(dir, de->name(de), O_RDONLY);
|
||||
if (!vf) {
|
||||
continue;
|
||||
}
|
||||
if (GBAIsROM(vf)) {
|
||||
context->rom = vf;
|
||||
break;
|
||||
}
|
||||
vf->close(vf);
|
||||
}
|
||||
dir->close(dir);
|
||||
} else {
|
||||
context->rom = VFileOpen(path, O_RDONLY);
|
||||
}
|
||||
|
||||
context->rom = GBADirectorySetOpenPath(&context->dirs, path, GBAIsROM);
|
||||
if (!context->rom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GBAIsROM(context->rom)) {
|
||||
context->rom->close(context->rom);
|
||||
context->rom = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
context->fname = path;
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
if (autoloadSave) {
|
||||
context->save = VDirOptionalOpenFile(0, path, 0, ".sav", O_RDWR | O_CREAT);
|
||||
char dirname[PATH_MAX];
|
||||
char basename[PATH_MAX];
|
||||
separatePath(context->fname, dirname, basename, 0);
|
||||
GBADirectorySetAttachBase(&context->dirs, VDirOpen(dirname));
|
||||
strncat(basename, ".sav", PATH_MAX - strlen(basename) - 1);
|
||||
context->save = context->dirs.save->openFile(context->dirs.save, basename, O_RDWR | O_CREAT);
|
||||
}
|
||||
#else
|
||||
UNUSED(autoloadSave);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAContextUnloadROM(struct GBAContext* context) {
|
||||
GBAUnloadROM(context->gba);
|
||||
if (context->bios) {
|
||||
context->bios->close(context->bios);
|
||||
context->bios = 0;
|
||||
}
|
||||
GBADirectorySetDetachBase(&context->dirs);
|
||||
if (context->rom) {
|
||||
context->rom->close(context->rom);
|
||||
context->rom = 0;
|
||||
|
@ -180,22 +172,31 @@ bool GBAContextStart(struct GBAContext* context) {
|
|||
context->gba->logLevel = opts.logLevel;
|
||||
context->gba->idleOptimization = opts.idleOptimization;
|
||||
|
||||
ARMReset(context->cpu);
|
||||
GBAContextReset(context);
|
||||
|
||||
// TODO: Move this into GBAContextReset
|
||||
if (opts.skipBios) {
|
||||
GBASkipBIOS(context->cpu);
|
||||
GBASkipBIOS(context->gba);
|
||||
}
|
||||
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) context->gba->memory.rom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(GBAConfigGetOverrides(&context->config), &override)) {
|
||||
struct Configuration* overrides = 0;
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
overrides = GBAConfigGetOverrides(&context->config);
|
||||
GBAConfigFreeOpts(&opts);
|
||||
#endif
|
||||
if (GBAOverrideFind(overrides, &override)) {
|
||||
GBAOverrideApply(context->gba, &override);
|
||||
}
|
||||
GBAConfigFreeOpts(&opts);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAContextReset(struct GBAContext* context) {
|
||||
ARMReset(context->cpu);
|
||||
}
|
||||
|
||||
void GBAContextStop(struct GBAContext* context) {
|
||||
UNUSED(context);
|
||||
// TODO?
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "util/common.h"
|
||||
|
||||
#include "gba/context/config.h"
|
||||
#include "gba/context/directories.h"
|
||||
#include "gba/context/sync.h"
|
||||
#include "gba/input.h"
|
||||
|
||||
|
@ -20,6 +21,7 @@ struct GBAContext {
|
|||
const char* fname;
|
||||
struct VFile* save;
|
||||
struct VFile* bios;
|
||||
struct GBADirectorySet dirs;
|
||||
struct ARMComponent* components[GBA_COMPONENT_MAX];
|
||||
struct GBAConfig config;
|
||||
struct GBAOptions opts;
|
||||
|
@ -37,6 +39,7 @@ void GBAContextUnloadROM(struct GBAContext* context);
|
|||
|
||||
bool GBAContextStart(struct GBAContext* context);
|
||||
void GBAContextStop(struct GBAContext* context);
|
||||
void GBAContextReset(struct GBAContext* context);
|
||||
void GBAContextFrame(struct GBAContext* context, uint16_t keys);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* 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 "util/vfs.h"
|
||||
|
||||
void GBADirectorySetInit(struct GBADirectorySet* dirs) {
|
||||
dirs->base = 0;
|
||||
dirs->archive = 0;
|
||||
dirs->save = 0;
|
||||
dirs->patch = 0;
|
||||
dirs->state = 0;
|
||||
dirs->screenshot = 0;
|
||||
}
|
||||
|
||||
void GBADirectorySetDeinit(struct GBADirectorySet* dirs) {
|
||||
GBADirectorySetDetachBase(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 GBADirectorySetAttachBase(struct GBADirectorySet* 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 GBADirectorySetDetachBase(struct GBADirectorySet* 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* GBADirectorySetOpenPath(struct GBADirectorySet* 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;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
|
@ -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/. */
|
||||
#ifndef DIRECTORIES_H
|
||||
#define DIRECTORIES_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
struct VDir;
|
||||
|
||||
struct GBADirectorySet {
|
||||
struct VDir* base;
|
||||
struct VDir* archive;
|
||||
struct VDir* save;
|
||||
struct VDir* patch;
|
||||
struct VDir* state;
|
||||
struct VDir* screenshot;
|
||||
};
|
||||
|
||||
void GBADirectorySetInit(struct GBADirectorySet* dirs);
|
||||
void GBADirectorySetDeinit(struct GBADirectorySet* dirs);
|
||||
|
||||
void GBADirectorySetAttachBase(struct GBADirectorySet* dirs, struct VDir* base);
|
||||
void GBADirectorySetDetachBase(struct GBADirectorySet* dirs);
|
||||
|
||||
struct VFile* GBADirectorySetOpenPath(struct GBADirectorySet* dirs, const char* path, bool (*filter)(struct VFile*));
|
||||
|
||||
#endif
|
|
@ -46,6 +46,10 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
// F-Zero - Climax
|
||||
{ "BFTJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Iridion II
|
||||
{ "AI2E", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AI2P", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Golden Sun: The Lost Age
|
||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
||||
|
||||
|
@ -128,7 +132,7 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
// Super Mario Advance 2
|
||||
{ "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
{ "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
{ "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
|
||||
{ "AA2P", SAVEDATA_AUTODETECT, HW_NONE, 0x800052E },
|
||||
|
||||
// Super Mario Advance 3
|
||||
{ "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
|
||||
|
@ -140,6 +144,10 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "AX4E", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
|
||||
{ "AX4P", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
|
||||
|
||||
// Super Monkey Ball Jr.
|
||||
{ "ALUE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "ALUP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Top Gun - Combat Zones
|
||||
{ "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
|
|
183
src/gba/gba.c
183
src/gba/gba.c
|
@ -5,6 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gba.h"
|
||||
|
||||
#include "arm/decoder.h"
|
||||
#include "arm/isa-inlines.h"
|
||||
|
||||
#include "gba/bios.h"
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/io.h"
|
||||
|
@ -13,8 +16,6 @@
|
|||
#include "gba/serialize.h"
|
||||
#include "gba/sio.h"
|
||||
|
||||
#include "isa-inlines.h"
|
||||
|
||||
#include "util/crc32.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
|
@ -27,6 +28,8 @@ const uint32_t GBA_COMPONENT_MAGIC = 0x1000000;
|
|||
static const size_t GBA_ROM_MAGIC_OFFSET = 3;
|
||||
static const uint8_t GBA_ROM_MAGIC[] = { 0xEA };
|
||||
|
||||
static const size_t GBA_MB_MAGIC_OFFSET = 0xC0;
|
||||
|
||||
static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component);
|
||||
static void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh);
|
||||
static void GBAProcessEvents(struct ARMCore* cpu);
|
||||
|
@ -95,15 +98,16 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
|
||||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
gba->idleLoop = IDLE_LOOP_NONE;
|
||||
gba->lastJump = 0;
|
||||
gba->haltPending = false;
|
||||
gba->idleDetectionStep = 0;
|
||||
gba->idleDetectionFailures = 0;
|
||||
|
||||
gba->realisticTiming = true;
|
||||
gba->hardCrash = true;
|
||||
gba->allowOpposingDirections = true;
|
||||
|
||||
gba->performingDMA = false;
|
||||
|
||||
gba->pristineRom = 0;
|
||||
gba->pristineRomSize = 0;
|
||||
gba->yankedRomSize = 0;
|
||||
}
|
||||
|
||||
void GBAUnloadROM(struct GBA* gba) {
|
||||
|
@ -124,6 +128,7 @@ void GBAUnloadROM(struct GBA* gba) {
|
|||
}
|
||||
|
||||
GBASavedataDeinit(&gba->memory.savedata);
|
||||
gba->idleLoop = IDLE_LOOP_NONE;
|
||||
}
|
||||
|
||||
void GBADestroy(struct GBA* gba) {
|
||||
|
@ -179,11 +184,21 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
|
||||
gba->timersEnabled = 0;
|
||||
memset(gba->timers, 0, sizeof(gba->timers));
|
||||
|
||||
gba->lastJump = 0;
|
||||
gba->haltPending = false;
|
||||
gba->idleDetectionStep = 0;
|
||||
gba->idleDetectionFailures = 0;
|
||||
}
|
||||
|
||||
void GBASkipBIOS(struct ARMCore* cpu) {
|
||||
void GBASkipBIOS(struct GBA* gba) {
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
|
||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||
if (gba->memory.rom) {
|
||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||
} else {
|
||||
cpu->gprs[ARM_PC] = BASE_WORKING_RAM;
|
||||
}
|
||||
int currentCycles = 0;
|
||||
ARM_WRITE_PC;
|
||||
}
|
||||
|
@ -252,7 +267,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
struct GBATimer* nextTimer;
|
||||
|
||||
timer = &gba->timers[0];
|
||||
if (timer->enable) {
|
||||
if (GBATimerFlagsIsEnable(timer->flags)) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
while (timer->nextEvent <= 0) {
|
||||
|
@ -261,7 +276,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
|
||||
if (timer->doIrq) {
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0);
|
||||
}
|
||||
|
||||
|
@ -276,7 +291,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
|
||||
nextTimer = &gba->timers[1];
|
||||
if (nextTimer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
|
||||
++gba->memory.io[REG_TM1CNT_LO >> 1];
|
||||
if (!gba->memory.io[REG_TM1CNT_LO >> 1]) {
|
||||
nextTimer->nextEvent = 0;
|
||||
|
@ -287,7 +302,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
|
||||
timer = &gba->timers[1];
|
||||
if (timer->enable) {
|
||||
if (GBATimerFlagsIsEnable(timer->flags)) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
if (timer->nextEvent <= 0) {
|
||||
|
@ -296,7 +311,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
gba->memory.io[REG_TM1CNT_LO >> 1] = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
|
||||
if (timer->doIrq) {
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER1);
|
||||
}
|
||||
|
||||
|
@ -310,12 +325,12 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
}
|
||||
|
||||
if (timer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
timer->nextEvent = INT_MAX;
|
||||
}
|
||||
|
||||
nextTimer = &gba->timers[2];
|
||||
if (nextTimer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
|
||||
++gba->memory.io[REG_TM2CNT_LO >> 1];
|
||||
if (!gba->memory.io[REG_TM2CNT_LO >> 1]) {
|
||||
nextTimer->nextEvent = 0;
|
||||
|
@ -328,7 +343,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
|
||||
timer = &gba->timers[2];
|
||||
if (timer->enable) {
|
||||
if (GBATimerFlagsIsEnable(timer->flags)) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
if (timer->nextEvent <= 0) {
|
||||
|
@ -337,16 +352,16 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
gba->memory.io[REG_TM2CNT_LO >> 1] = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
|
||||
if (timer->doIrq) {
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER2);
|
||||
}
|
||||
|
||||
if (timer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
timer->nextEvent = INT_MAX;
|
||||
}
|
||||
|
||||
nextTimer = &gba->timers[3];
|
||||
if (nextTimer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
|
||||
++gba->memory.io[REG_TM3CNT_LO >> 1];
|
||||
if (!gba->memory.io[REG_TM3CNT_LO >> 1]) {
|
||||
nextTimer->nextEvent = 0;
|
||||
|
@ -359,7 +374,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
}
|
||||
|
||||
timer = &gba->timers[3];
|
||||
if (timer->enable) {
|
||||
if (GBATimerFlagsIsEnable(timer->flags)) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
if (timer->nextEvent <= 0) {
|
||||
|
@ -368,11 +383,11 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
|
|||
gba->memory.io[REG_TM3CNT_LO >> 1] = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
|
||||
if (timer->doIrq) {
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER3);
|
||||
}
|
||||
|
||||
if (timer->countUp) {
|
||||
if (GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
timer->nextEvent = INT_MAX;
|
||||
}
|
||||
}
|
||||
|
@ -398,6 +413,35 @@ void GBADetachDebugger(struct GBA* gba) {
|
|||
gba->cpu->components[GBA_COMPONENT_DEBUGGER] = 0;
|
||||
}
|
||||
|
||||
bool GBALoadMB(struct GBA* gba, struct VFile* vf, const char* fname) {
|
||||
GBAUnloadROM(gba);
|
||||
gba->romVf = vf;
|
||||
gba->pristineRomSize = vf->size(vf);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (gba->pristineRomSize > SIZE_WORKING_RAM) {
|
||||
gba->pristineRomSize = SIZE_WORKING_RAM;
|
||||
}
|
||||
#ifdef _3DS
|
||||
gba->pristineRom = 0;
|
||||
if (gba->pristineRomSize <= romBufferSize) {
|
||||
gba->pristineRom = romBuffer;
|
||||
vf->read(vf, romBuffer, gba->pristineRomSize);
|
||||
}
|
||||
#else
|
||||
gba->pristineRom = vf->map(vf, gba->pristineRomSize, MAP_READ);
|
||||
#endif
|
||||
if (!gba->pristineRom) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM");
|
||||
return false;
|
||||
}
|
||||
gba->yankedRomSize = 0;
|
||||
gba->activeFile = fname;
|
||||
gba->memory.romSize = 0;
|
||||
gba->memory.romMask = 0;
|
||||
gba->romCrc32 = doCrc32(gba->pristineRom, gba->pristineRomSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) {
|
||||
GBAUnloadROM(gba);
|
||||
gba->romVf = vf;
|
||||
|
@ -481,47 +525,47 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
|
|||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer) {
|
||||
struct GBATimer* currentTimer = &gba->timers[timer];
|
||||
if (currentTimer->enable && !currentTimer->countUp) {
|
||||
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
int32_t prefetchSkew = 0;
|
||||
if (gba->memory.lastPrefetchedPc - gba->memory.lastPrefetchedLoads * WORD_SIZE_THUMB >= (uint32_t) gba->cpu->gprs[ARM_PC]) {
|
||||
prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB;
|
||||
}
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent - 2 + prefetchSkew) >> currentTimer->prescaleBits);
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
|
||||
gba->timers[timer].reload = reload;
|
||||
gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << gba->timers[timer].prescaleBits;
|
||||
gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << GBATimerFlagsGetPrescaleBits(gba->timers[timer].flags);
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
||||
struct GBATimer* currentTimer = &gba->timers[timer];
|
||||
GBATimerUpdateRegister(gba, timer);
|
||||
|
||||
int oldPrescale = currentTimer->prescaleBits;
|
||||
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
|
||||
switch (control & 0x0003) {
|
||||
case 0x0000:
|
||||
currentTimer->prescaleBits = 0;
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 0);
|
||||
break;
|
||||
case 0x0001:
|
||||
currentTimer->prescaleBits = 6;
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 6);
|
||||
break;
|
||||
case 0x0002:
|
||||
currentTimer->prescaleBits = 8;
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 8);
|
||||
break;
|
||||
case 0x0003:
|
||||
currentTimer->prescaleBits = 10;
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 10);
|
||||
break;
|
||||
}
|
||||
currentTimer->countUp = !!(control & 0x0004);
|
||||
currentTimer->doIrq = !!(control & 0x0040);
|
||||
currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << currentTimer->prescaleBits;
|
||||
int wasEnabled = currentTimer->enable;
|
||||
currentTimer->enable = !!(control & 0x0080);
|
||||
if (!wasEnabled && currentTimer->enable) {
|
||||
if (!currentTimer->countUp) {
|
||||
currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, control & 0x0004);
|
||||
currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040);
|
||||
currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << GBATimerFlagsGetPrescaleBits(currentTimer->flags);
|
||||
bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags);
|
||||
currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080);
|
||||
if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) {
|
||||
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
currentTimer->nextEvent = gba->cpu->cycles + currentTimer->overflowInterval;
|
||||
} else {
|
||||
currentTimer->nextEvent = INT_MAX;
|
||||
|
@ -530,12 +574,12 @@ void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
|||
currentTimer->oldReload = currentTimer->reload;
|
||||
currentTimer->lastEvent = gba->cpu->cycles;
|
||||
gba->timersEnabled |= 1 << timer;
|
||||
} else if (wasEnabled && !currentTimer->enable) {
|
||||
if (!currentTimer->countUp) {
|
||||
} else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
|
||||
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent) >> oldPrescale);
|
||||
}
|
||||
gba->timersEnabled &= ~(1 << timer);
|
||||
} else if (currentTimer->prescaleBits != oldPrescale && !currentTimer->countUp) {
|
||||
} else if (GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
// FIXME: this might be before present
|
||||
currentTimer->nextEvent = currentTimer->lastEvent + currentTimer->overflowInterval;
|
||||
}
|
||||
|
@ -574,12 +618,12 @@ void GBATestIRQ(struct ARMCore* cpu) {
|
|||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
if (gba->memory.io[REG_IME >> 1] && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) {
|
||||
gba->springIRQ = 1;
|
||||
gba->cpu->nextEvent = 0;
|
||||
gba->cpu->nextEvent = gba->cpu->cycles;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAHalt(struct GBA* gba) {
|
||||
gba->cpu->nextEvent = 0;
|
||||
gba->cpu->nextEvent = gba->cpu->cycles;
|
||||
gba->cpu->halted = 1;
|
||||
}
|
||||
|
||||
|
@ -587,7 +631,7 @@ void GBAStop(struct GBA* gba) {
|
|||
if (!gba->stopCallback) {
|
||||
return;
|
||||
}
|
||||
gba->cpu->nextEvent = 0;
|
||||
gba->cpu->nextEvent = gba->cpu->cycles;
|
||||
gba->stopCallback->stop(gba->stopCallback);
|
||||
}
|
||||
|
||||
|
@ -675,9 +719,45 @@ bool GBAIsROM(struct VFile* vf) {
|
|||
if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) {
|
||||
return false;
|
||||
}
|
||||
if (GBAIsBIOS(vf)) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(signature, GBA_ROM_MAGIC, sizeof(signature)) == 0;
|
||||
}
|
||||
|
||||
bool GBAIsMB(struct VFile* vf) {
|
||||
if (!GBAIsROM(vf)) {
|
||||
return false;
|
||||
}
|
||||
if (vf->size(vf) > SIZE_WORKING_RAM) {
|
||||
return false;
|
||||
}
|
||||
if (vf->seek(vf, GBA_MB_MAGIC_OFFSET, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t signature;
|
||||
if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t opcode;
|
||||
LOAD_32(opcode, 0, &signature);
|
||||
struct ARMInstructionInfo info;
|
||||
ARMDecodeARM(opcode, &info);
|
||||
if (info.branchType != ARM_BRANCH) {
|
||||
return false;
|
||||
}
|
||||
if (info.op1.immediate <= 0) {
|
||||
return false;
|
||||
} else if (info.op1.immediate == 28) {
|
||||
// Ancient toolchain that is known to throw MB detection for a loop
|
||||
return false;
|
||||
} else if (info.op1.immediate != 24) {
|
||||
return true;
|
||||
}
|
||||
// Found a libgba-linked cart...these are a bit harder to detect.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBAIsBIOS(struct VFile* vf) {
|
||||
if (vf->seek(vf, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
|
@ -704,11 +784,15 @@ void GBAGetGameCode(struct GBA* gba, char* out) {
|
|||
}
|
||||
|
||||
void GBAGetGameTitle(struct GBA* gba, char* out) {
|
||||
if (!gba->memory.rom) {
|
||||
strncpy(out, "(BIOS)", 12);
|
||||
if (gba->memory.rom) {
|
||||
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12);
|
||||
return;
|
||||
}
|
||||
memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12);
|
||||
if (gba->pristineRom) {
|
||||
memcpy(out, &((struct GBACartridge*) gba->pristineRom)->title, 12);
|
||||
return;
|
||||
}
|
||||
strncpy(out, "(BIOS)", 12);
|
||||
}
|
||||
|
||||
void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {
|
||||
|
@ -828,6 +912,13 @@ void GBAFrameEnded(struct GBA* gba) {
|
|||
if (thread->frameCallback) {
|
||||
thread->frameCallback(thread);
|
||||
}
|
||||
|
||||
if (gba->rr && gba->rr->queryReset(gba->rr)) {
|
||||
// TODO: Clean up reset scheduling
|
||||
MutexLock(&thread->stateMutex);
|
||||
thread->state = THREAD_RESETING;
|
||||
MutexUnlock(&thread->stateMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) {
|
||||
|
|
|
@ -59,16 +59,19 @@ struct GBAThread;
|
|||
struct Patch;
|
||||
struct VFile;
|
||||
|
||||
DECL_BITFIELD(GBATimerFlags, uint32_t);
|
||||
DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4);
|
||||
DECL_BIT(GBATimerFlags, CountUp, 4);
|
||||
DECL_BIT(GBATimerFlags, DoIrq, 5);
|
||||
DECL_BIT(GBATimerFlags, Enable, 6);
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
int32_t lastEvent;
|
||||
int32_t nextEvent;
|
||||
int32_t overflowInterval;
|
||||
unsigned prescaleBits : 4;
|
||||
unsigned countUp : 1;
|
||||
unsigned doIrq : 1;
|
||||
unsigned enable : 1;
|
||||
GBATimerFlags flags;
|
||||
};
|
||||
|
||||
struct GBA {
|
||||
|
@ -85,7 +88,7 @@ struct GBA {
|
|||
struct ARMDebugger* debugger;
|
||||
|
||||
uint32_t bus;
|
||||
bool performingDMA;
|
||||
int performingDMA;
|
||||
|
||||
int timersEnabled;
|
||||
struct GBATimer timers[4];
|
||||
|
@ -125,6 +128,7 @@ struct GBA {
|
|||
|
||||
bool realisticTiming;
|
||||
bool hardCrash;
|
||||
bool allowOpposingDirections;
|
||||
};
|
||||
|
||||
struct GBACartridge {
|
||||
|
@ -146,7 +150,7 @@ void GBACreate(struct GBA* gba);
|
|||
void GBADestroy(struct GBA* gba);
|
||||
|
||||
void GBAReset(struct ARMCore* cpu);
|
||||
void GBASkipBIOS(struct ARMCore* cpu);
|
||||
void GBASkipBIOS(struct GBA* gba);
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer);
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t value);
|
||||
|
@ -172,7 +176,10 @@ void GBAUnloadROM(struct GBA* gba);
|
|||
void GBALoadBIOS(struct GBA* gba, struct VFile* vf);
|
||||
void GBAApplyPatch(struct GBA* gba, struct Patch* patch);
|
||||
|
||||
bool GBALoadMB(struct GBA* gba, struct VFile* vf, const char* fname);
|
||||
|
||||
bool GBAIsROM(struct VFile* vf);
|
||||
bool GBAIsMB(struct VFile* vf);
|
||||
bool GBAIsBIOS(struct VFile* vf);
|
||||
void GBAGetGameCode(struct GBA* gba, char* out);
|
||||
void GBAGetGameTitle(struct GBA* gba, char* out);
|
||||
|
|
|
@ -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
|
||||
|
@ -6,9 +6,14 @@
|
|||
#include "gui-config.h"
|
||||
|
||||
#include "gba/gui/gui-runner.h"
|
||||
#include "gba/gui/remap.h"
|
||||
#include "util/gui/file-select.h"
|
||||
#include "util/gui/menu.h"
|
||||
|
||||
#ifndef GUI_MAX_INPUTS
|
||||
#define GUI_MAX_INPUTS 7
|
||||
#endif
|
||||
|
||||
void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra) {
|
||||
struct GUIMenu menu = {
|
||||
.title = "Configure",
|
||||
|
@ -22,8 +27,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 +37,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 +47,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",
|
||||
|
@ -76,7 +101,7 @@ 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);
|
||||
}
|
||||
|
@ -90,6 +115,10 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si
|
|||
GBAConfigSave(&runner->context.config);
|
||||
break;
|
||||
}
|
||||
if (!strcmp(item->data, "*REMAP")) {
|
||||
GBAGUIRemapKeys(&runner->params, &runner->context.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 +128,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ enum {
|
|||
RUNNER_LOAD_STATE,
|
||||
RUNNER_SCREENSHOT,
|
||||
RUNNER_CONFIG,
|
||||
RUNNER_RESET,
|
||||
RUNNER_COMMAND_MASK = 0xFFFF,
|
||||
|
||||
RUNNER_STATE_1 = 0x10000,
|
||||
|
@ -56,7 +57,7 @@ static void _drawState(struct GUIBackground* background, void* id) {
|
|||
gbaBackground->p->drawScreenshot(gbaBackground->p, gbaBackground->screenshot, true);
|
||||
return;
|
||||
}
|
||||
struct VFile* vf = GBAGetState(gbaBackground->p->context.gba, 0, stateId, false);
|
||||
struct VFile* vf = GBAGetState(gbaBackground->p->context.gba, gbaBackground->p->context.dirs.state, stateId, false);
|
||||
uint32_t* pixels = gbaBackground->screenshot;
|
||||
if (!pixels) {
|
||||
pixels = anonymousMemoryMap(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
|
@ -115,6 +116,13 @@ void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) {
|
|||
if (runner->setup) {
|
||||
runner->setup(runner);
|
||||
}
|
||||
|
||||
if (runner->context.config.port && runner->keySources) {
|
||||
size_t i;
|
||||
for (i = 0; runner->keySources[i].id; ++i) {
|
||||
GBAInputMapLoad(&runner->context.inputMap, runner->keySources[i].id, GBAConfigGetInput(&runner->context.config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAGUIDeinit(struct GBAGUIRunner* runner) {
|
||||
|
@ -122,13 +130,19 @@ void GBAGUIDeinit(struct GBAGUIRunner* runner) {
|
|||
runner->teardown(runner);
|
||||
}
|
||||
if (runner->context.config.port) {
|
||||
if (runner->keySources) {
|
||||
size_t i;
|
||||
for (i = 0; runner->keySources[i].id; ++i) {
|
||||
GBAInputMapSave(&runner->context.inputMap, runner->keySources[i].id, GBAConfigGetInput(&runner->context.config));
|
||||
}
|
||||
}
|
||||
GBAConfigSave(&runner->context.config);
|
||||
}
|
||||
CircleBufferDeinit(&runner->fpsBuffer);
|
||||
GBAContextDeinit(&runner->context);
|
||||
}
|
||||
|
||||
void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
||||
void GBAGUIRun(struct GBAGUIRunner* runner, const char* path) {
|
||||
struct GBAGUIBackground drawState = {
|
||||
.d = {
|
||||
.draw = _drawState
|
||||
|
@ -156,8 +170,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,178 +192,176 @@ 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;
|
||||
}
|
||||
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
GBAContextStart(&runner->context);
|
||||
if (runner->gameLoaded) {
|
||||
runner->gameLoaded(runner);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (guiKeys & (1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel < 10) {
|
||||
++runner->luminanceSource.luxLevel;
|
||||
}
|
||||
}
|
||||
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.drawStart();
|
||||
runner->drawFrame(runner, false);
|
||||
if (drawFps) {
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
}
|
||||
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 (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) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
GBAContextUnloadROM(&runner->context);
|
||||
drawState.screenshotId = 0;
|
||||
// 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 (!GBAContextLoadROM(&runner->context, path, true)) {
|
||||
int i;
|
||||
for (i = 0; i < 300; ++i) {
|
||||
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, "Load failed!");
|
||||
if (runner->params.guiFinish) {
|
||||
runner->params.guiFinish();
|
||||
}
|
||||
runner->params.drawEnd();
|
||||
}
|
||||
return;
|
||||
}
|
||||
bool running = GBAContextStart(&runner->context);
|
||||
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;
|
||||
|
||||
while (true) {
|
||||
uint32_t guiKeys;
|
||||
GUIPollInput(&runner->params, &guiKeys, 0);
|
||||
if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
|
||||
break;
|
||||
}
|
||||
if (guiKeys & (1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS)) {
|
||||
if (runner->luminanceSource.luxLevel < 10) {
|
||||
++runner->luminanceSource.luxLevel;
|
||||
}
|
||||
}
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
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 (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) {
|
||||
struct VFile* vf;
|
||||
switch (((int) item->data) & RUNNER_COMMAND_MASK) {
|
||||
case RUNNER_EXIT:
|
||||
running = false;
|
||||
keys = 0;
|
||||
break;
|
||||
case RUNNER_RESET:
|
||||
GBAContextReset(&runner->context);
|
||||
break;
|
||||
case RUNNER_SAVE_STATE:
|
||||
vf = GBAGetState(runner->context.gba, runner->context.dirs.state, ((int) item->data) >> 16, true);
|
||||
if (vf) {
|
||||
GBASaveStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
case RUNNER_LOAD_STATE:
|
||||
vf = GBAGetState(runner->context.gba, runner->context.dirs.state, ((int) item->data) >> 16, false);
|
||||
if (vf) {
|
||||
GBALoadStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
case RUNNER_SCREENSHOT:
|
||||
GBATakeScreenshot(runner->context.gba, runner->context.dirs.screenshot);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
GBAContextUnloadROM(&runner->context);
|
||||
drawState.screenshotId = 0;
|
||||
if (drawState.screenshot) {
|
||||
mappedMemoryFree(drawState.screenshot, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
}
|
||||
|
@ -359,3 +369,13 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
|||
GUIMenuItemListDeinit(&stateSaveMenu.items);
|
||||
GUIMenuItemListDeinit(&stateLoadMenu.items);
|
||||
}
|
||||
|
||||
void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
||||
while (true) {
|
||||
char path[PATH_MAX];
|
||||
if (!GUISelectFile(&runner->params, path, sizeof(path), 0)) {
|
||||
break;
|
||||
}
|
||||
GBAGUIRun(runner, path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -7,6 +7,7 @@
|
|||
#define GUI_RUNNER_H
|
||||
|
||||
#include "gba/context/context.h"
|
||||
#include "gba/gui/remap.h"
|
||||
#include "util/circle-buffer.h"
|
||||
#include "util/gui.h"
|
||||
|
||||
|
@ -39,6 +40,8 @@ struct GBAGUIRunner {
|
|||
struct GUIMenuItem* configExtra;
|
||||
size_t nConfigExtra;
|
||||
|
||||
struct GUIInputKeys* keySources;
|
||||
|
||||
float fps;
|
||||
int64_t lastFpsCheck;
|
||||
int32_t totalDelta;
|
||||
|
@ -59,6 +62,7 @@ struct GBAGUIRunner {
|
|||
|
||||
void GBAGUIInit(struct GBAGUIRunner*, const char* port);
|
||||
void GBAGUIDeinit(struct GBAGUIRunner*);
|
||||
void GBAGUIRun(struct GBAGUIRunner*, const char* path);
|
||||
void GBAGUIRunloop(struct GBAGUIRunner*);
|
||||
|
||||
#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 GBAGUIRemapKeys(struct GUIParams* params, struct GBAInputMap* 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 = GBAKeyNames[i],
|
||||
.data = (void*) (GUI_INPUT_MAX + i),
|
||||
.submenu = 0,
|
||||
.state = GBAInputQueryBinding(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) {
|
||||
GBAInputBindKey(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 GBAInputMap;
|
||||
|
||||
void GBAGUIRemapKeys(struct GUIParams*, struct GBAInputMap*, const struct GUIInputKeys*);
|
||||
|
||||
#endif
|
|
@ -87,9 +87,11 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
|
|||
GBALog(hw->p, GBA_LOG_WARN, "Invalid GPIO address");
|
||||
}
|
||||
if (hw->readWrite) {
|
||||
uint16_t old = hw->gpioBase[0];
|
||||
uint16_t old;
|
||||
LOAD_16(old, 0, hw->gpioBase);
|
||||
old &= ~hw->direction;
|
||||
hw->gpioBase[0] = old | hw->pinState;
|
||||
old |= hw->pinState;
|
||||
STORE_16(old, 0, hw->gpioBase);
|
||||
} else {
|
||||
hw->gpioBase[0] = 0;
|
||||
}
|
||||
|
@ -129,10 +131,11 @@ void _readPins(struct GBACartridgeHardware* hw) {
|
|||
|
||||
void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) {
|
||||
if (hw->readWrite) {
|
||||
uint16_t old = hw->gpioBase[0];
|
||||
uint16_t old;
|
||||
LOAD_16(old, 0, hw->gpioBase);
|
||||
old &= hw->direction;
|
||||
hw->pinState = old | (pins & ~hw->direction & 0xF);
|
||||
hw->gpioBase[0] = hw->pinState;
|
||||
STORE_16(hw->pinState, 0, hw->gpioBase);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,9 +489,7 @@ static const uint32_t _gbpTxData[] = {
|
|||
0xB1BA4E45, 0xB1BA4F44,
|
||||
0xB0BB4F44, 0xB0BB8002,
|
||||
0x10000010, 0x20000013,
|
||||
0x30000003, 0x30000003,
|
||||
0x30000003, 0x30000003,
|
||||
0x30000003, 0x00000000,
|
||||
0x30000003
|
||||
};
|
||||
|
||||
static const uint32_t _gbpRxData[] = {
|
||||
|
@ -498,9 +499,7 @@ static const uint32_t _gbpRxData[] = {
|
|||
0x4E45B1BA, 0x4F44B1BA,
|
||||
0x4F44B0BB, 0x8000B0BB,
|
||||
0x10000010, 0x20000013,
|
||||
0x40000004, 0x40000004,
|
||||
0x40000004, 0x40000004,
|
||||
0x40000004, 0x40000004
|
||||
0x40000004
|
||||
};
|
||||
|
||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
||||
|
@ -548,16 +547,17 @@ uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uin
|
|||
struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
if (value & 0x0080) {
|
||||
if (gbp->p->gbpTxPosition <= 16 && gbp->p->gbpTxPosition > 0) {
|
||||
uint32_t rx = gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] << 16);
|
||||
uint32_t rx = gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] << 16);
|
||||
if (gbp->p->gbpTxPosition < 12 && gbp->p->gbpTxPosition > 0) {
|
||||
uint32_t expected = _gbpRxData[gbp->p->gbpTxPosition];
|
||||
// TODO: Check expected
|
||||
uint32_t mask = 0;
|
||||
if (gbp->p->gbpTxPosition == 15) {
|
||||
mask = 0x22;
|
||||
if (gbp->p->p->rumble) {
|
||||
gbp->p->p->rumble->setRumble(gbp->p->p->rumble, (rx & mask) == mask);
|
||||
}
|
||||
} else if (gbp->p->gbpTxPosition >= 12) {
|
||||
uint32_t mask = 0x33;
|
||||
// 0x00 = Stop
|
||||
// 0x11 = Hard Stop
|
||||
// 0x22 = Start
|
||||
if (gbp->p->p->rumble) {
|
||||
gbp->p->p->rumble->setRumble(gbp->p->p->rumble, (rx & mask) == 0x22);
|
||||
}
|
||||
}
|
||||
gbp->p->gbpNextEvent = 2048;
|
||||
|
@ -572,9 +572,11 @@ int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
|||
gbp->p->gbpNextEvent -= cycles;
|
||||
if (gbp->p->gbpNextEvent <= 0) {
|
||||
uint32_t tx = 0;
|
||||
if (gbp->p->gbpTxPosition <= 16) {
|
||||
if (gbp->p->gbpTxPosition <= 12) {
|
||||
tx = _gbpTxData[gbp->p->gbpTxPosition];
|
||||
++gbp->p->gbpTxPosition;
|
||||
if (gbp->p->gbpTxPosition < 12) {
|
||||
++gbp->p->gbpTxPosition;
|
||||
}
|
||||
}
|
||||
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
||||
|
@ -591,41 +593,65 @@ int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
|||
// == Serialization
|
||||
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
state->hw.readWrite = hw->readWrite;
|
||||
state->hw.pinState = hw->pinState;
|
||||
state->hw.pinDirection = hw->direction;
|
||||
GBASerializedHWFlags1 flags1 = 0;
|
||||
GBASerializedHWFlags2 flags2 = 0;
|
||||
flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite);
|
||||
STORE_16(hw->pinState, 0, &state->hw.pinState);
|
||||
STORE_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
state->hw.devices = hw->devices;
|
||||
state->hw.rtc = hw->rtc;
|
||||
state->hw.gyroSample = hw->gyroSample;
|
||||
state->hw.gyroEdge = hw->gyroEdge;
|
||||
state->hw.tiltSampleX = hw->tiltX;
|
||||
state->hw.tiltSampleY = hw->tiltY;
|
||||
state->hw.tiltState = hw->tiltState;
|
||||
state->hw.lightCounter = hw->lightCounter;
|
||||
|
||||
STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtc.bytesRemaining);
|
||||
STORE_32(hw->rtc.transferStep, 0, &state->hw.rtc.transferStep);
|
||||
STORE_32(hw->rtc.bitsRead, 0, &state->hw.rtc.bitsRead);
|
||||
STORE_32(hw->rtc.bits, 0, &state->hw.rtc.bits);
|
||||
STORE_32(hw->rtc.commandActive, 0, &state->hw.rtc.commandActive);
|
||||
STORE_32(hw->rtc.command, 0, &state->hw.rtc.command);
|
||||
STORE_32(hw->rtc.control, 0, &state->hw.rtc.control);
|
||||
memcpy(state->hw.rtc.time, hw->rtc.time, sizeof(state->hw.rtc.time));
|
||||
|
||||
STORE_16(hw->gyroSample, 0, &state->hw.gyroSample);
|
||||
flags1 = GBASerializedHWFlags1SetGyroEdge(flags1, hw->gyroEdge);
|
||||
STORE_16(hw->tiltX, 0, &state->hw.tiltSampleX);
|
||||
STORE_16(hw->tiltY, 0, &state->hw.tiltSampleY);
|
||||
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
|
||||
flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter);
|
||||
state->hw.lightSample = hw->lightSample;
|
||||
state->hw.lightEdge = hw->lightEdge;
|
||||
state->hw.gbpInputsPosted = hw->gbpInputsPosted;
|
||||
state->hw.gbpTxPosition = hw->gbpTxPosition;
|
||||
state->hw.gbpNextEvent = hw->gbpNextEvent;
|
||||
flags1 = GBASerializedHWFlags1SetLightEdge(flags1, hw->lightEdge);
|
||||
flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->gbpInputsPosted);
|
||||
flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->gbpTxPosition);
|
||||
STORE_32(hw->gbpNextEvent, 0, &state->hw.gbpNextEvent);
|
||||
STORE_16(flags1, 0, &state->hw.flags1);
|
||||
state->hw.flags2 = flags2;
|
||||
}
|
||||
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||
hw->readWrite = state->hw.readWrite;
|
||||
hw->pinState = state->hw.pinState;
|
||||
hw->direction = state->hw.pinDirection;
|
||||
GBASerializedHWFlags1 flags1;
|
||||
LOAD_16(flags1, 0, &state->hw.flags1);
|
||||
hw->readWrite = GBASerializedHWFlags1GetReadWrite(flags1);
|
||||
LOAD_16(hw->pinState, 0, &state->hw.pinState);
|
||||
LOAD_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
hw->devices = state->hw.devices;
|
||||
hw->rtc = state->hw.rtc;
|
||||
hw->gyroSample = state->hw.gyroSample;
|
||||
hw->gyroEdge = state->hw.gyroEdge;
|
||||
hw->tiltX = state->hw.tiltSampleX;
|
||||
hw->tiltY = state->hw.tiltSampleY;
|
||||
hw->tiltState = state->hw.tiltState;
|
||||
hw->lightCounter = state->hw.lightCounter;
|
||||
|
||||
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtc.bytesRemaining);
|
||||
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtc.transferStep);
|
||||
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtc.bitsRead);
|
||||
LOAD_32(hw->rtc.bits, 0, &state->hw.rtc.bits);
|
||||
LOAD_32(hw->rtc.commandActive, 0, &state->hw.rtc.commandActive);
|
||||
LOAD_32(hw->rtc.command, 0, &state->hw.rtc.command);
|
||||
LOAD_32(hw->rtc.control, 0, &state->hw.rtc.control);
|
||||
memcpy(hw->rtc.time, state->hw.rtc.time, sizeof(hw->rtc.time));
|
||||
|
||||
LOAD_16(hw->gyroSample, 0, &state->hw.gyroSample);
|
||||
hw->gyroEdge = GBASerializedHWFlags1GetGyroEdge(flags1);
|
||||
LOAD_16(hw->tiltX, 0, &state->hw.tiltSampleX);
|
||||
LOAD_16(hw->tiltY, 0, &state->hw.tiltSampleY);
|
||||
hw->tiltState = GBASerializedHWFlags2GetTiltState(state->hw.flags2);
|
||||
hw->lightCounter = GBASerializedHWFlags1GetLightCounter(flags1);
|
||||
hw->lightSample = state->hw.lightSample;
|
||||
hw->lightEdge = state->hw.lightEdge;
|
||||
hw->gbpInputsPosted = state->hw.gbpInputsPosted;
|
||||
hw->gbpTxPosition = state->hw.gbpTxPosition;
|
||||
hw->gbpNextEvent = state->hw.gbpNextEvent;
|
||||
hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1);
|
||||
hw->gbpInputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2);
|
||||
hw->gbpTxPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
|
||||
LOAD_32(hw->gbpNextEvent, 0, &state->hw.gbpNextEvent);
|
||||
if (hw->devices & HW_GB_PLAYER) {
|
||||
GBASIOSetDriver(&hw->p->sio, &hw->gbpDriver.d, SIO_NORMAL_32);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ DECL_BITFIELD(GPIOPin, uint16_t);
|
|||
|
||||
struct GBACartridgeHardware {
|
||||
struct GBA* p;
|
||||
int devices;
|
||||
uint32_t devices;
|
||||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
|
|
|
@ -3,49 +3,50 @@
|
|||
#include "gba/memory.h"
|
||||
|
||||
const uint8_t hleBios[SIZE_BIOS] = {
|
||||
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x07, 0x00, 0x00, 0xea,
|
||||
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x0b, 0x00, 0x00, 0xea,
|
||||
0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1,
|
||||
0x28, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0x00, 0x00, 0x5d, 0xe3,
|
||||
0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9,
|
||||
0x02, 0xb0, 0x5e, 0xe5, 0x8c, 0xc0, 0xa0, 0xe3, 0x0b, 0xb1, 0x9c, 0xe7,
|
||||
0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0x10, 0x2d, 0xe9,
|
||||
0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, 0x0c, 0xf0, 0x29, 0xe1,
|
||||
0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, 0x1b, 0xff, 0x2f, 0x11,
|
||||
0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x10, 0xbd, 0xe8,
|
||||
0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2c, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0x03, 0xa0, 0xe3,
|
||||
0x03, 0x10, 0xd0, 0xe5, 0xea, 0x00, 0x51, 0xe3, 0x02, 0x04, 0xa0, 0x13,
|
||||
0x10, 0xff, 0x2f, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1,
|
||||
0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02,
|
||||
0x00, 0x58, 0x2d, 0xe9, 0x02, 0xb0, 0x5e, 0xe5, 0x9c, 0xc0, 0xa0, 0xe3,
|
||||
0x0b, 0xb1, 0x9c, 0xe7, 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1,
|
||||
0x00, 0x10, 0x2d, 0xe9, 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3,
|
||||
0x0c, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0x1b, 0xff, 0x2f, 0x11, 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3,
|
||||
0x00, 0x10, 0xbd, 0xe8, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8,
|
||||
0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xe8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
|
||||
0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2,
|
||||
0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x01, 0x00, 0xa0, 0xe3,
|
||||
0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3,
|
||||
0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1,
|
||||
0xb8, 0x30, 0x4c, 0xe1, 0x01, 0x03, 0xcc, 0xe5, 0x08, 0x02, 0xcc, 0xe5,
|
||||
0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0x13, 0xe0, 0x01, 0x30, 0x23, 0x10,
|
||||
0xb8, 0x30, 0x4c, 0x11, 0x08, 0x22, 0xcc, 0xe5, 0xf7, 0xff, 0xff, 0x0a,
|
||||
0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x02, 0x36, 0xa0, 0xe1,
|
||||
0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3,
|
||||
0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8,
|
||||
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba,
|
||||
0x16, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3,
|
||||
0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1,
|
||||
0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00,
|
||||
0xc8, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,
|
||||
0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8,
|
||||
0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5,
|
||||
0x01, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9,
|
||||
0x01, 0xc3, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3,
|
||||
0x01, 0x20, 0xa0, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1,
|
||||
0x01, 0x30, 0xc3, 0xe1, 0xb8, 0x30, 0x4c, 0xe1, 0x01, 0x03, 0xcc, 0xe5,
|
||||
0x08, 0x02, 0xcc, 0xe5, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0x13, 0xe0,
|
||||
0x01, 0x30, 0x23, 0x10, 0xb8, 0x30, 0x4c, 0x11, 0x08, 0x22, 0xcc, 0xe5,
|
||||
0xf7, 0xff, 0xff, 0x0a, 0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9,
|
||||
0x02, 0x36, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a,
|
||||
0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0,
|
||||
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8,
|
||||
0xfb, 0xff, 0xff, 0xba, 0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0,
|
||||
0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1,
|
||||
0xb2, 0x20, 0xd0, 0xb0, 0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba,
|
||||
0x00, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3,
|
||||
0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a,
|
||||
0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1,
|
||||
0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1,
|
||||
0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1,
|
||||
0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea,
|
||||
0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8,
|
||||
0xfb, 0xff, 0xff, 0xba, 0xf0, 0x87, 0xbd, 0xe8
|
||||
0x04, 0x00, 0xb0, 0xe8, 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8,
|
||||
0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3,
|
||||
0x01, 0x10, 0xc1, 0xe3, 0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1,
|
||||
0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba,
|
||||
0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
|
||||
0x23, 0x35, 0x81, 0xe0, 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8,
|
||||
0x04, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0x04, 0x00, 0x00, 0xea,
|
||||
0xa3, 0x35, 0x81, 0xe0, 0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xd0, 0xb0,
|
||||
0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0x00, 0x80, 0xbd, 0xe8,
|
||||
0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3, 0x02, 0x36, 0xa0, 0xe1,
|
||||
0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a, 0x00, 0x30, 0x90, 0xe5,
|
||||
0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1, 0x03, 0x60, 0xa0, 0xe1,
|
||||
0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1, 0x03, 0x90, 0xa0, 0xe1,
|
||||
0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xa1, 0xb8,
|
||||
0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea, 0x02, 0x00, 0x51, 0xe1,
|
||||
0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
|
||||
0xf0, 0x87, 0xbd, 0xe8
|
||||
};
|
||||
|
|
|
@ -17,7 +17,11 @@ b irqBase
|
|||
b fiqBase
|
||||
|
||||
resetBase:
|
||||
mov pc, #0x8000000
|
||||
mov r0, #0x8000000
|
||||
ldrb r1, [r0, #3]
|
||||
cmp r1, #0xEA
|
||||
movne r0, #0x2000000
|
||||
bx r0
|
||||
.word 0
|
||||
.word 0xE129F000
|
||||
|
||||
|
@ -143,8 +147,6 @@ b 3f
|
|||
# Halfword
|
||||
1:
|
||||
add r3, r1, r3, lsr #11
|
||||
bic r0, #1
|
||||
bic r1, #1
|
||||
2:
|
||||
cmp r1, r3
|
||||
ldrlth r2, [r0], #2
|
||||
|
|
|
@ -95,7 +95,11 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
impl->map = malloc(GBA_KEY_MAX * sizeof(int));
|
||||
int i;
|
||||
for (i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
impl->map[i] = GBA_KEY_NONE;
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
} else {
|
||||
impl = _lookupMap(map, type);
|
||||
|
@ -110,7 +114,11 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
impl->map = malloc(GBA_KEY_MAX * sizeof(int));
|
||||
int i;
|
||||
for (i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
impl->map[i] = GBA_KEY_NONE;
|
||||
}
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
|
@ -120,7 +128,11 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
impl->map = malloc(GBA_KEY_MAX * sizeof(int));
|
||||
int i;
|
||||
for (i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
impl->map[i] = GBA_KEY_NONE;
|
||||
}
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
}
|
||||
|
@ -378,18 +390,18 @@ void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input
|
|||
return;
|
||||
}
|
||||
if (impl) {
|
||||
impl->map[input] = GBA_NO_MAPPING;
|
||||
impl->map[input] = GBA_KEY_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||
if (input >= GBA_KEY_MAX) {
|
||||
return 0;
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl || !impl->map) {
|
||||
return 0;
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
return impl->map[input];
|
||||
|
|
|
@ -22,8 +22,6 @@ struct GBAAxis {
|
|||
int32_t deadLow;
|
||||
};
|
||||
|
||||
#define GBA_NO_MAPPING -1
|
||||
|
||||
extern const char* GBAKeyNames[];
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap*);
|
||||
|
|
264
src/gba/io.c
264
src/gba/io.c
|
@ -210,7 +210,7 @@ static const int _isValidRegister[REG_MAX >> 1] = {
|
|||
1, 1, 1, 0, 1, 0, 1, 0,
|
||||
1, 1, 1, 0, 1, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 0, 0,
|
||||
1, 1, 1, 1, 0, 0, 0, 0,
|
||||
// DMA
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
@ -239,7 +239,48 @@ static const int _isValidRegister[REG_MAX >> 1] = {
|
|||
1, 1, 1, 0, 1
|
||||
};
|
||||
|
||||
static const int _isSpecialRegister[REG_MAX >> 1] = {
|
||||
static const int _isRSpecialRegister[REG_MAX >> 1] = {
|
||||
// Video
|
||||
0, 0, 1, 1, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// Audio
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 0, 0, 0, 0,
|
||||
// DMA
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Timers
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// SIO
|
||||
1, 1, 1, 1, 1, 0, 0, 0,
|
||||
1, 1, 1, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 0, 1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Interrupts
|
||||
};
|
||||
|
||||
static const int _isWSpecialRegister[REG_MAX >> 1] = {
|
||||
// Video
|
||||
0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
@ -250,7 +291,7 @@ static const int _isSpecialRegister[REG_MAX >> 1] = {
|
|||
// Audio
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 0, 0, 0, 0,
|
||||
// DMA
|
||||
|
@ -306,7 +347,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
// Audio
|
||||
case REG_SOUND1CNT_LO:
|
||||
GBAAudioWriteSOUND1CNT_LO(&gba->audio, value);
|
||||
value &= 0x00FF;
|
||||
value &= 0x007F;
|
||||
break;
|
||||
case REG_SOUND1CNT_HI:
|
||||
GBAAudioWriteSOUND1CNT_HI(&gba->audio, value);
|
||||
|
@ -345,12 +386,16 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
break;
|
||||
case REG_SOUNDCNT_LO:
|
||||
GBAAudioWriteSOUNDCNT_LO(&gba->audio, value);
|
||||
value &= 0xFF77;
|
||||
break;
|
||||
case REG_SOUNDCNT_HI:
|
||||
GBAAudioWriteSOUNDCNT_HI(&gba->audio, value);
|
||||
value &= 0x770F;
|
||||
break;
|
||||
case REG_SOUNDCNT_X:
|
||||
GBAAudioWriteSOUNDCNT_X(&gba->audio, value);
|
||||
value &= 0x0080;
|
||||
value |= gba->memory.io[REG_SOUNDCNT_X >> 1] & 0xF;
|
||||
break;
|
||||
case REG_SOUNDBIAS:
|
||||
GBAAudioWriteSOUNDBIAS(&gba->audio, value);
|
||||
|
@ -494,7 +539,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
// Some bad interrupt libraries will write to this
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register write: %03x", address);
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register write: %03X", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Write to unused I/O register: %03X", address);
|
||||
return;
|
||||
|
@ -539,28 +584,28 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
|
|||
GBAAudioWriteFIFO(&gba->audio, address, value);
|
||||
break;
|
||||
case REG_DMA0SAD_LO:
|
||||
GBAMemoryWriteDMASAD(gba, 0, value);
|
||||
value = GBAMemoryWriteDMASAD(gba, 0, value);
|
||||
break;
|
||||
case REG_DMA0DAD_LO:
|
||||
GBAMemoryWriteDMADAD(gba, 0, value);
|
||||
value = GBAMemoryWriteDMADAD(gba, 0, value);
|
||||
break;
|
||||
case REG_DMA1SAD_LO:
|
||||
GBAMemoryWriteDMASAD(gba, 1, value);
|
||||
value = GBAMemoryWriteDMASAD(gba, 1, value);
|
||||
break;
|
||||
case REG_DMA1DAD_LO:
|
||||
GBAMemoryWriteDMADAD(gba, 1, value);
|
||||
value = GBAMemoryWriteDMADAD(gba, 1, value);
|
||||
break;
|
||||
case REG_DMA2SAD_LO:
|
||||
GBAMemoryWriteDMASAD(gba, 2, value);
|
||||
value = GBAMemoryWriteDMASAD(gba, 2, value);
|
||||
break;
|
||||
case REG_DMA2DAD_LO:
|
||||
GBAMemoryWriteDMADAD(gba, 2, value);
|
||||
value = GBAMemoryWriteDMADAD(gba, 2, value);
|
||||
break;
|
||||
case REG_DMA3SAD_LO:
|
||||
GBAMemoryWriteDMASAD(gba, 3, value);
|
||||
value = GBAMemoryWriteDMASAD(gba, 3, value);
|
||||
break;
|
||||
case REG_DMA3DAD_LO:
|
||||
GBAMemoryWriteDMADAD(gba, 3, value);
|
||||
value = GBAMemoryWriteDMADAD(gba, 3, value);
|
||||
break;
|
||||
default:
|
||||
GBAIOWrite(gba, address, value & 0xFFFF);
|
||||
|
@ -583,10 +628,22 @@ bool GBAIOIsReadConstant(uint32_t address) {
|
|||
case REG_WINOUT:
|
||||
case REG_BLDCNT:
|
||||
case REG_BLDALPHA:
|
||||
case REG_DMA0CNT_LO:
|
||||
case REG_DMA1CNT_LO:
|
||||
case REG_DMA2CNT_LO:
|
||||
case REG_DMA3CNT_LO:
|
||||
case REG_SOUND1CNT_LO:
|
||||
case REG_SOUND1CNT_HI:
|
||||
case REG_SOUND1CNT_X:
|
||||
case REG_SOUND2CNT_LO:
|
||||
case REG_SOUND2CNT_HI:
|
||||
case REG_SOUND3CNT_LO:
|
||||
case REG_SOUND3CNT_HI:
|
||||
case REG_SOUND3CNT_X:
|
||||
case REG_SOUND4CNT_LO:
|
||||
case REG_SOUND4CNT_HI:
|
||||
case REG_SOUNDCNT_LO:
|
||||
case REG_SOUNDCNT_HI:
|
||||
case REG_TM0CNT_HI:
|
||||
case REG_TM1CNT_HI:
|
||||
case REG_TM2CNT_HI:
|
||||
case REG_TM3CNT_HI:
|
||||
case REG_KEYINPUT:
|
||||
case REG_IE:
|
||||
return true;
|
||||
|
@ -623,6 +680,17 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
} else if (gba->keySource) {
|
||||
input = *gba->keySource;
|
||||
}
|
||||
if (!gba->allowOpposingDirections) {
|
||||
unsigned rl = input & 0x030;
|
||||
unsigned ud = input & 0x0C0;
|
||||
input &= 0x30F;
|
||||
if (rl != 0x030) {
|
||||
input |= rl;
|
||||
}
|
||||
if (ud != 0x0C0) {
|
||||
input |= ud;
|
||||
}
|
||||
}
|
||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||
gba->rr->logInput(gba->rr, input);
|
||||
}
|
||||
|
@ -638,23 +706,72 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
gba->memory.io[REG_JOYSTAT >> 1] &= ~2;
|
||||
break;
|
||||
|
||||
case REG_BG0HOFS:
|
||||
case REG_BG0VOFS:
|
||||
case REG_BG1HOFS:
|
||||
case REG_BG1VOFS:
|
||||
case REG_BG2HOFS:
|
||||
case REG_BG2VOFS:
|
||||
case REG_BG3HOFS:
|
||||
case REG_BG3VOFS:
|
||||
case REG_BG2PA:
|
||||
case REG_BG2PB:
|
||||
case REG_BG2PC:
|
||||
case REG_BG2PD:
|
||||
case REG_BG2X_LO:
|
||||
case REG_BG2X_HI:
|
||||
case REG_BG2Y_LO:
|
||||
case REG_BG2Y_HI:
|
||||
case REG_BG3PA:
|
||||
case REG_BG3PB:
|
||||
case REG_BG3PC:
|
||||
case REG_BG3PD:
|
||||
case REG_BG3X_LO:
|
||||
case REG_BG3X_HI:
|
||||
case REG_BG3Y_LO:
|
||||
case REG_BG3Y_HI:
|
||||
case REG_WIN0H:
|
||||
case REG_WIN1H:
|
||||
case REG_WIN0V:
|
||||
case REG_WIN1V:
|
||||
case REG_MOSAIC:
|
||||
case REG_BLDY:
|
||||
case REG_FIFO_A_LO:
|
||||
case REG_FIFO_A_HI:
|
||||
case REG_FIFO_B_LO:
|
||||
case REG_FIFO_B_HI:
|
||||
case REG_DMA0SAD_LO:
|
||||
case REG_DMA0SAD_HI:
|
||||
case REG_DMA0DAD_LO:
|
||||
case REG_DMA0DAD_HI:
|
||||
case REG_DMA0CNT_LO:
|
||||
case REG_DMA1SAD_LO:
|
||||
case REG_DMA1SAD_HI:
|
||||
case REG_DMA1DAD_LO:
|
||||
case REG_DMA1DAD_HI:
|
||||
case REG_DMA1CNT_LO:
|
||||
case REG_DMA2SAD_LO:
|
||||
case REG_DMA2SAD_HI:
|
||||
case REG_DMA2DAD_LO:
|
||||
case REG_DMA2DAD_HI:
|
||||
case REG_DMA2CNT_LO:
|
||||
case REG_DMA3SAD_LO:
|
||||
case REG_DMA3SAD_HI:
|
||||
case REG_DMA3DAD_LO:
|
||||
case REG_DMA3DAD_HI:
|
||||
case REG_DMA3CNT_LO:
|
||||
// Write-only register
|
||||
return 0;
|
||||
case REG_DISPCNT:
|
||||
case REG_DISPSTAT:
|
||||
case REG_VCOUNT:
|
||||
case REG_BG0CNT:
|
||||
case REG_BG1CNT:
|
||||
case REG_BG2CNT:
|
||||
case REG_BG3CNT:
|
||||
case REG_WININ:
|
||||
case REG_WINOUT:
|
||||
case REG_BLDCNT:
|
||||
case REG_BLDALPHA:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Read from write-only I/O register: %03X", address);
|
||||
return GBALoadBad(gba->cpu);
|
||||
|
||||
case REG_SOUNDBIAS:
|
||||
case REG_JOYCNT:
|
||||
case REG_JOY_RECV:
|
||||
case REG_JOY_TRANS:
|
||||
case REG_KEYCNT:
|
||||
case REG_POSTFLG:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register read: %03x", address);
|
||||
break;
|
||||
case REG_SOUND1CNT_LO:
|
||||
case REG_SOUND1CNT_HI:
|
||||
case REG_SOUND1CNT_X:
|
||||
|
@ -667,10 +784,39 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
case REG_SOUND4CNT_HI:
|
||||
case REG_SOUNDCNT_LO:
|
||||
case REG_SOUNDCNT_HI:
|
||||
if (!GBARegisterSOUNDCNT_XIsEnable(gba->memory.io[REG_SOUNDCNT_X >> 1])) {
|
||||
// TODO: Is writing allowed when the circuit is disabled?
|
||||
return 0;
|
||||
}
|
||||
// Fall through
|
||||
case REG_DISPCNT:
|
||||
case REG_DISPSTAT:
|
||||
case REG_VCOUNT:
|
||||
case REG_BG0CNT:
|
||||
case REG_BG1CNT:
|
||||
case REG_BG2CNT:
|
||||
case REG_BG3CNT:
|
||||
case REG_WININ:
|
||||
case REG_WINOUT:
|
||||
case REG_BLDCNT:
|
||||
case REG_BLDALPHA:
|
||||
case REG_SOUNDCNT_X:
|
||||
case REG_WAVE_RAM0_LO:
|
||||
case REG_WAVE_RAM0_HI:
|
||||
case REG_WAVE_RAM1_LO:
|
||||
case REG_WAVE_RAM1_HI:
|
||||
case REG_WAVE_RAM2_LO:
|
||||
case REG_WAVE_RAM2_HI:
|
||||
case REG_WAVE_RAM3_LO:
|
||||
case REG_WAVE_RAM3_HI:
|
||||
case REG_DMA0CNT_HI:
|
||||
case REG_DMA1CNT_HI:
|
||||
case REG_DMA2CNT_HI:
|
||||
case REG_DMA3CNT_HI:
|
||||
case REG_TM0CNT_HI:
|
||||
case REG_TM1CNT_HI:
|
||||
case REG_TM2CNT_HI:
|
||||
case REG_TM3CNT_HI:
|
||||
case REG_SIOMULTI0:
|
||||
case REG_SIOMULTI1:
|
||||
case REG_SIOMULTI2:
|
||||
|
@ -690,12 +836,8 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
// Some bad interrupt libraries will read from this
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register read: %03x", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return 0; // TODO: Reuse LOAD_BAD
|
||||
}
|
||||
break;
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return GBALoadBad(gba->cpu);
|
||||
}
|
||||
return gba->memory.io[address >> 1];
|
||||
}
|
||||
|
@ -703,51 +845,65 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
if (_isSpecialRegister[i >> 1]) {
|
||||
state->io[i >> 1] = gba->memory.io[i >> 1];
|
||||
if (_isRSpecialRegister[i >> 1]) {
|
||||
STORE_16(gba->memory.io[i >> 1], i, state->io);
|
||||
} else if (_isValidRegister[i >> 1]) {
|
||||
state->io[i >> 1] = GBAIORead(gba, i);
|
||||
uint16_t reg = GBAIORead(gba, i);
|
||||
STORE_16(reg, i, state->io);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
state->io[(REG_DMA0CNT_LO + i * 12) >> 1] = gba->memory.io[(REG_DMA0CNT_LO + i * 12) >> 1];
|
||||
state->dma[i].nextSource = gba->memory.dma[i].nextSource;
|
||||
state->dma[i].nextDest = gba->memory.dma[i].nextDest;
|
||||
state->dma[i].nextCount = gba->memory.dma[i].nextCount;
|
||||
state->dma[i].nextEvent = gba->memory.dma[i].nextEvent;
|
||||
STORE_16(gba->memory.io[(REG_DMA0CNT_LO + i * 12) >> 1], (REG_DMA0CNT_LO + i * 12), state->io);
|
||||
STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload);
|
||||
STORE_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
|
||||
STORE_32(gba->timers[i].lastEvent, 0, &state->timers[i].lastEvent);
|
||||
STORE_32(gba->timers[i].nextEvent, 0, &state->timers[i].nextEvent);
|
||||
STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);
|
||||
STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags);
|
||||
STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
|
||||
STORE_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount);
|
||||
STORE_32(gba->memory.dma[i].nextEvent, 0, &state->dma[i].nextEvent);
|
||||
}
|
||||
|
||||
memcpy(state->timers, gba->timers, sizeof(state->timers));
|
||||
GBAHardwareSerialize(&gba->memory.hw, state);
|
||||
}
|
||||
|
||||
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
if (_isSpecialRegister[i >> 1]) {
|
||||
gba->memory.io[i >> 1] = state->io[i >> 1];
|
||||
if (_isWSpecialRegister[i >> 1]) {
|
||||
LOAD_16(gba->memory.io[i >> 1], i, state->io);
|
||||
} else if (_isValidRegister[i >> 1]) {
|
||||
GBAIOWrite(gba, i, state->io[i >> 1]);
|
||||
uint16_t reg;
|
||||
LOAD_16(reg, i, state->io);
|
||||
GBAIOWrite(gba, i, reg);
|
||||
}
|
||||
}
|
||||
|
||||
gba->timersEnabled = 0;
|
||||
memcpy(gba->timers, state->timers, sizeof(gba->timers));
|
||||
for (i = 0; i < 4; ++i) {
|
||||
gba->memory.dma[i].reg = state->io[(REG_DMA0CNT_HI + i * 12) >> 1];
|
||||
gba->memory.dma[i].nextSource = state->dma[i].nextSource;
|
||||
gba->memory.dma[i].nextDest = state->dma[i].nextDest;
|
||||
gba->memory.dma[i].nextCount = state->dma[i].nextCount;
|
||||
gba->memory.dma[i].nextEvent = state->dma[i].nextEvent;
|
||||
LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload);
|
||||
LOAD_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
|
||||
LOAD_32(gba->timers[i].lastEvent, 0, &state->timers[i].lastEvent);
|
||||
LOAD_32(gba->timers[i].nextEvent, 0, &state->timers[i].nextEvent);
|
||||
LOAD_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);
|
||||
LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags);
|
||||
LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io);
|
||||
LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
LOAD_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
|
||||
LOAD_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount);
|
||||
LOAD_32(gba->memory.dma[i].nextEvent, 0, &state->dma[i].nextEvent);
|
||||
if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) {
|
||||
GBAMemoryScheduleDMA(gba, i, &gba->memory.dma[i]);
|
||||
}
|
||||
|
||||
if (gba->timers[i].enable) {
|
||||
if (GBATimerFlagsIsEnable(gba->timers[i].flags)) {
|
||||
gba->timersEnabled |= 1 << i;
|
||||
}
|
||||
}
|
||||
GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]);
|
||||
GBAMemoryUpdateDMAs(gba, 0);
|
||||
GBAHardwareDeserialize(&gba->memory.hw, state);
|
||||
}
|
||||
|
|
212
src/gba/memory.c
212
src/gba/memory.c
|
@ -96,6 +96,10 @@ void GBAMemoryReset(struct GBA* gba) {
|
|||
mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM);
|
||||
}
|
||||
gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM);
|
||||
if (gba->pristineRom && !gba->memory.rom) {
|
||||
// Multiboot
|
||||
memcpy(gba->memory.wram, gba->pristineRom, gba->pristineRomSize);
|
||||
}
|
||||
|
||||
if (gba->memory.iwram) {
|
||||
mappedMemoryFree(gba->memory.iwram, SIZE_WORKING_IRAM);
|
||||
|
@ -360,8 +364,8 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \
|
||||
} else { \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \
|
||||
value = (address >> 1) & 0xFFFF; \
|
||||
value |= ((address + 2) >> 1) << 16; \
|
||||
value = ((address & ~3) >> 1) & 0xFFFF; \
|
||||
value |= (((address & ~3) + 2) >> 1) << 16; \
|
||||
}
|
||||
|
||||
#define LOAD_SRAM \
|
||||
|
@ -370,6 +374,13 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
value |= value << 8; \
|
||||
value |= value << 16;
|
||||
|
||||
uint32_t GBALoadBad(struct ARMCore* cpu) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
uint32_t value = 0;
|
||||
LOAD_BAD;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
|
@ -442,7 +453,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
LOAD_16(value, address, memory->bios);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load16: 0x%08X", address);
|
||||
LOAD_16(value, address & 2, &memory->biosPrefetch);
|
||||
value = (memory->biosPrefetch >> ((address & 2) * 8)) & 0xFFFF;
|
||||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
|
||||
|
@ -535,12 +546,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
value = ((uint8_t*) memory->bios)[address];
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address);
|
||||
value = ((uint8_t*) &memory->biosPrefetch)[address & 3];
|
||||
value = (memory->biosPrefetch >> ((address & 3) * 8)) & 0xFF;
|
||||
}
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
LOAD_BAD;
|
||||
value = ((uint8_t*) &value)[address & 3];
|
||||
value = (value >> ((address & 3) * 8)) & 0xFF;
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
|
@ -564,7 +575,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load8: 0x%08X", address);
|
||||
value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)];
|
||||
break;
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
|
@ -587,6 +598,9 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame");
|
||||
GBASavedataInitSRAM(&memory->savedata);
|
||||
}
|
||||
if (gba->performingDMA == 1) {
|
||||
break;
|
||||
}
|
||||
if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
value = memory->savedata.data[address & (SIZE_CART_SRAM - 1)];
|
||||
} else if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) {
|
||||
|
@ -602,7 +616,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
|
||||
LOAD_BAD;
|
||||
value = ((uint8_t*) &value)[address & 3];
|
||||
value = (value >> ((address & 3) * 8)) & 0xFF;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -654,7 +668,14 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address);
|
||||
|
||||
#define STORE_SRAM \
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address);
|
||||
if (address & 0x3) { \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Unaligned SRAM Store32: 0x%08X", address); \
|
||||
value = 0; \
|
||||
} \
|
||||
GBAStore8(cpu, address & ~0x3, value, cycleCounter); \
|
||||
GBAStore8(cpu, (address & ~0x3) | 1, value, cycleCounter); \
|
||||
GBAStore8(cpu, (address & ~0x3) | 2, value, cycleCounter); \
|
||||
GBAStore8(cpu, (address & ~0x3) | 3, value, cycleCounter);
|
||||
|
||||
#define STORE_BAD \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Store32: 0x%08X", address);
|
||||
|
@ -760,7 +781,8 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
|||
break;
|
||||
case REGION_CART_SRAM:
|
||||
case REGION_CART_SRAM_MIRROR:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store16: 0x%08X", address);
|
||||
GBAStore8(cpu, (address & ~0x1), value, cycleCounter);
|
||||
GBAStore8(cpu, (address & ~0x1) | 1, value, cycleCounter);
|
||||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Store16: 0x%08X", address);
|
||||
|
@ -793,10 +815,10 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
GBAIOWrite8(gba, address & (SIZE_IO - 1), value);
|
||||
break;
|
||||
case REGION_PALETTE_RAM:
|
||||
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store8: 0x%08X", address);
|
||||
GBAStore16(cpu, address & ~1, ((uint8_t) value) | ((uint8_t) value << 8), cycleCounter);
|
||||
break;
|
||||
case REGION_VRAM:
|
||||
if (address >= 0x06018000) {
|
||||
if ((address & 0x0001FFFF) >= ((GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) == 4) ? 0x00014000 : 0x00010000)) {
|
||||
// TODO: check BG mode
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address);
|
||||
break;
|
||||
|
@ -847,6 +869,117 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
}
|
||||
}
|
||||
|
||||
uint32_t GBAView32(struct ARMCore* cpu, uint32_t address) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
uint32_t value = 0;
|
||||
address &= ~3;
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_BIOS:
|
||||
if (address < SIZE_BIOS) {
|
||||
LOAD_32(value, address, gba->memory.bios);
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_PALETTE_RAM:
|
||||
case REGION_VRAM:
|
||||
case REGION_OAM:
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
value = GBALoad32(cpu, address, 0);
|
||||
break;
|
||||
case REGION_IO:
|
||||
if ((address & OFFSET_MASK) < REG_MAX) {
|
||||
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
||||
value |= gba->memory.io[((address & OFFSET_MASK) >> 1) + 1] << 16;
|
||||
}
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
value = GBALoad8(cpu, address, 0);
|
||||
value |= GBALoad8(cpu, address + 1, 0) << 8;
|
||||
value |= GBALoad8(cpu, address + 2, 0) << 16;
|
||||
value |= GBALoad8(cpu, address + 3, 0) << 24;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t GBAView16(struct ARMCore* cpu, uint32_t address) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
uint16_t value = 0;
|
||||
address &= ~1;
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_BIOS:
|
||||
if (address < SIZE_BIOS) {
|
||||
LOAD_16(value, address, gba->memory.bios);
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_PALETTE_RAM:
|
||||
case REGION_VRAM:
|
||||
case REGION_OAM:
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
value = GBALoad16(cpu, address, 0);
|
||||
break;
|
||||
case REGION_IO:
|
||||
if ((address & OFFSET_MASK) < REG_MAX) {
|
||||
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
||||
}
|
||||
break;
|
||||
case REGION_CART_SRAM:
|
||||
value = GBALoad8(cpu, address, 0);
|
||||
value |= GBALoad8(cpu, address + 1, 0) << 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t GBAView8(struct ARMCore* cpu, uint32_t address) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
uint8_t value = 0;
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_BIOS:
|
||||
if (address < SIZE_BIOS) {
|
||||
value = ((uint8_t*) gba->memory.bios)[address];
|
||||
}
|
||||
break;
|
||||
case REGION_WORKING_RAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
case REGION_CART_SRAM:
|
||||
value = GBALoad8(cpu, address, 0);
|
||||
break;
|
||||
case REGION_IO:
|
||||
case REGION_PALETTE_RAM:
|
||||
case REGION_VRAM:
|
||||
case REGION_OAM:
|
||||
value = GBAView16(cpu, address) >> ((address & 1) * 8);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
|
@ -1096,7 +1229,9 @@ uint32_t GBALoadMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum L
|
|||
}
|
||||
|
||||
uint32_t addressMisalign = address & 0x3;
|
||||
address &= 0xFFFFFFFC;
|
||||
if (address >> BASE_OFFSET < REGION_CART_SRAM) {
|
||||
address &= 0xFFFFFFFC;
|
||||
}
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_BIOS:
|
||||
|
@ -1209,7 +1344,9 @@ uint32_t GBAStoreMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum
|
|||
}
|
||||
|
||||
uint32_t addressMisalign = address & 0x3;
|
||||
address &= 0xFFFFFFFC;
|
||||
if (address >> BASE_OFFSET < REGION_CART_SRAM) {
|
||||
address &= 0xFFFFFFFC;
|
||||
}
|
||||
|
||||
switch (address >> BASE_OFFSET) {
|
||||
case REGION_WORKING_RAM:
|
||||
|
@ -1307,14 +1444,33 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
|
|||
cpu->memory.activeNonseqCycles16 = memory->waitstatesNonseq16[memory->activeRegion];
|
||||
}
|
||||
|
||||
void GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
memory->dma[dma].source = address & 0x0FFFFFFE;
|
||||
static bool _isValidDMASAD(int dma, uint32_t address) {
|
||||
if (dma == 0 && address >= BASE_CART0 && address < BASE_CART_SRAM) {
|
||||
return false;
|
||||
}
|
||||
return address >= BASE_WORKING_RAM;
|
||||
}
|
||||
|
||||
void GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
static bool _isValidDMADAD(int dma, uint32_t address) {
|
||||
return dma == 3 || address < BASE_CART0;
|
||||
}
|
||||
|
||||
uint32_t GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
memory->dma[dma].dest = address & 0x0FFFFFFE;
|
||||
address &= 0x0FFFFFFE;
|
||||
if (_isValidDMASAD(dma, address)) {
|
||||
memory->dma[dma].source = address;
|
||||
}
|
||||
return memory->dma[dma].source;
|
||||
}
|
||||
|
||||
uint32_t GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
address &= 0x0FFFFFFE;
|
||||
if (_isValidDMADAD(dma, address)) {
|
||||
memory->dma[dma].dest = address;
|
||||
}
|
||||
return memory->dma[dma].dest;
|
||||
}
|
||||
|
||||
void GBAMemoryWriteDMACNT_LO(struct GBA* gba, int dma, uint16_t count) {
|
||||
|
@ -1326,6 +1482,11 @@ uint16_t GBAMemoryWriteDMACNT_HI(struct GBA* gba, int dma, uint16_t control) {
|
|||
struct GBAMemory* memory = &gba->memory;
|
||||
struct GBADMA* currentDma = &memory->dma[dma];
|
||||
int wasEnabled = GBADMARegisterIsEnable(currentDma->reg);
|
||||
if (dma < 3) {
|
||||
control &= 0xF7E0;
|
||||
} else {
|
||||
control &= 0xFFE0;
|
||||
}
|
||||
currentDma->reg = control;
|
||||
|
||||
if (GBADMARegisterIsDRQ(currentDma->reg)) {
|
||||
|
@ -1346,8 +1507,8 @@ void GBAMemoryScheduleDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
struct ARMCore* cpu = gba->cpu;
|
||||
switch (GBADMARegisterGetTiming(info->reg)) {
|
||||
case DMA_TIMING_NOW:
|
||||
info->nextEvent = cpu->cycles;
|
||||
GBAMemoryUpdateDMAs(gba, 0);
|
||||
info->nextEvent = cpu->cycles + 2;
|
||||
GBAMemoryUpdateDMAs(gba, -1);
|
||||
break;
|
||||
case DMA_TIMING_HBLANK:
|
||||
// Handled implicitly
|
||||
|
@ -1450,9 +1611,10 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
uint32_t destRegion = dest >> BASE_OFFSET;
|
||||
int32_t cycles = 2;
|
||||
|
||||
if (source == info->source) {
|
||||
// TODO: support 4 cycles for ROM access
|
||||
cycles += 2;
|
||||
if (source == info->source && dest == info->dest && wordsRemaining == info->count) {
|
||||
if (sourceRegion < REGION_CART0 || destRegion < REGION_CART0) {
|
||||
cycles += 2;
|
||||
}
|
||||
if (width == 4) {
|
||||
cycles += memory->waitstatesNonseq32[sourceRegion] + memory->waitstatesNonseq32[destRegion];
|
||||
source &= 0xFFFFFFFC;
|
||||
|
@ -1468,7 +1630,7 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
}
|
||||
}
|
||||
|
||||
gba->performingDMA = true;
|
||||
gba->performingDMA = 1 | (number << 1);
|
||||
int32_t word;
|
||||
if (width == 4) {
|
||||
word = cpu->memory.load32(cpu, source, 0);
|
||||
|
@ -1505,7 +1667,7 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
--wordsRemaining;
|
||||
}
|
||||
}
|
||||
gba->performingDMA = false;
|
||||
gba->performingDMA = 0;
|
||||
|
||||
if (!wordsRemaining) {
|
||||
if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) {
|
||||
|
|
|
@ -151,10 +151,16 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
|||
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||
|
||||
uint32_t GBALoadBad(struct ARMCore* cpu);
|
||||
|
||||
void GBAStore32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter);
|
||||
void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter);
|
||||
void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter);
|
||||
|
||||
uint32_t GBAView32(struct ARMCore* cpu, uint32_t address);
|
||||
uint16_t GBAView16(struct ARMCore* cpu, uint32_t address);
|
||||
uint8_t GBAView8(struct ARMCore* cpu, uint32_t address);
|
||||
|
||||
void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old);
|
||||
void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old);
|
||||
void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old);
|
||||
|
@ -166,8 +172,8 @@ uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum
|
|||
|
||||
void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters);
|
||||
|
||||
void GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address);
|
||||
void GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address);
|
||||
uint32_t GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address);
|
||||
uint32_t GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address);
|
||||
void GBAMemoryWriteDMACNT_LO(struct GBA* gba, int dma, uint16_t count);
|
||||
uint16_t GBAMemoryWriteDMACNT_HI(struct GBA* gba, int dma, uint16_t control);
|
||||
|
||||
|
|
|
@ -7,28 +7,41 @@
|
|||
|
||||
#include "gba/gba.h"
|
||||
|
||||
#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \
|
||||
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \
|
||||
x += background->dx; \
|
||||
y += background->dy; \
|
||||
\
|
||||
#define MODE_2_COORD_OVERFLOW \
|
||||
localX = x & (sizeAdjusted - 1); \
|
||||
localY = y & (sizeAdjusted - 1); \
|
||||
|
||||
#define MODE_2_COORD_NO_OVERFLOW \
|
||||
if ((x | y) & ~(sizeAdjusted - 1)) { \
|
||||
continue; \
|
||||
} else { \
|
||||
localX = x; \
|
||||
localY = y; \
|
||||
}
|
||||
|
||||
#define MODE_2_MOSAIC(COORD) \
|
||||
if (!mosaicWait) { \
|
||||
if (background->overflow) { \
|
||||
localX = x & (sizeAdjusted - 1); \
|
||||
localY = y & (sizeAdjusted - 1); \
|
||||
} else if ((x | y) & ~(sizeAdjusted - 1)) { \
|
||||
continue; \
|
||||
} else { \
|
||||
localX = x; \
|
||||
localY = y; \
|
||||
} \
|
||||
COORD \
|
||||
mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \
|
||||
pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; \
|
||||
\
|
||||
mosaicWait = mosaicH; \
|
||||
} else { \
|
||||
--mosaicWait; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define MODE_2_NO_MOSAIC(COORD) \
|
||||
COORD \
|
||||
mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \
|
||||
pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)];
|
||||
|
||||
|
||||
#define MODE_2_LOOP(MOSAIC, COORD, BLEND, OBJWIN) \
|
||||
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \
|
||||
x += background->dx; \
|
||||
y += background->dy; \
|
||||
\
|
||||
MOSAIC(COORD) \
|
||||
\
|
||||
uint32_t current = *pixel; \
|
||||
if (pixelData && IS_WRITABLE(current)) { \
|
||||
|
@ -36,6 +49,21 @@
|
|||
} \
|
||||
}
|
||||
|
||||
#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \
|
||||
if (background->overflow) { \
|
||||
if (mosaicH > 1) { \
|
||||
MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \
|
||||
} else { \
|
||||
MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \
|
||||
} \
|
||||
} else { \
|
||||
if (mosaicH > 1) { \
|
||||
MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \
|
||||
} else { \
|
||||
MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \
|
||||
} \
|
||||
}
|
||||
|
||||
void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
|
||||
int sizeAdjusted = 0x8000 << background->size;
|
||||
|
||||
|
@ -96,7 +124,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer
|
|||
}
|
||||
|
||||
uint32_t current = *pixel;
|
||||
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
|
||||
if (!objwinSlowPath || (!(current & FLAG_OBJWIN)) != objwinOnly) {
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
|
@ -138,7 +166,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer
|
|||
if (color && IS_WRITABLE(current)) {
|
||||
if (!objwinSlowPath) {
|
||||
_compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current);
|
||||
} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
|
||||
} else if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) {
|
||||
color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
|
@ -185,7 +213,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer
|
|||
}
|
||||
|
||||
uint32_t current = *pixel;
|
||||
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
|
||||
if (!objwinSlowPath || (!(current & FLAG_OBJWIN)) != objwinOnly) {
|
||||
unsigned mergedFlags = flags;
|
||||
if (current & FLAG_OBJWIN) {
|
||||
mergedFlags = objwinFlags;
|
||||
|
|
|
@ -468,7 +468,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
|
|||
|
||||
unsigned xBase;
|
||||
|
||||
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND;
|
||||
uint32_t flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND;
|
||||
flags |= FLAG_TARGET_2 * background->target2;
|
||||
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed));
|
||||
objwinFlags |= flags;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
|
||||
continue; \
|
||||
} \
|
||||
renderer->spriteCyclesRemaining -= 1; \
|
||||
SPRITE_XBASE_ ## DEPTH(inX); \
|
||||
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
|
||||
}
|
||||
|
@ -44,10 +45,11 @@
|
|||
unsigned tileData; \
|
||||
unsigned widthMask = ~(width - 1); \
|
||||
unsigned heightMask = ~(height - 1); \
|
||||
for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \
|
||||
for (; outX < condition; ++outX, ++inX) { \
|
||||
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
|
||||
continue; \
|
||||
} \
|
||||
renderer->spriteCyclesRemaining -= 2; \
|
||||
xAccum += mat.a; \
|
||||
yAccum += mat.c; \
|
||||
int localX = (xAccum >> 8) + (width >> 1); \
|
||||
|
@ -140,21 +142,27 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY;
|
||||
flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
|
||||
flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN);
|
||||
int32_t x = GBAObjAttributesBGetX(sprite->b) << 23;
|
||||
int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23;
|
||||
x >>= 23;
|
||||
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
|
||||
unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;
|
||||
if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) {
|
||||
return 0;
|
||||
}
|
||||
int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||
if (renderer->spriteCyclesRemaining <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int variant = renderer->target1Obj &&
|
||||
GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) &&
|
||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) {
|
||||
int target2 = renderer->target2Bd << 4;
|
||||
target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority);
|
||||
target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority);
|
||||
target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority);
|
||||
target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority);
|
||||
if ((1 << GBAObjAttributesCGetPriority(sprite->c)) < target2) {
|
||||
if ((1 << GBAObjAttributesCGetPriority(sprite->c)) <= target2) {
|
||||
variant = 0;
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +184,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
if (GBAObjAttributesAIsTransformed(sprite->a)) {
|
||||
int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||
int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||
renderer->spriteCyclesRemaining -= 10;
|
||||
struct GBAOAMMatrix mat;
|
||||
LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
|
||||
LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
|
||||
|
@ -186,10 +195,15 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
inY += 256;
|
||||
}
|
||||
int outX = x >= start ? x : start;
|
||||
int condition = x + totalWidth;
|
||||
int inX = outX - x;
|
||||
int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1));
|
||||
int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1));
|
||||
|
||||
if (end < condition) {
|
||||
condition = end;
|
||||
}
|
||||
|
||||
if (!GBAObjAttributesAIs256Color(sprite->a)) {
|
||||
palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
|
||||
if (flags & FLAG_OBJWIN) {
|
||||
|
@ -209,6 +223,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
SPRITE_TRANSFORMED_LOOP(256, NORMAL);
|
||||
}
|
||||
}
|
||||
if (x + totalWidth > VIDEO_HORIZONTAL_PIXELS) {
|
||||
renderer->spriteCyclesRemaining -= (x + totalWidth - VIDEO_HORIZONTAL_PIXELS) * 2;
|
||||
}
|
||||
} else {
|
||||
int outX = x >= start ? x : start;
|
||||
int condition = x + width;
|
||||
|
@ -267,6 +284,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
SPRITE_NORMAL_LOOP(256, NORMAL);
|
||||
}
|
||||
}
|
||||
if (x + width > VIDEO_HORIZONTAL_PIXELS) {
|
||||
renderer->spriteCyclesRemaining -= x + width - VIDEO_HORIZONTAL_PIXELS;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render
|
|||
if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
|
||||
color = _mix(renderer->blda, current, renderer->bldb, color);
|
||||
} else {
|
||||
color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND);
|
||||
color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND);
|
||||
}
|
||||
} else {
|
||||
color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN);
|
||||
|
@ -55,7 +55,7 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend
|
|||
if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
|
||||
color = _mix(renderer->blda, current, renderer->bldb, color);
|
||||
} else {
|
||||
color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND);
|
||||
color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND);
|
||||
}
|
||||
} else {
|
||||
color = color & ~FLAG_TARGET_2;
|
||||
|
@ -69,7 +69,7 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* rend
|
|||
if (color < current) {
|
||||
color |= (current & FLAG_OBJWIN);
|
||||
} else {
|
||||
color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND);
|
||||
color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND);
|
||||
}
|
||||
*pixel = color;
|
||||
}
|
||||
|
@ -78,13 +78,13 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
uint32_t current) {
|
||||
UNUSED(renderer);
|
||||
if (color >= current) {
|
||||
color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND);
|
||||
color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND);
|
||||
}
|
||||
*pixel = color;
|
||||
}
|
||||
|
||||
#define COMPOSITE_16_OBJWIN(BLEND) \
|
||||
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
|
||||
if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
|
@ -97,7 +97,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
_composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current);
|
||||
|
||||
#define COMPOSITE_256_OBJWIN(BLEND) \
|
||||
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
|
||||
if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
|
@ -177,7 +177,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
int32_t localX; \
|
||||
int32_t localY; \
|
||||
\
|
||||
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \
|
||||
uint32_t flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \
|
||||
flags |= FLAG_TARGET_2 * background->target2; \
|
||||
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \
|
||||
GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \
|
||||
|
@ -306,8 +306,8 @@ static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB)
|
|||
if (c & 0x0020) {
|
||||
c = (c & ~0x003F) | 0x001F;
|
||||
}
|
||||
if (c & 0x10000) {
|
||||
c = (c & ~0x1F800) | 0xF800;
|
||||
if (c & 0x8000) {
|
||||
c = (c & ~0xF800) | 0x7C00;
|
||||
}
|
||||
c = (c & 0x7C1F) | ((c >> 16) & 0x03E0);
|
||||
#endif
|
||||
|
|
|
@ -145,19 +145,19 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
|
|||
GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
|
||||
break;
|
||||
case REG_BG0CNT:
|
||||
value &= 0xFFCF;
|
||||
value &= 0xDFFF;
|
||||
GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
|
||||
break;
|
||||
case REG_BG1CNT:
|
||||
value &= 0xFFCF;
|
||||
value &= 0xDFFF;
|
||||
GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
|
||||
break;
|
||||
case REG_BG2CNT:
|
||||
value &= 0xFFCF;
|
||||
value &= 0xFFFF;
|
||||
GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
|
||||
break;
|
||||
case REG_BG3CNT:
|
||||
value &= 0xFFCF;
|
||||
value &= 0xFFFF;
|
||||
GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
|
||||
break;
|
||||
case REG_BG0HOFS:
|
||||
|
@ -242,6 +242,7 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
|
|||
break;
|
||||
case REG_BLDCNT:
|
||||
GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
|
||||
value &= 0x3FFF;
|
||||
break;
|
||||
case REG_BLDALPHA:
|
||||
softwareRenderer->blda = value & 0x1F;
|
||||
|
@ -252,6 +253,7 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
|
|||
if (softwareRenderer->bldb > 0x10) {
|
||||
softwareRenderer->bldb = 0x10;
|
||||
}
|
||||
value &= 0x1F1F;
|
||||
break;
|
||||
case REG_BLDY:
|
||||
softwareRenderer->bldy = value & 0x1F;
|
||||
|
@ -313,10 +315,12 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
|
|||
}
|
||||
break;
|
||||
case REG_WININ:
|
||||
value &= 0x3F3F;
|
||||
softwareRenderer->winN[0].control.packed = value;
|
||||
softwareRenderer->winN[1].control.packed = value >> 8;
|
||||
break;
|
||||
case REG_WINOUT:
|
||||
value &= 0x3F3F;
|
||||
softwareRenderer->winout.packed = value;
|
||||
softwareRenderer->objwin.packed = value >> 8;
|
||||
break;
|
||||
|
@ -725,8 +729,6 @@ static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer*
|
|||
renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
|
||||
renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
|
||||
|
||||
renderer->anyTarget2 = value & 0x3F00;
|
||||
|
||||
if (oldEffect != renderer->blendEffect) {
|
||||
_updatePalettes(renderer);
|
||||
}
|
||||
|
@ -746,6 +748,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
|
|||
if (renderer->oamDirty) {
|
||||
_cleanOAM(renderer);
|
||||
}
|
||||
renderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(renderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
|
||||
int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
|
||||
int mosaicY = y - (y % mosaicV);
|
||||
for (w = 0; w < renderer->nWindows; ++w) {
|
||||
|
@ -759,6 +762,9 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
|
|||
int drawn;
|
||||
for (i = 0; i < renderer->oamMax; ++i) {
|
||||
int localY = y;
|
||||
if (renderer->spriteCyclesRemaining <= 0) {
|
||||
break;
|
||||
}
|
||||
struct GBAVideoSoftwareSprite* sprite = &renderer->sprites[i];
|
||||
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
|
||||
localY = mosaicY;
|
||||
|
@ -772,7 +778,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
|
|||
}
|
||||
}
|
||||
|
||||
int priority;
|
||||
unsigned priority;
|
||||
for (priority = 0; priority < 4; ++priority) {
|
||||
renderer->end = 0;
|
||||
for (w = 0; w < renderer->nWindows; ++w) {
|
||||
|
|
|
@ -23,9 +23,9 @@ struct GBAVideoSoftwareSprite {
|
|||
};
|
||||
|
||||
struct GBAVideoSoftwareBackground {
|
||||
int index;
|
||||
unsigned index;
|
||||
int enabled;
|
||||
int priority;
|
||||
unsigned priority;
|
||||
uint32_t charBase;
|
||||
int mosaic;
|
||||
int multipalette;
|
||||
|
@ -71,10 +71,10 @@ enum {
|
|||
#define FLAG_INDEX 0x30000000
|
||||
#define FLAG_IS_BACKGROUND 0x08000000
|
||||
#define FLAG_UNWRITTEN 0xFC000000
|
||||
#define FLAG_REBLEND 0x04000000
|
||||
#define FLAG_TARGET_1 0x02000000
|
||||
#define FLAG_TARGET_2 0x01000000
|
||||
#define FLAG_OBJWIN 0x01000000
|
||||
#define FLAG_REBLEND 0x01000000
|
||||
#define FLAG_ORDER_MASK 0xF8000000
|
||||
|
||||
#define IS_WRITABLE(PIXEL) ((PIXEL) & 0xFE000000)
|
||||
|
@ -122,6 +122,7 @@ struct GBAVideoSoftwareRenderer {
|
|||
|
||||
uint32_t row[VIDEO_HORIZONTAL_PIXELS];
|
||||
uint32_t spriteLayer[VIDEO_HORIZONTAL_PIXELS];
|
||||
int32_t spriteCyclesRemaining;
|
||||
|
||||
// BLDCNT
|
||||
unsigned target1Obj;
|
||||
|
@ -131,7 +132,6 @@ struct GBAVideoSoftwareRenderer {
|
|||
enum BlendEffect blendEffect;
|
||||
color_t normalPalette[512];
|
||||
color_t variantPalette[512];
|
||||
int anyTarget2;
|
||||
|
||||
uint16_t blda;
|
||||
uint16_t bldb;
|
||||
|
|
|
@ -30,6 +30,7 @@ static bool GBAMGMIsRecording(const struct GBARRContext*);
|
|||
static void GBAMGMNextFrame(struct GBARRContext*);
|
||||
static void GBAMGMLogInput(struct GBARRContext*, uint16_t input);
|
||||
static uint16_t GBAMGMQueryInput(struct GBARRContext*);
|
||||
static bool GBAMGMQueryReset(struct GBARRContext*);
|
||||
|
||||
static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
@ -71,6 +72,7 @@ void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
|
|||
mgm->d.nextFrame = GBAMGMNextFrame;
|
||||
mgm->d.logInput = GBAMGMLogInput;
|
||||
mgm->d.queryInput = GBAMGMQueryInput;
|
||||
mgm->d.queryReset = GBAMGMQueryReset;
|
||||
|
||||
mgm->d.stateSaved = GBAMGMStateSaved;
|
||||
mgm->d.stateLoaded = GBAMGMStateLoaded;
|
||||
|
@ -324,6 +326,15 @@ uint16_t GBAMGMQueryInput(struct GBARRContext* rr) {
|
|||
return mgm->currentInput;
|
||||
}
|
||||
|
||||
bool GBAMGMQueryReset(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->peekedTag == TAG_RESET;
|
||||
}
|
||||
|
||||
void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isRecording(rr)) {
|
||||
|
@ -441,6 +452,7 @@ enum GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) {
|
|||
// Empty markers
|
||||
case TAG_FRAME:
|
||||
case TAG_LAG:
|
||||
case TAG_RESET:
|
||||
case TAG_BEGIN:
|
||||
case TAG_END:
|
||||
case TAG_INVALID:
|
||||
|
|
|
@ -20,6 +20,7 @@ enum GBAMGMTag {
|
|||
TAG_INPUT = 0x01,
|
||||
TAG_FRAME = 0x02,
|
||||
TAG_LAG = 0x03,
|
||||
TAG_RESET = 0x04,
|
||||
|
||||
// Stream chunking tags
|
||||
TAG_BEGIN = 0x10,
|
||||
|
|
|
@ -27,7 +27,7 @@ void GBARRInitRecord(struct GBA* gba) {
|
|||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, false);
|
||||
GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
@ -51,7 +51,7 @@ void GBARRInitPlay(struct GBA* gba) {
|
|||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf);
|
||||
GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
|
|
@ -33,6 +33,7 @@ struct GBARRContext {
|
|||
void (*nextFrame)(struct GBARRContext*);
|
||||
void (*logInput)(struct GBARRContext*, uint16_t input);
|
||||
uint16_t (*queryInput)(struct GBARRContext*);
|
||||
bool (*queryReset)(struct GBARRContext*);
|
||||
|
||||
void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*);
|
||||
void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*);
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
#include "gba/serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
static const char VBM_MAGIC[] = "VBM\x1A";
|
||||
|
||||
static void GBAVBMContextDestroy(struct GBARRContext*);
|
||||
|
@ -23,6 +27,7 @@ static bool GBAVBMIsRecording(const struct GBARRContext*);
|
|||
|
||||
static void GBAVBMNextFrame(struct GBARRContext*);
|
||||
static uint16_t GBAVBMQueryInput(struct GBARRContext*);
|
||||
static bool GBAVBMQueryReset(struct GBARRContext*);
|
||||
|
||||
static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
@ -46,6 +51,7 @@ void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
|
|||
vbm->d.nextFrame = GBAVBMNextFrame;
|
||||
vbm->d.logInput = 0;
|
||||
vbm->d.queryInput = GBAVBMQueryInput;
|
||||
vbm->d.queryReset = GBAVBMQueryReset;
|
||||
|
||||
vbm->d.stateSaved = GBAVBMStateSaved;
|
||||
vbm->d.stateLoaded = GBAVBMStateLoaded;
|
||||
|
@ -114,6 +120,18 @@ uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
|||
return input & 0x3FF;
|
||||
}
|
||||
|
||||
bool GBAVBMQueryReset(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
uint16_t input;
|
||||
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
|
||||
return input & 0x800;
|
||||
}
|
||||
|
||||
void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
UNUSED(rr);
|
||||
UNUSED(state);
|
||||
|
@ -125,9 +143,72 @@ void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState*
|
|||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||
UNUSED(rr);
|
||||
UNUSED(flags);
|
||||
#ifndef USE_ZLIB
|
||||
UNUSED(rr);
|
||||
return 0;
|
||||
#else
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
off_t pos = vbm->vbmFile->seek(vbm->vbmFile, 0, SEEK_CUR);
|
||||
uint32_t saveType, flashSize, sramOffset;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, 0x18, SEEK_SET);
|
||||
vbm->vbmFile->read(vbm->vbmFile, &saveType, sizeof(saveType));
|
||||
vbm->vbmFile->read(vbm->vbmFile, &flashSize, sizeof(flashSize));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, 0x38, SEEK_SET);
|
||||
vbm->vbmFile->read(vbm->vbmFile, &sramOffset, sizeof(sramOffset));
|
||||
if (!sramOffset) {
|
||||
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
|
||||
return 0;
|
||||
}
|
||||
vbm->vbmFile->seek(vbm->vbmFile, sramOffset, SEEK_SET);
|
||||
struct VFile* save = VFileMemChunk(0, 0);
|
||||
size_t size;
|
||||
switch (saveType) {
|
||||
case 1:
|
||||
size = SIZE_CART_SRAM;
|
||||
break;
|
||||
case 2:
|
||||
size = flashSize;
|
||||
if (size > SIZE_CART_FLASH1M) {
|
||||
size = SIZE_CART_FLASH1M;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
size = SIZE_CART_EEPROM;
|
||||
break;
|
||||
default:
|
||||
size = SIZE_CART_FLASH1M;
|
||||
break;
|
||||
}
|
||||
uLong zlen = vbm->inputOffset - sramOffset;
|
||||
char buffer[8761];
|
||||
char* zbuffer = malloc(zlen);
|
||||
vbm->vbmFile->read(vbm->vbmFile, zbuffer, zlen);
|
||||
z_stream zstr;
|
||||
zstr.zalloc = Z_NULL;
|
||||
zstr.zfree = Z_NULL;
|
||||
zstr.opaque = Z_NULL;
|
||||
zstr.avail_in = zlen;
|
||||
zstr.next_in = (Bytef*) zbuffer;
|
||||
zstr.avail_out = 0;
|
||||
inflateInit2(&zstr, 31);
|
||||
// Skip header, we know where the save file is
|
||||
zstr.avail_out = sizeof(buffer);
|
||||
zstr.next_out = (Bytef*) &buffer;
|
||||
int err = inflate(&zstr, 0);
|
||||
while (err != Z_STREAM_END && !zstr.avail_out) {
|
||||
zstr.avail_out = sizeof(buffer);
|
||||
zstr.next_out = (Bytef*) &buffer;
|
||||
int err = inflate(&zstr, 0);
|
||||
if (err < 0) {
|
||||
break;
|
||||
}
|
||||
save->write(save, buffer, sizeof(buffer) - zstr.avail_out);
|
||||
}
|
||||
inflateEnd(&zstr);
|
||||
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
|
||||
return save;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||
|
@ -163,14 +244,18 @@ bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
|
|||
|
||||
uint8_t flags;
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
if (flags & 2) {
|
||||
#if USE_ZLIB
|
||||
vbm->d.initFrom = INIT_FROM_SAVEGAME;
|
||||
#else
|
||||
// zlib is needed to parse the savegame
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
if (flags & 1) {
|
||||
// Incompatible savestate format
|
||||
return false;
|
||||
}
|
||||
if (flags & 2) {
|
||||
// TODO: Implement SRAM loading
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->seek(vf, 1, SEEK_CUR);
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
|
|
|
@ -123,6 +123,33 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
|
||||
if (savedata->data) {
|
||||
switch (savedata->type) {
|
||||
case SAVEDATA_SRAM:
|
||||
return in->read(in, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
|
||||
case SAVEDATA_FLASH512:
|
||||
return in->read(in, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
|
||||
case SAVEDATA_FLASH1M:
|
||||
return in->read(in, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
|
||||
case SAVEDATA_EEPROM:
|
||||
return in->read(in, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
|
||||
case SAVEDATA_AUTODETECT:
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
return true;
|
||||
}
|
||||
} else if (savedata->vf) {
|
||||
off_t read = 0;
|
||||
uint8_t buffer[2048];
|
||||
do {
|
||||
in->read(in, buffer, read);
|
||||
read = savedata->vf->write(savedata->vf, buffer, sizeof(buffer));
|
||||
} while (read == sizeof(buffer));
|
||||
return read >= 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
|
||||
if (savedata->type != SAVEDATA_AUTODETECT) {
|
||||
struct VFile* vf = savedata->vf;
|
||||
|
@ -441,21 +468,21 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) {
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
|
||||
state->savedata.type = savedata->type;
|
||||
state->savedata.command = savedata->command;
|
||||
state->savedata.flashState = savedata->flashState;
|
||||
state->savedata.flashBank = savedata->currentBank == &savedata->data[0x10000];
|
||||
state->savedata.readBitsRemaining = savedata->readBitsRemaining;
|
||||
state->savedata.readAddress = savedata->readAddress;
|
||||
state->savedata.writeAddress = savedata->writeAddress;
|
||||
state->savedata.settlingSector = savedata->settling;
|
||||
state->savedata.settlingDust = savedata->dust;
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
GBASerializedSavedataFlags flags = 0;
|
||||
flags = GBASerializedSavedataFlagsSetFlashState(flags, savedata->flashState);
|
||||
flags = GBASerializedSavedataFlagsTestFillFlashBank(flags, savedata->currentBank == &savedata->data[0x10000]);
|
||||
state->savedata.flags = flags;
|
||||
STORE_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
|
||||
STORE_32(savedata->readAddress, 0, &state->savedata.readAddress);
|
||||
STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
|
||||
STORE_16(savedata->settling, 0, &state->savedata.settlingSector);
|
||||
STORE_16(savedata->dust, 0, &state->savedata.settlingDust);
|
||||
}
|
||||
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData) {
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state) {
|
||||
if (state->savedata.type == SAVEDATA_FORCE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
@ -463,18 +490,17 @@ void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerial
|
|||
GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming);
|
||||
}
|
||||
savedata->command = state->savedata.command;
|
||||
savedata->flashState = state->savedata.flashState;
|
||||
savedata->readBitsRemaining = state->savedata.readBitsRemaining;
|
||||
savedata->readAddress = state->savedata.readAddress;
|
||||
savedata->writeAddress = state->savedata.writeAddress;
|
||||
savedata->settling = state->savedata.settlingSector;
|
||||
savedata->dust = state->savedata.settlingDust;
|
||||
GBASerializedSavedataFlags flags = state->savedata.flags;
|
||||
savedata->flashState = GBASerializedSavedataFlagsGetFlashState(flags);
|
||||
LOAD_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
|
||||
LOAD_32(savedata->readAddress, 0, &state->savedata.readAddress);
|
||||
LOAD_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
|
||||
LOAD_16(savedata->settling, 0, &state->savedata.settlingSector);
|
||||
LOAD_16(savedata->dust, 0, &state->savedata.settlingDust);
|
||||
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
_flashSwitchBank(savedata, state->savedata.flashBank);
|
||||
_flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags));
|
||||
}
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
||||
|
|
|
@ -94,6 +94,7 @@ void GBASavedataDeinit(struct GBASavedata* savedata);
|
|||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
|
||||
void GBASavedataUnmask(struct GBASavedata* savedata);
|
||||
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
|
||||
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in);
|
||||
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming);
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming);
|
||||
|
@ -109,7 +110,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
|||
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "util/vfs.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include "util/png-io.h"
|
||||
|
@ -24,10 +25,21 @@
|
|||
|
||||
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
|
||||
|
||||
struct GBABundledState {
|
||||
struct GBASerializedState* state;
|
||||
struct GBAExtdata* extdata;
|
||||
};
|
||||
|
||||
struct GBAExtdataHeader {
|
||||
uint32_t tag;
|
||||
int32_t size;
|
||||
int64_t offset;
|
||||
};
|
||||
|
||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
state->versionMagic = GBA_SAVESTATE_MAGIC;
|
||||
state->biosChecksum = gba->biosChecksum;
|
||||
state->romCrc32 = gba->romCrc32;
|
||||
STORE_32(GBA_SAVESTATE_MAGIC, 0, &state->versionMagic);
|
||||
STORE_32(gba->biosChecksum, 0, &state->biosChecksum);
|
||||
STORE_32(gba->romCrc32, 0, &state->romCrc32);
|
||||
|
||||
if (gba->memory.rom) {
|
||||
state->id = ((struct GBACartridge*) gba->memory.rom)->id;
|
||||
|
@ -37,24 +49,40 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
memset(state->title, 0, sizeof(state->title));
|
||||
}
|
||||
|
||||
memcpy(state->cpu.gprs, gba->cpu->gprs, sizeof(state->cpu.gprs));
|
||||
state->cpu.cpsr = gba->cpu->cpsr;
|
||||
state->cpu.spsr = gba->cpu->spsr;
|
||||
state->cpu.cycles = gba->cpu->cycles;
|
||||
state->cpu.nextEvent = gba->cpu->nextEvent;
|
||||
memcpy(state->cpu.bankedRegisters, gba->cpu->bankedRegisters, 6 * 7 * sizeof(int32_t));
|
||||
memcpy(state->cpu.bankedSPSRs, gba->cpu->bankedSPSRs, 6 * sizeof(int32_t));
|
||||
int i;
|
||||
for (i = 0; i < 16; ++i) {
|
||||
STORE_32(gba->cpu->gprs[i], i * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
|
||||
}
|
||||
STORE_32(gba->cpu->cpsr.packed, 0, &state->cpu.cpsr.packed);
|
||||
STORE_32(gba->cpu->spsr.packed, 0, &state->cpu.spsr.packed);
|
||||
STORE_32(gba->cpu->cycles, 0, &state->cpu.cycles);
|
||||
STORE_32(gba->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||
for (i = 0; i < 6; ++i) {
|
||||
int j;
|
||||
for (j = 0; j < 7; ++j) {
|
||||
STORE_32(gba->cpu->bankedRegisters[i][j], (i * 7 + j) * sizeof(gba->cpu->bankedRegisters[0][0]), state->cpu.bankedRegisters);
|
||||
}
|
||||
STORE_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
|
||||
}
|
||||
|
||||
state->biosPrefetch = gba->memory.biosPrefetch;
|
||||
state->cpuPrefetch[0] = gba->cpu->prefetch[0];
|
||||
state->cpuPrefetch[1] = gba->cpu->prefetch[1];
|
||||
STORE_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
|
||||
STORE_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
|
||||
|
||||
GBAMemorySerialize(&gba->memory, state);
|
||||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state, false);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state);
|
||||
|
||||
struct timeval tv;
|
||||
if (!gettimeofday(&tv, 0)) {
|
||||
uint64_t usec = tv.tv_usec;
|
||||
usec += tv.tv_sec * 1000000LL;
|
||||
STORE_64(usec, 0, &state->creationUsec);
|
||||
} else {
|
||||
state->creationUsec = 0;
|
||||
}
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
gba->rr->stateSaved(gba->rr, state);
|
||||
|
@ -63,13 +91,19 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
|
||||
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
bool error = false;
|
||||
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
||||
int32_t check;
|
||||
uint32_t ucheck;
|
||||
LOAD_32(ucheck, 0, &state->versionMagic);
|
||||
if (ucheck != GBA_SAVESTATE_MAGIC) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC, ucheck);
|
||||
error = true;
|
||||
}
|
||||
if (state->biosChecksum != gba->biosChecksum) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS");
|
||||
if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) {
|
||||
LOAD_32(ucheck, 0, &state->biosChecksum);
|
||||
if (ucheck != gba->biosChecksum) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS: expected %08X, got %08X", gba->biosChecksum, ucheck);
|
||||
uint32_t pc;
|
||||
LOAD_32(pc, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
|
||||
if (pc < SIZE_BIOS && pc >= 0x20) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
@ -80,84 +114,60 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded");
|
||||
error = true;
|
||||
}
|
||||
if (state->romCrc32 != gba->romCrc32) {
|
||||
LOAD_32(ucheck, 0, &state->romCrc32);
|
||||
if (ucheck != gba->romCrc32) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game");
|
||||
}
|
||||
if (state->cpu.cycles < 0) {
|
||||
LOAD_32(check, 0, &state->cpu.cycles);
|
||||
if (check < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->cpu.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative");
|
||||
if (check >= (int32_t) GBA_ARM7TDMI_FREQUENCY) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are too high");
|
||||
error = true;
|
||||
}
|
||||
if (state->video.eventDiff < 0) {
|
||||
LOAD_32(check, 0, &state->video.eventDiff);
|
||||
if (check < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->video.nextHblank - state->video.eventDiff < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->timers[0].nextEvent < 0 || state->timers[1].nextEvent < 0 || state->timers[2].nextEvent < 0 || state->timers[3].nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->audio.eventDiff < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
|
||||
error = true;
|
||||
}
|
||||
if (!state->audio.ch1Dead && (state->audio.ch1.envelopeNextStep < 0 ||
|
||||
state->audio.ch1.waveNextStep < 0 ||
|
||||
state->audio.ch1.sweepNextStep < 0 ||
|
||||
state->audio.ch1.nextEvent < 0)) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative");
|
||||
error = true;
|
||||
}
|
||||
if (!state->audio.ch2Dead && (state->audio.ch2.envelopeNextStep < 0 ||
|
||||
state->audio.ch2.waveNextStep < 0 ||
|
||||
state->audio.ch2.nextEvent < 0)) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative");
|
||||
error = true;
|
||||
}
|
||||
if (state->audio.ch3.nextEvent < 0) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative");
|
||||
error = true;
|
||||
}
|
||||
if (!state->audio.ch4Dead && (state->audio.ch4.envelopeNextStep < 0 ||
|
||||
state->audio.ch4.nextEvent < 0)) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative");
|
||||
error = true;
|
||||
}
|
||||
int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);
|
||||
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
|
||||
LOAD_32(check, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
|
||||
int region = (check >> BASE_OFFSET);
|
||||
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((check - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
|
||||
error = true;
|
||||
}
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs));
|
||||
gba->cpu->cpsr = state->cpu.cpsr;
|
||||
gba->cpu->spsr = state->cpu.spsr;
|
||||
gba->cpu->cycles = state->cpu.cycles;
|
||||
gba->cpu->nextEvent = state->cpu.nextEvent;
|
||||
memcpy(gba->cpu->bankedRegisters, state->cpu.bankedRegisters, 6 * 7 * sizeof(int32_t));
|
||||
memcpy(gba->cpu->bankedSPSRs, state->cpu.bankedSPSRs, 6 * sizeof(int32_t));
|
||||
size_t i;
|
||||
for (i = 0; i < 16; ++i) {
|
||||
LOAD_32(gba->cpu->gprs[i], i * sizeof(gba->cpu->gprs[0]), state->cpu.gprs);
|
||||
}
|
||||
LOAD_32(gba->cpu->cpsr.packed, 0, &state->cpu.cpsr.packed);
|
||||
LOAD_32(gba->cpu->spsr.packed, 0, &state->cpu.spsr.packed);
|
||||
LOAD_32(gba->cpu->cycles, 0, &state->cpu.cycles);
|
||||
LOAD_32(gba->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||
for (i = 0; i < 6; ++i) {
|
||||
int j;
|
||||
for (j = 0; j < 7; ++j) {
|
||||
LOAD_32(gba->cpu->bankedRegisters[i][j], (i * 7 + j) * sizeof(gba->cpu->bankedRegisters[0][0]), state->cpu.bankedRegisters);
|
||||
}
|
||||
LOAD_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
|
||||
}
|
||||
gba->cpu->privilegeMode = gba->cpu->cpsr.priv;
|
||||
gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]);
|
||||
if (state->biosPrefetch) {
|
||||
gba->memory.biosPrefetch = state->biosPrefetch;
|
||||
LOAD_32(gba->memory.biosPrefetch, 0, &state->biosPrefetch);
|
||||
}
|
||||
if (gba->cpu->cpsr.t) {
|
||||
gba->cpu->executionMode = MODE_THUMB;
|
||||
if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
|
||||
gba->cpu->prefetch[0] = state->cpuPrefetch[0] & 0xFFFF;
|
||||
gba->cpu->prefetch[1] = state->cpuPrefetch[1] & 0xFFFF;
|
||||
LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
|
||||
LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
|
||||
gba->cpu->prefetch[0] &= 0xFFFF;
|
||||
gba->cpu->prefetch[1] &= 0xFFFF;
|
||||
} else {
|
||||
// Maintain backwards compat
|
||||
LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
|
@ -166,8 +176,8 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
} else {
|
||||
gba->cpu->executionMode = MODE_ARM;
|
||||
if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
|
||||
gba->cpu->prefetch[0] = state->cpuPrefetch[0];
|
||||
gba->cpu->prefetch[1] = state->cpuPrefetch[1];
|
||||
LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
|
||||
LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
|
||||
} else {
|
||||
// Maintain backwards compat
|
||||
LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
|
||||
|
@ -179,7 +189,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBAIODeserialize(gba, state);
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state, false);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state);
|
||||
|
||||
if (gba->rr) {
|
||||
gba->rr->stateLoaded(gba->rr, state);
|
||||
|
@ -188,13 +198,15 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
}
|
||||
|
||||
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) {
|
||||
char suffix[5] = { '\0' };
|
||||
snprintf(suffix, sizeof(suffix), ".ss%d", slot);
|
||||
return VDirOptionalOpenFile(dir, gba->activeFile, "savestate", suffix, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
char basename[PATH_MAX];
|
||||
separatePath(gba->activeFile, 0, basename, 0);
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s.ss%i", basename, slot);
|
||||
return dir->openFile(dir, path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
}
|
||||
|
||||
#ifdef USE_PNG
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
|
||||
unsigned stride;
|
||||
const void* pixels = 0;
|
||||
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
|
||||
|
@ -225,27 +237,75 @@ static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
|||
}
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 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_32(i, 0, data);
|
||||
STORE_32(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) {
|
||||
if (strcmp((const char*) chunk->name, "gbAs") != 0) {
|
||||
struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
|
||||
if (!bundle) {
|
||||
return 0;
|
||||
}
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
uLongf len = sizeof(*state);
|
||||
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
|
||||
if (!GBADeserialize(png_get_user_chunk_ptr(png), state)) {
|
||||
GBADeallocateState(state);
|
||||
longjmp(png_jmpbuf(png), 1);
|
||||
if (!strcmp((const char*) chunk->name, "gbAs")) {
|
||||
struct GBASerializedState* state = bundle->state;
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
uLongf len = sizeof(*state);
|
||||
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
|
||||
return 1;
|
||||
}
|
||||
GBADeallocateState(state);
|
||||
return 1;
|
||||
if (!strcmp((const char*) chunk->name, "gbAx")) {
|
||||
struct GBAExtdata* extdata = bundle->extdata;
|
||||
if (!extdata) {
|
||||
return 0;
|
||||
}
|
||||
struct GBAExtdataItem item;
|
||||
if (chunk->size < sizeof(uint32_t) * 2) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t tag;
|
||||
LOAD_32(tag, 0, chunk->data);
|
||||
LOAD_32(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;
|
||||
GBAExtdataPut(extdata, tag, &item);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
||||
static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* 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);
|
||||
|
@ -259,44 +319,85 @@ static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
|||
return false;
|
||||
}
|
||||
|
||||
PNGInstallChunkHandler(png, gba, _loadPNGChunkHandler, "gbAs");
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
struct GBABundledState bundle = {
|
||||
.state = state,
|
||||
.extdata = extdata
|
||||
};
|
||||
|
||||
PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
|
||||
bool success = PNGReadHeader(png, info);
|
||||
success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
|
||||
success = success && PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
if (success) {
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels);
|
||||
GBASyncForceFrame(gba->sync);
|
||||
}
|
||||
|
||||
free(pixels);
|
||||
return success;
|
||||
if (success) {
|
||||
struct GBAExtdataItem item = {
|
||||
.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
|
||||
.data = pixels,
|
||||
.clean = free
|
||||
};
|
||||
GBAExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
|
||||
} else {
|
||||
free(pixels);
|
||||
GBADeallocateState(state);
|
||||
return 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _3DS
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) {
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, int flags) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
#if SAVESTATE_DEBUG
|
||||
vf = GBAGetState(threadContext->gba, dir, slot, false);
|
||||
if (vf) {
|
||||
struct GBA* backup = anonymousMemoryMap(sizeof(*backup));
|
||||
memcpy(backup, threadContext->gba, sizeof(*backup));
|
||||
memset(threadContext->gba->memory.io, 0, sizeof(threadContext->gba->memory.io));
|
||||
memset(threadContext->gba->timers, 0, sizeof(threadContext->gba->timers));
|
||||
GBALoadStateNamed(threadContext->gba, vf, flags);
|
||||
if (memcmp(backup, threadContext->gba, sizeof(*backup))) {
|
||||
char suffix[16] = { '\0' };
|
||||
struct VFile* vf2;
|
||||
snprintf(suffix, sizeof(suffix), ".dump.0.%d", slot);
|
||||
vf2 = VDirOptionalOpenFile(dir, threadContext->gba->activeFile, "savestate", suffix, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
if (vf2) {
|
||||
vf2->write(vf2, backup, sizeof(*backup));
|
||||
vf2->close(vf2);
|
||||
}
|
||||
snprintf(suffix, sizeof(suffix), ".dump.1.%d", slot);
|
||||
vf2 = VDirOptionalOpenFile(dir, threadContext->gba->activeFile, "savestate", suffix, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
if (vf2) {
|
||||
vf2->write(vf2, threadContext->gba, sizeof(*threadContext->gba));
|
||||
vf2->close(vf2);
|
||||
}
|
||||
}
|
||||
mappedMemoryFree(backup, sizeof(*backup));
|
||||
vf->close(vf);
|
||||
}
|
||||
#endif
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot);
|
||||
} else {
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
|
||||
bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot, int flags) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
threadContext->rewindBufferSize = 0;
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf);
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot);
|
||||
|
@ -305,49 +406,222 @@ bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
|
|||
}
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) {
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
|
||||
struct GBAExtdata extdata;
|
||||
GBAExtdataInit(&extdata);
|
||||
if (flags & SAVESTATE_SAVEDATA) {
|
||||
// TODO: A better way to do this would be nice
|
||||
void* sram = malloc(SIZE_CART_FLASH1M);
|
||||
struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
|
||||
if (GBASavedataClone(&gba->memory.savedata, svf)) {
|
||||
struct GBAExtdataItem item = {
|
||||
.size = svf->seek(svf, 0, SEEK_CUR),
|
||||
.data = sram,
|
||||
.clean = free
|
||||
};
|
||||
GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
|
||||
} else {
|
||||
free(sram);
|
||||
}
|
||||
svf->close(svf);
|
||||
}
|
||||
#ifdef USE_PNG
|
||||
if (!screenshot) {
|
||||
if (!(flags & SAVESTATE_SCREENSHOT)) {
|
||||
#else
|
||||
UNUSED(screenshot);
|
||||
UNUSED(flags);
|
||||
#endif
|
||||
vf->truncate(vf, sizeof(struct GBASerializedState));
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
|
||||
if (!state) {
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
|
||||
GBAExtdataSerialize(&extdata, vf);
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return true;
|
||||
#ifdef USE_PNG
|
||||
}
|
||||
else {
|
||||
return _savePNGState(gba, vf);
|
||||
bool success = _savePNGState(gba, vf, &extdata);
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
||||
struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata) {
|
||||
#ifdef USE_PNG
|
||||
if (isPNG(vf)) {
|
||||
return _loadPNGState(gba, vf);
|
||||
return _loadPNGState(vf, extdata);
|
||||
}
|
||||
#endif
|
||||
if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
|
||||
return false;
|
||||
}
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
|
||||
GBADeallocateState(state);
|
||||
return 0;
|
||||
}
|
||||
if (extdata) {
|
||||
GBAExtdataDeserialize(extdata, vf);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
|
||||
struct GBAExtdata extdata;
|
||||
GBAExtdataInit(&extdata);
|
||||
struct GBASerializedState* state = GBAExtractState(vf, &extdata);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBADeserialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
GBADeallocateState(state);
|
||||
|
||||
struct GBAExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
|
||||
if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
|
||||
GBASyncForceFrame(gba->sync);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_WARN, "Savestate includes invalid screenshot");
|
||||
}
|
||||
}
|
||||
if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
|
||||
struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
if (svf) {
|
||||
GBASavedataLoad(&gba->memory.savedata, svf);
|
||||
svf->close(svf);
|
||||
}
|
||||
}
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GBAExtdataInit(struct GBAExtdata* extdata) {
|
||||
memset(extdata->data, 0, sizeof(extdata->data));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAExtdataDeinit(struct GBAExtdata* 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 GBAExtdataPut(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* 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 GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
|
||||
if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*item = extdata->data[tag];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) {
|
||||
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
|
||||
ssize_t size = 2;
|
||||
size_t i = 0;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data) {
|
||||
size += sizeof(uint64_t) * 2;
|
||||
}
|
||||
}
|
||||
if (size == 2) {
|
||||
return true;
|
||||
}
|
||||
struct GBAExtdataHeader* header = malloc(size);
|
||||
position += size;
|
||||
|
||||
size_t j;
|
||||
for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
|
||||
if (extdata->data[i].data) {
|
||||
STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]);
|
||||
STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]);
|
||||
STORE_64(position, offsetof(struct GBAExtdataHeader, 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 GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) {
|
||||
while (true) {
|
||||
struct GBAExtdataHeader buffer, header;
|
||||
if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||
return false;
|
||||
}
|
||||
LOAD_32(header.tag, 0, &buffer.tag);
|
||||
LOAD_32(header.size, 0, &buffer.size);
|
||||
LOAD_64(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 GBAExtdataItem 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;
|
||||
}
|
||||
GBAExtdataPut(extdata, header.tag, &item);
|
||||
vf->seek(vf, position, SEEK_SET);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
struct GBASerializedState* GBAAllocateState(void) {
|
||||
return anonymousMemoryMap(sizeof(struct GBASerializedState));
|
||||
}
|
||||
|
@ -437,7 +711,9 @@ void GBATakeScreenshot(struct GBA* gba, struct VDir* dir) {
|
|||
#ifdef USE_PNG
|
||||
unsigned stride;
|
||||
const void* pixels = 0;
|
||||
struct VFile* vf = VDirOptionalOpenIncrementFile(dir, gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
char basename[PATH_MAX];
|
||||
separatePath(gba->activeFile, 0, basename, 0);
|
||||
struct VFile* vf = VDirFindNextAvailable(dir, basename, "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
bool success = false;
|
||||
if (vf) {
|
||||
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
|
||||
|
@ -451,6 +727,8 @@ void GBATakeScreenshot(struct GBA* gba, struct VDir* dir) {
|
|||
GBALog(gba, GBA_LOG_STATUS, "Screenshot saved");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
UNUSED(dir);
|
||||
#endif
|
||||
GBALog(gba, GBA_LOG_STATUS, "Failed to take screenshot");
|
||||
}
|
||||
|
|
|
@ -174,7 +174,9 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
|
||||
* | 0x002FC - 0x002FF: CPU prefetch (fetch slot)
|
||||
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
|
||||
* 0x00304 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00304 - 0x0030F: Reserved (leave zero)
|
||||
* 0x00310 - 0x00317: Savestate creation time (usec since 1970)
|
||||
* 0x00318 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00400 - 0x007FF: I/O memory
|
||||
* 0x00800 - 0x00BFF: Palette
|
||||
* 0x00C00 - 0x00FFF: OAM
|
||||
|
@ -184,6 +186,33 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* Total size: 0x61000 (397,312) bytes
|
||||
*/
|
||||
|
||||
DECL_BITFIELD(GBASerializedAudioFlags, uint32_t);
|
||||
DECL_BITS(GBASerializedAudioFlags, Ch1Volume, 0, 4);
|
||||
DECL_BIT(GBASerializedAudioFlags, Ch1Dead, 4);
|
||||
DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 5);
|
||||
DECL_BITS(GBASerializedAudioFlags, Ch2Volume, 8, 4);
|
||||
DECL_BIT(GBASerializedAudioFlags, Ch2Dead, 12);
|
||||
DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 13);
|
||||
DECL_BITS(GBASerializedAudioFlags, Ch4Volume, 16, 4);
|
||||
DECL_BIT(GBASerializedAudioFlags, Ch4Dead, 20);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags1, uint16_t);
|
||||
DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0);
|
||||
DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);
|
||||
DECL_BIT(GBASerializedHWFlags1, LightEdge, 2);
|
||||
DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags2, uint8_t);
|
||||
DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2);
|
||||
DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 2);
|
||||
DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 5);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags3, uint16_t);
|
||||
|
||||
DECL_BITFIELD(GBASerializedSavedataFlags, uint8_t);
|
||||
DECL_BITS(GBASerializedSavedataFlags, FlashState, 0, 2);
|
||||
DECL_BIT(GBASerializedSavedataFlags, FlashBank, 4);
|
||||
|
||||
struct GBASerializedState {
|
||||
uint32_t versionMagic;
|
||||
uint32_t biosChecksum;
|
||||
|
@ -236,18 +265,7 @@ struct GBASerializedState {
|
|||
int32_t eventDiff;
|
||||
int32_t nextSample;
|
||||
uint32_t fifoSize;
|
||||
unsigned ch1Volume : 4;
|
||||
unsigned ch1Dead : 1;
|
||||
unsigned ch1Hi : 1;
|
||||
unsigned : 2;
|
||||
unsigned ch2Volume : 4;
|
||||
unsigned ch2Dead : 1;
|
||||
unsigned ch2Hi : 1;
|
||||
unsigned : 2;
|
||||
unsigned ch4Volume : 4;
|
||||
unsigned ch4Dead : 1;
|
||||
unsigned : 3;
|
||||
unsigned : 8;
|
||||
GBASerializedAudioFlags flags;
|
||||
} audio;
|
||||
|
||||
struct {
|
||||
|
@ -275,33 +293,23 @@ struct GBASerializedState {
|
|||
uint16_t pinDirection;
|
||||
struct GBARTC rtc;
|
||||
uint8_t devices;
|
||||
// Do not change these to uint16_t, this breaks bincompat with some older compilers
|
||||
unsigned gyroSample : 16;
|
||||
unsigned tiltSampleX : 16;
|
||||
unsigned tiltSampleY : 16;
|
||||
unsigned readWrite : 1;
|
||||
unsigned gyroEdge : 1;
|
||||
unsigned lightEdge : 1;
|
||||
unsigned : 1;
|
||||
unsigned lightCounter : 12;
|
||||
unsigned lightSample : 8;
|
||||
unsigned tiltState : 2;
|
||||
unsigned gbpInputsPosted : 2;
|
||||
unsigned gbpTxPosition : 5;
|
||||
unsigned : 15;
|
||||
uint32_t gbpNextEvent : 32;
|
||||
uint16_t gyroSample;
|
||||
uint16_t tiltSampleX;
|
||||
uint16_t tiltSampleY;
|
||||
GBASerializedHWFlags1 flags1;
|
||||
uint8_t lightSample;
|
||||
GBASerializedHWFlags2 flags2;
|
||||
GBASerializedHWFlags3 flags3;
|
||||
uint32_t gbpNextEvent;
|
||||
} hw;
|
||||
|
||||
uint32_t reservedHardware[6];
|
||||
|
||||
struct {
|
||||
unsigned type : 8;
|
||||
unsigned command : 8;
|
||||
unsigned flashState : 2;
|
||||
unsigned : 2;
|
||||
unsigned flashBank : 1;
|
||||
unsigned : 3;
|
||||
unsigned : 8;
|
||||
uint8_t type;
|
||||
uint8_t command;
|
||||
GBASerializedSavedataFlags flags;
|
||||
uint8_t reserved;
|
||||
int32_t readBitsRemaining;
|
||||
uint32_t readAddress;
|
||||
uint32_t writeAddress;
|
||||
|
@ -313,8 +321,11 @@ struct GBASerializedState {
|
|||
uint32_t cpuPrefetch[2];
|
||||
|
||||
uint32_t associatedStreamId;
|
||||
uint32_t reservedRr[3];
|
||||
|
||||
uint32_t reserved[63];
|
||||
uint64_t creationUsec;
|
||||
|
||||
uint32_t reserved[58];
|
||||
|
||||
uint16_t io[SIZE_IO >> 1];
|
||||
uint16_t pram[SIZE_PALETTE_RAM >> 1];
|
||||
|
@ -324,19 +335,47 @@ struct GBASerializedState {
|
|||
uint8_t wram[SIZE_WORKING_RAM];
|
||||
};
|
||||
|
||||
enum GBAExtdataTag {
|
||||
EXTDATA_NONE = 0,
|
||||
EXTDATA_SCREENSHOT = 1,
|
||||
EXTDATA_SAVEDATA = 2,
|
||||
EXTDATA_MAX
|
||||
};
|
||||
|
||||
#define SAVESTATE_SCREENSHOT 1
|
||||
#define SAVESTATE_SAVEDATA 2
|
||||
|
||||
struct GBAExtdataItem {
|
||||
int32_t size;
|
||||
void* data;
|
||||
void (*clean)(void*);
|
||||
};
|
||||
|
||||
struct GBAExtdata {
|
||||
struct GBAExtdataItem data[EXTDATA_MAX];
|
||||
};
|
||||
|
||||
struct VDir;
|
||||
struct GBAThread;
|
||||
|
||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||
|
||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
|
||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, int flags);
|
||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot, int flags);
|
||||
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write);
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf);
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
|
||||
bool GBAExtdataInit(struct GBAExtdata*);
|
||||
void GBAExtdataDeinit(struct GBAExtdata*);
|
||||
void GBAExtdataPut(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*);
|
||||
bool GBAExtdataGet(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*);
|
||||
bool GBAExtdataSerialize(struct GBAExtdata* extpdata, struct VFile* vf);
|
||||
bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf);
|
||||
|
||||
struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata);
|
||||
struct GBASerializedState* GBAAllocateState(void);
|
||||
void GBADeallocateState(struct GBASerializedState* state);
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ struct GBASIO {
|
|||
struct GBASIODriver* activeDriver;
|
||||
|
||||
uint16_t rcnt;
|
||||
// TODO: Convert to bitfields
|
||||
union {
|
||||
struct {
|
||||
unsigned sc : 1;
|
||||
|
|
|
@ -14,8 +14,10 @@ static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
|
|||
static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeMultiProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeNormalProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
||||
lockstep->players[0] = 0;
|
||||
|
@ -27,7 +29,8 @@ void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
|||
lockstep->multiRecv[2] = 0xFFFF;
|
||||
lockstep->multiRecv[3] = 0xFFFF;
|
||||
lockstep->attached = 0;
|
||||
lockstep->loaded = 0;
|
||||
lockstep->loadedMulti = 0;
|
||||
lockstep->loadedNormal = 0;
|
||||
lockstep->transferActive = false;
|
||||
lockstep->waiting = 0;
|
||||
lockstep->nextEvent = LOCKSTEP_INCREMENT;
|
||||
|
@ -45,8 +48,8 @@ void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
|||
node->d.deinit = GBASIOLockstepNodeDeinit;
|
||||
node->d.load = GBASIOLockstepNodeLoad;
|
||||
node->d.unload = GBASIOLockstepNodeUnload;
|
||||
node->d.writeRegister = GBASIOLockstepNodeWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeProcessEvents;
|
||||
node->d.writeRegister = 0;
|
||||
node->d.processEvents = 0;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
|
@ -93,12 +96,26 @@ void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
|
|||
bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
node->mode = driver->p->mode;
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->loaded;
|
||||
node->d.p->rcnt |= 3;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeMultiProcessEvents;
|
||||
++node->p->loadedMulti;
|
||||
node->d.p->rcnt |= 3;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
}
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeNormalProcessEvents;
|
||||
++node->p->loadedNormal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
|
@ -107,13 +124,22 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
|||
bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
MutexLock(&node->p->mutex);
|
||||
--node->p->loaded;
|
||||
switch (node->mode) {
|
||||
case SIO_MULTI:
|
||||
--node->p->loadedMulti;
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
--node->p->loadedNormal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ConditionWake(&node->p->barrier);
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||
|
@ -137,13 +163,13 @@ static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uin
|
|||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
static int32_t GBASIOLockstepNodeMultiProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent -= cycles;
|
||||
while (node->nextEvent <= 0) {
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->waiting;
|
||||
if (node->p->waiting < node->p->loaded) {
|
||||
if (node->p->waiting < node->p->loadedMulti) {
|
||||
ConditionWait(&node->p->barrier, &node->p->mutex);
|
||||
} else {
|
||||
if (node->p->transferActive) {
|
||||
|
@ -193,7 +219,87 @@ static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int3
|
|||
node->d.p->multiplayerControl.busy = 1;
|
||||
}
|
||||
}
|
||||
node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached;
|
||||
node->d.p->multiplayerControl.ready = node->p->loadedMulti == node->p->attached;
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
||||
|
||||
static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||
value &= 0xFF8B;
|
||||
MutexLock(&node->p->mutex);
|
||||
if (value & 0x0080) {
|
||||
// Internal shift clock
|
||||
if (value & 1) {
|
||||
node->p->transferActive = true;
|
||||
}
|
||||
// Frequency
|
||||
if (value & 2) {
|
||||
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 1024;
|
||||
} else {
|
||||
node->p->transferCycles = GBA_ARM7TDMI_FREQUENCY / 8192;
|
||||
}
|
||||
node->normalSO = !!(value & 8);
|
||||
// Opponent's SO
|
||||
if (node->id) {
|
||||
value |= node->p->players[node->id - 1]->normalSO << 2;
|
||||
}
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
} else if (address == REG_SIODATA32_LO) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIODATA32_LO <- %04x", node->id, value);
|
||||
} else if (address == REG_SIODATA32_HI) {
|
||||
GBALog(node->d.p->p, GBA_LOG_SIO, "Lockstep %i: SIODATA32_HI <- %04x", node->id, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeNormalProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent -= cycles;
|
||||
while (node->nextEvent <= 0) {
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->waiting;
|
||||
if (node->p->waiting < node->p->loadedNormal) {
|
||||
ConditionWait(&node->p->barrier, &node->p->mutex);
|
||||
} else {
|
||||
if (node->p->transferActive) {
|
||||
node->p->transferCycles -= node->p->nextEvent;
|
||||
if (node->p->transferCycles > 0) {
|
||||
if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
|
||||
node->p->nextEvent = node->p->transferCycles;
|
||||
}
|
||||
} else {
|
||||
node->p->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->p->transferActive = false;
|
||||
int i;
|
||||
for (i = 0; i < node->p->attached; ++i) {
|
||||
node->p->players[i]->state = LOCKSTEP_FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
node->p->waiting = 0;
|
||||
ConditionWake(&node->p->barrier);
|
||||
}
|
||||
if (node->state == LOCKSTEP_FINISHED) {
|
||||
int i;
|
||||
for (i = 1; i < node->p->loadedNormal; ++i) {
|
||||
node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_LO >> 1];
|
||||
node->p->players[i]->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = node->p->players[i - 1]->d.p->p->memory.io[REG_SIODATA32_HI >> 1];
|
||||
}
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
if (node->d.p->normalControl.irq) {
|
||||
GBARaiseIRQ(node->d.p->p, IRQ_SIO);
|
||||
}
|
||||
node->d.p->multiplayerControl.id = node->id;
|
||||
node->d.p->normalControl.start = 0;
|
||||
} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
|
||||
node->state = LOCKSTEP_STARTED;
|
||||
}
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ enum LockstepState {
|
|||
struct GBASIOLockstep {
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attached;
|
||||
int loaded;
|
||||
int loadedMulti;
|
||||
int loadedNormal;
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
bool transferActive;
|
||||
|
@ -37,8 +38,10 @@ struct GBASIOLockstepNode {
|
|||
|
||||
int32_t nextEvent;
|
||||
uint16_t multiSend;
|
||||
bool normalSO;
|
||||
enum LockstepState state;
|
||||
int id;
|
||||
enum GBASIOMode mode;
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
|
|
|
@ -106,7 +106,7 @@ static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
|
||||
GBALoadState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue);
|
||||
GBALoadState(gbaDebugger->context, gbaDebugger->context->dirs.state, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||
}
|
||||
|
||||
static void _rewind(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||
|
@ -133,6 +133,6 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
|
||||
|
||||
GBASaveState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, true);
|
||||
GBASaveState(gbaDebugger->context, gbaDebugger->context->dirs.state, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -22,11 +22,41 @@
|
|||
|
||||
#include <signal.h>
|
||||
|
||||
static void _loadGameDir(struct GBAThread* threadContext);
|
||||
|
||||
static const float _defaultFPSTarget = 60.f;
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
|
||||
static bool _reloadDirectories(struct GBAThread* threadContext) {
|
||||
GBADirectorySetDetachBase(&threadContext->dirs);
|
||||
|
||||
char basename[PATH_MAX];
|
||||
if (threadContext->fname) {
|
||||
char dirname[PATH_MAX];
|
||||
separatePath(threadContext->fname, dirname, basename, 0);
|
||||
GBADirectorySetAttachBase(&threadContext->dirs, VDirOpen(dirname));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s.sav", basename);
|
||||
threadContext->save = threadContext->dirs.save->openFile(threadContext->dirs.save, path, O_CREAT | O_RDWR);
|
||||
|
||||
if (!threadContext->patch) {
|
||||
snprintf(path, sizeof(path), "%s.ups", basename);
|
||||
threadContext->patch = threadContext->dirs.patch->openFile(threadContext->dirs.patch, path, O_RDONLY);
|
||||
}
|
||||
if (!threadContext->patch) {
|
||||
snprintf(path, sizeof(path), "%s.ips", basename);
|
||||
threadContext->patch = threadContext->dirs.patch->openFile(threadContext->dirs.patch, path, O_RDONLY);
|
||||
}
|
||||
if (!threadContext->patch) {
|
||||
snprintf(path, sizeof(path), "%s.bps", basename);
|
||||
threadContext->patch = threadContext->dirs.patch->openFile(threadContext->dirs.patch, path, O_RDONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_PTHREADS
|
||||
static pthread_key_t _contextKey;
|
||||
static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
|
||||
|
@ -71,13 +101,15 @@ static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState
|
|||
while (threadContext->state == oldState) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
|
||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
if (!MutexTryLock(&threadContext->sync.videoFrameMutex)) {
|
||||
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||
}
|
||||
|
||||
MutexLock(&threadContext->sync.audioBufferMutex);
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.audioBufferMutex);
|
||||
if (!MutexTryLock(&threadContext->sync.audioBufferMutex)) {
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
MutexUnlock(&threadContext->sync.audioBufferMutex);
|
||||
}
|
||||
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
|
@ -167,10 +199,14 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
}
|
||||
|
||||
if (threadContext->rom) {
|
||||
GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
|
||||
if (GBAIsMB(threadContext->rom)) {
|
||||
GBALoadMB(&gba, threadContext->rom, threadContext->fname);
|
||||
} else {
|
||||
GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
|
||||
}
|
||||
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba.pristineRom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(threadContext->overrides, &override)) {
|
||||
GBAOverrideApply(&gba, &override);
|
||||
|
@ -190,11 +226,9 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
|
||||
if (threadContext->movie) {
|
||||
struct VDir* movieDir = VDirOpen(threadContext->movie);
|
||||
#ifdef USE_LIBZIP
|
||||
if (!movieDir) {
|
||||
movieDir = VDirOpenZip(threadContext->movie, 0);
|
||||
movieDir = VDirOpenArchive(threadContext->movie);
|
||||
}
|
||||
#endif
|
||||
if (movieDir) {
|
||||
struct GBAMGMContext* mgm = malloc(sizeof(*mgm));
|
||||
GBAMGMContextCreate(mgm);
|
||||
|
@ -225,8 +259,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
GBARRInitPlay(&gba);
|
||||
}
|
||||
|
||||
if (threadContext->skipBios && gba.memory.rom) {
|
||||
GBASkipBIOS(&cpu);
|
||||
if (threadContext->skipBios && gba.pristineRom) {
|
||||
GBASkipBIOS(&gba);
|
||||
}
|
||||
|
||||
if (!threadContext->cheats) {
|
||||
|
@ -305,8 +339,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
MutexUnlock(&threadContext->stateMutex);
|
||||
if (resetScheduled) {
|
||||
ARMReset(&cpu);
|
||||
if (threadContext->skipBios && gba.memory.rom) {
|
||||
GBASkipBIOS(&cpu);
|
||||
if (threadContext->skipBios && gba.pristineRom) {
|
||||
GBASkipBIOS(&gba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -370,12 +404,7 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr
|
|||
}
|
||||
|
||||
void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
|
||||
if (args->dirmode) {
|
||||
threadContext->gameDir = VDirOpen(args->fname);
|
||||
threadContext->stateDir = threadContext->gameDir;
|
||||
} else {
|
||||
GBAThreadLoadROM(threadContext, args->fname);
|
||||
}
|
||||
GBAThreadLoadROM(threadContext, args->fname);
|
||||
threadContext->fname = args->fname;
|
||||
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
|
||||
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
|
||||
|
@ -407,26 +436,13 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
threadContext->rom = 0;
|
||||
}
|
||||
|
||||
if (threadContext->gameDir) {
|
||||
_loadGameDir(threadContext);
|
||||
}
|
||||
|
||||
if (!threadContext->rom && !bootBios) {
|
||||
threadContext->state = THREAD_SHUTDOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
|
||||
|
||||
if (!threadContext->patch) {
|
||||
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ups", O_RDONLY);
|
||||
}
|
||||
if (!threadContext->patch) {
|
||||
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ips", O_RDONLY);
|
||||
}
|
||||
if (!threadContext->patch) {
|
||||
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".bps", O_RDONLY);
|
||||
}
|
||||
GBADirectorySetInit(&threadContext->dirs);
|
||||
_reloadDirectories(threadContext);
|
||||
|
||||
MutexInit(&threadContext->stateMutex);
|
||||
ConditionInit(&threadContext->stateCond);
|
||||
|
@ -556,18 +572,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
|
|||
threadContext->patch = 0;
|
||||
}
|
||||
|
||||
if (threadContext->gameDir) {
|
||||
if (threadContext->stateDir == threadContext->gameDir) {
|
||||
threadContext->stateDir = 0;
|
||||
}
|
||||
threadContext->gameDir->close(threadContext->gameDir);
|
||||
threadContext->gameDir = 0;
|
||||
}
|
||||
|
||||
if (threadContext->stateDir) {
|
||||
threadContext->stateDir->close(threadContext->stateDir);
|
||||
threadContext->stateDir = 0;
|
||||
}
|
||||
GBADirectorySetDeinit(&threadContext->dirs);
|
||||
}
|
||||
|
||||
bool GBAThreadIsActive(struct GBAThread* threadContext) {
|
||||
|
@ -681,32 +686,7 @@ void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
|
|||
}
|
||||
|
||||
void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) {
|
||||
threadContext->rom = VFileOpen(fname, O_RDONLY);
|
||||
threadContext->gameDir = 0;
|
||||
if (!threadContext->gameDir) {
|
||||
threadContext->gameDir = VDirOpenArchive(fname);
|
||||
}
|
||||
}
|
||||
|
||||
static void _loadGameDir(struct GBAThread* threadContext) {
|
||||
threadContext->gameDir->rewind(threadContext->gameDir);
|
||||
struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
|
||||
while (dirent) {
|
||||
struct Patch patchTemp;
|
||||
struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
|
||||
if (!vf) {
|
||||
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
|
||||
continue;
|
||||
}
|
||||
if (!threadContext->rom && GBAIsROM(vf)) {
|
||||
threadContext->rom = vf;
|
||||
} else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
|
||||
threadContext->patch = vf;
|
||||
} else {
|
||||
vf->close(vf);
|
||||
}
|
||||
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
|
||||
}
|
||||
threadContext->rom = GBADirectorySetOpenPath(&threadContext->dirs, fname, GBAIsROM);
|
||||
}
|
||||
|
||||
void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname) {
|
||||
|
@ -722,13 +702,15 @@ void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname) {
|
|||
threadContext->save = 0;
|
||||
}
|
||||
|
||||
GBAThreadLoadROM(threadContext, fname);
|
||||
if (threadContext->gameDir) {
|
||||
_loadGameDir(threadContext);
|
||||
if (threadContext->dirs.archive) {
|
||||
threadContext->dirs.archive->close(threadContext->dirs.archive);
|
||||
threadContext->dirs.archive = 0;
|
||||
}
|
||||
|
||||
GBAThreadLoadROM(threadContext, fname);
|
||||
|
||||
threadContext->fname = fname;
|
||||
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
|
||||
_reloadDirectories(threadContext);
|
||||
|
||||
GBARaiseIRQ(threadContext->gba, IRQ_GAMEPAK);
|
||||
GBALoadROM(threadContext->gba, threadContext->rom, threadContext->save, threadContext->fname);
|
||||
|
@ -747,7 +729,7 @@ struct GBAThread* GBAThreadGetContext(void) {
|
|||
#endif
|
||||
|
||||
void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
|
||||
GBATakeScreenshot(threadContext->gba, threadContext->stateDir);
|
||||
GBATakeScreenshot(threadContext->gba, threadContext->dirs.screenshot);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/input.h"
|
||||
#include "gba/context/directories.h"
|
||||
#include "gba/context/overrides.h"
|
||||
#include "gba/context/sync.h"
|
||||
|
||||
|
@ -47,8 +48,7 @@ struct GBAThread {
|
|||
struct GBAVideoRenderer* renderer;
|
||||
struct GBASIODriverSet sioDrivers;
|
||||
struct ARMDebugger* debugger;
|
||||
struct VDir* gameDir;
|
||||
struct VDir* stateDir;
|
||||
struct GBADirectorySet dirs;
|
||||
struct VFile* rom;
|
||||
struct VFile* save;
|
||||
struct VFile* bios;
|
||||
|
|
|
@ -71,7 +71,6 @@ void GBAVideoReset(struct GBAVideo* video) {
|
|||
}
|
||||
video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
|
||||
|
||||
video->lastHblank = 0;
|
||||
video->nextHblank = VIDEO_HDRAW_LENGTH;
|
||||
video->nextEvent = video->nextHblank;
|
||||
video->eventDiff = 0;
|
||||
|
@ -89,13 +88,8 @@ void GBAVideoReset(struct GBAVideo* video) {
|
|||
video->vram = anonymousMemoryMap(SIZE_VRAM);
|
||||
video->renderer->vram = video->vram;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 128; ++i) {
|
||||
video->oam.raw[i * 4] = 0x0200;
|
||||
video->oam.raw[i * 4 + 1] = 0x0000;
|
||||
video->oam.raw[i * 4 + 2] = 0x0000;
|
||||
video->oam.raw[i * 4 + 3] = 0x0000;
|
||||
}
|
||||
memset(video->palette, 0, sizeof(video->palette));
|
||||
memset(video->oam.raw, 0, sizeof(video->oam.raw));
|
||||
|
||||
video->renderer->deinit(video->renderer);
|
||||
video->renderer->init(video->renderer);
|
||||
|
@ -120,7 +114,6 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
|
|||
video->eventDiff += cycles;
|
||||
if (video->nextEvent <= 0) {
|
||||
int32_t lastEvent = video->nextEvent;
|
||||
video->lastHblank -= video->eventDiff;
|
||||
video->nextHblank -= video->eventDiff;
|
||||
video->nextHblankIRQ -= video->eventDiff;
|
||||
video->nextVcounterIRQ -= video->eventDiff;
|
||||
|
@ -167,6 +160,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
|
|||
GBAFrameEnded(video->p);
|
||||
--video->frameskipCounter;
|
||||
if (video->frameskipCounter < 0) {
|
||||
GBASyncPostFrame(video->p->sync);
|
||||
video->frameskipCounter = video->frameskip;
|
||||
}
|
||||
++video->frameCounter;
|
||||
|
@ -178,8 +172,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
|
|||
} else {
|
||||
// Begin Hblank
|
||||
dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
|
||||
video->lastHblank = video->nextHblank;
|
||||
video->nextEvent = video->lastHblank + VIDEO_HBLANK_LENGTH;
|
||||
video->nextEvent = video->nextHblank + VIDEO_HBLANK_LENGTH;
|
||||
video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH;
|
||||
video->nextHblankIRQ = video->nextHblank;
|
||||
|
||||
|
@ -276,32 +269,33 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState*
|
|||
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
|
||||
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
||||
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
||||
state->video.nextEvent = video->nextEvent;
|
||||
state->video.eventDiff = video->eventDiff;
|
||||
state->video.lastHblank = video->lastHblank;
|
||||
state->video.nextHblank = video->nextHblank;
|
||||
state->video.nextHblankIRQ = video->nextHblankIRQ;
|
||||
state->video.nextVblankIRQ = video->nextVblankIRQ;
|
||||
state->video.nextVcounterIRQ = video->nextVcounterIRQ;
|
||||
state->video.frameCounter = video->frameCounter;
|
||||
STORE_32(video->nextEvent, 0, &state->video.nextEvent);
|
||||
STORE_32(video->eventDiff, 0, &state->video.eventDiff);
|
||||
STORE_32(video->nextHblank, 0, &state->video.nextHblank);
|
||||
STORE_32(video->nextHblankIRQ, 0, &state->video.nextHblankIRQ);
|
||||
STORE_32(video->nextVblankIRQ, 0, &state->video.nextVblankIRQ);
|
||||
STORE_32(video->nextVcounterIRQ, 0, &state->video.nextVcounterIRQ);
|
||||
STORE_32(video->frameCounter, 0, &state->video.frameCounter);
|
||||
}
|
||||
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) {
|
||||
memcpy(video->renderer->vram, state->vram, SIZE_VRAM);
|
||||
uint16_t value;
|
||||
int i;
|
||||
for (i = 0; i < SIZE_OAM; i += 2) {
|
||||
GBAStore16(video->p->cpu, BASE_OAM | i, state->oam[i >> 1], 0);
|
||||
LOAD_16(value, i, state->oam);
|
||||
GBAStore16(video->p->cpu, BASE_OAM | i, value, 0);
|
||||
}
|
||||
for (i = 0; i < SIZE_PALETTE_RAM; i += 2) {
|
||||
GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, state->pram[i >> 1], 0);
|
||||
LOAD_16(value, i, state->pram);
|
||||
GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0);
|
||||
}
|
||||
video->nextEvent = state->video.nextEvent;
|
||||
video->eventDiff = state->video.eventDiff;
|
||||
video->lastHblank = state->video.lastHblank;
|
||||
video->nextHblank = state->video.nextHblank;
|
||||
video->nextHblankIRQ = state->video.nextHblankIRQ;
|
||||
video->nextVblankIRQ = state->video.nextVblankIRQ;
|
||||
video->nextVcounterIRQ = state->video.nextVcounterIRQ;
|
||||
video->frameCounter = state->video.frameCounter;
|
||||
video->vcount = state->io[REG_VCOUNT >> 1];
|
||||
LOAD_32(video->nextEvent, 0, &state->video.nextEvent);
|
||||
LOAD_32(video->eventDiff, 0, &state->video.eventDiff);
|
||||
LOAD_32(video->nextHblank, 0, &state->video.nextHblank);
|
||||
LOAD_32(video->nextHblankIRQ, 0, &state->video.nextHblankIRQ);
|
||||
LOAD_32(video->nextVblankIRQ, 0, &state->video.nextVblankIRQ);
|
||||
LOAD_32(video->nextVcounterIRQ, 0, &state->video.nextVcounterIRQ);
|
||||
LOAD_32(video->frameCounter, 0, &state->video.frameCounter);
|
||||
LOAD_16(video->vcount, REG_VCOUNT, state->io);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ enum {
|
|||
|
||||
VIDEO_TOTAL_LENGTH = VIDEO_HORIZONTAL_LENGTH * VIDEO_VERTICAL_TOTAL_PIXELS,
|
||||
|
||||
REG_DISPSTAT_MASK = 0xFF38,
|
||||
OBJ_HBLANK_FREE_LENGTH = 954,
|
||||
OBJ_LENGTH = 1210,
|
||||
|
||||
BASE_TILE = 0x00010000
|
||||
};
|
||||
|
@ -185,7 +186,6 @@ struct GBAVideo {
|
|||
// VCOUNT
|
||||
int vcount;
|
||||
|
||||
int32_t lastHblank;
|
||||
int32_t nextHblank;
|
||||
int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
|
|
|
@ -18,7 +18,7 @@ struct VFile3DS {
|
|||
|
||||
struct VDirEntry3DS {
|
||||
struct VDirEntry d;
|
||||
FS_dirent ent;
|
||||
FS_DirectoryEntry ent;
|
||||
char utf8Name[256];
|
||||
};
|
||||
|
||||
|
@ -49,14 +49,15 @@ static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path);
|
|||
static const char* _vd3deName(struct VDirEntry* vde);
|
||||
static enum VFSType _vd3deType(struct VDirEntry* vde);
|
||||
|
||||
struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags) {
|
||||
struct VFile* VFileOpen3DS(FS_Archive* archive, const char* path, int flags) {
|
||||
struct VFile3DS* vf3d = malloc(sizeof(struct VFile3DS));
|
||||
if (!vf3d) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FS_path newPath = FS_makePath(PATH_CHAR, path);
|
||||
Result res = FSUSER_OpenFile(0, &vf3d->handle, *archive, newPath, flags, FS_ATTRIBUTE_NONE);
|
||||
// TODO: Use UTF-16
|
||||
FS_Path newPath = fsMakePath(PATH_ASCII, path);
|
||||
Result res = FSUSER_OpenFile(&vf3d->handle, *archive, newPath, flags, 0);
|
||||
if (res & 0xFFFC03FF) {
|
||||
free(vf3d);
|
||||
return 0;
|
||||
|
@ -67,7 +68,7 @@ struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags) {
|
|||
vf3d->d.close = _vf3dClose;
|
||||
vf3d->d.seek = _vf3dSeek;
|
||||
vf3d->d.read = _vf3dRead;
|
||||
vf3d->d.readline = 0;
|
||||
vf3d->d.readline = VFileReadline;
|
||||
vf3d->d.write = _vf3dWrite;
|
||||
vf3d->d.map = _vf3dMap;
|
||||
vf3d->d.unmap = _vf3dUnmap;
|
||||
|
@ -175,8 +176,9 @@ struct VDir* VDirOpen(const char* path) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
FS_path newPath = FS_makePath(PATH_CHAR, path);
|
||||
Result res = FSUSER_OpenDirectory(0, &vd3d->handle, sdmcArchive, newPath);
|
||||
// TODO: Use UTF-16
|
||||
FS_Path newPath = fsMakePath(PATH_ASCII, path);
|
||||
Result res = FSUSER_OpenDirectory(&vd3d->handle, sdmcArchive, newPath);
|
||||
if (res & 0xFFFC03FF) {
|
||||
free(vd3d);
|
||||
return 0;
|
||||
|
@ -207,8 +209,9 @@ static bool _vd3dClose(struct VDir* vd) {
|
|||
static void _vd3dRewind(struct VDir* vd) {
|
||||
struct VDir3DS* vd3d = (struct VDir3DS*) vd;
|
||||
FSDIR_Close(vd3d->handle);
|
||||
FS_path newPath = FS_makePath(PATH_CHAR, vd3d->path);
|
||||
FSUSER_OpenDirectory(0, &vd3d->handle, sdmcArchive, newPath);
|
||||
// TODO: Use UTF-16
|
||||
FS_Path newPath = fsMakePath(PATH_ASCII, vd3d->path);
|
||||
FSUSER_OpenDirectory(&vd3d->handle, sdmcArchive, newPath);
|
||||
}
|
||||
|
||||
static struct VDirEntry* _vd3dListNext(struct VDir* vd) {
|
||||
|
@ -257,14 +260,14 @@ static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path) {
|
|||
static const char* _vd3deName(struct VDirEntry* vde) {
|
||||
struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde;
|
||||
if (!vd3de->utf8Name[0]) {
|
||||
utf16_to_utf8(vd3de->utf8Name, vd3de->ent.name, sizeof(vd3de->ent.name));
|
||||
utf16_to_utf8(vd3de->utf8Name, vd3de->ent.name, sizeof(vd3de->utf8Name));
|
||||
}
|
||||
return vd3de->utf8Name;
|
||||
}
|
||||
|
||||
static enum VFSType _vd3deType(struct VDirEntry* vde) {
|
||||
struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde;
|
||||
if (vd3de->ent.isDirectory) {
|
||||
if (vd3de->ent.attributes & FS_ATTRIBUTE_DIRECTORY) {
|
||||
return VFS_DIRECTORY;
|
||||
}
|
||||
return VFS_FILE;
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
#include <3ds.h>
|
||||
|
||||
extern FS_archive sdmcArchive;
|
||||
extern FS_Archive sdmcArchive;
|
||||
|
||||
struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags);
|
||||
struct VFile* VFileOpen3DS(FS_Archive* archive, const char* path, int flags);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,7 +10,7 @@ find_program(RAW2C raw2c)
|
|||
find_program(STRIP ${cross_prefix}strip)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE)
|
||||
set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5)
|
||||
set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5 IOAPI_NO_64)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
list(APPEND OS_LIB ctru)
|
||||
|
@ -22,12 +22,13 @@ if(USE_VFS_3DS)
|
|||
list(APPEND OS_DEFINES USE_VFS_3DS)
|
||||
else()
|
||||
list(APPEND OS_DEFINES USE_VFS_FILE)
|
||||
list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
|
||||
endif()
|
||||
set(VFS_SRC ${VFS_SRC} PARENT_SCOPE)
|
||||
set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE)
|
||||
set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE)
|
||||
|
||||
list(APPEND GUI_SRC
|
||||
${CMAKE_CURRENT_BINARY_DIR}/icons.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/font.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/uishader.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/uishader.h
|
||||
|
@ -38,6 +39,7 @@ list(APPEND GUI_SRC
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.h)
|
||||
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/icons.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/font.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/uishader.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/uishader.h
|
||||
|
@ -59,6 +61,10 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c
|
|||
COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icons.c
|
||||
COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/icons.raw
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/icons.raw)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h
|
||||
MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh
|
||||
|
|
|
@ -45,5 +45,11 @@ set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags")
|
|||
set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags")
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH ${DEVKITARM}/arm-none-eabi)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE INTERNAL "")
|
||||
set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE)
|
||||
|
||||
set(3DS ON)
|
||||
add_definitions(-D_3DS -DARM11)
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <3ds.h>
|
||||
#include <3ds/gpu/gpu.h>
|
||||
#include <3ds/gpu/gx.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
@ -42,57 +44,12 @@ static int pendingEvents = 0;
|
|||
|
||||
static const struct ctrTexture* activeTexture = NULL;
|
||||
|
||||
static u32 _f24FromFloat(float f) {
|
||||
u32 i;
|
||||
memcpy(&i, &f, 4);
|
||||
|
||||
u32 mantissa = (i << 9) >> 9;
|
||||
s32 exponent = (i << 1) >> 24;
|
||||
u32 sign = (i << 0) >> 31;
|
||||
|
||||
// Truncate mantissa
|
||||
mantissa >>= 7;
|
||||
|
||||
// Re-bias exponent
|
||||
exponent = exponent - 127 + 63;
|
||||
if (exponent < 0) {
|
||||
// Underflow: flush to zero
|
||||
return sign << 23;
|
||||
} else if (exponent > 0x7F) {
|
||||
// Overflow: saturate to infinity
|
||||
return sign << 23 | 0x7F << 16;
|
||||
}
|
||||
|
||||
return sign << 23 | exponent << 16 | mantissa;
|
||||
}
|
||||
|
||||
static u32 _f31FromFloat(float f) {
|
||||
u32 i;
|
||||
memcpy(&i, &f, 4);
|
||||
|
||||
u32 mantissa = (i << 9) >> 9;
|
||||
s32 exponent = (i << 1) >> 24;
|
||||
u32 sign = (i << 0) >> 31;
|
||||
|
||||
// Re-bias exponent
|
||||
exponent = exponent - 127 + 63;
|
||||
if (exponent < 0) {
|
||||
// Underflow: flush to zero
|
||||
return sign << 30;
|
||||
} else if (exponent > 0x7F) {
|
||||
// Overflow: saturate to infinity
|
||||
return sign << 30 | 0x7F << 23;
|
||||
}
|
||||
|
||||
return sign << 30 | exponent << 23 | mantissa;
|
||||
}
|
||||
|
||||
void ctrClearPending(int events) {
|
||||
int toClear = events & pendingEvents;
|
||||
if (toClear & (1 << GSPEVENT_PSC0)) {
|
||||
if (toClear & (1 << GSPGPU_EVENT_PSC0)) {
|
||||
gspWaitForPSC0();
|
||||
}
|
||||
if (toClear & (1 << GSPEVENT_PPF)) {
|
||||
if (toClear & (1 << GSPGPU_EVENT_PPF)) {
|
||||
gspWaitForPPF();
|
||||
}
|
||||
pendingEvents ^= toClear;
|
||||
|
@ -103,39 +60,39 @@ static void _GPU_SetFramebuffer(intptr_t colorBuffer, intptr_t depthBuffer, u16
|
|||
u32 buf[4];
|
||||
|
||||
// Unknown
|
||||
GPUCMD_AddWrite(GPUREG_0111, 0x00000001);
|
||||
GPUCMD_AddWrite(GPUREG_0110, 0x00000001);
|
||||
GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_FLUSH, 0x00000001);
|
||||
GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_INVALIDATE, 0x00000001);
|
||||
|
||||
// Set depth/color buffer address and dimensions
|
||||
buf[0] = depthBuffer >> 3;
|
||||
buf[1] = colorBuffer >> 3;
|
||||
buf[2] = (0x01) << 24 | ((h-1) & 0xFFF) << 12 | (w & 0xFFF) << 0;
|
||||
GPUCMD_AddIncrementalWrites(GPUREG_DEPTHBUFFER_LOC, buf, 3);
|
||||
GPUCMD_AddWrite(GPUREG_006E, buf[2]);
|
||||
GPUCMD_AddWrite(GPUREG_RENDERBUF_DIM, buf[2]);
|
||||
|
||||
// Set depth/color buffer pixel format
|
||||
GPUCMD_AddWrite(GPUREG_DEPTHBUFFER_FORMAT, 3 /* D248S */ );
|
||||
GPUCMD_AddWrite(GPUREG_COLORBUFFER_FORMAT, 0 /* RGBA8 */ << 16 | 2 /* Unknown */);
|
||||
GPUCMD_AddWrite(GPUREG_011B, 0); // Unknown
|
||||
GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_BLOCK32, 0); // Unknown
|
||||
|
||||
// Enable color/depth buffers
|
||||
buf[0] = colorBuffer != 0 ? 0xF : 0x0;
|
||||
buf[1] = buf[0];
|
||||
buf[2] = depthBuffer != 0 ? 0x2 : 0x0;
|
||||
buf[3] = buf[2];
|
||||
GPUCMD_AddIncrementalWrites(GPUREG_0112, buf, 4);
|
||||
GPUCMD_AddIncrementalWrites(GPUREG_COLORBUFFER_READ, buf, 4);
|
||||
}
|
||||
|
||||
static void _GPU_SetViewportEx(u16 x, u16 y, u16 w, u16 h) {
|
||||
u32 buf[4];
|
||||
|
||||
buf[0] = _f24FromFloat(w / 2.0f);
|
||||
buf[1] = _f31FromFloat(2.0f / w) << 1;
|
||||
buf[2] = _f24FromFloat(h / 2.0f);
|
||||
buf[3] = _f31FromFloat(2.0f / h) << 1;
|
||||
GPUCMD_AddIncrementalWrites(GPUREG_0041, buf, 4);
|
||||
buf[0] = f32tof24(w / 2.0f);
|
||||
buf[1] = f32tof31(2.0f / w) << 1;
|
||||
buf[2] = f32tof24(h / 2.0f);
|
||||
buf[3] = f32tof31(2.0f / h) << 1;
|
||||
GPUCMD_AddIncrementalWrites(GPUREG_VIEWPORT_WIDTH, buf, 4);
|
||||
|
||||
GPUCMD_AddWrite(GPUREG_0068, (y & 0xFFFF) << 16 | (x & 0xFFFF) << 0);
|
||||
GPUCMD_AddWrite(GPUREG_VIEWPORT_XY, (y & 0xFFFF) << 16 | (x & 0xFFFF) << 0);
|
||||
|
||||
buf[0] = 0;
|
||||
buf[1] = 0;
|
||||
|
@ -259,7 +216,7 @@ void ctrGpuBeginFrame(int screen) {
|
|||
fw = 320;
|
||||
}
|
||||
|
||||
_GPU_SetFramebuffer(osConvertVirtToPhys((u32)gpuColorBuffer[screen]), 0, 240, fw);
|
||||
_GPU_SetFramebuffer(osConvertVirtToPhys(gpuColorBuffer[screen]), 0, 240, fw);
|
||||
}
|
||||
|
||||
void ctrGpuBeginDrawing(void) {
|
||||
|
@ -282,8 +239,8 @@ void ctrGpuBeginDrawing(void) {
|
|||
GPU_SetAlphaTest(false, GPU_ALWAYS, 0);
|
||||
|
||||
// Unknown
|
||||
GPUCMD_AddMaskedWrite(GPUREG_0062, 0x1, 0);
|
||||
GPUCMD_AddWrite(GPUREG_0118, 0);
|
||||
GPUCMD_AddMaskedWrite(GPUREG_EARLYDEPTH_TEST1, 0x1, 0);
|
||||
GPUCMD_AddWrite(GPUREG_EARLYDEPTH_TEST2, 0);
|
||||
|
||||
GPU_SetTexEnv(0,
|
||||
GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // RGB
|
||||
|
@ -299,7 +256,7 @@ void ctrGpuBeginDrawing(void) {
|
|||
_setDummyTexEnv(5);
|
||||
|
||||
// Configure vertex attribute format
|
||||
u32 bufferOffsets[] = { osConvertVirtToPhys((u32)ctrVertexBuffer) - VRAM_BASE };
|
||||
u32 bufferOffsets[] = { osConvertVirtToPhys(ctrVertexBuffer) - VRAM_BASE };
|
||||
u64 arrayTargetAttributes[] = { 0x210 };
|
||||
u8 numAttributesInArray[] = { 3 };
|
||||
GPU_SetAttributeBuffers(
|
||||
|
@ -332,29 +289,29 @@ void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h) {
|
|||
|
||||
const u32 GX_CROP_INPUT_LINES = (1 << 2);
|
||||
|
||||
ctrClearPending(1 << GSPEVENT_PSC0);
|
||||
ctrClearPending(1 << GSPEVENT_PPF);
|
||||
ctrClearPending(1 << GSPGPU_EVENT_PSC0);
|
||||
ctrClearPending(1 << GSPGPU_EVENT_PPF);
|
||||
|
||||
GX_SetDisplayTransfer(NULL,
|
||||
GX_DisplayTransfer(
|
||||
colorBuffer, GX_BUFFER_DIM(240, fw),
|
||||
outputFramebuffer, GX_BUFFER_DIM(h, w),
|
||||
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) |
|
||||
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) |
|
||||
GX_CROP_INPUT_LINES);
|
||||
pendingEvents |= (1 << GSPEVENT_PPF);
|
||||
pendingEvents |= (1 << GSPGPU_EVENT_PPF);
|
||||
}
|
||||
|
||||
void ctrGpuEndDrawing(void) {
|
||||
ctrClearPending(1 << GSPEVENT_PPF);
|
||||
ctrClearPending(1 << GSPGPU_EVENT_PPF);
|
||||
gfxSwapBuffersGpu();
|
||||
gspWaitForEvent(GSPEVENT_VBlank0, false);
|
||||
gspWaitForEvent(GSPGPU_EVENT_VBlank0, false);
|
||||
|
||||
void* gpuColorBuffer0End = (char*)gpuColorBuffer[0] + 240 * 400 * 4;
|
||||
void* gpuColorBuffer1End = (char*)gpuColorBuffer[1] + 240 * 320 * 4;
|
||||
GX_SetMemoryFill(NULL,
|
||||
GX_MemoryFill(
|
||||
gpuColorBuffer[0], 0x00000000, gpuColorBuffer0End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER,
|
||||
gpuColorBuffer[1], 0x00000000, gpuColorBuffer1End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER);
|
||||
pendingEvents |= 1 << GSPEVENT_PSC0;
|
||||
pendingEvents |= 1 << GSPGPU_EVENT_PSC0;
|
||||
}
|
||||
|
||||
void ctrSetViewportSize(s16 w, s16 h) {
|
||||
|
@ -382,7 +339,7 @@ void ctrActivateTexture(const struct ctrTexture* texture) {
|
|||
|
||||
GPU_SetTextureEnable(GPU_TEXUNIT0);
|
||||
GPU_SetTexture(
|
||||
GPU_TEXUNIT0, (u32*)osConvertVirtToPhys((u32)texture->data),
|
||||
GPU_TEXUNIT0, (u32*)osConvertVirtToPhys(texture->data),
|
||||
texture->width, texture->height,
|
||||
GPU_TEXTURE_MAG_FILTER(texture->filter) | GPU_TEXTURE_MIN_FILTER(texture->filter) |
|
||||
GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER),
|
||||
|
@ -444,15 +401,15 @@ void ctrFlushBatch(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
ctrClearPending((1 << GSPEVENT_PSC0));
|
||||
ctrClearPending((1 << GSPGPU_EVENT_PSC0));
|
||||
|
||||
GSPGPU_FlushDataCache(NULL, (u8*)ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE);
|
||||
GPU_DrawElements(GPU_UNKPRIM, (u32*)(osConvertVirtToPhys((u32)ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6);
|
||||
GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE);
|
||||
GPU_DrawElements(GPU_GEOMETRY_PRIM, (u32*)(osConvertVirtToPhys(ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6);
|
||||
|
||||
GPU_FinishDrawing();
|
||||
GPUCMD_Finalize();
|
||||
GSPGPU_FlushDataCache(NULL, (u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32));
|
||||
GPUCMD_FlushAndRun(NULL);
|
||||
GSPGPU_FlushDataCache((u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32));
|
||||
GPUCMD_FlushAndRun();
|
||||
|
||||
gspWaitForP3D();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ struct ctrTexture {
|
|||
u16 height;
|
||||
};
|
||||
|
||||
inline void ctrTexture_Init(struct ctrTexture* tex) {
|
||||
static inline void ctrTexture_Init(struct ctrTexture* tex) {
|
||||
tex->data = NULL;
|
||||
tex->format = GPU_RGB565;
|
||||
tex->filter = GPU_NEAREST;
|
||||
|
|
|
@ -26,18 +26,23 @@
|
|||
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
u32 __linear_heap;
|
||||
u32 __heapBase;
|
||||
static u32 __heap_size = 0x02400000;
|
||||
static u32 __linear_heap_size = 0x01400000;
|
||||
extern u32 __ctru_linear_heap;
|
||||
extern u32 __ctru_heap;
|
||||
extern u32 __ctru_heap_size;
|
||||
extern u32 __ctru_linear_heap_size;
|
||||
static u32 __custom_heap_size = 0x02400000;
|
||||
static u32 __custom_linear_heap_size = 0x01400000;
|
||||
|
||||
extern void (*__system_retAddr)(void);
|
||||
|
||||
void __destroy_handle_list(void);
|
||||
void envDestroyHandles(void);
|
||||
|
||||
void __appExit();
|
||||
|
||||
void __libc_fini_array(void);
|
||||
|
||||
Result __sync_fini(void) __attribute__((weak));
|
||||
|
||||
uint32_t* romBuffer;
|
||||
size_t romBufferSize;
|
||||
|
||||
|
@ -58,36 +63,16 @@ bool allocateRomBuffer(void) {
|
|||
void __system_allocateHeaps() {
|
||||
u32 tmp=0;
|
||||
|
||||
__ctru_heap_size = __custom_heap_size;
|
||||
__ctru_linear_heap_size = __custom_linear_heap_size;
|
||||
|
||||
// Allocate the application heap
|
||||
__heapBase = 0x08000000;
|
||||
svcControlMemory(&tmp, __heapBase, 0x0, __heap_size, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE);
|
||||
__ctru_heap = 0x08000000;
|
||||
svcControlMemory(&tmp, __ctru_heap, 0x0, __ctru_heap_size, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE);
|
||||
|
||||
// Allocate the linear heap
|
||||
svcControlMemory(&__linear_heap, 0x0, 0x0, __linear_heap_size, MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE);
|
||||
svcControlMemory(&__ctru_linear_heap, 0x0, 0x0, __ctru_linear_heap_size, MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE);
|
||||
// Set up newlib heap
|
||||
fake_heap_start = (char*)__heapBase;
|
||||
fake_heap_end = fake_heap_start + __heap_size;
|
||||
}
|
||||
|
||||
void __attribute__((noreturn)) __libctru_exit(int rc)
|
||||
{
|
||||
UNUSED(rc);
|
||||
|
||||
u32 tmp=0;
|
||||
|
||||
// Unmap the linear heap
|
||||
svcControlMemory(&tmp, __linear_heap, 0x0, __linear_heap_size, MEMOP_FREE, 0x0);
|
||||
|
||||
// Unmap the application heap
|
||||
svcControlMemory(&tmp, __heapBase, 0x0, __heap_size, MEMOP_FREE, 0x0);
|
||||
|
||||
// Close some handles
|
||||
__destroy_handle_list();
|
||||
|
||||
// Jump to the loader if it provided a callback
|
||||
if (__system_retAddr)
|
||||
__system_retAddr();
|
||||
|
||||
// Since above did not jump, end this process
|
||||
svcExitProcess();
|
||||
fake_heap_start = (char*)__ctru_heap;
|
||||
fake_heap_end = fake_heap_start + __ctru_heap_size;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
#include "util/gui/font-metrics.h"
|
||||
#include "util/png-io.h"
|
||||
#include "util/vfs.h"
|
||||
#include "platform/3ds/ctr-gpu.h"
|
||||
#include "icons.h"
|
||||
#include "font.h"
|
||||
#include "ctr-gpu.h"
|
||||
|
||||
#define CELL_HEIGHT 16
|
||||
#define CELL_WIDTH 16
|
||||
|
@ -16,6 +17,7 @@
|
|||
|
||||
struct GUIFont {
|
||||
struct ctrTexture texture;
|
||||
struct ctrTexture icons;
|
||||
};
|
||||
|
||||
struct GUIFont* GUIFontCreate(void) {
|
||||
|
@ -31,8 +33,19 @@ struct GUIFont* GUIFontCreate(void) {
|
|||
tex->width = 256;
|
||||
tex->height = 128;
|
||||
|
||||
GSPGPU_FlushDataCache(NULL, (u8*)font, font_size);
|
||||
GX_RequestDma(NULL, (u32*)font, tex->data, font_size);
|
||||
GSPGPU_FlushDataCache(font, font_size);
|
||||
GX_RequestDma((u32*) font, tex->data, font_size);
|
||||
gspWaitForDMA();
|
||||
|
||||
tex = &guiFont->icons;
|
||||
ctrTexture_Init(tex);
|
||||
tex->data = vramAlloc(256 * 64 * 2);
|
||||
tex->format = GPU_RGBA5551;
|
||||
tex->width = 256;
|
||||
tex->height = 64;
|
||||
|
||||
GSPGPU_FlushDataCache(icons, icons_size);
|
||||
GX_RequestDma((u32*) icons, tex->data, icons_size);
|
||||
gspWaitForDMA();
|
||||
|
||||
return guiFont;
|
||||
|
@ -40,6 +53,7 @@ struct GUIFont* GUIFontCreate(void) {
|
|||
|
||||
void GUIFontDestroy(struct GUIFont* font) {
|
||||
vramFree(font->texture.data);
|
||||
vramFree(font->icons.data);
|
||||
free(font);
|
||||
}
|
||||
|
||||
|
@ -60,7 +74,7 @@ void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint
|
|||
ctrActivateTexture(&font->texture);
|
||||
|
||||
if (glyph > 0x7F) {
|
||||
glyph = 0;
|
||||
glyph = '?';
|
||||
}
|
||||
|
||||
struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph];
|
||||
|
@ -71,3 +85,42 @@ void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint
|
|||
|
||||
ctrAddRect(color, x, y, u, v, CELL_WIDTH, CELL_HEIGHT);
|
||||
}
|
||||
|
||||
void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) {
|
||||
ctrActivateTexture(&font->icons);
|
||||
|
||||
if (icon >= GUI_ICON_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GUIIconMetric metric = defaultIconMetrics[icon];
|
||||
switch (align & GUI_ALIGN_HCENTER) {
|
||||
case GUI_ALIGN_HCENTER:
|
||||
x -= metric.width / 2;
|
||||
break;
|
||||
case GUI_ALIGN_RIGHT:
|
||||
x -= metric.width;
|
||||
break;
|
||||
}
|
||||
switch (align & GUI_ALIGN_VCENTER) {
|
||||
case GUI_ALIGN_VCENTER:
|
||||
y -= metric.height / 2;
|
||||
break;
|
||||
case GUI_ALIGN_BOTTOM:
|
||||
y -= metric.height;
|
||||
break;
|
||||
}
|
||||
switch (orient) {
|
||||
case GUI_ORIENT_HMIRROR:
|
||||
ctrAddRectScaled(color, x + metric.width, y, -metric.width, metric.height, metric.x, metric.y, metric.width, metric.height);
|
||||
break;
|
||||
case GUI_ORIENT_VMIRROR:
|
||||
ctrAddRectScaled(color, x, y + metric.height, metric.width, -metric.height, metric.x, metric.y, metric.width, metric.height);
|
||||
break;
|
||||
case GUI_ORIENT_0:
|
||||
default:
|
||||
// TODO: Rotation
|
||||
ctrAddRect(color, x, y, metric.x, metric.y, metric.width, metric.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -18,6 +18,7 @@
|
|||
#include "ctr-gpu.h"
|
||||
|
||||
#include <3ds.h>
|
||||
#include <3ds/gpu/gx.h>
|
||||
|
||||
static enum ScreenMode {
|
||||
SM_PA_BOTTOM,
|
||||
|
@ -29,10 +30,12 @@ static enum ScreenMode {
|
|||
SM_MAX
|
||||
} screenMode = SM_PA_TOP;
|
||||
|
||||
#define _3DS_INPUT 0x3344534B
|
||||
|
||||
#define AUDIO_SAMPLES 0x80
|
||||
#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 24)
|
||||
|
||||
FS_archive sdmcArchive;
|
||||
FS_Archive sdmcArchive;
|
||||
|
||||
static struct GBA3DSRotationSource {
|
||||
struct GBARotationSource d;
|
||||
|
@ -67,6 +70,38 @@ enum {
|
|||
|
||||
extern bool allocateRomBuffer(void);
|
||||
|
||||
static void _map3DSKey(struct GBAInputMap* map, int ctrKey, enum GBAKey key) {
|
||||
GBAInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
|
||||
}
|
||||
|
||||
static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size)
|
||||
{
|
||||
u32 pleft = 0, pright = 0;
|
||||
|
||||
int loopMode = (flags >> 10) & 3;
|
||||
if (!loopMode) {
|
||||
flags |= SOUND_ONE_SHOT;
|
||||
}
|
||||
|
||||
pleft = osConvertVirtToPhys(left);
|
||||
pright = osConvertVirtToPhys(right);
|
||||
|
||||
u32 timer = CSND_TIMER(sampleRate);
|
||||
if (timer < 0x0042) {
|
||||
timer = 0x0042;
|
||||
}
|
||||
else if (timer > 0xFFFF) {
|
||||
timer = 0xFFFF;
|
||||
}
|
||||
flags &= ~0xFFFF001F;
|
||||
flags |= SOUND_ENABLE | (timer << 16);
|
||||
|
||||
u32 volumes = CSND_VOL(vol, -1.0);
|
||||
CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
|
||||
volumes = CSND_VOL(vol, 1.0);
|
||||
CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
|
||||
}
|
||||
|
||||
static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio);
|
||||
|
||||
static void _drawStart(void) {
|
||||
|
@ -121,8 +156,8 @@ static void _drawEnd(void) {
|
|||
static int _batteryState(void) {
|
||||
u8 charge;
|
||||
u8 adapter;
|
||||
PTMU_GetBatteryLevel(0, &charge);
|
||||
PTMU_GetBatteryChargeState(0, &adapter);
|
||||
PTMU_GetBatteryLevel(&charge);
|
||||
PTMU_GetBatteryChargeState(&adapter);
|
||||
int state = 0;
|
||||
if (adapter) {
|
||||
state |= BATTERY_CHARGING;
|
||||
|
@ -156,6 +191,17 @@ static void _setup(struct GBAGUIRunner* runner) {
|
|||
runner->context.gba->stream = &stream;
|
||||
}
|
||||
|
||||
_map3DSKey(&runner->context.inputMap, KEY_A, GBA_KEY_A);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_B, GBA_KEY_B);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_START, GBA_KEY_START);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_SELECT, GBA_KEY_SELECT);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_UP, GBA_KEY_UP);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_DOWN, GBA_KEY_DOWN);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_LEFT, GBA_KEY_LEFT);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_L, GBA_KEY_L);
|
||||
_map3DSKey(&runner->context.inputMap, KEY_R, GBA_KEY_R);
|
||||
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
renderer.outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
|
||||
renderer.outputBufferStride = 256;
|
||||
|
@ -176,6 +222,7 @@ static void _gameLoaded(struct GBAGUIRunner* runner) {
|
|||
if (runner->context.gba->memory.hw.devices & HW_GYRO) {
|
||||
HIDUSER_EnableGyroscope();
|
||||
}
|
||||
osSetSpeedupEnable(true);
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
|
||||
|
@ -186,8 +233,8 @@ static void _gameLoaded(struct GBAGUIRunner* runner) {
|
|||
memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
|
||||
memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
|
||||
audioPos = 0;
|
||||
csndPlaySound(0x8, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, -1.0, audioLeft, audioLeft, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
|
||||
csndPlaySound(0x9, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, 1.0, audioRight, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
|
||||
_csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
|
||||
csndExecCmds(false);
|
||||
}
|
||||
unsigned mode;
|
||||
if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) {
|
||||
|
@ -202,6 +249,7 @@ static void _gameUnloaded(struct GBAGUIRunner* runner) {
|
|||
CSND_SetPlayState(9, 0);
|
||||
csndExecCmds(false);
|
||||
}
|
||||
osSetSpeedupEnable(false);
|
||||
|
||||
if (runner->context.gba->memory.hw.devices & HW_TILT) {
|
||||
HIDUSER_DisableAccelerometer();
|
||||
|
@ -254,8 +302,8 @@ static void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
|
|||
void* outputBuffer = renderer.outputBuffer;
|
||||
struct ctrTexture* tex = &gbaOutputTexture;
|
||||
|
||||
GSPGPU_FlushDataCache(NULL, outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
|
||||
GX_SetDisplayTransfer(NULL,
|
||||
GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
|
||||
GX_DisplayTransfer(
|
||||
outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
|
||||
tex->data, GX_BUFFER_DIM(256, 256),
|
||||
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
|
||||
|
@ -294,9 +342,9 @@ static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels,
|
|||
memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
|
||||
}
|
||||
|
||||
GSPGPU_FlushDataCache(NULL, (void*)newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
|
||||
GX_SetDisplayTransfer(NULL,
|
||||
(void*)newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
|
||||
GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
|
||||
GX_DisplayTransfer(
|
||||
(u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
|
||||
tex->data, GX_BUFFER_DIM(256, 256),
|
||||
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
|
||||
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
|
||||
|
@ -312,9 +360,10 @@ static uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
|
|||
UNUSED(runner);
|
||||
|
||||
hidScanInput();
|
||||
uint32_t activeKeys = hidKeysHeld() & 0xF00003FF;
|
||||
activeKeys |= activeKeys >> 24;
|
||||
return activeKeys;
|
||||
uint32_t activeKeys = hidKeysHeld();
|
||||
uint16_t keys = GBAInputMapKeyBits(&runner->context.inputMap, _3DS_INPUT, activeKeys, 0);
|
||||
keys |= (activeKeys >> 24) & 0xF0;
|
||||
return keys;
|
||||
}
|
||||
|
||||
static void _incrementScreenMode(struct GBAGUIRunner* runner) {
|
||||
|
@ -361,7 +410,7 @@ static uint32_t _pollInput(void) {
|
|||
return keys;
|
||||
}
|
||||
|
||||
static enum GUICursorState _pollCursor(int* x, int* y) {
|
||||
static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
|
||||
hidScanInput();
|
||||
if (!(hidKeysHeld() & KEY_TOUCH)) {
|
||||
return GUI_CURSOR_NOT_PRESENT;
|
||||
|
@ -403,8 +452,8 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio)
|
|||
#elif RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
GBAAudioCopy(audio, &audioLeft[audioPos], &audioRight[audioPos], AUDIO_SAMPLES);
|
||||
#endif
|
||||
GSPGPU_FlushDataCache(0, (void*) &audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
|
||||
GSPGPU_FlushDataCache(0, (void*) &audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
|
||||
GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
|
||||
GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
|
||||
audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
|
||||
if (audioPos == AUDIO_SAMPLES * 3) {
|
||||
u8 playing = 0;
|
||||
|
@ -418,9 +467,6 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio)
|
|||
}
|
||||
|
||||
int main() {
|
||||
ptmInit();
|
||||
hasSound = !csndInit();
|
||||
|
||||
rotation.d.sample = _sampleRotation;
|
||||
rotation.d.readTiltX = _readTiltX;
|
||||
rotation.d.readTiltY = _readTiltY;
|
||||
|
@ -434,6 +480,9 @@ int main() {
|
|||
return 1;
|
||||
}
|
||||
|
||||
ptmuInit();
|
||||
hasSound = !csndInit();
|
||||
|
||||
if (hasSound) {
|
||||
audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
|
||||
audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
|
||||
|
@ -442,6 +491,7 @@ int main() {
|
|||
gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
|
||||
|
||||
if (ctrInitGpu() < 0) {
|
||||
gbaOutputTexture.data = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
|
@ -453,18 +503,22 @@ int main() {
|
|||
gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
|
||||
void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
|
||||
|
||||
if (!gbaOutputTexture.data) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Zero texture data to make sure no garbage around the border interferes with filtering
|
||||
GX_SetMemoryFill(NULL,
|
||||
GX_MemoryFill(
|
||||
gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
|
||||
NULL, 0, NULL, 0);
|
||||
gspWaitForPSC0();
|
||||
|
||||
sdmcArchive = (FS_archive) {
|
||||
ARCH_SDMC,
|
||||
(FS_path) { PATH_EMPTY, 1, (const u8*)"" },
|
||||
0, 0
|
||||
sdmcArchive = (FS_Archive) {
|
||||
ARCHIVE_SDMC,
|
||||
(FS_Path) { PATH_EMPTY, 1, "" },
|
||||
0
|
||||
};
|
||||
FSUSER_OpenArchive(0, &sdmcArchive);
|
||||
FSUSER_OpenArchive(&sdmcArchive);
|
||||
|
||||
struct GUIFont* font = GUIFontCreate();
|
||||
|
||||
|
@ -483,6 +537,44 @@ int main() {
|
|||
|
||||
GUI_PARAMS_TRAIL
|
||||
},
|
||||
.keySources = (struct GUIInputKeys[]) {
|
||||
{
|
||||
.name = "3DS Input",
|
||||
.id = _3DS_INPUT,
|
||||
.keyNames = (const char*[]) {
|
||||
"A",
|
||||
"B",
|
||||
"Select",
|
||||
"Start",
|
||||
"D-Pad Right",
|
||||
"D-Pad Left",
|
||||
"D-Pad Up",
|
||||
"D-Pad Down",
|
||||
"R",
|
||||
"L",
|
||||
"X",
|
||||
"Y",
|
||||
0,
|
||||
0,
|
||||
"ZL",
|
||||
"ZR",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"C-Stick Right",
|
||||
"C-Stick Left",
|
||||
"C-Stick Up",
|
||||
"C-Stick Down",
|
||||
},
|
||||
.nKeys = 28
|
||||
},
|
||||
{ .id = 0 }
|
||||
},
|
||||
.configExtra = (struct GUIMenuItem[]) {
|
||||
{
|
||||
.title = "Screen mode",
|
||||
|
@ -496,8 +588,8 @@ int main() {
|
|||
"Pixel-Accurate/Top",
|
||||
"Aspect-Ratio Fit/Top",
|
||||
"Stretched/Top",
|
||||
0
|
||||
}
|
||||
},
|
||||
.nStates = 6
|
||||
}
|
||||
},
|
||||
.nConfigExtra = 1,
|
||||
|
@ -519,10 +611,14 @@ int main() {
|
|||
GBAGUIDeinit(&runner);
|
||||
|
||||
cleanup:
|
||||
linearFree(renderer.outputBuffer);
|
||||
if (renderer.outputBuffer) {
|
||||
linearFree(renderer.outputBuffer);
|
||||
}
|
||||
|
||||
ctrDeinitGpu();
|
||||
vramFree(gbaOutputTexture.data);
|
||||
if (gbaOutputTexture.data) {
|
||||
ctrDeinitGpu();
|
||||
vramFree(gbaOutputTexture.data);
|
||||
}
|
||||
|
||||
gfxExit();
|
||||
|
||||
|
@ -530,7 +626,8 @@ cleanup:
|
|||
linearFree(audioLeft);
|
||||
linearFree(audioRight);
|
||||
}
|
||||
|
||||
csndExit();
|
||||
ptmExit();
|
||||
ptmuExit();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
#include <3ds.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#ifdef _3DS
|
||||
// ctrulib already has a type called Thread
|
||||
#define Thread CustomThread
|
||||
#endif
|
||||
|
||||
#define THREAD_ENTRY void
|
||||
typedef ThreadFunc ThreadEntry;
|
||||
|
||||
|
@ -37,6 +42,10 @@ static inline int MutexLock(Mutex* mutex) {
|
|||
return svcWaitSynchronization(*mutex, U64_MAX);
|
||||
}
|
||||
|
||||
static inline int MutexTryLock(Mutex* mutex) {
|
||||
return svcWaitSynchronization(*mutex, 10);
|
||||
}
|
||||
|
||||
static inline int MutexUnlock(Mutex* mutex) {
|
||||
return svcReleaseMutex(*mutex);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ 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 }
|
||||
};
|
||||
|
||||
|
@ -68,10 +69,19 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
// 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) {
|
||||
opts->showVersion = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
GBAConfigSetDefaultValue(config, "bios", optarg);
|
||||
GBAConfigSetOverrideValue(config, "bios", optarg);
|
||||
break;
|
||||
case 'c':
|
||||
opts->cheatsFile = strdup(optarg);
|
||||
|
@ -99,13 +109,13 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
opts->showHelp = true;
|
||||
break;
|
||||
case 'l':
|
||||
GBAConfigSetDefaultValue(config, "logLevel", optarg);
|
||||
GBAConfigSetOverrideValue(config, "logLevel", optarg);
|
||||
break;
|
||||
case 'p':
|
||||
opts->patch = strdup(optarg);
|
||||
break;
|
||||
case 's':
|
||||
GBAConfigSetDefaultValue(config, "frameskip", optarg);
|
||||
GBAConfigSetOverrideValue(config, "frameskip", optarg);
|
||||
break;
|
||||
case 'v':
|
||||
opts->movie = strdup(optarg);
|
||||
|
@ -122,7 +132,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc != 1) {
|
||||
return opts->showHelp;
|
||||
return opts->showHelp || opts->showVersion;
|
||||
}
|
||||
opts->fname = strdup(argv[0]);
|
||||
return true;
|
||||
|
@ -154,7 +164,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
|||
switch (option) {
|
||||
case 'f':
|
||||
graphicsOpts->fullscreen = true;
|
||||
GBAConfigSetDefaultIntValue(config, "fullscreen", 1);
|
||||
GBAConfigSetOverrideIntValue(config, "fullscreen", 1);
|
||||
return true;
|
||||
case '1':
|
||||
case '2':
|
||||
|
@ -166,8 +176,8 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
|||
return false;
|
||||
}
|
||||
graphicsOpts->multiplier = option - '0';
|
||||
GBAConfigSetDefaultIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier);
|
||||
GBAConfigSetDefaultIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier);
|
||||
GBAConfigSetOverrideIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier);
|
||||
GBAConfigSetOverrideIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -228,7 +238,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);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ struct GBAArguments {
|
|||
enum DebuggerType debuggerType;
|
||||
bool debugAtStart;
|
||||
bool showHelp;
|
||||
bool showVersion;
|
||||
};
|
||||
|
||||
struct SubParser {
|
||||
|
@ -52,6 +53,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
void freeArguments(struct GBAArguments* opts);
|
||||
|
||||
void usage(const char* arg0, const char* extraOptions);
|
||||
void version(const char* arg0);
|
||||
|
||||
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts);
|
||||
struct ARMDebugger* createDebugger(struct GBAArguments* opts, struct GBAThread* context);
|
||||
|
|
|
@ -19,26 +19,40 @@ void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
|||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->frameskip = 2;
|
||||
encoder->delayMs = -1;
|
||||
}
|
||||
|
||||
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(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
encoder->currentFrame = 0;
|
||||
return true;
|
||||
}
|
||||
void ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) {
|
||||
|
||||
bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) {
|
||||
if (!encoder->wand) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue);
|
||||
free(encoder->outfile);
|
||||
free(encoder->frame);
|
||||
|
||||
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) {
|
||||
|
@ -64,10 +78,17 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
|
|||
MagickConstituteImage(encoder->wand, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, "RGBP", CharPixel, encoder->frame);
|
||||
uint64_t ts = encoder->currentFrame;
|
||||
uint64_t nts = encoder->currentFrame + encoder->frameskip + 1;
|
||||
ts *= VIDEO_TOTAL_LENGTH * 100;
|
||||
nts *= VIDEO_TOTAL_LENGTH * 100;
|
||||
ts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
nts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -21,11 +21,13 @@ struct ImageMagickGIFEncoder {
|
|||
|
||||
unsigned currentFrame;
|
||||
int frameskip;
|
||||
int delayMs;
|
||||
};
|
||||
|
||||
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
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/context/context.h"
|
||||
|
@ -17,8 +18,6 @@
|
|||
#define SAMPLES 1024
|
||||
#define RUMBLE_PWM 35
|
||||
|
||||
#define SOLAR_SENSOR_LEVEL "mgba_solar_sensor_level"
|
||||
|
||||
static retro_environment_t environCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
static retro_audio_sample_batch_t audioCallback;
|
||||
|
@ -45,6 +44,43 @@ static struct CircleBuffer rumbleHistory;
|
|||
static struct GBARumble rumble;
|
||||
static struct GBALuminanceSource lux;
|
||||
static int luxLevel;
|
||||
static struct GBACheatDevice cheats;
|
||||
static struct GBACheatSet cheatSet;
|
||||
|
||||
static void _reloadSettings(void) {
|
||||
struct GBAOptions opts = {
|
||||
.useBios = true,
|
||||
.idleOptimization = IDLE_LOOP_REMOVE
|
||||
};
|
||||
|
||||
struct retro_variable var;
|
||||
|
||||
var.key = "mgba_use_bios";
|
||||
var.value = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
opts.useBios = strcmp(var.value, "ON") == 0;
|
||||
}
|
||||
|
||||
var.key = "mgba_skip_bios";
|
||||
var.value = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
opts.skipBios = strcmp(var.value, "ON") == 0;
|
||||
}
|
||||
|
||||
var.key = "mgba_idle_optimization";
|
||||
var.value = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
if (strcmp(var.value, "Don't Remove") == 0) {
|
||||
opts.idleOptimization = IDLE_LOOP_IGNORE;
|
||||
} else if (strcmp(var.value, "Remove Known") == 0) {
|
||||
opts.idleOptimization = IDLE_LOOP_REMOVE;
|
||||
} else if (strcmp(var.value, "Detect and Remove") == 0) {
|
||||
opts.idleOptimization = IDLE_LOOP_DETECT;
|
||||
}
|
||||
}
|
||||
|
||||
GBAConfigLoadDefaults(&context.config, &opts);
|
||||
}
|
||||
|
||||
unsigned retro_api_version(void) {
|
||||
return RETRO_API_VERSION;
|
||||
|
@ -54,7 +90,11 @@ void retro_set_environment(retro_environment_t env) {
|
|||
environCallback = env;
|
||||
|
||||
struct retro_variable vars[] = {
|
||||
{ SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
|
||||
{ "mgba_solar_sensor_level", "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
|
||||
{ "mgba_allow_opposing_directions", "Allow opposing directional input; OFF|ON" },
|
||||
{ "mgba_use_bios", "Use BIOS file if found; ON|OFF" },
|
||||
{ "mgba_skip_bios", "Skip BIOS intro; OFF|ON" },
|
||||
{ "mgba_idle_optimization", "Idle loop removal; Remove Known|Detect and Remove|Don't Remove" },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
|
@ -94,6 +134,7 @@ void retro_get_system_av_info(struct retro_system_av_info* info) {
|
|||
info->geometry.base_height = VIDEO_VERTICAL_PIXELS;
|
||||
info->geometry.max_width = VIDEO_HORIZONTAL_PIXELS;
|
||||
info->geometry.max_height = VIDEO_VERTICAL_PIXELS;
|
||||
info->geometry.aspect_ratio = 3.0 / 2.0;
|
||||
info->timing.fps = GBA_ARM7TDMI_FREQUENCY / (float) VIDEO_TOTAL_LENGTH;
|
||||
info->timing.sample_rate = 32768;
|
||||
}
|
||||
|
@ -157,11 +198,6 @@ void retro_init(void) {
|
|||
stream.postVideoFrame = 0;
|
||||
|
||||
GBAContextInit(&context, 0);
|
||||
struct GBAOptions opts = {
|
||||
.useBios = true,
|
||||
.idleOptimization = IDLE_LOOP_REMOVE
|
||||
};
|
||||
GBAConfigLoadDefaults(&context.config, &opts);
|
||||
context.gba->logHandler = GBARetroLog;
|
||||
context.gba->stream = &stream;
|
||||
if (rumbleCallback) {
|
||||
|
@ -190,10 +226,18 @@ void retro_init(void) {
|
|||
blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
#endif
|
||||
|
||||
GBACheatDeviceCreate(&cheats);
|
||||
GBACheatAttachDevice(context.gba, &cheats);
|
||||
GBACheatSetInit(&cheatSet, "libretro");
|
||||
GBACheatAddSet(&cheats, &cheatSet);
|
||||
}
|
||||
|
||||
void retro_deinit(void) {
|
||||
GBAContextDeinit(&context);
|
||||
GBACheatRemoveSet(&cheats, &cheatSet);
|
||||
GBACheatDeviceDestroy(&cheats);
|
||||
GBACheatSetDeinit(&cheatSet);
|
||||
free(renderer.outputBuffer);
|
||||
}
|
||||
|
||||
|
@ -201,6 +245,18 @@ void retro_run(void) {
|
|||
uint16_t keys;
|
||||
inputPollCallback();
|
||||
|
||||
struct retro_variable var = {
|
||||
.key = "mgba_allow_opposing_directions",
|
||||
.value = 0
|
||||
};
|
||||
|
||||
bool updated = false;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
context.gba->allowOpposingDirections = strcmp(var.value, "yes") == 0;
|
||||
}
|
||||
}
|
||||
|
||||
keys = 0;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
|
||||
|
@ -268,6 +324,7 @@ bool retro_load_game(const struct retro_game_info* game) {
|
|||
savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
|
||||
struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
|
||||
_reloadSettings();
|
||||
GBAContextLoadROMFromVFile(&context, rom, save);
|
||||
GBAContextStart(&context);
|
||||
return true;
|
||||
|
@ -303,14 +360,31 @@ bool retro_unserialize(const void* data, size_t size) {
|
|||
}
|
||||
|
||||
void retro_cheat_reset(void) {
|
||||
// TODO: Cheats
|
||||
GBACheatSetDeinit(&cheatSet);
|
||||
GBACheatSetInit(&cheatSet, "libretro");
|
||||
}
|
||||
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char* code) {
|
||||
// TODO: Cheats
|
||||
UNUSED(index);
|
||||
UNUSED(enabled);
|
||||
UNUSED(code);
|
||||
// Convert the super wonky unportable libretro format to something normal
|
||||
char realCode[] = "XXXXXXXX XXXXXXXX";
|
||||
size_t len = strlen(code) + 1; // Include null terminator
|
||||
size_t i, pos;
|
||||
for (i = 0, pos = 0; i < len; ++i) {
|
||||
if (isspace((int) code[i]) || code[i] == '+') {
|
||||
realCode[pos] = ' ';
|
||||
} else {
|
||||
realCode[pos] = code[i];
|
||||
}
|
||||
if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) {
|
||||
realCode[pos] = '\0';
|
||||
GBACheatAddLine(&cheatSet, realCode);
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned retro_get_region(void) {
|
||||
|
@ -421,12 +495,13 @@ static void _setRumble(struct GBARumble* rumble, int enable) {
|
|||
}
|
||||
CircleBufferWrite8(&rumbleHistory, enable);
|
||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||
rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||
}
|
||||
|
||||
static void _updateLux(struct GBALuminanceSource* lux) {
|
||||
UNUSED(lux);
|
||||
struct retro_variable var = {
|
||||
.key = SOLAR_SENSOR_LEVEL,
|
||||
.key = "mgba_solar_sensor_level",
|
||||
.value = 0
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${PROJECT_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>mGBA</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.endrift.mgba</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${VERSION_STRING}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>OEGameCoreController</string>
|
||||
<key>OEGameCoreClass</key>
|
||||
<string>mGBAGameCore</string>
|
||||
<key>OEGameCoreOptions</key>
|
||||
<dict>
|
||||
<key>openemu.system.gba</key>
|
||||
<dict>
|
||||
<key>OEGameCoreRewindBufferSeconds</key>
|
||||
<integer>1200</integer>
|
||||
<key>OEGameCoreRewindInterval</key>
|
||||
<integer>0</integer>
|
||||
<key>OEGameCoreSupportsRewinding</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>OEGameCorePlayerCount</key>
|
||||
<string>1</string>
|
||||
<key>OEProjectURL</key>
|
||||
<string>https://mgba.io/</string>
|
||||
<key>OESystemIdentifiers</key>
|
||||
<array>
|
||||
<string>openemu.system.gba</string>
|
||||
</array>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<string>1</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://raw.github.com/OpenEmu/OpenEmu-Update/master/mgba_appcast.xml</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright (c) 2011, OpenEmu Team
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the OpenEmu Team nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@protocol OESystemResponderClient;
|
||||
|
||||
typedef enum _OEGBAButton
|
||||
{
|
||||
OEGBAButtonUp,
|
||||
OEGBAButtonDown,
|
||||
OEGBAButtonLeft,
|
||||
OEGBAButtonRight,
|
||||
OEGBAButtonA,
|
||||
OEGBAButtonB,
|
||||
OEGBAButtonL,
|
||||
OEGBAButtonR,
|
||||
OEGBAButtonStart,
|
||||
OEGBAButtonSelect,
|
||||
OEGBAButtonCount
|
||||
} OEGBAButton;
|
||||
|
||||
@protocol OEGBASystemResponderClient <OESystemResponderClient, NSObject>
|
||||
|
||||
- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player;
|
||||
- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player;
|
||||
|
||||
@end
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import <OpenEmuBase/OEGameCore.h>
|
||||
|
||||
OE_EXPORTED_CLASS
|
||||
@interface mGBAGameCore : OEGameCore
|
||||
@end
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
Copyright (c) 2016, Jeffrey Pfau
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "mGBAGameCore.h"
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/context/context.h"
|
||||
#include "util/circle-buffer.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#import <OpenEmuBase/OERingBuffer.h>
|
||||
#import "OEGBASystemResponderClient.h"
|
||||
#import <OpenGL/gl.h>
|
||||
|
||||
#define SAMPLES 1024
|
||||
|
||||
@interface mGBAGameCore () <OEGBASystemResponderClient>
|
||||
{
|
||||
struct GBAContext context;
|
||||
struct GBAVideoSoftwareRenderer renderer;
|
||||
struct GBACheatDevice cheats;
|
||||
struct GBACheatSet cheatSet;
|
||||
uint16_t keys;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation mGBAGameCore
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// TODO: Add a log handler
|
||||
GBAContextInit(&context, 0);
|
||||
struct GBAOptions opts = {
|
||||
.useBios = true,
|
||||
.idleOptimization = IDLE_LOOP_REMOVE
|
||||
};
|
||||
GBAConfigLoadDefaults(&context.config, &opts);
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer.outputBufferStride = 256;
|
||||
context.renderer = &renderer.d;
|
||||
GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
|
||||
GBACheatDeviceCreate(&cheats);
|
||||
GBACheatAttachDevice(context.gba, &cheats);
|
||||
GBACheatSetInit(&cheatSet, "openemu");
|
||||
GBACheatAddSet(&cheats, &cheatSet);
|
||||
keys = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
GBAContextDeinit(&context);
|
||||
GBACheatRemoveSet(&cheats, &cheatSet);
|
||||
GBACheatDeviceDestroy(&cheats);
|
||||
GBACheatSetDeinit(&cheatSet);
|
||||
free(renderer.outputBuffer);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
#pragma mark - Execution
|
||||
|
||||
- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
|
||||
{
|
||||
NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
|
||||
[[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil];
|
||||
if (context.dirs.save) {
|
||||
context.dirs.save->close(context.dirs.save);
|
||||
}
|
||||
context.dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
|
||||
|
||||
if (!GBAContextLoadROM(&context, [path UTF8String], true)) {
|
||||
*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!GBAContextStart(&context)) {
|
||||
*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotStartCoreError userInfo:nil];
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)executeFrame
|
||||
{
|
||||
GBAContextFrame(&context, keys);
|
||||
|
||||
int16_t samples[SAMPLES * 2];
|
||||
size_t available = 0;
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
available = blip_samples_avail(context.gba->audio.left);
|
||||
blip_read_samples(context.gba->audio.left, samples, available, true);
|
||||
blip_read_samples(context.gba->audio.right, samples + 1, available, true);
|
||||
#else
|
||||
#error BLIP_BUF is required for now
|
||||
#endif
|
||||
[[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
|
||||
}
|
||||
|
||||
- (void)resetEmulation
|
||||
{
|
||||
ARMReset(context.cpu);
|
||||
}
|
||||
|
||||
- (void)stopEmulation
|
||||
{
|
||||
GBAContextStop(&context);
|
||||
[super stopEmulation];
|
||||
}
|
||||
|
||||
- (void)setupEmulation
|
||||
{
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Video
|
||||
|
||||
- (OEIntSize)aspectSize
|
||||
{
|
||||
return OEIntSizeMake(3, 2);
|
||||
}
|
||||
|
||||
- (OEIntRect)screenRect
|
||||
{
|
||||
return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
}
|
||||
|
||||
- (OEIntSize)bufferSize
|
||||
{
|
||||
return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
|
||||
}
|
||||
|
||||
- (const void *)videoBuffer
|
||||
{
|
||||
return renderer.outputBuffer;
|
||||
}
|
||||
|
||||
- (GLenum)pixelFormat
|
||||
{
|
||||
return GL_RGBA;
|
||||
}
|
||||
|
||||
- (GLenum)pixelType
|
||||
{
|
||||
return GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
}
|
||||
|
||||
- (GLenum)internalPixelFormat
|
||||
{
|
||||
return GL_RGB8;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)frameInterval
|
||||
{
|
||||
return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
|
||||
}
|
||||
|
||||
#pragma mark - Audio
|
||||
|
||||
- (NSUInteger)channelCount
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (double)audioSampleRate
|
||||
{
|
||||
return 32768;
|
||||
}
|
||||
|
||||
#pragma mark - Save State
|
||||
|
||||
- (NSData *)serializeStateWithError:(NSError **)outError
|
||||
{
|
||||
struct VFile* vf = VFileMemChunk(nil, 0);
|
||||
if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
|
||||
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
|
||||
vf->close(vf);
|
||||
return nil;
|
||||
}
|
||||
size_t size = vf->size(vf);
|
||||
void* data = vf->map(vf, size, MAP_READ);
|
||||
NSData *nsdata = [NSData dataWithBytes:data length:size];
|
||||
vf->unmap(vf, data, size);
|
||||
vf->close(vf);
|
||||
return nsdata;
|
||||
}
|
||||
|
||||
- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
|
||||
{
|
||||
struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
|
||||
if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
|
||||
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
|
||||
vf->close(vf);
|
||||
return NO;
|
||||
}
|
||||
vf->close(vf);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
||||
{
|
||||
struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
|
||||
block(GBASaveStateNamed(context.gba, vf, 0), nil);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
||||
{
|
||||
struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
|
||||
block(GBALoadStateNamed(context.gba, vf, 0), nil);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
#pragma mark - Input
|
||||
|
||||
const int GBAMap[] = {
|
||||
GBA_KEY_UP,
|
||||
GBA_KEY_DOWN,
|
||||
GBA_KEY_LEFT,
|
||||
GBA_KEY_RIGHT,
|
||||
GBA_KEY_A,
|
||||
GBA_KEY_B,
|
||||
GBA_KEY_L,
|
||||
GBA_KEY_R,
|
||||
GBA_KEY_START,
|
||||
GBA_KEY_SELECT
|
||||
};
|
||||
|
||||
- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
|
||||
{
|
||||
UNUSED(player);
|
||||
keys |= 1 << GBAMap[button];
|
||||
}
|
||||
|
||||
- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
|
||||
{
|
||||
UNUSED(player);
|
||||
keys &= ~(1 << GBAMap[button]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -6,7 +6,9 @@
|
|||
#ifndef GL_H
|
||||
#define GL_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_EPOXY
|
||||
#include <epoxy/gl.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <OpenGL/gl.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
#include "gles2.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
#include "util/configuration.h"
|
||||
#include "util/formatting.h"
|
||||
#include "util/vector.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define MAX_PASSES 8
|
||||
|
||||
static const char* const _vertexShader =
|
||||
"attribute vec4 position;\n"
|
||||
|
@ -13,17 +19,40 @@ static const char* const _vertexShader =
|
|||
|
||||
"void main() {\n"
|
||||
" gl_Position = position;\n"
|
||||
" texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n"
|
||||
" texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.5, -0.5);\n"
|
||||
"}";
|
||||
|
||||
static const char* const _nullVertexShader =
|
||||
"attribute vec4 position;\n"
|
||||
"varying vec2 texCoord;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" gl_Position = position;\n"
|
||||
" texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5);\n"
|
||||
"}";
|
||||
|
||||
static const char* const _fragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
"uniform float gamma;\n"
|
||||
"uniform vec3 scale;\n"
|
||||
"uniform vec3 bias;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = texture2D(tex, texCoord);\n"
|
||||
" color.a = 1.;\n"
|
||||
" color.rgb = scale * pow(color.rgb, vec3(gamma, gamma, gamma)) + bias;\n"
|
||||
" gl_FragColor = color;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _nullFragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = texture2D(tex, texCoord);\n"
|
||||
" color.a = 1.;\n"
|
||||
" gl_FragColor = color;"
|
||||
" gl_FragColor = color;\n"
|
||||
"}";
|
||||
|
||||
static const GLfloat _vertices[] = {
|
||||
|
@ -38,41 +67,64 @@ static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
|||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glGenTextures(1, &context->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
glShaderSource(context->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0);
|
||||
glShaderSource(context->vertexShader, 1, (const GLchar**) &_vertexShader, 0);
|
||||
glAttachShader(context->program, context->vertexShader);
|
||||
glAttachShader(context->program, context->fragmentShader);
|
||||
char log[1024];
|
||||
glCompileShader(context->fragmentShader);
|
||||
glCompileShader(context->vertexShader);
|
||||
glGetShaderInfoLog(context->fragmentShader, 1024, 0, log);
|
||||
glGetShaderInfoLog(context->vertexShader, 1024, 0, log);
|
||||
glLinkProgram(context->program);
|
||||
glGetProgramInfoLog(context->program, 1024, 0, log);
|
||||
printf("%s\n", log);
|
||||
context->texLocation = glGetUniformLocation(context->program, "tex");
|
||||
context->positionLocation = glGetAttribLocation(context->program, "position");
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
|
||||
struct GBAGLES2Uniform* uniforms = malloc(sizeof(struct GBAGLES2Uniform) * 3);
|
||||
uniforms[0].name = "gamma";
|
||||
uniforms[0].readableName = "Gamma";
|
||||
uniforms[0].type = GL_FLOAT;
|
||||
uniforms[0].value.f = 1.0f;
|
||||
uniforms[0].min.f = 0.1f;
|
||||
uniforms[0].max.f = 3.0f;
|
||||
uniforms[1].name = "scale";
|
||||
uniforms[1].readableName = "Scale";
|
||||
uniforms[1].type = GL_FLOAT_VEC3;
|
||||
uniforms[1].value.fvec3[0] = 1.0f;
|
||||
uniforms[1].value.fvec3[1] = 1.0f;
|
||||
uniforms[1].value.fvec3[2] = 1.0f;
|
||||
uniforms[1].min.fvec3[0] = -1.0f;
|
||||
uniforms[1].min.fvec3[1] = -1.0f;
|
||||
uniforms[1].min.fvec3[2] = -1.0f;
|
||||
uniforms[1].max.fvec3[0] = 2.0f;
|
||||
uniforms[1].max.fvec3[1] = 2.0f;
|
||||
uniforms[1].max.fvec3[2] = 2.0f;
|
||||
uniforms[2].name = "bias";
|
||||
uniforms[2].readableName = "Bias";
|
||||
uniforms[2].type = GL_FLOAT_VEC3;
|
||||
uniforms[2].value.fvec3[0] = 0.0f;
|
||||
uniforms[2].value.fvec3[1] = 0.0f;
|
||||
uniforms[2].value.fvec3[2] = 0.0f;
|
||||
uniforms[2].min.fvec3[0] = -1.0f;
|
||||
uniforms[2].min.fvec3[1] = -1.0f;
|
||||
uniforms[2].min.fvec3[2] = -1.0f;
|
||||
uniforms[2].max.fvec3[0] = 1.0f;
|
||||
uniforms[2].max.fvec3[1] = 1.0f;
|
||||
uniforms[2].max.fvec3[2] = 1.0f;
|
||||
GBAGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, uniforms, 3);
|
||||
GBAGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, 0, 0);
|
||||
glDeleteFramebuffers(1, &context->finalShader.fbo);
|
||||
context->finalShader.fbo = 0;
|
||||
}
|
||||
|
||||
static void GBAGLES2ContextDeinit(struct VideoBackend* v) {
|
||||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glDeleteTextures(1, &context->tex);
|
||||
GBAGLES2ShaderDeinit(&context->initialShader);
|
||||
GBAGLES2ShaderDeinit(&context->finalShader);
|
||||
free(context->initialShader.uniforms);
|
||||
}
|
||||
|
||||
static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) {
|
||||
|
@ -85,7 +137,7 @@ static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) {
|
|||
drawH = w * 2 / 3;
|
||||
}
|
||||
}
|
||||
glViewport(0, 0, 240, 160);
|
||||
glViewport(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
|
@ -97,30 +149,131 @@ static void GBAGLES2ContextClear(struct VideoBackend* v) {
|
|||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void _drawShader(struct GBAGLES2Shader* shader) {
|
||||
GLint viewport[4];
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo);
|
||||
if (shader->blend) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
int drawW = shader->width;
|
||||
int drawH = shader->height;
|
||||
int padW = 0;
|
||||
int padH = 0;
|
||||
if (!shader->width) {
|
||||
drawW = viewport[2];
|
||||
padW = viewport[0];
|
||||
}
|
||||
if (!shader->height) {
|
||||
drawH = viewport[3];
|
||||
padH = viewport[1];
|
||||
}
|
||||
glViewport(padW, padH, drawW, drawH);
|
||||
if (!shader->width || !shader->height) {
|
||||
GLint oldTex;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTex);
|
||||
glBindTexture(GL_TEXTURE_2D, shader->tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, oldTex);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glUseProgram(shader->program);
|
||||
glUniform1i(shader->texLocation, 0);
|
||||
glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
|
||||
glEnableVertexAttribArray(shader->positionLocation);
|
||||
size_t u;
|
||||
for (u = 0; u < shader->nUniforms; ++u) {
|
||||
struct GBAGLES2Uniform* uniform = &shader->uniforms[u];
|
||||
switch (uniform->type) {
|
||||
case GL_FLOAT:
|
||||
glUniform1f(uniform->location, uniform->value.f);
|
||||
break;
|
||||
case GL_INT:
|
||||
glUniform1i(uniform->location, uniform->value.i);
|
||||
break;
|
||||
case GL_BOOL:
|
||||
glUniform1i(uniform->location, uniform->value.b);
|
||||
break;
|
||||
case GL_FLOAT_VEC2:
|
||||
glUniform2fv(uniform->location, 1, uniform->value.fvec2);
|
||||
break;
|
||||
case GL_FLOAT_VEC3:
|
||||
glUniform3fv(uniform->location, 1, uniform->value.fvec3);
|
||||
break;
|
||||
case GL_FLOAT_VEC4:
|
||||
glUniform4fv(uniform->location, 1, uniform->value.fvec4);
|
||||
break;
|
||||
case GL_INT_VEC2:
|
||||
glUniform2iv(uniform->location, 1, uniform->value.ivec2);
|
||||
break;
|
||||
case GL_INT_VEC3:
|
||||
glUniform3iv(uniform->location, 1, uniform->value.ivec3);
|
||||
break;
|
||||
case GL_INT_VEC4:
|
||||
glUniform4iv(uniform->location, 1, uniform->value.ivec4);
|
||||
break;
|
||||
case GL_BOOL_VEC2:
|
||||
glUniform2i(uniform->location, uniform->value.bvec2[0], uniform->value.bvec2[1]);
|
||||
break;
|
||||
case GL_BOOL_VEC3:
|
||||
glUniform3i(uniform->location, uniform->value.bvec3[0], uniform->value.bvec3[1], uniform->value.bvec3[2]);
|
||||
break;
|
||||
case GL_BOOL_VEC4:
|
||||
glUniform4i(uniform->location, uniform->value.bvec4[0], uniform->value.bvec4[1], uniform->value.bvec4[2], uniform->value.bvec4[3]);
|
||||
break;
|
||||
case GL_FLOAT_MAT2:
|
||||
glUniformMatrix2fv(uniform->location, 1, GL_FALSE, uniform->value.fmat2x2);
|
||||
break;
|
||||
case GL_FLOAT_MAT3:
|
||||
glUniformMatrix3fv(uniform->location, 1, GL_FALSE, uniform->value.fmat3x3);
|
||||
break;
|
||||
case GL_FLOAT_MAT4:
|
||||
glUniformMatrix4fv(uniform->location, 1, GL_FALSE, uniform->value.fmat4x4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glBindTexture(GL_TEXTURE_2D, shader->tex);
|
||||
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
||||
}
|
||||
|
||||
void GBAGLES2ContextDrawFrame(struct VideoBackend* v) {
|
||||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glUseProgram(context->program);
|
||||
glUniform1i(context->texLocation, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
glVertexAttribPointer(context->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
|
||||
glEnableVertexAttribArray(context->positionLocation);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
context->finalShader.filter = v->filter;
|
||||
_drawShader(&context->initialShader);
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
_drawShader(&context->shaders[n]);
|
||||
}
|
||||
_drawShader(&context->finalShader);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
|
||||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 256);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
#endif
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
void GBAGLES2ContextCreate(struct GBAGLES2Context* context) {
|
||||
|
@ -133,4 +286,569 @@ void GBAGLES2ContextCreate(struct GBAGLES2Context* context) {
|
|||
context->d.drawFrame = GBAGLES2ContextDrawFrame;
|
||||
context->d.setMessage = 0;
|
||||
context->d.clearMessage = 0;
|
||||
context->shaders = 0;
|
||||
context->nShaders = 0;
|
||||
}
|
||||
|
||||
void GBAGLES2ShaderInit(struct GBAGLES2Shader* shader, const char* vs, const char* fs, int width, int height, struct GBAGLES2Uniform* uniforms, size_t nUniforms) {
|
||||
shader->width = width >= 0 ? width : VIDEO_HORIZONTAL_PIXELS;
|
||||
shader->height = height >= 0 ? height : VIDEO_VERTICAL_PIXELS;
|
||||
shader->filter = false;
|
||||
shader->blend = false;
|
||||
shader->uniforms = uniforms;
|
||||
shader->nUniforms = nUniforms;
|
||||
glGenFramebuffers(1, &shader->fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo);
|
||||
|
||||
glGenTextures(1, &shader->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, shader->tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
if (shader->width && shader->height) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, shader->width, shader->height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
}
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, shader->tex, 0);
|
||||
shader->program = glCreateProgram();
|
||||
shader->vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
shader->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (vs) {
|
||||
glShaderSource(shader->vertexShader, 1, (const GLchar**) &vs, 0);
|
||||
} else {
|
||||
glShaderSource(shader->vertexShader, 1, (const GLchar**) &_nullVertexShader, 0);
|
||||
}
|
||||
if (fs) {
|
||||
glShaderSource(shader->fragmentShader, 1, (const GLchar**) &fs, 0);
|
||||
} else {
|
||||
glShaderSource(shader->fragmentShader, 1, (const GLchar**) &_nullFragmentShader, 0);
|
||||
}
|
||||
glAttachShader(shader->program, shader->vertexShader);
|
||||
glAttachShader(shader->program, shader->fragmentShader);
|
||||
char log[1024];
|
||||
glCompileShader(shader->fragmentShader);
|
||||
glGetShaderInfoLog(shader->fragmentShader, 1024, 0, log);
|
||||
if (log[0]) {
|
||||
printf("%s\n", log);
|
||||
}
|
||||
glCompileShader(shader->vertexShader);
|
||||
glGetShaderInfoLog(shader->vertexShader, 1024, 0, log);
|
||||
if (log[0]) {
|
||||
printf("%s\n", log);
|
||||
}
|
||||
glLinkProgram(shader->program);
|
||||
glGetProgramInfoLog(shader->program, 1024, 0, log);
|
||||
if (log[0]) {
|
||||
printf("%s\n", log);
|
||||
}
|
||||
|
||||
shader->texLocation = glGetUniformLocation(shader->program, "tex");
|
||||
shader->positionLocation = glGetAttribLocation(shader->program, "position");
|
||||
size_t i;
|
||||
for (i = 0; i < shader->nUniforms; ++i) {
|
||||
shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void GBAGLES2ShaderDeinit(struct GBAGLES2Shader* shader) {
|
||||
glDeleteTextures(1, &shader->tex);
|
||||
glDeleteShader(shader->fragmentShader);
|
||||
glDeleteProgram(shader->program);
|
||||
glDeleteFramebuffers(1, &shader->fbo);
|
||||
}
|
||||
|
||||
void GBAGLES2ShaderAttach(struct GBAGLES2Context* context, struct GBAGLES2Shader* shaders, size_t nShaders) {
|
||||
if (context->shaders) {
|
||||
if (context->shaders == shaders && context->nShaders == nShaders) {
|
||||
return;
|
||||
}
|
||||
GBAGLES2ShaderDetach(context);
|
||||
}
|
||||
context->shaders = shaders;
|
||||
context->nShaders = nShaders;
|
||||
size_t i;
|
||||
for (i = 0; i < nShaders; ++i) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, context->shaders[i].fbo);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void GBAGLES2ShaderDetach(struct GBAGLES2Context* context) {
|
||||
if (!context->shaders) {
|
||||
return;
|
||||
}
|
||||
context->shaders = 0;
|
||||
context->nShaders = 0;
|
||||
}
|
||||
|
||||
static bool _lookupIntValue(const struct Configuration* config, const char* section, const char* key, int* out) {
|
||||
const char* charValue = ConfigurationGetValue(config, section, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
}
|
||||
char* end;
|
||||
unsigned long value = strtol(charValue, &end, 10);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _lookupFloatValue(const struct Configuration* config, const char* section, const char* key, float* out) {
|
||||
const char* charValue = ConfigurationGetValue(config, section, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
}
|
||||
char* end;
|
||||
float value = strtof_u(charValue, &end);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _lookupBoolValue(const struct Configuration* config, const char* section, const char* key, GLboolean* out) {
|
||||
const char* charValue = ConfigurationGetValue(config, section, key);
|
||||
if (!charValue) {
|
||||
return false;
|
||||
}
|
||||
if (!strcmp(charValue, "true")) {
|
||||
*out = GL_TRUE;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(charValue, "false")) {
|
||||
*out = GL_FALSE;
|
||||
return true;
|
||||
}
|
||||
char* end;
|
||||
unsigned long value = strtol(charValue, &end, 10);
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
DECLARE_VECTOR(GBAGLES2UniformList, struct GBAGLES2Uniform);
|
||||
DEFINE_VECTOR(GBAGLES2UniformList, struct GBAGLES2Uniform);
|
||||
|
||||
static void _uniformHandler(const char* sectionName, void* user) {
|
||||
struct GBAGLES2UniformList* uniforms = user;
|
||||
unsigned passId;
|
||||
int sentinel;
|
||||
if (sscanf(sectionName, "pass.%u.uniform.%n", &passId, &sentinel) < 1) {
|
||||
return;
|
||||
}
|
||||
struct GBAGLES2Uniform* u = GBAGLES2UniformListAppend(uniforms);
|
||||
u->name = sectionName;
|
||||
}
|
||||
|
||||
|
||||
static void _loadValue(struct Configuration* description, const char* name, GLenum type, const char* field, union GBAGLES2UniformValue* value) {
|
||||
char fieldName[16];
|
||||
switch (type) {
|
||||
case GL_FLOAT:
|
||||
value->f = 0;
|
||||
_lookupFloatValue(description, name, field, &value->f);
|
||||
break;
|
||||
case GL_FLOAT_VEC2:
|
||||
value->fvec2[0] = 0;
|
||||
value->fvec2[1] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec2[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec2[1]);
|
||||
break;
|
||||
case GL_FLOAT_VEC3:
|
||||
value->fvec3[0] = 0;
|
||||
value->fvec3[1] = 0;
|
||||
value->fvec3[2] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec3[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec3[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec3[2]);
|
||||
break;
|
||||
case GL_FLOAT_VEC4:
|
||||
value->fvec4[0] = 0;
|
||||
value->fvec4[1] = 0;
|
||||
value->fvec4[2] = 0;
|
||||
value->fvec4[3] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec4[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec4[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec4[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fvec4[3]);
|
||||
break;
|
||||
case GL_FLOAT_MAT2:
|
||||
value->fmat2x2[0] = 0;
|
||||
value->fmat2x2[1] = 0;
|
||||
value->fmat2x2[2] = 0;
|
||||
value->fmat2x2[3] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat2x2[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat2x2[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat2x2[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat2x2[3]);
|
||||
break;
|
||||
case GL_FLOAT_MAT3:
|
||||
value->fmat3x3[0] = 0;
|
||||
value->fmat3x3[1] = 0;
|
||||
value->fmat3x3[2] = 0;
|
||||
value->fmat3x3[3] = 0;
|
||||
value->fmat3x3[4] = 0;
|
||||
value->fmat3x3[5] = 0;
|
||||
value->fmat3x3[6] = 0;
|
||||
value->fmat3x3[7] = 0;
|
||||
value->fmat3x3[8] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[3]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[4]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[5]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[6]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[7]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat3x3[8]);
|
||||
break;
|
||||
case GL_FLOAT_MAT4:
|
||||
value->fmat4x4[0] = 0;
|
||||
value->fmat4x4[1] = 0;
|
||||
value->fmat4x4[2] = 0;
|
||||
value->fmat4x4[3] = 0;
|
||||
value->fmat4x4[4] = 0;
|
||||
value->fmat4x4[5] = 0;
|
||||
value->fmat4x4[6] = 0;
|
||||
value->fmat4x4[7] = 0;
|
||||
value->fmat4x4[8] = 0;
|
||||
value->fmat4x4[9] = 0;
|
||||
value->fmat4x4[10] = 0;
|
||||
value->fmat4x4[11] = 0;
|
||||
value->fmat4x4[12] = 0;
|
||||
value->fmat4x4[13] = 0;
|
||||
value->fmat4x4[14] = 0;
|
||||
value->fmat4x4[15] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0,3]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[3]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[4]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[5]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[6]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1,3]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[7]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[8]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[9]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[10]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2,3]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[11]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3,0]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[12]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3,1]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[13]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3,2]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[14]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3,3]", field);
|
||||
_lookupFloatValue(description, name, fieldName, &value->fmat4x4[15]);
|
||||
break;
|
||||
case GL_INT:
|
||||
value->i = 0;
|
||||
_lookupIntValue(description, name, field, &value->i);
|
||||
break;
|
||||
case GL_INT_VEC2:
|
||||
value->ivec2[0] = 0;
|
||||
value->ivec2[1] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec2[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec2[1]);
|
||||
break;
|
||||
case GL_INT_VEC3:
|
||||
value->ivec3[0] = 0;
|
||||
value->ivec3[1] = 0;
|
||||
value->ivec3[2] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec3[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec3[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec3[2]);
|
||||
break;
|
||||
case GL_INT_VEC4:
|
||||
value->ivec4[0] = 0;
|
||||
value->ivec4[1] = 0;
|
||||
value->ivec4[2] = 0;
|
||||
value->ivec4[3] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec4[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec4[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec4[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
|
||||
_lookupIntValue(description, name, fieldName, &value->ivec4[3]);
|
||||
break;
|
||||
case GL_BOOL:
|
||||
value->b = 0;
|
||||
_lookupBoolValue(description, name, field, &value->b);
|
||||
break;
|
||||
case GL_BOOL_VEC2:
|
||||
value->bvec2[0] = 0;
|
||||
value->bvec2[1] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec2[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec2[1]);
|
||||
break;
|
||||
case GL_BOOL_VEC3:
|
||||
value->bvec3[0] = 0;
|
||||
value->bvec3[1] = 0;
|
||||
value->bvec3[2] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec3[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec3[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec3[2]);
|
||||
break;
|
||||
case GL_BOOL_VEC4:
|
||||
value->bvec4[0] = 0;
|
||||
value->bvec4[1] = 0;
|
||||
value->bvec4[2] = 0;
|
||||
value->bvec4[3] = 0;
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec4[0]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec4[1]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec4[2]);
|
||||
snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
|
||||
_lookupBoolValue(description, name, fieldName, &value->bvec4[3]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _loadUniform(struct Configuration* description, size_t pass, struct GBAGLES2Uniform* uniform) {
|
||||
unsigned passId;
|
||||
if (sscanf(uniform->name, "pass.%u.uniform.", &passId) < 1 || passId != pass) {
|
||||
return false;
|
||||
}
|
||||
const char* type = ConfigurationGetValue(description, uniform->name, "type");
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
if (!strcmp(type, "float")) {
|
||||
uniform->type = GL_FLOAT;
|
||||
} else if (!strcmp(type, "float2")) {
|
||||
uniform->type = GL_FLOAT_VEC2;
|
||||
} else if (!strcmp(type, "float3")) {
|
||||
uniform->type = GL_FLOAT_VEC3;
|
||||
} else if (!strcmp(type, "float4")) {
|
||||
uniform->type = GL_FLOAT_VEC4;
|
||||
} else if (!strcmp(type, "float2x2")) {
|
||||
uniform->type = GL_FLOAT_MAT2;
|
||||
} else if (!strcmp(type, "float3x3")) {
|
||||
uniform->type = GL_FLOAT_MAT3;
|
||||
} else if (!strcmp(type, "float4x4")) {
|
||||
uniform->type = GL_FLOAT_MAT4;
|
||||
} else if (!strcmp(type, "int")) {
|
||||
uniform->type = GL_INT;
|
||||
} else if (!strcmp(type, "int2")) {
|
||||
uniform->type = GL_INT_VEC2;
|
||||
} else if (!strcmp(type, "int3")) {
|
||||
uniform->type = GL_INT_VEC3;
|
||||
} else if (!strcmp(type, "int4")) {
|
||||
uniform->type = GL_INT_VEC4;
|
||||
} else if (!strcmp(type, "bool")) {
|
||||
uniform->type = GL_BOOL;
|
||||
} else if (!strcmp(type, "int2")) {
|
||||
uniform->type = GL_BOOL_VEC2;
|
||||
} else if (!strcmp(type, "int3")) {
|
||||
uniform->type = GL_BOOL_VEC3;
|
||||
} else if (!strcmp(type, "int4")) {
|
||||
uniform->type = GL_BOOL_VEC4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
_loadValue(description, uniform->name, uniform->type, "default", &uniform->value);
|
||||
_loadValue(description, uniform->name, uniform->type, "min", &uniform->min);
|
||||
_loadValue(description, uniform->name, uniform->type, "max", &uniform->max);
|
||||
const char* readable = ConfigurationGetValue(description, uniform->name, "readableName");
|
||||
if (readable) {
|
||||
uniform->readableName = strdup(readable);
|
||||
} else {
|
||||
uniform->readableName = 0;
|
||||
}
|
||||
uniform->name = strdup(strstr(uniform->name, "uniform.") + strlen("uniform."));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
|
||||
struct VFile* manifest = dir->openFile(dir, "manifest.ini", O_RDONLY);
|
||||
if (!manifest) {
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
struct Configuration description;
|
||||
ConfigurationInit(&description);
|
||||
if (ConfigurationReadVFile(&description, manifest)) {
|
||||
int inShaders;
|
||||
success = _lookupIntValue(&description, "shader", "passes", &inShaders);
|
||||
if (inShaders > MAX_PASSES || inShaders < 1) {
|
||||
success = false;
|
||||
}
|
||||
if (success) {
|
||||
struct GBAGLES2Shader* shaderBlock = malloc(sizeof(struct GBAGLES2Shader) * inShaders);
|
||||
int n;
|
||||
for (n = 0; n < inShaders; ++n) {
|
||||
char passName[12];
|
||||
snprintf(passName, sizeof(passName), "pass.%u", n);
|
||||
const char* fs = ConfigurationGetValue(&description, passName, "fragmentShader");
|
||||
const char* vs = ConfigurationGetValue(&description, passName, "vertexShader");
|
||||
if (fs && (fs[0] == '.' || strstr(fs, PATH_SEP))) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
if (vs && (vs[0] == '.' || strstr(vs, PATH_SEP))) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
char* fssrc = 0;
|
||||
char* vssrc = 0;
|
||||
if (fs) {
|
||||
struct VFile* fsf = dir->openFile(dir, fs, O_RDONLY);
|
||||
if (!fsf) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
fssrc = malloc(fsf->size(fsf) + 1);
|
||||
fssrc[fsf->size(fsf)] = '\0';
|
||||
fsf->read(fsf, fssrc, fsf->size(fsf));
|
||||
fsf->close(fsf);
|
||||
}
|
||||
if (vs) {
|
||||
struct VFile* vsf = dir->openFile(dir, vs, O_RDONLY);
|
||||
if (!vsf) {
|
||||
success = false;
|
||||
free(fssrc);
|
||||
break;
|
||||
}
|
||||
vssrc = malloc(vsf->size(vsf) + 1);
|
||||
vssrc[vsf->size(vsf)] = '\0';
|
||||
vsf->read(vsf, vssrc, vsf->size(vsf));
|
||||
vsf->close(vsf);
|
||||
}
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
_lookupIntValue(&description, passName, "width", &width);
|
||||
_lookupIntValue(&description, passName, "height", &height);
|
||||
|
||||
struct GBAGLES2UniformList uniformVector;
|
||||
GBAGLES2UniformListInit(&uniformVector, 0);
|
||||
ConfigurationEnumerateSections(&description, _uniformHandler, &uniformVector);
|
||||
size_t u;
|
||||
for (u = 0; u < GBAGLES2UniformListSize(&uniformVector); ++u) {
|
||||
struct GBAGLES2Uniform* uniform = GBAGLES2UniformListGetPointer(&uniformVector, u);
|
||||
if (!_loadUniform(&description, n, uniform)) {
|
||||
GBAGLES2UniformListShift(&uniformVector, u, 1);
|
||||
--u;
|
||||
}
|
||||
}
|
||||
u = GBAGLES2UniformListSize(&uniformVector);
|
||||
struct GBAGLES2Uniform* uniformBlock = malloc(sizeof(*uniformBlock) * u);
|
||||
memcpy(uniformBlock, GBAGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
|
||||
GBAGLES2UniformListDeinit(&uniformVector);
|
||||
|
||||
GBAGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, uniformBlock, u);
|
||||
int b = 0;
|
||||
_lookupIntValue(&description, passName, "blend", &b);
|
||||
if (b) {
|
||||
shaderBlock[n].blend = b;
|
||||
}
|
||||
b = 0;
|
||||
_lookupIntValue(&description, passName, "filter", &b);
|
||||
if (b) {
|
||||
shaderBlock[n].filter = b;
|
||||
}
|
||||
free(fssrc);
|
||||
free(vssrc);
|
||||
}
|
||||
if (success) {
|
||||
shader->nPasses = inShaders;
|
||||
shader->passes = shaderBlock;
|
||||
shader->name = ConfigurationGetValue(&description, "shader", "name");
|
||||
if (shader->name) {
|
||||
shader->name = strdup(shader->name);
|
||||
}
|
||||
shader->author = ConfigurationGetValue(&description, "shader", "author");
|
||||
if (shader->author) {
|
||||
shader->author = strdup(shader->author);
|
||||
}
|
||||
shader->description = ConfigurationGetValue(&description, "shader", "description");
|
||||
if (shader->description) {
|
||||
shader->description = strdup(shader->description);
|
||||
}
|
||||
} else {
|
||||
inShaders = n;
|
||||
for (n = 0; n < inShaders; ++n) {
|
||||
GBAGLES2ShaderDeinit(&shaderBlock[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigurationDeinit(&description);
|
||||
return success;
|
||||
}
|
||||
|
||||
void GBAGLES2ShaderFree(struct VideoShader* shader) {
|
||||
free((void*) shader->name);
|
||||
free((void*) shader->author);
|
||||
free((void*) shader->description);
|
||||
shader->name = 0;
|
||||
shader->author = 0;
|
||||
shader->description = 0;
|
||||
struct GBAGLES2Shader* shaders = shader->passes;
|
||||
size_t n;
|
||||
for (n = 0; n < shader->nPasses; ++n) {
|
||||
GBAGLES2ShaderDeinit(&shaders[n]);
|
||||
size_t u;
|
||||
for (u = 0; u < shaders[n].nUniforms; ++u) {
|
||||
free((void*) shaders[n].uniforms[u].name);
|
||||
free((void*) shaders[n].uniforms[u].readableName);
|
||||
}
|
||||
}
|
||||
free(shaders);
|
||||
shader->passes = 0;
|
||||
shader->nPasses = 0;
|
||||
}
|
||||
|
|
|
@ -6,22 +6,90 @@
|
|||
#ifndef GLES2_H
|
||||
#define GLES2_H
|
||||
|
||||
#ifdef USE_EPOXY
|
||||
#include <epoxy/gl.h>
|
||||
#elif defined(BUILD_GL)
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl3.h>
|
||||
#else
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
#else
|
||||
#include <GLES2/gl2.h>
|
||||
#endif
|
||||
|
||||
#include "platform/video-backend.h"
|
||||
|
||||
union GBAGLES2UniformValue {
|
||||
GLfloat f;
|
||||
GLint i;
|
||||
GLboolean b;
|
||||
GLfloat fvec2[2];
|
||||
GLfloat fvec3[3];
|
||||
GLfloat fvec4[4];
|
||||
GLint ivec2[2];
|
||||
GLint ivec3[3];
|
||||
GLint ivec4[4];
|
||||
GLboolean bvec2[2];
|
||||
GLboolean bvec3[3];
|
||||
GLboolean bvec4[4];
|
||||
GLfloat fmat2x2[4];
|
||||
GLfloat fmat3x3[9];
|
||||
GLfloat fmat4x4[16];
|
||||
};
|
||||
|
||||
struct GBAGLES2Uniform {
|
||||
const char* name;
|
||||
GLenum type;
|
||||
union GBAGLES2UniformValue value;
|
||||
GLuint location;
|
||||
union GBAGLES2UniformValue min;
|
||||
union GBAGLES2UniformValue max;
|
||||
const char* readableName;
|
||||
};
|
||||
|
||||
struct GBAGLES2Shader {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
bool filter;
|
||||
bool blend;
|
||||
GLuint tex;
|
||||
GLuint fbo;
|
||||
GLuint fragmentShader;
|
||||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
GLuint texLocation;
|
||||
GLuint positionLocation;
|
||||
|
||||
struct GBAGLES2Uniform* uniforms;
|
||||
size_t nUniforms;
|
||||
};
|
||||
|
||||
struct GBAGLES2Context {
|
||||
struct VideoBackend d;
|
||||
|
||||
GLuint tex;
|
||||
GLuint fragmentShader;
|
||||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
GLuint bufferObject;
|
||||
GLuint texLocation;
|
||||
GLuint positionLocation;
|
||||
|
||||
struct GBAGLES2Shader initialShader;
|
||||
struct GBAGLES2Shader finalShader;
|
||||
|
||||
struct GBAGLES2Shader* shaders;
|
||||
size_t nShaders;
|
||||
};
|
||||
|
||||
void GBAGLES2ContextCreate(struct GBAGLES2Context*);
|
||||
|
||||
void GBAGLES2ShaderInit(struct GBAGLES2Shader*, const char* vs, const char* fs, int width, int height, struct GBAGLES2Uniform* uniforms, size_t nUniforms);
|
||||
void GBAGLES2ShaderDeinit(struct GBAGLES2Shader*);
|
||||
void GBAGLES2ShaderAttach(struct GBAGLES2Context*, struct GBAGLES2Shader*, size_t nShaders);
|
||||
void GBAGLES2ShaderDetach(struct GBAGLES2Context*);
|
||||
|
||||
struct VDir;
|
||||
bool GBAGLES2ShaderLoad(struct VideoShader*, struct VDir*);
|
||||
void GBAGLES2ShaderFree(struct VideoShader*);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -33,6 +33,10 @@ static inline int MutexLock(Mutex* mutex) {
|
|||
return pthread_mutex_lock(mutex);
|
||||
}
|
||||
|
||||
static inline int MutexTryLock(Mutex* mutex) {
|
||||
return pthread_mutex_trylock(mutex);
|
||||
}
|
||||
|
||||
static inline int MutexUnlock(Mutex* mutex) {
|
||||
return pthread_mutex_unlock(mutex);
|
||||
}
|
||||
|
@ -82,8 +86,11 @@ static inline int ThreadSetName(const char* name) {
|
|||
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
return 0;
|
||||
#else
|
||||
#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old
|
||||
return pthread_setname_np(pthread_self(), name);
|
||||
#else
|
||||
UNUSED(name);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -4,25 +4,38 @@ find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share)
|
|||
find_file(EXTRADB extra.json PATHS ${VITASDK}${VITASDK}/bin ${VITASDK}/share)
|
||||
find_program(STRIP ${cross_prefix}strip)
|
||||
|
||||
set(OS_DEFINES IOAPI_NO_64)
|
||||
set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE)
|
||||
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c)
|
||||
set(OS_SRC ${OS_SRC} PARENT_SCOPE)
|
||||
source_group("PS Vita-specific code" FILES ${OS_SRC})
|
||||
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c)
|
||||
set(VFS_SRC ${VFS_SRC} PARENT_SCOPE)
|
||||
list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c)
|
||||
set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE)
|
||||
|
||||
set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -lpng -lz -l${M_LIBRARY})
|
||||
set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -l${M_LIBRARY})
|
||||
set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm)
|
||||
|
||||
list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c)
|
||||
|
||||
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON)
|
||||
add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o main.c)
|
||||
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/icons.o
|
||||
${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||
${CMAKE_CURRENT_BINARY_DIR}/backdrop.o
|
||||
PROPERTIES GENERATED ON)
|
||||
add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} main.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/icons.o
|
||||
${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||
${CMAKE_CURRENT_BINARY_DIR}/backdrop.o)
|
||||
set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB})
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||
COMMAND ${OBJCOPY_CMD} font.png ${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||
COMMAND ${OBJCOPY_CMD} font2x.png ${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icons.o
|
||||
COMMAND ${OBJCOPY_CMD} icons2x.png ${CMAKE_CURRENT_BINARY_DIR}/icons.o
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o
|
||||
|
|
|
@ -34,8 +34,12 @@ set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags")
|
|||
set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags")
|
||||
|
||||
set(CMAKE_PREFIX_PATH ${VITASDK}/arm-vita-eabi)
|
||||
set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_FIND_ROOT_PATH ${VITASDK}/arm-vita-eabi;${VITASDK})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE INTERNAL "")
|
||||
set(ENV{PKG_CONFIG_PATH} ${VITASDK}/arm-vita-eabi/lib/pkgconfig)
|
||||
set(ENV{PKG_CONFIG_LIBDIR} ${VITASDK}/arm-vita-eabi/lib/pkgconfig)
|
||||
|
||||
set(PSP2 ON)
|
||||
add_definitions(-DPSP2)
|
||||
|
|
|
@ -8,14 +8,16 @@
|
|||
|
||||
#include <vita2d.h>
|
||||
|
||||
#define CELL_HEIGHT 16
|
||||
#define CELL_WIDTH 16
|
||||
#define GLYPH_HEIGHT 12
|
||||
#define CELL_HEIGHT 32
|
||||
#define CELL_WIDTH 32
|
||||
#define GLYPH_HEIGHT 24
|
||||
|
||||
extern const uint8_t _binary_font_png_start[];
|
||||
extern const uint8_t _binary_font2x_png_start[];
|
||||
extern const uint8_t _binary_icons2x_png_start[];
|
||||
|
||||
struct GUIFont {
|
||||
vita2d_texture* tex;
|
||||
vita2d_texture* icons;
|
||||
};
|
||||
|
||||
struct GUIFont* GUIFontCreate(void) {
|
||||
|
@ -23,37 +25,85 @@ struct GUIFont* GUIFontCreate(void) {
|
|||
if (!font) {
|
||||
return 0;
|
||||
}
|
||||
font->tex = vita2d_load_PNG_buffer(_binary_font_png_start);
|
||||
font->tex = vita2d_load_PNG_buffer(_binary_font2x_png_start);
|
||||
font->icons = vita2d_load_PNG_buffer(_binary_icons2x_png_start);
|
||||
return font;
|
||||
}
|
||||
|
||||
void GUIFontDestroy(struct GUIFont* font) {
|
||||
vita2d_free_texture(font->tex);
|
||||
vita2d_free_texture(font->icons);
|
||||
free(font);
|
||||
}
|
||||
|
||||
unsigned GUIFontHeight(const struct GUIFont* font) {
|
||||
UNUSED(font);
|
||||
return GLYPH_HEIGHT * 2;
|
||||
return GLYPH_HEIGHT;
|
||||
}
|
||||
|
||||
unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) {
|
||||
UNUSED(font);
|
||||
if (glyph > 0x7F) {
|
||||
glyph = 0;
|
||||
glyph = '?';
|
||||
}
|
||||
return defaultFontMetrics[glyph].width * 2;
|
||||
}
|
||||
|
||||
void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) {
|
||||
if (glyph > 0x7F) {
|
||||
glyph = 0;
|
||||
glyph = '?';
|
||||
}
|
||||
struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph];
|
||||
vita2d_draw_texture_tint_part_scale(font->tex, x, y + (-GLYPH_HEIGHT + metric.padding.top) * 2,
|
||||
(glyph & 15) * CELL_WIDTH + metric.padding.left,
|
||||
(glyph >> 4) * CELL_HEIGHT + metric.padding.top,
|
||||
CELL_WIDTH - (metric.padding.left + metric.padding.right),
|
||||
CELL_HEIGHT - (metric.padding.top + metric.padding.bottom),
|
||||
2, 2, color);
|
||||
vita2d_draw_texture_tint_part_scale(font->tex, x, y - GLYPH_HEIGHT + metric.padding.top * 2,
|
||||
(glyph & 15) * CELL_WIDTH + metric.padding.left * 2,
|
||||
(glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2,
|
||||
CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2,
|
||||
CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2,
|
||||
1, 1, color);
|
||||
}
|
||||
|
||||
void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) {
|
||||
if (icon >= GUI_ICON_MAX) {
|
||||
return;
|
||||
}
|
||||
struct GUIIconMetric metric = defaultIconMetrics[icon];
|
||||
switch (align & GUI_ALIGN_HCENTER) {
|
||||
case GUI_ALIGN_HCENTER:
|
||||
x -= metric.width;
|
||||
break;
|
||||
case GUI_ALIGN_RIGHT:
|
||||
x -= metric.width * 2;
|
||||
break;
|
||||
}
|
||||
switch (align & GUI_ALIGN_VCENTER) {
|
||||
case GUI_ALIGN_VCENTER:
|
||||
y -= metric.height;
|
||||
break;
|
||||
case GUI_ALIGN_BOTTOM:
|
||||
y -= metric.height * 2;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (orient) {
|
||||
case GUI_ORIENT_HMIRROR:
|
||||
vita2d_draw_texture_tint_part_scale(font->icons, x, y,
|
||||
metric.x * 2, metric.y * 2,
|
||||
metric.width * 2, metric.height * 2,
|
||||
-1, 1, color);
|
||||
return;
|
||||
case GUI_ORIENT_VMIRROR:
|
||||
vita2d_draw_texture_tint_part_scale(font->icons, x, y,
|
||||
metric.x * 2, metric.y * 2,
|
||||
metric.width * 2, metric.height * 2,
|
||||
1, -1, color);
|
||||
return;
|
||||
case GUI_ORIENT_0:
|
||||
default:
|
||||
// TOOD: Rotate
|
||||
vita2d_draw_texture_tint_part(font->icons, x, y,
|
||||
metric.x * 2, metric.y * 2,
|
||||
metric.width * 2, metric.height * 2,
|
||||
color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue