Merge branch 'master' into feature/sio-dolphin

This commit is contained in:
Jeffrey Pfau 2016-01-10 19:32:55 -08:00
commit 5c082b86dc
252 changed files with 30535 additions and 3723 deletions

View File

@ -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

View File

@ -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
View File

@ -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:

View File

@ -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}")

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
res/font2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
res/icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
res/icons2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

17572
res/nointro.dat Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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; \

View File

@ -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), \

View File

@ -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, &currentCycles); ARM_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, &currentCycles); ARM_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, address, &currentCycles)); ARM_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, address, &currentCycles)); 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, &currentCycles)) : ARM_SXT_16(cpu->memory.load16(cpu, address, &currentCycles)); ARM_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STR, cpu->memory.store32(cpu, address, cpu->gprs[rd], &currentCycles); ARM_STORE_POST_BODY;)
DEFINE_LOAD_STORE_INSTRUCTION_ARM(STRB, cpu->memory.store8(cpu, address, cpu->gprs[rd], &currentCycles); ARM_STORE_POST_BODY;)
DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address, cpu->gprs[rd], &currentCycles); 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, &currentCycles);
int32_t r = cpu->memory.load8(cpu, address, &currentCycles);
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, &currentCycles);
int32_t r = cpu->memory.load32(cpu, address, &currentCycles);
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], &currentCycles);
cpu->memory.store8(cpu, address, r, &currentCycles);
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], &currentCycles);
cpu->memory.store32(cpu, address, r, &currentCycles);
ARMSetPrivilegeMode(cpu, priv);
ARM_STORE_POST_BODY;)

View File

@ -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) {

View File

@ -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], &currentCycles); THUMB_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], &currentCycles); 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], &currentCycles)); 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], &currentCycles)); 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, &currentCycles)) : ARM_SXT_16(cpu->memory.load16(cpu, rm, &currentCycles)); THUMB_LOAD_POST_BODY;)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, cpu->memory.store32(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, cpu->memory.store8(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); 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; \
})

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -128,7 +128,7 @@ struct GBAAudioChannel4 {
int32_t endTime;
} control;
unsigned lfsr;
uint32_t lfsr;
int8_t sample;
};

View File

@ -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;

View File

@ -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;

View File

@ -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*);

View File

@ -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;
}

View File

@ -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);

View File

@ -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?

View File

@ -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

View File

@ -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;
}

View 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

View File

@ -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 },

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

62
src/gba/gui/remap.c Normal file
View File

@ -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
}
}
}

23
src/gba/gui/remap.h Normal file
View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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
};

View File

@ -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

View File

@ -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];

View File

@ -22,8 +22,6 @@ struct GBAAxis {
int32_t deadLow;
};
#define GBA_NO_MAPPING -1
extern const char* GBAKeyNames[];
void GBAInputMapInit(struct GBAInputMap*);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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:

View File

@ -20,6 +20,7 @@ enum GBAMGMTag {
TAG_INPUT = 0x01,
TAG_FRAME = 0x02,
TAG_LAG = 0x03,
TAG_RESET = 0x04,
// Stream chunking tags
TAG_BEGIN = 0x10,

View File

@ -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);

View File

@ -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*);

View File

@ -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));

View File

@ -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) {

View File

@ -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

View File

@ -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");
}

View File

@ -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);

View File

@ -32,6 +32,7 @@ struct GBASIO {
struct GBASIODriver* activeDriver;
uint16_t rcnt;
// TODO: Convert to bitfields
union {
struct {
unsigned sc : 1;

View File

@ -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);
}

View File

@ -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*);

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View 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

View File

@ -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

View File

@ -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)

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

BIN
src/platform/3ds/icons.raw Normal file

Binary file not shown.

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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
};

View File

@ -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>

View File

@ -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

View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#import <OpenEmuBase/OEGameCore.h>
OE_EXPORTED_CLASS
@interface mGBAGameCore : OEGameCore
@end

View File

@ -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

View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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