mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'port/wii' into port/crucible
This commit is contained in:
commit
e0f2932073
19
CHANGES
19
CHANGES
|
@ -24,6 +24,12 @@ Features:
|
|||
- Preliminary support for yanking out the game pak while a game is running
|
||||
- Thumb-drive mode by putting a file called portable.ini in the same folder
|
||||
- Configurable display driver, between software and OpenGL
|
||||
- Undo-able savestate loading and saving
|
||||
- Controller profiles now store shortcut settings
|
||||
- Default controller profiles for several common controllers
|
||||
- Libretro now supports BIOS, rumble and solar sensor
|
||||
- Implement BIOS call Stop, for sleep mode
|
||||
- Automatically load patches, if found
|
||||
Bugfixes:
|
||||
- ARM7: Fix SWI and IRQ timings
|
||||
- GBA Audio: Force audio FIFOs to 32-bit
|
||||
|
@ -57,6 +63,10 @@ Bugfixes:
|
|||
- ARM7: ARMHotplugDetach should call deinit
|
||||
- Qt: Fix window being too tall after exiting fullscreen
|
||||
- Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor
|
||||
- GBA Cheats: Fix Pro Action Replay and GameShark issues when used together
|
||||
- Qt: Fix analog buttons not getting unmapped
|
||||
- GBA Video: Prevent tiles < 512 from being used in modes 3 - 5
|
||||
- Qt: Fix passing command line options
|
||||
Misc:
|
||||
- Qt: Handle saving input settings better
|
||||
- Debugger: Free watchpoints in addition to breakpoints
|
||||
|
@ -96,6 +106,15 @@ Misc:
|
|||
- GBA Audio: Implement audio reset for channels A/B
|
||||
- GBA Hardware: Backport generic RTC source into core
|
||||
- All: Proper handling of Unicode file paths
|
||||
- GBA Video: Slightly optimize mode 0 mosaic rendering
|
||||
- VFS: Add sync method to force syncing with backing
|
||||
- GBA: Savedata is now synced shortly after data finishes being written
|
||||
- GBA Input: Allow axes and buttons to be mapped to the same key
|
||||
- GBA BIOS: Stub out SoundBias
|
||||
- Qt: Gamepads can now have both buttons and analog axes mapped to the same key
|
||||
- Qt: Increase usability of key mapper
|
||||
- Qt: Show checkmark for window sizes
|
||||
- Qt: Set window path to loaded ROM
|
||||
|
||||
0.2.1: (2015-05-13)
|
||||
Bugfixes:
|
||||
|
|
|
@ -21,6 +21,7 @@ set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
|||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
set(BUILD_GL ON CACHE STRING "Build with OpenGL")
|
||||
set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2")
|
||||
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
|
||||
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
|
||||
file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)
|
||||
|
@ -43,7 +44,11 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
if (NOT DEFINED LIBDIR)
|
||||
set(LIBDIR "lib")
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
@ -76,51 +81,16 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
|||
endfunction()
|
||||
|
||||
# Version information
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 3)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_ABI 0.3)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(GIT_REV STREQUAL "")
|
||||
set(GIT_REV -1)
|
||||
endif()
|
||||
if(NOT GIT_TAG STREQUAL "")
|
||||
set(VERSION_STRING ${GIT_TAG})
|
||||
elseif(GIT_BRANCH STREQUAL "")
|
||||
set(VERSION_STRING ${LIB_VERSION_STRING})
|
||||
else()
|
||||
if(GIT_BRANCH STREQUAL "master")
|
||||
set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||
else()
|
||||
set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT})
|
||||
endif()
|
||||
|
||||
if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH)
|
||||
set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DGIT_COMMIT=${GIT_COMMIT}
|
||||
-DGIT_COMMIT_SHORT=${GIT_COMMIT_SHORT}
|
||||
-DGIT_BRANCH=${GIT_BRANCH}
|
||||
-DGIT_REV=${GIT_REV}
|
||||
-DBINARY_NAME=${BINARY_NAME}
|
||||
-DPROJECT_NAME=${PROJECT_NAME}
|
||||
-DVERSION_STRING=${VERSION_STRING}
|
||||
-D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DCONFIG_FILE=${CMAKE_SOURCE_DIR}/src/util/version.c.in
|
||||
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.c
|
||||
-P ${CMAKE_SOURCE_DIR}/version.cmake
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/version.cmake)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/src/util/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||
list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c)
|
||||
source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c)
|
||||
|
||||
|
@ -156,7 +126,14 @@ endif()
|
|||
if(BUILD_GL)
|
||||
find_package(OpenGL QUIET)
|
||||
if(NOT OPENGL_FOUND)
|
||||
set(BUILD_GL OFF)
|
||||
set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE)
|
||||
endif()
|
||||
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)
|
||||
if(NOT OPENGLES2_INCLUDE_DIR OR NOT OPENGLES2_LIBRARY)
|
||||
set(BUILD_GLES2 OFF CACHE BOOL "OpenGL|ES 2 not found" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale")
|
||||
|
@ -219,11 +196,16 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
|
|||
endif()
|
||||
|
||||
if(WII)
|
||||
add_definitions(-U__STRICT_ANSI__)
|
||||
add_executable(${BINARY_NAME}.elf ${CMAKE_SOURCE_DIR}/src/platform/wii/main.c)
|
||||
target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB})
|
||||
add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol)
|
||||
endif()
|
||||
|
||||
if(BUILD_RASPI)
|
||||
set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE)
|
||||
endif()
|
||||
|
||||
if(BUILD_PANDORA)
|
||||
add_definitions(-DBUILD_PANDORA)
|
||||
endif()
|
||||
|
@ -245,6 +227,7 @@ endif()
|
|||
check_function_exists(newlocale HAVE_NEWLOCALE)
|
||||
check_function_exists(freelocale HAVE_FREELOCALE)
|
||||
check_function_exists(uselocale HAVE_USELOCALE)
|
||||
check_function_exists(setlocale HAVE_SETLOCALE)
|
||||
|
||||
if(HAVE_STRDUP)
|
||||
add_definitions(-DHAVE_STRDUP)
|
||||
|
@ -264,6 +247,9 @@ if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_SETLOCALE)
|
||||
add_definitions(-DHAVE_SETLOCALE)
|
||||
endif()
|
||||
|
||||
# Features
|
||||
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
||||
|
@ -410,10 +396,11 @@ endif()
|
|||
|
||||
if(BUILD_SHARED)
|
||||
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
|
||||
add_dependencies(${BINARY_NAME}-static version-info)
|
||||
endif()
|
||||
else()
|
||||
|
@ -423,7 +410,7 @@ endif()
|
|||
add_dependencies(${BINARY_NAME} version-info)
|
||||
|
||||
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
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})
|
||||
|
@ -441,6 +428,10 @@ if(BUILD_GL)
|
|||
add_definitions(-DBUILD_GL)
|
||||
endif()
|
||||
|
||||
if(BUILD_GLES2)
|
||||
add_definitions(-DBUILD_GLES2)
|
||||
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})
|
||||
|
|
13
PORTING.md
13
PORTING.md
|
@ -12,7 +12,7 @@ Port-specific TODO
|
|||
|
||||
The ports are vaguely usable, but by no means should be considered stable.
|
||||
|
||||
### 3DS
|
||||
### 3DS (port/3ds)
|
||||
* Add menu
|
||||
* Add audio
|
||||
* Thread support testing
|
||||
|
@ -20,7 +20,7 @@ The ports are vaguely usable, but by no means should be considered stable.
|
|||
* ARMv6 dynarec
|
||||
* Hardware acceleration
|
||||
|
||||
### PSP
|
||||
### PSP (port/psp)
|
||||
* Add menu
|
||||
* Add audio
|
||||
* Thread support
|
||||
|
@ -28,7 +28,14 @@ The ports are vaguely usable, but by no means should be considered stable.
|
|||
* MIPS dynarec
|
||||
* Hardware acceleration
|
||||
|
||||
### Wii
|
||||
### PS Vita (port/psp2)
|
||||
* Add menu
|
||||
* Add audio
|
||||
* Make it faster
|
||||
* Threaded renderer shim
|
||||
* Hardware acceleration
|
||||
|
||||
### Wii (port/wii)
|
||||
* Add menu
|
||||
* Add audio
|
||||
* Thread support
|
||||
|
|
|
@ -126,8 +126,6 @@ Footnotes
|
|||
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141))
|
||||
- Game Pak prefetch ([Bug #195](http://mgba.io/b/195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199))
|
||||
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
|
||||
|
||||
|
|
BIN
res/keymap.qpic
BIN
res/keymap.qpic
Binary file not shown.
268
res/keymap.svg
268
res/keymap.svg
|
@ -7,130 +7,266 @@
|
|||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M480,450c0,16.5684-13.4316,30-30,30H30c-16.5684,0-30-13.4316-30-30V30C0,13.4316,13.4316,0,30,0
|
||||
h420c16.5684,0,30,13.4316,30,30V450z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="239.9995" y1="-479" x2="239.9994" y2="1159.0004">
|
||||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<path fill="url(#SVGID_1_)" d="M480,450c0,16.568-13.432,30-30,30H30c-16.568,0-30-13.432-30-30V30C0,13.432,13.432,0,30,0h420
|
||||
c16.568,0,30,13.432,30,30V450z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="239.9995" y1="-479.0015" x2="239.9994" y2="1158.9995">
|
||||
<stop offset="0" style="stop-color:#7762F5"/>
|
||||
<stop offset="1" style="stop-color:#372E75"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M30,473c-12.682,0-23-10.317-23-23V30C7,17.318,17.318,7,30,7h420c12.683,0,23,10.318,23,23v420
|
||||
c0,12.683-10.317,23-23,23H30z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="118.3335" y1="291.667" x2="118.3335" y2="47.9995">
|
||||
<stop offset="0" style="stop-color:#7466CF"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M30,473c-12.6821,0-23-10.3174-23-23V30C7,17.3179,17.3179,7,30,7h420c12.6826,0,23,10.3179,23,23
|
||||
v420c0,12.6826-10.3174,23-23,23H30z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="118.3335" y1="291.667" x2="118.3335" y2="47.9991">
|
||||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M118.3335,122.333c-46.7603,0-84.667,37.9067-84.667,84.667c0,46.7607,37.9067,84.667,84.667,84.667
|
||||
s84.667-37.9062,84.667-84.667C203.0005,160.2397,165.0938,122.333,118.3335,122.333z M118.334,278.7344
|
||||
c-39.6172,0-71.7339-32.1172-71.7339-71.7344c0-39.6177,32.1167-71.7339,71.7339-71.7339S190.0679,167.3823,190.0679,207
|
||||
C190.0679,246.6172,157.9512,278.7344,118.334,278.7344z"/>
|
||||
<path fill="url(#SVGID_3_)" d="M118.333,122.333c-46.76,0-84.667,37.907-84.667,84.667c0,46.761,37.907,84.667,84.667,84.667
|
||||
S203,253.761,203,207C203,160.24,165.094,122.333,118.333,122.333z M118.334,278.734C78.717,278.734,46.6,246.617,46.6,207
|
||||
c0-39.618,32.117-71.734,71.734-71.734s71.734,32.116,71.734,71.734C190.068,246.617,157.951,278.734,118.334,278.734z"/>
|
||||
<g>
|
||||
|
||||
<radialGradient id="SVGID_4_" cx="118.8335" cy="138.167" r="103.2725" fx="165.1467" fy="138.167" gradientTransform="matrix(-4.371139e-08 1 -1.42 -6.206884e-08 315.0264 19.3335)" gradientUnits="userSpaceOnUse">
|
||||
<radialGradient id="SVGID_4_" cx="118.8335" cy="138.167" r="103.2721" fx="165.1465" fy="138.167" gradientTransform="matrix(-4.371139e-08 1 -1.42 -6.206886e-08 315.0265 19.3335)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#9C9CB3"/>
|
||||
<stop offset="1" style="stop-color:#333045"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M172.1973,207c0-9.667-3.0029-17.333-3.0029-17.333H135.667v-33.5283
|
||||
c0,0-7.667-3.0029-17.3335-3.0029s-17.333,3.0029-17.333,3.0029v33.5283H67.4727c0,0-3.0029,7.666-3.0029,17.333
|
||||
c0,9.666,3.0029,17.333,3.0029,17.333h33.5278v33.5273c0,0,7.6665,3.0039,17.333,3.0039s17.3335-3.0039,17.3335-3.0039V224.333
|
||||
h33.5273C169.1943,224.333,172.1973,216.666,172.1973,207z"/>
|
||||
<path fill="#5C567D" d="M118.3335,258.8643c-6.9185,0-12.833-1.625-15.333-2.4287V222.333h-34.103
|
||||
c-0.8027-2.5-2.4277-8.4146-2.4277-15.333s1.6245-12.833,2.4277-15.333h34.103v-34.1035c2.5-0.8027,8.4146-2.4277,15.333-2.4277
|
||||
c6.9453,0,12.8408,1.6226,15.3335,2.4258v34.1055h34.1025c0.8032,2.5,2.4277,8.4146,2.4277,15.333
|
||||
c0,6.9448-1.6226,12.8403-2.4258,15.333H133.667v34.1025C131.167,257.2393,125.252,258.8643,118.3335,258.8643z"/>
|
||||
<radialGradient id="SVGID_5_" cx="118.333" cy="220.397" r="69.7522" gradientUnits="userSpaceOnUse">
|
||||
<path fill="url(#SVGID_4_)" d="M172.197,207c0-9.667-3.003-17.333-3.003-17.333h-33.527v-33.528c0,0-7.667-3.003-17.333-3.003
|
||||
S101,156.139,101,156.139v33.528H67.473c0,0-3.003,7.666-3.003,17.333c0,9.666,3.003,17.333,3.003,17.333H101v33.527
|
||||
c0,0,7.667,3.004,17.333,3.004s17.333-3.004,17.333-3.004v-33.527h33.527C169.194,224.333,172.197,216.666,172.197,207z"/>
|
||||
<path fill="#5C567D" d="M118.333,258.864c-6.918,0-12.833-1.625-15.333-2.429v-34.103H68.897c-0.803-2.5-2.428-8.415-2.428-15.333
|
||||
s1.625-12.833,2.428-15.333H103v-34.104c2.5-0.803,8.415-2.428,15.333-2.428c6.945,0,12.841,1.623,15.333,2.426v34.105h34.103
|
||||
c0.803,2.5,2.428,8.415,2.428,15.333c0,6.945-1.623,12.84-2.426,15.333h-34.104v34.103
|
||||
C131.167,257.239,125.252,258.864,118.333,258.864z"/>
|
||||
<radialGradient id="SVGID_5_" cx="118.333" cy="220.397" r="69.7523" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#706F8A"/>
|
||||
<stop offset="0.9951" style="stop-color:#2D2842"/>
|
||||
</radialGradient>
|
||||
<circle fill="url(#SVGID_5_)" cx="118.333" cy="207" r="21.2749"/>
|
||||
<circle fill="url(#SVGID_5_)" cx="118.333" cy="207" r="21.275"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="490" x2="198.9673" y2="384.981">
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="490" x2="198.9673" y2="384.9809">
|
||||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_6_)" d="M198.9678,387.0303c-18.8418,0-34.1162,15.2734-34.1162,34.1162
|
||||
c0,18.8418,15.2744,34.1152,34.1162,34.1152c18.8408,0,34.1152-15.2734,34.1152-34.1152
|
||||
C233.083,402.3037,217.8086,387.0303,198.9678,387.0303z M198.9678,445.7871c-13.6089,0-24.6401-11.0332-24.6401-24.6406
|
||||
c0-13.6094,11.0312-24.6406,24.6401-24.6406c13.6074,0,24.6396,11.0312,24.6396,24.6406
|
||||
C223.6074,434.7539,212.5752,445.7871,198.9678,445.7871z"/>
|
||||
<path fill="url(#SVGID_6_)" d="M198.968,387.03c-18.842,0-34.116,15.273-34.116,34.116c0,18.842,15.274,34.115,34.116,34.115
|
||||
c18.841,0,34.115-15.273,34.115-34.115C233.083,402.304,217.809,387.03,198.968,387.03z M198.968,445.787
|
||||
c-13.609,0-24.64-11.033-24.64-24.641c0-13.609,11.031-24.641,24.64-24.641c13.607,0,24.64,11.031,24.64,24.641
|
||||
C223.607,434.754,212.575,445.787,198.968,445.787z"/>
|
||||
<g>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="437.4414" x2="198.9673" y2="404.8516">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<circle fill="url(#SVGID_7_)" cx="198.9673" cy="421.1465" r="16.2949"/>
|
||||
<circle fill="#5C567D" cx="198.9673" cy="421.1465" r="15.0737"/>
|
||||
<circle fill="url(#SVGID_7_)" cx="198.967" cy="421.146" r="16.295"/>
|
||||
<circle fill="#5C567D" cx="198.967" cy="421.146" r="15.074"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="281.0312" y1="490" x2="281.0312" y2="384.981">
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="281.0312" y1="490" x2="281.0312" y2="384.9809">
|
||||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_8_)" d="M281.0322,387.0303c-18.8418,0-34.1162,15.2734-34.1162,34.1162
|
||||
c0,18.8418,15.2744,34.1152,34.1162,34.1152c18.8408,0,34.1152-15.2734,34.1152-34.1152
|
||||
C315.1475,402.3037,299.873,387.0303,281.0322,387.0303z M281.0322,445.7871c-13.6084,0-24.6396-11.0332-24.6396-24.6406
|
||||
c0-13.6094,11.0312-24.6406,24.6396-24.6406c13.6074,0,24.6396,11.0312,24.6396,24.6406
|
||||
C305.6719,434.7539,294.6396,445.7871,281.0322,445.7871z"/>
|
||||
<path fill="url(#SVGID_8_)" d="M281.032,387.03c-18.842,0-34.116,15.273-34.116,34.116c0,18.842,15.274,34.115,34.116,34.115
|
||||
c18.841,0,34.115-15.273,34.115-34.115C315.147,402.304,299.873,387.03,281.032,387.03z M281.032,445.787
|
||||
c-13.608,0-24.64-11.033-24.64-24.641c0-13.609,11.031-24.641,24.64-24.641c13.607,0,24.64,11.031,24.64,24.641
|
||||
C305.672,434.754,294.64,445.787,281.032,445.787z"/>
|
||||
<g>
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="281.0322" y1="437.4414" x2="281.0322" y2="404.8516">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<circle fill="url(#SVGID_9_)" cx="281.0322" cy="421.1465" r="16.2949"/>
|
||||
<circle fill="#5C567D" cx="281.0317" cy="421.1465" r="15.0737"/>
|
||||
<circle fill="url(#SVGID_9_)" cx="281.032" cy="421.146" r="16.295"/>
|
||||
<circle fill="#5C567D" cx="281.032" cy="421.146" r="15.074"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="356.1787" y1="280.2178" x2="356.1787" y2="134.9952">
|
||||
<stop offset="0" style="stop-color:#7A65F5"/>
|
||||
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="356.1787" y1="280.2178" x2="356.1787" y2="134.9951">
|
||||
<stop offset="0" style="stop-color:#7466CF"/>
|
||||
<stop offset="1" style="stop-color:#302575"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_10_)" d="M437.9521,179.5503c-7.0088-23.9434-32.0986-37.6724-56.043-30.6646l-76.8369,22.4912
|
||||
c-23.9453,7.0073-37.6748,32.0991-30.666,56.043c7.0088,23.9443,32.0996,37.6719,56.0449,30.6621l76.8369-22.4902
|
||||
C431.2314,228.585,444.96,203.4946,437.9521,179.5503z M426.3311,209.6025c-4.6377,8.4756-12.2979,14.6382-21.5703,17.3516
|
||||
l-76.8379,22.4902c-3.3301,0.9746-6.7559,1.4688-10.1816,1.4688c-0.001,0-0.001,0-0.002,0
|
||||
c-15.9443-0.001-30.2109-10.7012-34.6953-26.0215c-2.7148-9.2729-1.6553-19.0479,2.9824-27.5244
|
||||
c4.6387-8.4761,12.2998-14.6387,21.5732-17.3525l76.8379-22.4912c3.3281-0.9741,6.7539-1.4683,10.1807-1.4683
|
||||
c15.9434,0,30.2109,10.7012,34.6963,26.0234C432.0283,191.3516,430.9688,201.1265,426.3311,209.6025z"/>
|
||||
<path fill="url(#SVGID_10_)" d="M437.952,179.55c-7.009-23.943-32.099-37.672-56.043-30.665l-76.837,22.491
|
||||
c-23.945,7.007-37.675,32.099-30.666,56.043c7.009,23.944,32.1,37.672,56.045,30.662l76.837-22.49
|
||||
C431.231,228.585,444.96,203.495,437.952,179.55z M426.331,209.603c-4.638,8.476-12.298,14.638-21.57,17.352l-76.838,22.49
|
||||
c-3.33,0.975-6.756,1.469-10.182,1.469c-0.001,0-0.001,0-0.001,0c-15.945-0.001-30.212-10.701-34.696-26.021
|
||||
c-2.715-9.273-1.655-19.048,2.982-27.524c4.639-8.476,12.3-14.639,21.573-17.353l76.838-22.491
|
||||
c3.328-0.974,6.754-1.468,10.181-1.468c15.943,0,30.211,10.701,34.696,26.023C432.028,191.352,430.969,201.126,426.331,209.603z"/>
|
||||
<g>
|
||||
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="395.9062" y1="217.8188" x2="395.9062" y2="166.6519">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<circle fill="url(#SVGID_11_)" cx="395.9062" cy="192.2358" r="25.584"/>
|
||||
<circle fill="#5C567D" cx="395.9072" cy="192.2358" r="23.666"/>
|
||||
<circle fill="url(#SVGID_11_)" cx="395.906" cy="192.236" r="25.584"/>
|
||||
<circle fill="#5C567D" cx="395.907" cy="192.236" r="23.666"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="318.9785" y1="239.6406" x2="318.9785" y2="188.4722">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<circle fill="url(#SVGID_12_)" cx="318.979" cy="214.0571" r="25.5835"/>
|
||||
<circle fill="#5C567D" cx="318.98" cy="214.0571" r="23.6665"/>
|
||||
<circle fill="url(#SVGID_12_)" cx="318.979" cy="214.057" r="25.583"/>
|
||||
<circle fill="#5C567D" cx="318.98" cy="214.057" r="23.667"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-15.0425" y1="77.9346" x2="55.6245" y2="-5.3989">
|
||||
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-15.0435" y1="77.9331" x2="55.6236" y2="-5.4006">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_13_)" d="M33.6665,0H61.5c2.9697,1.0112,2.3306,7,2.3306,7l0.0581,8.4844
|
||||
C63.8887,31.3535,48.9922,50,27.9922,50H7.0068c0,0-5.2197,2.312-7.0068-8.0029V30C0,11.5,12.333,0,33.6665,0z"/>
|
||||
<path fill="#5C567D" d="M9.7783,49.1743c-0.2793-0.251-1.166-1.2764-1.7744-4.5308V33.0029c0-16.5234,10.813-26,29.6665-26h26.0854
|
||||
c0.1318,0.709,0.1821,1.7549,0.0996,2.5908L63.835,9.8032l0.0576,8.7114c0,14.3774-13.6406,30.4883-31.8965,30.4883H10.1646
|
||||
L9.7783,49.1743z"/>
|
||||
<path fill="url(#SVGID_13_)" d="M33.667,0H61.5c2.97,1.011,2.331,7,2.331,7l0.058,8.484C63.889,31.354,48.992,50,27.992,50H7.007
|
||||
c0,0-5.22,2.312-7.007-8.003V30C0,11.5,12.333,0,33.667,0z"/>
|
||||
<path fill="#5C567D" d="M9.778,49.174c-0.279-0.251-1.166-1.276-1.774-4.531V33.003c0-16.523,10.813-26,29.667-26h26.085
|
||||
c0.132,0.709,0.182,1.755,0.1,2.591l-0.021,0.209l0.058,8.711c0,14.377-13.641,30.488-31.896,30.488H10.165L9.778,49.174z"/>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="401.0615" y1="77.9336" x2="471.7285" y2="-5.3999" gradientTransform="matrix(-1 0 0 1 896.1055 0)">
|
||||
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="401.0605" y1="77.9331" x2="471.7277" y2="-5.4007" gradientTransform="matrix(-1 0 0 1 896.1055 0)">
|
||||
<stop offset="0" style="stop-color:#333045"/>
|
||||
<stop offset="1" style="stop-color:#9C9CB3"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_14_)" d="M446.334,0h-27.833c-2.9697,1.0112-2.3311,7-2.3311,7l-0.0576,8.4844
|
||||
C416.1123,31.3535,431.0088,50,452.0088,50h20.9854c0,0,5.2197,2.312,7.0068-8.0029V30C480.001,11.5,467.668,0,446.334,0z"/>
|
||||
<path fill="#5C567D" d="M470.2227,49.1743c0.2793-0.251,1.166-1.2764,1.7744-4.5308V33.0029c0-16.5234-10.8135-26-29.667-26
|
||||
h-26.085c-0.1318,0.709-0.1826,1.7549-0.0996,2.5908l0.0205,0.2095l-0.0576,8.7114c0,14.3774,13.6406,30.4883,31.8965,30.4883
|
||||
h21.8311L470.2227,49.1743z"/>
|
||||
<path fill="url(#SVGID_14_)" d="M446.334,0h-27.833c-2.97,1.011-2.331,7-2.331,7l-0.058,8.484
|
||||
c0,15.869,14.896,34.516,35.896,34.516h20.985c0,0,5.22,2.312,7.007-8.003V30C480.001,11.5,467.668,0,446.334,0z"/>
|
||||
<path fill="#5C567D" d="M470.223,49.174c0.279-0.251,1.166-1.276,1.774-4.531V33.003c0-16.523-10.813-26-29.667-26h-26.085
|
||||
c-0.132,0.709-0.183,1.755-0.1,2.591l0.021,0.209l-0.058,8.711c0,14.377,13.641,30.488,31.896,30.488h21.831L470.223,49.174z"/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<path fill="#9C9CB3" stroke="#9C9CB3" stroke-miterlimit="10" d="M312.197,226v-21.475h8.057c1.641,0,2.956,0.217,3.947,0.652
|
||||
s1.768,1.104,2.33,2.007c0.561,0.903,0.842,1.848,0.842,2.834c0,0.918-0.249,1.782-0.747,2.593s-1.25,1.465-2.256,1.963
|
||||
c1.299,0.381,2.297,1.03,2.995,1.948s1.048,2.002,1.048,3.252c0,1.006-0.212,1.941-0.638,2.805c-0.424,0.864-0.949,1.531-1.574,2
|
||||
s-1.409,0.823-2.352,1.062S321.753,226,320.386,226H312.197z M315.039,213.549h4.644c1.26,0,2.163-0.083,2.71-0.249
|
||||
c0.723-0.215,1.268-0.571,1.633-1.069c0.367-0.498,0.55-1.123,0.55-1.875c0-0.713-0.171-1.34-0.513-1.882s-0.83-0.913-1.465-1.113
|
||||
s-1.724-0.3-3.267-0.3h-4.292V213.549z M315.039,223.466h5.347c0.918,0,1.562-0.034,1.934-0.103
|
||||
c0.654-0.117,1.201-0.312,1.641-0.586s0.801-0.671,1.084-1.194s0.425-1.125,0.425-1.809c0-0.801-0.205-1.497-0.615-2.087
|
||||
s-0.979-1.006-1.707-1.245s-1.774-0.359-3.142-0.359h-4.966V223.466z"/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<path fill="#9C9CB3" stroke="#9C9CB3" stroke-miterlimit="10" d="M385.956,203l8.247-21.475h3.062L406.054,203h-3.237l-2.505-6.504
|
||||
h-8.979L388.974,203H385.956z M392.152,194.182h7.28l-2.241-5.947c-0.684-1.807-1.191-3.291-1.523-4.453
|
||||
c-0.273,1.377-0.659,2.744-1.157,4.102L392.152,194.182z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#333045" d="M312.197,224v-21.475h8.057c1.641,0,2.956,0.217,3.947,0.652s1.768,1.104,2.33,2.007
|
||||
c0.561,0.903,0.842,1.848,0.842,2.834c0,0.918-0.249,1.782-0.747,2.593s-1.25,1.465-2.256,1.963
|
||||
c1.299,0.381,2.297,1.03,2.995,1.948s1.048,2.002,1.048,3.252c0,1.006-0.212,1.941-0.638,2.805c-0.424,0.864-0.949,1.531-1.574,2
|
||||
s-1.409,0.823-2.352,1.062S321.753,224,320.386,224H312.197z M315.039,211.549h4.644c1.26,0,2.163-0.083,2.71-0.249
|
||||
c0.723-0.215,1.268-0.571,1.633-1.069c0.367-0.498,0.55-1.123,0.55-1.875c0-0.713-0.171-1.34-0.513-1.882s-0.83-0.913-1.465-1.113
|
||||
s-1.724-0.3-3.267-0.3h-4.292V211.549z M315.039,221.466h5.347c0.918,0,1.562-0.034,1.934-0.103
|
||||
c0.654-0.117,1.201-0.312,1.641-0.586s0.801-0.671,1.084-1.194s0.425-1.125,0.425-1.809c0-0.801-0.205-1.497-0.615-2.087
|
||||
s-0.979-1.006-1.707-1.245s-1.774-0.359-3.142-0.359h-4.966V221.466z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#333045" d="M385.956,201l8.247-21.475h3.062L406.054,201h-3.237l-2.505-6.504h-8.979L388.974,201H385.956z
|
||||
M392.152,192.182h7.28l-2.241-5.947c-0.684-1.807-1.191-3.291-1.523-4.453c-0.273,1.377-0.659,2.744-1.157,4.102L392.152,192.182z
|
||||
"/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<g>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M320.35,425.264l2.658-0.258
|
||||
c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627c0.88,0,1.543-0.186,1.988-0.559
|
||||
c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586
|
||||
c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723
|
||||
c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117
|
||||
c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457
|
||||
c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822
|
||||
c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375
|
||||
c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508
|
||||
c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C321.076,427.93,320.528,426.777,320.35,425.264z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M336.694,429.666v-11.24h-4.015v-2.289h10.751
|
||||
v2.289h-4.005v11.24H336.694z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M355.99,429.666h-2.972l-1.182-3.072h-5.407
|
||||
l-1.117,3.072h-2.897l5.27-13.529h2.889L355.99,429.666z M350.961,424.314l-1.864-5.021l-1.827,5.021H350.961z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M357.449,429.666v-13.529h5.749
|
||||
c1.445,0,2.496,0.123,3.151,0.365c0.655,0.244,1.18,0.676,1.573,1.297s0.591,1.332,0.591,2.131c0,1.016-0.299,1.854-0.896,2.516
|
||||
s-1.488,1.078-2.676,1.25c0.591,0.346,1.078,0.723,1.463,1.135c0.384,0.412,0.902,1.145,1.555,2.197l1.652,2.639h-3.268
|
||||
l-1.975-2.943c-0.701-1.053-1.182-1.715-1.439-1.988c-0.259-0.273-0.532-0.463-0.821-0.562c-0.289-0.102-0.748-0.152-1.375-0.152
|
||||
h-0.554v5.646H357.449z M360.181,421.859h2.021c1.311,0,2.129-0.055,2.455-0.166s0.581-0.301,0.766-0.572s0.277-0.609,0.277-1.016
|
||||
c0-0.455-0.122-0.822-0.364-1.102c-0.243-0.281-0.587-0.457-1.029-0.531c-0.222-0.031-0.886-0.047-1.993-0.047h-2.132V421.859z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M374.134,429.666v-11.24h-4.015v-2.289h10.751
|
||||
v2.289h-4.005v11.24H374.134z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<g>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M87.35,425.264l2.658-0.258
|
||||
c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627c0.88,0,1.543-0.186,1.988-0.559
|
||||
c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586
|
||||
c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723
|
||||
c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117
|
||||
c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457
|
||||
c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822
|
||||
c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375
|
||||
c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508
|
||||
c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C88.076,427.93,87.528,426.777,87.35,425.264z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M100.648,429.666v-13.529h10.031v2.289h-7.3v3
|
||||
h6.792v2.279h-6.792v3.682h7.559v2.279H100.648z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M113.328,429.666v-13.418h2.731v11.139h6.792
|
||||
v2.279H113.328z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M124.799,429.666v-13.529h10.031v2.289h-7.3v3
|
||||
h6.792v2.279h-6.792v3.682h7.559v2.279H124.799z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M146.062,424.691l2.648,0.84
|
||||
c-0.406,1.477-1.081,2.574-2.025,3.291s-2.143,1.074-3.595,1.074c-1.796,0-3.272-0.613-4.43-1.84
|
||||
c-1.156-1.229-1.734-2.906-1.734-5.035c0-2.252,0.581-4,1.744-5.246c1.162-1.246,2.691-1.869,4.586-1.869
|
||||
c1.655,0,3,0.49,4.033,1.469c0.615,0.578,1.076,1.408,1.384,2.49l-2.703,0.646c-0.16-0.701-0.494-1.256-1.002-1.66
|
||||
c-0.507-0.406-1.124-0.609-1.85-0.609c-1.003,0-1.817,0.359-2.441,1.08c-0.624,0.719-0.937,1.885-0.937,3.496
|
||||
c0,1.711,0.308,2.93,0.923,3.654c0.615,0.727,1.415,1.09,2.399,1.09c0.726,0,1.351-0.23,1.873-0.691
|
||||
C145.459,426.408,145.834,425.684,146.062,424.691z"/>
|
||||
<path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M154.101,429.666v-11.24h-4.015v-2.289h10.751
|
||||
v2.289h-4.005v11.24H154.101z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#7A65F5" d="M320.35,423.264l2.658-0.258c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627
|
||||
c0.88,0,1.543-0.186,1.988-0.559c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586
|
||||
c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723
|
||||
c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117
|
||||
c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457
|
||||
c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822
|
||||
c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375
|
||||
c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508
|
||||
c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C321.076,425.93,320.528,424.777,320.35,423.264z"/>
|
||||
<path fill="#7A65F5" d="M336.694,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H336.694z"/>
|
||||
<path fill="#7A65F5" d="M355.99,427.666h-2.972l-1.182-3.072h-5.407l-1.117,3.072h-2.897l5.27-13.529h2.889L355.99,427.666z
|
||||
M350.961,422.314l-1.864-5.021l-1.827,5.021H350.961z"/>
|
||||
<path fill="#7A65F5" d="M357.449,427.666v-13.529h5.749c1.445,0,2.496,0.123,3.151,0.365c0.655,0.244,1.18,0.676,1.573,1.297
|
||||
s0.591,1.332,0.591,2.131c0,1.016-0.299,1.854-0.896,2.516s-1.488,1.078-2.676,1.25c0.591,0.346,1.078,0.723,1.463,1.135
|
||||
c0.384,0.412,0.902,1.145,1.555,2.197l1.652,2.639h-3.268l-1.975-2.943c-0.701-1.053-1.182-1.715-1.439-1.988
|
||||
c-0.259-0.273-0.532-0.463-0.821-0.562c-0.289-0.102-0.748-0.152-1.375-0.152h-0.554v5.646H357.449z M360.181,419.859h2.021
|
||||
c1.311,0,2.129-0.055,2.455-0.166s0.581-0.301,0.766-0.572s0.277-0.609,0.277-1.016c0-0.455-0.122-0.822-0.364-1.102
|
||||
c-0.243-0.281-0.587-0.457-1.029-0.531c-0.222-0.031-0.886-0.047-1.993-0.047h-2.132V419.859z"/>
|
||||
<path fill="#7A65F5" d="M374.134,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H374.134z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#7A65F5" d="M87.35,423.264l2.658-0.258c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627
|
||||
c0.88,0,1.543-0.186,1.988-0.559c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586
|
||||
c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723
|
||||
c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117
|
||||
c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457
|
||||
c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822
|
||||
c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375
|
||||
c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508
|
||||
c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C88.076,425.93,87.528,424.777,87.35,423.264z"/>
|
||||
<path fill="#7A65F5" d="M100.648,427.666v-13.529h10.031v2.289h-7.3v3h6.792v2.279h-6.792v3.682h7.559v2.279H100.648z"/>
|
||||
<path fill="#7A65F5" d="M113.328,427.666v-13.418h2.731v11.139h6.792v2.279H113.328z"/>
|
||||
<path fill="#7A65F5" d="M124.799,427.666v-13.529h10.031v2.289h-7.3v3h6.792v2.279h-6.792v3.682h7.559v2.279H124.799z"/>
|
||||
<path fill="#7A65F5" d="M146.062,422.691l2.648,0.84c-0.406,1.477-1.081,2.574-2.025,3.291s-2.143,1.074-3.595,1.074
|
||||
c-1.796,0-3.272-0.613-4.43-1.84c-1.156-1.229-1.734-2.906-1.734-5.035c0-2.252,0.581-4,1.744-5.246
|
||||
c1.162-1.246,2.691-1.869,4.586-1.869c1.655,0,3,0.49,4.033,1.469c0.615,0.578,1.076,1.408,1.384,2.49l-2.703,0.646
|
||||
c-0.16-0.701-0.494-1.256-1.002-1.66c-0.507-0.406-1.124-0.609-1.85-0.609c-1.003,0-1.817,0.359-2.441,1.08
|
||||
c-0.624,0.719-0.937,1.885-0.937,3.496c0,1.711,0.308,2.93,0.923,3.654c0.615,0.727,1.415,1.09,2.399,1.09
|
||||
c0.726,0,1.351-0.23,1.873-0.691C145.459,424.408,145.834,423.684,146.062,422.691z"/>
|
||||
<path fill="#7A65F5" d="M154.101,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H154.101z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 525 KiB After Width: | Height: | Size: 518 KiB |
|
@ -6,6 +6,7 @@ Terminal=false
|
|||
Type=Application
|
||||
Name=mGBA
|
||||
GenericName=Game Boy Advance Emulator
|
||||
Comment=Nintendo Game Boy Advance Emulator
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;
|
||||
|
||||
Keywords=emulator;Nintendo;advance;gba;Game Boy Advance;
|
||||
|
|
|
@ -153,7 +153,7 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
|
|||
|
||||
#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \
|
||||
DEFINE_THUMB_DECODER(NAME, MNEMONIC, \
|
||||
info->op1.reg = (opcode >> 6) & 0x0007; \
|
||||
info->op1.reg = (opcode >> 8) & 0x0007; \
|
||||
info->memory.baseReg = REG; \
|
||||
info->memory.offset.immediate = (opcode & 0x00FF) << 2; \
|
||||
info->memory.width = ARM_ACCESS_WORD; \
|
||||
|
|
|
@ -56,6 +56,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
|||
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
|
||||
{ "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
|
||||
{ "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
|
||||
{ "c", _continue, 0, "Continue execution" },
|
||||
{ "continue", _continue, 0, "Continue execution" },
|
||||
{ "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" },
|
||||
|
|
|
@ -191,6 +191,9 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
case 0x2:
|
||||
GBAHalt(gba);
|
||||
break;
|
||||
case 0x3:
|
||||
GBAStop(gba);
|
||||
break;
|
||||
case 0x05:
|
||||
// VBlankIntrWait
|
||||
// Fall through:
|
||||
|
@ -297,6 +300,10 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case 0x19:
|
||||
// SoundBias is mostly meaningless here
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: SoundBias (19)");
|
||||
break;
|
||||
case 0x1F:
|
||||
_MidiKey2Freq(gba);
|
||||
break;
|
||||
|
|
|
@ -197,6 +197,7 @@ bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
|
|||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
case 3:
|
||||
GBACheatSetGameSharkVersion(set, 1);
|
||||
// Fall through
|
||||
case 1:
|
||||
|
|
|
@ -297,9 +297,10 @@ bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t
|
|||
|
||||
switch (set->gsaVersion) {
|
||||
case 0:
|
||||
case 1:
|
||||
GBACheatSetGameSharkVersion(set, 3);
|
||||
// Fall through
|
||||
case 1:
|
||||
case 3:
|
||||
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
|
||||
return GBACheatAddProActionReplayRaw(set, o1, o2);
|
||||
}
|
||||
|
|
|
@ -79,8 +79,10 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->biosVf = 0;
|
||||
|
||||
gba->logHandler = 0;
|
||||
gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||
gba->logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||
gba->stream = 0;
|
||||
gba->keyCallback = 0;
|
||||
gba->stopCallback = 0;
|
||||
|
||||
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||
|
||||
|
@ -92,6 +94,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->idleDetectionFailures = 0;
|
||||
|
||||
gba->realisticTiming = true;
|
||||
gba->hardCrash = true;
|
||||
|
||||
gba->performingDMA = false;
|
||||
}
|
||||
|
@ -552,6 +555,14 @@ void GBAHalt(struct GBA* gba) {
|
|||
gba->cpu->halted = 1;
|
||||
}
|
||||
|
||||
void GBAStop(struct GBA* gba) {
|
||||
if (!gba->stopCallback) {
|
||||
return;
|
||||
}
|
||||
gba->cpu->nextEvent = 0;
|
||||
gba->stopCallback->stop(gba->stopCallback);
|
||||
}
|
||||
|
||||
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||
enum GBALogLevel logLevel = GBA_LOG_ALL;
|
||||
|
@ -756,6 +767,8 @@ void GBAFrameStarted(struct GBA* gba) {
|
|||
}
|
||||
|
||||
void GBAFrameEnded(struct GBA* gba) {
|
||||
GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter);
|
||||
|
||||
if (gba->rr) {
|
||||
gba->rr->nextFrame(gba->rr);
|
||||
}
|
||||
|
@ -775,6 +788,10 @@ void GBAFrameEnded(struct GBA* gba) {
|
|||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||
}
|
||||
|
||||
if (gba->memory.hw.devices & (HW_GB_PLAYER | HW_GB_PLAYER_DETECTION)) {
|
||||
GBAHardwarePlayerUpdate(gba);
|
||||
}
|
||||
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "arm.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
#include "gba/interface.h"
|
||||
#include "gba/memory.h"
|
||||
#include "gba/video.h"
|
||||
#include "gba/audio.h"
|
||||
|
@ -35,43 +36,6 @@ enum GBAIRQ {
|
|||
IRQ_GAMEPAK = 0xD
|
||||
};
|
||||
|
||||
enum GBALogLevel {
|
||||
GBA_LOG_FATAL = 0x01,
|
||||
GBA_LOG_ERROR = 0x02,
|
||||
GBA_LOG_WARN = 0x04,
|
||||
GBA_LOG_INFO = 0x08,
|
||||
GBA_LOG_DEBUG = 0x10,
|
||||
GBA_LOG_STUB = 0x20,
|
||||
|
||||
GBA_LOG_GAME_ERROR = 0x100,
|
||||
GBA_LOG_SWI = 0x200,
|
||||
GBA_LOG_STATUS = 0x400,
|
||||
GBA_LOG_SIO = 0x800,
|
||||
|
||||
GBA_LOG_ALL = 0xF3F,
|
||||
|
||||
#ifdef NDEBUG
|
||||
GBA_LOG_DANGER = GBA_LOG_ERROR
|
||||
#else
|
||||
GBA_LOG_DANGER = GBA_LOG_FATAL
|
||||
#endif
|
||||
};
|
||||
|
||||
enum GBAKey {
|
||||
GBA_KEY_A = 0,
|
||||
GBA_KEY_B = 1,
|
||||
GBA_KEY_SELECT = 2,
|
||||
GBA_KEY_START = 3,
|
||||
GBA_KEY_RIGHT = 4,
|
||||
GBA_KEY_LEFT = 5,
|
||||
GBA_KEY_UP = 6,
|
||||
GBA_KEY_DOWN = 7,
|
||||
GBA_KEY_R = 8,
|
||||
GBA_KEY_L = 9,
|
||||
GBA_KEY_MAX,
|
||||
GBA_KEY_NONE = -1
|
||||
};
|
||||
|
||||
enum GBAComponent {
|
||||
GBA_COMPONENT_DEBUGGER,
|
||||
GBA_COMPONENT_CHEAT_DEVICE,
|
||||
|
@ -91,19 +55,10 @@ enum {
|
|||
};
|
||||
|
||||
struct GBA;
|
||||
struct GBARotationSource;
|
||||
struct GBAThread;
|
||||
struct Patch;
|
||||
struct VFile;
|
||||
|
||||
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||
};
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
|
@ -156,6 +111,8 @@ struct GBA {
|
|||
GBALogHandler logHandler;
|
||||
enum GBALogLevel logLevel;
|
||||
struct GBAAVStream* stream;
|
||||
struct GBAKeyCallback* keyCallback;
|
||||
struct GBAStopCallback* stopCallback;
|
||||
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
uint32_t idleLoop;
|
||||
|
@ -167,6 +124,7 @@ struct GBA {
|
|||
bool taintedRegisters[16];
|
||||
|
||||
bool realisticTiming;
|
||||
bool hardCrash;
|
||||
};
|
||||
|
||||
struct GBACartridge {
|
||||
|
@ -199,6 +157,7 @@ void GBAWriteIME(struct GBA* gba, uint16_t value);
|
|||
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq);
|
||||
void GBATestIRQ(struct ARMCore* cpu);
|
||||
void GBAHalt(struct GBA* gba);
|
||||
void GBAStop(struct GBA* gba);
|
||||
|
||||
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger);
|
||||
void GBADetachDebugger(struct GBA* gba);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "hardware.h"
|
||||
|
||||
#include "gba/io.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
|
@ -12,6 +13,8 @@
|
|||
#include <psp2/rtc.h>
|
||||
#endif
|
||||
|
||||
const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };
|
||||
|
||||
static void _readPins(struct GBACartridgeHardware* hw);
|
||||
static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
|
||||
|
||||
|
@ -29,6 +32,10 @@ static void _rumbleReadPins(struct GBACartridgeHardware* hw);
|
|||
|
||||
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
||||
|
||||
static uint16_t _gbpRead(struct GBAKeyCallback*);
|
||||
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
|
||||
static const int RTC_BYTES[8] = {
|
||||
0, // Force reset
|
||||
0, // Empty
|
||||
|
@ -43,6 +50,16 @@ static const int RTC_BYTES[8] = {
|
|||
void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
||||
hw->gpioBase = base;
|
||||
GBAHardwareClear(hw);
|
||||
|
||||
hw->gbpCallback.d.readKeys = _gbpRead;
|
||||
hw->gbpCallback.p = hw;
|
||||
hw->gbpDriver.d.init = 0;
|
||||
hw->gbpDriver.d.deinit = 0;
|
||||
hw->gbpDriver.d.load = 0;
|
||||
hw->gbpDriver.d.unload = 0;
|
||||
hw->gbpDriver.d.writeRegister = _gbpSioWriteRegister;
|
||||
hw->gbpDriver.d.processEvents = _gbpSioProcessEvents;
|
||||
hw->gbpDriver.p = hw;
|
||||
}
|
||||
|
||||
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||
|
@ -469,6 +486,30 @@ static const uint16_t _logoPalette[] = {
|
|||
|
||||
static const uint32_t _logoHash = 0xEEDA6963;
|
||||
|
||||
static const uint32_t _gbpTxData[] = {
|
||||
0x0000494E, 0x0000494E,
|
||||
0xB6B1494E, 0xB6B1544E,
|
||||
0xABB1544E, 0xABB14E45,
|
||||
0xB1BA4E45, 0xB1BA4F44,
|
||||
0xB0BB4F44, 0xB0BB8002,
|
||||
0x10000010, 0x20000013,
|
||||
0x30000003, 0x30000003,
|
||||
0x30000003, 0x30000003,
|
||||
0x30000003, 0x00000000,
|
||||
};
|
||||
|
||||
static const uint32_t _gbpRxData[] = {
|
||||
0x00000000, 0x494EB6B1,
|
||||
0x494EB6B1, 0x544EB6B1,
|
||||
0x544EABB1, 0x4E45ABB1,
|
||||
0x4E45B1BA, 0x4F44B1BA,
|
||||
0x4F44B0BB, 0x8000B0BB,
|
||||
0x10000010, 0x20000013,
|
||||
0x40000004, 0x40000004,
|
||||
0x40000004, 0x40000004,
|
||||
0x40000004, 0x40000004
|
||||
};
|
||||
|
||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
||||
if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) {
|
||||
return false;
|
||||
|
@ -477,6 +518,83 @@ bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
|||
return hash == _logoHash;
|
||||
}
|
||||
|
||||
void GBAHardwarePlayerUpdate(struct GBA* gba) {
|
||||
if (gba->memory.hw.devices & HW_GB_PLAYER) {
|
||||
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
||||
++gba->memory.hw.gbpInputsPosted;
|
||||
gba->memory.hw.gbpInputsPosted %= 3;
|
||||
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
||||
} else {
|
||||
// TODO: Save and restore
|
||||
gba->keyCallback = 0;
|
||||
}
|
||||
gba->memory.hw.gbpTxPosition = 0;
|
||||
return;
|
||||
}
|
||||
if (gba->keyCallback || gba->sio.drivers.normal) {
|
||||
return;
|
||||
}
|
||||
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
||||
gba->memory.hw.devices |= HW_GB_PLAYER;
|
||||
gba->memory.hw.gbpInputsPosted = 0;
|
||||
gba->memory.hw.gbpNextEvent = INT_MAX;
|
||||
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
||||
GBASIOSetDriver(&gba->sio, &gba->memory.hw.gbpDriver.d, SIO_NORMAL_32);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t _gbpRead(struct GBAKeyCallback* callback) {
|
||||
struct GBAGBPKeyCallback* gbpCallback = (struct GBAGBPKeyCallback*) callback;
|
||||
if (gbpCallback->p->gbpInputsPosted == 2) {
|
||||
return 0x30F;
|
||||
}
|
||||
return 0x3FF;
|
||||
}
|
||||
|
||||
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
gbp->p->gbpNextEvent = 2048;
|
||||
}
|
||||
value &= 0x78FB;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver;
|
||||
gbp->p->gbpNextEvent -= cycles;
|
||||
if (gbp->p->gbpNextEvent <= 0) {
|
||||
uint32_t tx = 0;
|
||||
if (gbp->p->gbpTxPosition <= 16) {
|
||||
tx = _gbpTxData[gbp->p->gbpTxPosition];
|
||||
++gbp->p->gbpTxPosition;
|
||||
}
|
||||
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
||||
if (gbp->d.p->normalControl.irq) {
|
||||
GBARaiseIRQ(gbp->p->p, IRQ_SIO);
|
||||
}
|
||||
gbp->d.p->normalControl.start = 0;
|
||||
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt;
|
||||
gbp->p->gbpNextEvent = INT_MAX;
|
||||
}
|
||||
return gbp->p->gbpNextEvent;
|
||||
}
|
||||
|
||||
// == Serialization
|
||||
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
|
@ -493,6 +611,9 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
|
|||
state->hw.lightCounter = 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;
|
||||
}
|
||||
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||
|
@ -508,4 +629,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
|||
hw->lightCounter = state->hw.lightCounter;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#define GBA_HARDWARE_H
|
||||
|
||||
#include "util/common.h"
|
||||
#include "gba/interface.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
|
@ -14,27 +15,6 @@
|
|||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
struct GBARotationSource {
|
||||
void (*sample)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readTiltX)(struct GBARotationSource*);
|
||||
int32_t (*readTiltY)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readGyroZ)(struct GBARotationSource*);
|
||||
};
|
||||
|
||||
struct GBALuminanceSource {
|
||||
void (*sample)(struct GBALuminanceSource*);
|
||||
|
||||
uint8_t (*readLuminance)(struct GBALuminanceSource*);
|
||||
};
|
||||
|
||||
struct GBARTCSource {
|
||||
void (*sample)(struct GBARTCSource*);
|
||||
|
||||
time_t (*unixTime)(struct GBARTCSource*);
|
||||
};
|
||||
|
||||
struct GBARTCGenericSource {
|
||||
struct GBARTCSource d;
|
||||
struct GBA* p;
|
||||
|
@ -53,7 +33,9 @@ enum GBAHardwareDevice {
|
|||
HW_RUMBLE = 2,
|
||||
HW_LIGHT_SENSOR = 4,
|
||||
HW_GYRO = 8,
|
||||
HW_TILT = 16
|
||||
HW_TILT = 16,
|
||||
HW_GB_PLAYER = 32,
|
||||
HW_GB_PLAYER_DETECTION = 64
|
||||
};
|
||||
|
||||
enum GPIORegister {
|
||||
|
@ -102,6 +84,16 @@ struct GBARumble {
|
|||
void (*setRumble)(struct GBARumble*, int enable);
|
||||
};
|
||||
|
||||
struct GBAGBPKeyCallback {
|
||||
struct GBAKeyCallback d;
|
||||
struct GBACartridgeHardware* p;
|
||||
};
|
||||
|
||||
struct GBAGBPSIODriver {
|
||||
struct GBASIODriver d;
|
||||
struct GBACartridgeHardware* p;
|
||||
};
|
||||
|
||||
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||
|
||||
struct GBACartridgeHardware {
|
||||
|
@ -125,6 +117,12 @@ struct GBACartridgeHardware {
|
|||
uint16_t tiltX;
|
||||
uint16_t tiltY;
|
||||
int tiltState;
|
||||
|
||||
unsigned gbpInputsPosted;
|
||||
int gbpTxPosition;
|
||||
int32_t gbpNextEvent;
|
||||
struct GBAGBPKeyCallback gbpCallback;
|
||||
struct GBAGBPSIODriver gbpDriver;
|
||||
};
|
||||
|
||||
void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);
|
||||
|
@ -141,6 +139,7 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
|
|||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||
|
||||
struct GBAVideo;
|
||||
void GBAHardwarePlayerUpdate(struct GBA* gba);
|
||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video);
|
||||
|
||||
void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba);
|
||||
|
|
|
@ -257,7 +257,10 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||
static bool _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||
if (!ConfigurationHasSection(config, sectionName)) {
|
||||
return false;
|
||||
}
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
|
@ -279,6 +282,7 @@ static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* section
|
|||
_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
||||
|
@ -348,6 +352,20 @@ enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key
|
|||
return GBA_KEY_NONE;
|
||||
}
|
||||
|
||||
int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset) {
|
||||
int keys = 0;
|
||||
for (; bits; bits >>= 1, ++offset) {
|
||||
if (bits & 1) {
|
||||
enum GBAKey key = GBAInputMapKey(map, type, offset);
|
||||
if (key == GBA_KEY_NONE) {
|
||||
continue;
|
||||
}
|
||||
keys |= 1 << key;
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) {
|
||||
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
GBAInputUnbindKey(map, type, input);
|
||||
|
@ -362,7 +380,6 @@ void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input
|
|||
if (impl) {
|
||||
impl->map[input] = GBA_NO_MAPPING;
|
||||
}
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &input);
|
||||
}
|
||||
|
||||
int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
|
||||
|
@ -416,9 +433,10 @@ int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, in
|
|||
|
||||
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
|
||||
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
|
||||
struct GBAAxis d2 = *description;
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection);
|
||||
TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection);
|
||||
struct GBAAxis* dup = malloc(sizeof(struct GBAAxis));
|
||||
GBAInputUnbindKey(map, type, description->lowDirection);
|
||||
GBAInputUnbindKey(map, type, description->highDirection);
|
||||
*dup = *description;
|
||||
TableInsert(&impl->axes, axis, dup);
|
||||
}
|
||||
|
@ -469,11 +487,11 @@ void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Config
|
|||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||
bool GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_loadAll(map, type, sectionName, config);
|
||||
return _loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||
|
|
|
@ -30,6 +30,7 @@ void GBAInputMapInit(struct GBAInputMap*);
|
|||
void GBAInputMapDeinit(struct GBAInputMap*);
|
||||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
||||
int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset);
|
||||
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
||||
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
|
@ -45,7 +46,7 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
|
|||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||
bool GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||
|
||||
const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId);
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/* 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 INTERFACE_H
|
||||
#define INTERFACE_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
enum GBALogLevel {
|
||||
GBA_LOG_FATAL = 0x01,
|
||||
GBA_LOG_ERROR = 0x02,
|
||||
GBA_LOG_WARN = 0x04,
|
||||
GBA_LOG_INFO = 0x08,
|
||||
GBA_LOG_DEBUG = 0x10,
|
||||
GBA_LOG_STUB = 0x20,
|
||||
|
||||
GBA_LOG_GAME_ERROR = 0x100,
|
||||
GBA_LOG_SWI = 0x200,
|
||||
GBA_LOG_STATUS = 0x400,
|
||||
GBA_LOG_SIO = 0x800,
|
||||
|
||||
GBA_LOG_ALL = 0xF3F,
|
||||
};
|
||||
|
||||
enum GBAKey {
|
||||
GBA_KEY_A = 0,
|
||||
GBA_KEY_B = 1,
|
||||
GBA_KEY_SELECT = 2,
|
||||
GBA_KEY_START = 3,
|
||||
GBA_KEY_RIGHT = 4,
|
||||
GBA_KEY_LEFT = 5,
|
||||
GBA_KEY_UP = 6,
|
||||
GBA_KEY_DOWN = 7,
|
||||
GBA_KEY_R = 8,
|
||||
GBA_KEY_L = 9,
|
||||
GBA_KEY_MAX,
|
||||
GBA_KEY_NONE = -1
|
||||
};
|
||||
|
||||
enum GBASIOMode {
|
||||
SIO_NORMAL_8 = 0,
|
||||
SIO_NORMAL_32 = 1,
|
||||
SIO_MULTI = 2,
|
||||
SIO_UART = 3,
|
||||
SIO_GPIO = 8,
|
||||
SIO_JOYBUS = 12
|
||||
};
|
||||
|
||||
struct GBA;
|
||||
struct GBAAudio;
|
||||
struct GBASIO;
|
||||
struct GBAThread;
|
||||
struct GBAVideoRenderer;
|
||||
|
||||
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||
};
|
||||
|
||||
struct GBAKeyCallback {
|
||||
uint16_t (*readKeys)(struct GBAKeyCallback*);
|
||||
};
|
||||
|
||||
struct GBAStopCallback {
|
||||
void (*stop)(struct GBAStopCallback*);
|
||||
};
|
||||
|
||||
struct GBARotationSource {
|
||||
void (*sample)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readTiltX)(struct GBARotationSource*);
|
||||
int32_t (*readTiltY)(struct GBARotationSource*);
|
||||
|
||||
int32_t (*readGyroZ)(struct GBARotationSource*);
|
||||
};
|
||||
|
||||
extern const int GBA_LUX_LEVELS[10];
|
||||
|
||||
struct GBALuminanceSource {
|
||||
void (*sample)(struct GBALuminanceSource*);
|
||||
|
||||
uint8_t (*readLuminance)(struct GBALuminanceSource*);
|
||||
};
|
||||
|
||||
struct GBARTCSource {
|
||||
void (*sample)(struct GBARTCSource*);
|
||||
|
||||
time_t (*unixTime)(struct GBARTCSource*);
|
||||
};
|
||||
|
||||
struct GBASIODriver {
|
||||
struct GBASIO* p;
|
||||
|
||||
bool (*init)(struct GBASIODriver* driver);
|
||||
void (*deinit)(struct GBASIODriver* driver);
|
||||
bool (*load)(struct GBASIODriver* driver);
|
||||
bool (*unload)(struct GBASIODriver* driver);
|
||||
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles);
|
||||
};
|
||||
|
||||
#endif
|
12
src/gba/io.c
12
src/gba/io.c
|
@ -505,7 +505,7 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
|
|||
if (!value) {
|
||||
GBAHalt(gba);
|
||||
} else {
|
||||
GBALog(gba, GBA_LOG_STUB, "Stop unimplemented");
|
||||
GBAStop(gba);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -584,14 +584,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
case REG_KEYINPUT:
|
||||
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||
} else if (gba->keySource) {
|
||||
uint16_t input = *gba->keySource;
|
||||
} else {
|
||||
uint16_t input = 0x3FF;
|
||||
if (gba->keyCallback) {
|
||||
input = gba->keyCallback->readKeys(gba->keyCallback);
|
||||
} else if (gba->keySource) {
|
||||
input = *gba->keySource;
|
||||
}
|
||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||
gba->rr->logInput(gba->rr, input);
|
||||
}
|
||||
return 0x3FF ^ input;
|
||||
}
|
||||
break;
|
||||
|
||||
case REG_SIOCNT:
|
||||
return gba->sio.siocnt;
|
||||
|
|
|
@ -278,9 +278,11 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
memory->activeRegion = -1;
|
||||
cpu->memory.activeRegion = _deadbeef;
|
||||
cpu->memory.activeMask = 0;
|
||||
if (!gba->yankedRomSize) {
|
||||
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
|
||||
enum GBALogLevel errorLevel = GBA_LOG_FATAL;
|
||||
if (gba->yankedRomSize || !gba->hardCrash) {
|
||||
errorLevel = GBA_LOG_GAME_ERROR;
|
||||
}
|
||||
GBALog(gba, errorLevel, "Jumped to invalid address: %08X", address);
|
||||
return;
|
||||
}
|
||||
cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion];
|
||||
|
@ -634,8 +636,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
#define STORE_VRAM \
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
|
||||
STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \
|
||||
} else { \
|
||||
STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \
|
||||
} \
|
||||
wait += waitstatesRegion[REGION_VRAM];
|
||||
|
||||
|
@ -728,8 +734,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
|||
case REGION_VRAM:
|
||||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
|
||||
} else {
|
||||
STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
|
@ -794,8 +802,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address);
|
||||
break;
|
||||
}
|
||||
((int8_t*) gba->video.renderer->vram)[address & 0x1FFFE] = value;
|
||||
((int8_t*) gba->video.renderer->vram)[(address & 0x1FFFE) | 1] = value;
|
||||
gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
|
||||
break;
|
||||
case REGION_OAM:
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OAM: 0x%08X", address);
|
||||
|
@ -818,6 +826,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
|
|||
GBASavedataWriteFlash(&memory->savedata, address, value);
|
||||
} else if (memory->savedata.type == SAVEDATA_SRAM) {
|
||||
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
|
||||
memory->savedata.dirty |= SAVEDATA_DIRT_NEW;
|
||||
} else if (memory->hw.devices & HW_TILT) {
|
||||
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);
|
||||
} else {
|
||||
|
|
|
@ -98,11 +98,7 @@
|
|||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
} \
|
||||
|
@ -126,11 +122,7 @@
|
|||
tileData &= 0xF; \
|
||||
tileData |= tileData << 4; \
|
||||
tileData |= tileData << 8; \
|
||||
tileData |= tileData << 12; \
|
||||
tileData |= tileData << 16; \
|
||||
tileData |= tileData << 20; \
|
||||
tileData |= tileData << 24; \
|
||||
tileData |= tileData << 28; \
|
||||
carryData = tileData; \
|
||||
} \
|
||||
mosaicWait = mosaicH; \
|
||||
|
@ -414,7 +406,7 @@
|
|||
return; \
|
||||
} \
|
||||
if (UNLIKELY(end < outX)) { \
|
||||
GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \
|
||||
GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw!"); \
|
||||
return; \
|
||||
} \
|
||||
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
|
||||
|
|
|
@ -116,6 +116,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
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 (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) {
|
||||
int target2 = renderer->target2Bd << 4;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
|
||||
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||
static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
|
@ -46,6 +47,7 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
renderer->d.reset = GBAVideoSoftwareRendererReset;
|
||||
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
|
||||
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
|
||||
renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
|
||||
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
|
||||
renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
|
||||
renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
|
||||
|
@ -327,6 +329,11 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
|
|||
return value;
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(address);
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
|
||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||
softwareRenderer->oamDirty = 1;
|
||||
|
@ -403,12 +410,10 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
|||
if (win->h.end >= oldWindow.endX) {
|
||||
// Trim off extra windows we've overwritten
|
||||
for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
|
||||
#ifdef DEBUG
|
||||
if (activeWindow >= MAX_WINDOW) {
|
||||
GBALog(0, GBA_LOG_DANGER, "Out of bounds window write will occur");
|
||||
if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) {
|
||||
GBALog(0, GBA_LOG_FATAL, "Out of bounds window write will occur");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
|
||||
--softwareRenderer->nWindows;
|
||||
}
|
||||
|
@ -428,7 +433,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
|||
}
|
||||
#ifdef DEBUG
|
||||
if (softwareRenderer->nWindows > MAX_WINDOW) {
|
||||
GBALog(0, GBA_LOG_ABORT, "Out of bounds window write occurred!");
|
||||
GBALog(0, GBA_LOG_FATAL, "Out of bounds window write occurred!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -532,7 +537,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
}
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef __ARM_NEON
|
||||
#if defined(__ARM_NEON) && !defined(__APPLE__)
|
||||
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
||||
#else
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip).
|
||||
// An average estimation is as follows.
|
||||
#define FLASH_SETTLE_CYCLES 18000
|
||||
#define CLEANUP_THRESHOLD 15
|
||||
|
||||
static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
|
||||
static void _flashErase(struct GBASavedata* savedata);
|
||||
|
@ -33,6 +34,8 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
|
|||
savedata->vf = vf;
|
||||
savedata->realVf = vf;
|
||||
savedata->mapMode = MAP_WRITE;
|
||||
savedata->dirty = 0;
|
||||
savedata->dirtAge = 0;
|
||||
}
|
||||
|
||||
void GBASavedataDeinit(struct GBASavedata* savedata) {
|
||||
|
@ -252,6 +255,7 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
|
|||
case FLASH_STATE_RAW:
|
||||
switch (savedata->command) {
|
||||
case FLASH_COMMAND_PROGRAM:
|
||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
||||
savedata->currentBank[address] = value;
|
||||
savedata->command = FLASH_COMMAND_NONE;
|
||||
break;
|
||||
|
@ -359,6 +363,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
|||
uint8_t current = savedata->data[savedata->writeAddress >> 3];
|
||||
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
||||
current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
|
||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
||||
savedata->data[savedata->writeAddress >> 3] = current;
|
||||
++savedata->writeAddress;
|
||||
} else {
|
||||
|
@ -401,6 +406,41 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
|
||||
if (!savedata->vf) {
|
||||
return;
|
||||
}
|
||||
if (savedata->dirty & SAVEDATA_DIRT_NEW) {
|
||||
savedata->dirty &= ~SAVEDATA_DIRT_NEW;
|
||||
if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) {
|
||||
savedata->dirtAge = frameCount;
|
||||
savedata->dirty |= SAVEDATA_DIRT_SEEN;
|
||||
}
|
||||
} else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) {
|
||||
size_t size;
|
||||
switch (savedata->type) {
|
||||
case SAVEDATA_EEPROM:
|
||||
size = SIZE_CART_EEPROM;
|
||||
break;
|
||||
case SAVEDATA_SRAM:
|
||||
size = SIZE_CART_SRAM;
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
size = SIZE_CART_FLASH512;
|
||||
break;
|
||||
case SAVEDATA_FLASH1M:
|
||||
size = SIZE_CART_FLASH1M;
|
||||
break;
|
||||
default:
|
||||
size = 0;
|
||||
break;
|
||||
}
|
||||
savedata->vf->sync(savedata->vf, savedata->data, size);
|
||||
savedata->dirty = 0;
|
||||
GBALog(0, GBA_LOG_INFO, "Savedata synced");
|
||||
}
|
||||
}
|
||||
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) {
|
||||
state->savedata.type = savedata->type;
|
||||
state->savedata.command = savedata->command;
|
||||
|
@ -451,6 +491,7 @@ void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
|||
|
||||
void _flashErase(struct GBASavedata* savedata) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash chip erase");
|
||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
||||
size_t size = SIZE_CART_FLASH512;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
size = SIZE_CART_FLASH1M;
|
||||
|
@ -460,6 +501,7 @@ void _flashErase(struct GBASavedata* savedata) {
|
|||
|
||||
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
|
||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
||||
size_t size = 0x1000;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
|
||||
|
|
|
@ -51,6 +51,11 @@ enum FlashManufacturer {
|
|||
FLASH_MFG_SANYO = 0x1362
|
||||
};
|
||||
|
||||
enum SavedataDirty {
|
||||
SAVEDATA_DIRT_NEW = 1,
|
||||
SAVEDATA_DIRT_SEEN = 2
|
||||
};
|
||||
|
||||
enum {
|
||||
SAVEDATA_FLASH_BASE = 0x0E005555,
|
||||
|
||||
|
@ -77,6 +82,9 @@ struct GBASavedata {
|
|||
unsigned settling;
|
||||
int dust;
|
||||
|
||||
enum SavedataDirty dirty;
|
||||
uint32_t dirtAge;
|
||||
|
||||
enum FlashStateMachine flashState;
|
||||
};
|
||||
|
||||
|
@ -98,6 +106,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
|
|||
uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata);
|
||||
void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize);
|
||||
|
||||
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);
|
||||
|
|
|
@ -136,7 +136,8 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | bit 2: Has light sensor value
|
||||
* | bit 3: Has gyroscope value
|
||||
* | bit 4: Has tilt values
|
||||
* | bits 5 - 7: Reserved
|
||||
* | bit 5: Has Game Boy Player attached
|
||||
* | bits 6 - 7: Reserved
|
||||
* | 0x002B8 - 0x002B9: Gyroscope sample
|
||||
* | 0x002BA - 0x002BB: Tilt x sample
|
||||
* | 0x002BC - 0x002BD: Tilt y sample
|
||||
|
@ -149,8 +150,11 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | 0x002C0 - 0x002C0: Light sample
|
||||
* | 0x002C1 - 0x002C3: Flags
|
||||
* | bits 0 - 1: Tilt state machine
|
||||
* | bits 2 - 31: Reserved
|
||||
* 0x002C4 - 0x002DF: Reserved (leave zero)
|
||||
* | bits 2 - 3: GB Player inputs posted
|
||||
* | bits 4 - 8: GB Player transmit position
|
||||
* | bits 9 - 23: Reserved
|
||||
* 0x002C4 - 0x002C7: Game Boy Player next event
|
||||
* 0x002C8 - 0x002DF: Reserved (leave zero)
|
||||
* 0x002E0 - 0x002EF: Savedata state
|
||||
* | 0x002E0 - 0x002E0: Savedata type
|
||||
* | 0x002E1 - 0x002E1: Savedata command (see savedata.h)
|
||||
|
@ -282,10 +286,13 @@ struct GBASerializedState {
|
|||
unsigned lightCounter : 12;
|
||||
unsigned lightSample : 8;
|
||||
unsigned tiltState : 2;
|
||||
unsigned : 22;
|
||||
unsigned gbpInputsPosted : 2;
|
||||
unsigned gbpTxPosition : 5;
|
||||
unsigned : 15;
|
||||
uint32_t gbpNextEvent : 32;
|
||||
} hw;
|
||||
|
||||
uint32_t reservedHardware[7];
|
||||
uint32_t reservedHardware[6];
|
||||
|
||||
struct {
|
||||
unsigned type : 8;
|
||||
|
|
|
@ -8,36 +8,16 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/interface.h"
|
||||
|
||||
#define MAX_GBAS 4
|
||||
|
||||
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
||||
|
||||
enum GBASIOMode {
|
||||
SIO_NORMAL_8 = 0,
|
||||
SIO_NORMAL_32 = 1,
|
||||
SIO_MULTI = 2,
|
||||
SIO_UART = 3,
|
||||
SIO_GPIO = 8,
|
||||
SIO_JOYBUS = 12
|
||||
};
|
||||
|
||||
enum {
|
||||
RCNT_INITIAL = 0x8000
|
||||
};
|
||||
|
||||
struct GBASIO;
|
||||
|
||||
struct GBASIODriver {
|
||||
struct GBASIO* p;
|
||||
|
||||
bool (*init)(struct GBASIODriver* driver);
|
||||
void (*deinit)(struct GBASIODriver* driver);
|
||||
bool (*load)(struct GBASIODriver* driver);
|
||||
bool (*unload)(struct GBASIODriver* driver);
|
||||
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles);
|
||||
};
|
||||
|
||||
struct GBASIODriverSet {
|
||||
struct GBASIODriver* normal;
|
||||
struct GBASIODriver* multiplayer;
|
||||
|
|
|
@ -116,16 +116,49 @@ bool GBAConfigLoad(struct GBAConfig* config) {
|
|||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return ConfigurationRead(&config->configTable, path);
|
||||
return GBAConfigLoadPath(config, path);
|
||||
}
|
||||
|
||||
bool GBAConfigSave(const struct GBAConfig* config) {
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, PATH_MAX);
|
||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||
return GBAConfigSavePath(config, path);
|
||||
}
|
||||
|
||||
bool GBAConfigLoadPath(struct GBAConfig* config, const char* path) {
|
||||
return ConfigurationRead(&config->configTable, path);
|
||||
}
|
||||
|
||||
bool GBAConfigSavePath(const struct GBAConfig* config, const char* path) {
|
||||
return ConfigurationWrite(&config->configTable, path);
|
||||
}
|
||||
|
||||
void GBAConfigMakePortable(const struct GBAConfig* config) {
|
||||
struct VFile* portable;
|
||||
#ifndef _WIN32
|
||||
char out[PATH_MAX];
|
||||
getcwd(out, PATH_MAX);
|
||||
strncat(out, PATH_SEP "portable.ini", PATH_MAX - strlen(out));
|
||||
portable = VFileOpen(out, O_WRONLY | O_CREAT);
|
||||
#else
|
||||
char out[MAX_PATH];
|
||||
wchar_t wpath[MAX_PATH];
|
||||
wchar_t wprojectName[MAX_PATH];
|
||||
MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
GetModuleFileNameW(hModule, wpath, MAX_PATH);
|
||||
PathRemoveFileSpecW(wpath);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0);
|
||||
StringCchCatA(out, MAX_PATH, "\\portable.ini");
|
||||
portable = VFileOpen(out, O_WRONLY | O_CREAT);
|
||||
#endif
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
GBAConfigSave(config);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAConfigDirectory(char* out, size_t outLength) {
|
||||
struct VFile* portable;
|
||||
#ifndef _WIN32
|
||||
|
|
|
@ -51,7 +51,10 @@ void GBAConfigDeinit(struct GBAConfig*);
|
|||
|
||||
bool GBAConfigLoad(struct GBAConfig*);
|
||||
bool GBAConfigSave(const struct GBAConfig*);
|
||||
bool GBAConfigLoadPath(struct GBAConfig*, const char* path);
|
||||
bool GBAConfigSavePath(const struct GBAConfig*, const char* path);
|
||||
|
||||
void GBAConfigMakePortable(const struct GBAConfig*);
|
||||
void GBAConfigDirectory(char* out, size_t outLength);
|
||||
|
||||
const char* GBAConfigGetValue(const struct GBAConfig*, const char* key);
|
||||
|
|
|
@ -285,6 +285,12 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
|
|||
if (override->hardware & HW_TILT) {
|
||||
GBAHardwareInitTilt(&gba->memory.hw);
|
||||
}
|
||||
|
||||
if (override->hardware & HW_GB_PLAYER_DETECTION) {
|
||||
gba->memory.hw.devices |= HW_GB_PLAYER_DETECTION;
|
||||
} else {
|
||||
gba->memory.hw.devices &= ~HW_GB_PLAYER_DETECTION;
|
||||
}
|
||||
}
|
||||
|
||||
if (override->idleLoop != IDLE_LOOP_NONE) {
|
||||
|
@ -294,3 +300,12 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAOverrideApplyDefaults(struct GBA* gba) {
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(0, &override)) {
|
||||
GBAOverrideApply(gba, &override);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,5 +25,6 @@ void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* o
|
|||
|
||||
struct GBA;
|
||||
void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*);
|
||||
void GBAOverrideApplyDefaults(struct GBA*);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -96,10 +96,22 @@ static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
|
|||
}
|
||||
}
|
||||
|
||||
struct GBAThreadStop {
|
||||
struct GBAStopCallback d;
|
||||
struct GBAThread* p;
|
||||
};
|
||||
|
||||
static void _stopCallback(struct GBAStopCallback* stop) {
|
||||
struct GBAThreadStop* callback = (struct GBAThreadStop*) stop;
|
||||
if (callback->p->stopCallback(callback->p)) {
|
||||
_changeState(callback->p, THREAD_EXITING, false);
|
||||
}
|
||||
}
|
||||
|
||||
static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_once(&_contextOnce, _createTLS);
|
||||
#else
|
||||
#elif _WIN32
|
||||
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||
#endif
|
||||
|
||||
|
@ -129,10 +141,18 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
gba.logLevel = threadContext->logLevel;
|
||||
gba.logHandler = threadContext->logHandler;
|
||||
gba.stream = threadContext->stream;
|
||||
|
||||
struct GBAThreadStop stop;
|
||||
if (threadContext->stopCallback) {
|
||||
stop.d.stop = _stopCallback;
|
||||
stop.p = threadContext;
|
||||
gba.stopCallback = &stop.d;
|
||||
}
|
||||
|
||||
gba.idleOptimization = threadContext->idleOptimization;
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_setspecific(_contextKey, threadContext);
|
||||
#else
|
||||
#elif _WIN32
|
||||
TlsSetValue(_contextKey, threadContext);
|
||||
#endif
|
||||
|
||||
|
@ -399,6 +419,16 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
MutexInit(&threadContext->stateMutex);
|
||||
ConditionInit(&threadContext->stateCond);
|
||||
|
||||
|
@ -410,7 +440,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
|
|||
|
||||
threadContext->interruptDepth = 0;
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef USE_PTHREADS
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
sigaddset(&signals, SIGINT);
|
||||
|
@ -722,6 +752,10 @@ struct GBAThread* GBAThreadGetContext(void) {
|
|||
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||
return TlsGetValue(_contextKey);
|
||||
}
|
||||
#else
|
||||
struct GBAThread* GBAThreadGetContext(void) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
|
|
|
@ -21,6 +21,7 @@ struct GBACheatSet;
|
|||
struct GBAOptions;
|
||||
|
||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||
typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext);
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INITIALIZED = -1,
|
||||
|
@ -86,6 +87,7 @@ struct GBAThread {
|
|||
ThreadCallback startCallback;
|
||||
ThreadCallback cleanCallback;
|
||||
ThreadCallback frameCallback;
|
||||
ThreadStopCallback stopCallback;
|
||||
void* userData;
|
||||
void (*run)(struct GBAThread*);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
|
|||
static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||
static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
|
||||
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
|
||||
|
@ -47,6 +48,7 @@ static struct GBAVideoRenderer dummyRenderer = {
|
|||
.reset = GBAVideoDummyRendererReset,
|
||||
.deinit = GBAVideoDummyRendererDeinit,
|
||||
.writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
|
||||
.writeVRAM = GBAVideoDummyRendererWriteVRAM,
|
||||
.writePalette = GBAVideoDummyRendererWritePalette,
|
||||
.writeOAM = GBAVideoDummyRendererWriteOAM,
|
||||
.drawScanline = GBAVideoDummyRendererDrawScanline,
|
||||
|
@ -222,6 +224,12 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer*
|
|||
return value;
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(address);
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(address);
|
||||
|
|
|
@ -161,6 +161,7 @@ struct GBAVideoRenderer {
|
|||
void (*deinit)(struct GBAVideoRenderer* renderer);
|
||||
|
||||
uint16_t (*writeVideoRegister)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
void (*writeVRAM)(struct GBAVideoRenderer* renderer, uint32_t address);
|
||||
void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||
void (*drawScanline)(struct GBAVideoRenderer* renderer, int y);
|
||||
|
|
|
@ -8,13 +8,18 @@
|
|||
#include "util/common.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/interface.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/overrides.h"
|
||||
#include "gba/video.h"
|
||||
#include "util/circle-buffer.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#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;
|
||||
|
@ -22,11 +27,15 @@ static retro_audio_sample_batch_t audioCallback;
|
|||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputCallback;
|
||||
static retro_log_printf_t logCallback;
|
||||
static retro_set_rumble_state_t rumbleCallback;
|
||||
|
||||
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
||||
|
||||
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
||||
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _setRumble(struct GBARumble* rumble, int enable);
|
||||
static uint8_t _readLux(struct GBALuminanceSource* lux);
|
||||
static void _updateLux(struct GBALuminanceSource* lux);
|
||||
|
||||
static struct GBA gba;
|
||||
static struct ARMCore cpu;
|
||||
|
@ -35,7 +44,13 @@ static struct VFile* rom;
|
|||
static void* data;
|
||||
static struct VFile* save;
|
||||
static void* savedata;
|
||||
static struct VFile* bios;
|
||||
static struct GBAAVStream stream;
|
||||
static int rumbleLevel;
|
||||
static struct CircleBuffer rumbleHistory;
|
||||
static struct GBARumble rumble;
|
||||
static struct GBALuminanceSource lux;
|
||||
static int luxLevel;
|
||||
|
||||
unsigned retro_api_version(void) {
|
||||
return RETRO_API_VERSION;
|
||||
|
@ -43,6 +58,13 @@ unsigned retro_api_version(void) {
|
|||
|
||||
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" },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars);
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t video) {
|
||||
|
@ -107,12 +129,27 @@ void retro_init(void) {
|
|||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" }
|
||||
};
|
||||
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
|
||||
|
||||
// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
|
||||
// TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE
|
||||
|
||||
struct retro_rumble_interface rumbleInterface;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
|
||||
rumbleCallback = rumbleInterface.set_rumble_state;
|
||||
CircleBufferInit(&rumbleHistory, RUMBLE_PWM);
|
||||
rumble.setRumble = _setRumble;
|
||||
} else {
|
||||
rumbleCallback = 0;
|
||||
}
|
||||
|
||||
luxLevel = 0;
|
||||
lux.readLuminance = _readLux;
|
||||
lux.sample = _updateLux;
|
||||
_updateLux(&lux);
|
||||
|
||||
struct retro_log_callback log;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
||||
|
@ -132,8 +169,22 @@ void retro_init(void) {
|
|||
gba.logHandler = GBARetroLog;
|
||||
gba.stream = &stream;
|
||||
gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
|
||||
if (rumbleCallback) {
|
||||
gba.rumble = &rumble;
|
||||
}
|
||||
gba.luminanceSource = &lux;
|
||||
rom = 0;
|
||||
|
||||
const char* sysDir = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir)) {
|
||||
char biosPath[PATH_MAX];
|
||||
snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, "gba_bios.bin");
|
||||
bios = VFileOpen(biosPath, O_RDONLY);
|
||||
if (bios) {
|
||||
GBALoadBIOS(&gba, bios);
|
||||
}
|
||||
}
|
||||
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer.outputBufferStride = 256;
|
||||
|
@ -148,6 +199,10 @@ void retro_init(void) {
|
|||
}
|
||||
|
||||
void retro_deinit(void) {
|
||||
if (bios) {
|
||||
bios->close(bios);
|
||||
bios = 0;
|
||||
}
|
||||
GBADestroy(&gba);
|
||||
}
|
||||
|
||||
|
@ -168,6 +223,26 @@ void retro_run(void) {
|
|||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
|
||||
|
||||
static bool wasAdjustingLux = false;
|
||||
if (wasAdjustingLux) {
|
||||
wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
|
||||
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
|
||||
} else {
|
||||
if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
|
||||
++luxLevel;
|
||||
if (luxLevel > 10) {
|
||||
luxLevel = 10;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
|
||||
--luxLevel;
|
||||
if (luxLevel < 0) {
|
||||
luxLevel = 0;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
}
|
||||
}
|
||||
|
||||
int frameCount = gba.video.frameCounter;
|
||||
while (gba.video.frameCounter == frameCount) {
|
||||
ARMRunLoop(&cpu);
|
||||
|
@ -176,6 +251,10 @@ void retro_run(void) {
|
|||
|
||||
void retro_reset(void) {
|
||||
ARMReset(&cpu);
|
||||
|
||||
if (rumbleCallback) {
|
||||
CircleBufferClear(&rumbleHistory);
|
||||
}
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info* game) {
|
||||
|
@ -198,13 +277,7 @@ bool retro_load_game(const struct retro_game_info* game) {
|
|||
save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
|
||||
GBALoadROM(&gba, rom, save, game->path);
|
||||
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(0, &override)) {
|
||||
GBAOverrideApply(&gba, &override);
|
||||
}
|
||||
GBAOverrideApplyDefaults(&gba);
|
||||
|
||||
ARMReset(&cpu);
|
||||
return true;
|
||||
|
@ -219,6 +292,7 @@ void retro_unload_game(void) {
|
|||
save = 0;
|
||||
free(savedata);
|
||||
savedata = 0;
|
||||
CircleBufferDeinit(&rumbleHistory);
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void) {
|
||||
|
@ -317,10 +391,12 @@ void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* f
|
|||
case GBA_LOG_INFO:
|
||||
case GBA_LOG_GAME_ERROR:
|
||||
case GBA_LOG_SWI:
|
||||
case GBA_LOG_STATUS:
|
||||
retroLevel = RETRO_LOG_INFO;
|
||||
break;
|
||||
case GBA_LOG_DEBUG:
|
||||
case GBA_LOG_STUB:
|
||||
case GBA_LOG_SIO:
|
||||
retroLevel = RETRO_LOG_DEBUG;
|
||||
break;
|
||||
}
|
||||
|
@ -352,3 +428,55 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
renderer->getPixels(renderer, &stride, &pixels);
|
||||
videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride);
|
||||
}
|
||||
|
||||
static void _setRumble(struct GBARumble* rumble, int enable) {
|
||||
UNUSED(rumble);
|
||||
if (!rumbleCallback) {
|
||||
return;
|
||||
}
|
||||
rumbleLevel += enable;
|
||||
if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
|
||||
int8_t oldLevel;
|
||||
CircleBufferRead8(&rumbleHistory, &oldLevel);
|
||||
rumbleLevel -= oldLevel;
|
||||
}
|
||||
CircleBufferWrite8(&rumbleHistory, enable);
|
||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||
}
|
||||
|
||||
static void _updateLux(struct GBALuminanceSource* lux) {
|
||||
UNUSED(lux);
|
||||
struct retro_variable var = {
|
||||
.key = SOLAR_SENSOR_LEVEL,
|
||||
.value = 0
|
||||
};
|
||||
|
||||
bool updated = false;
|
||||
if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
|
||||
return;
|
||||
}
|
||||
if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* end;
|
||||
int newLuxLevel = strtol(var.value, &end, 10);
|
||||
if (!*end) {
|
||||
if (newLuxLevel > 10) {
|
||||
luxLevel = 10;
|
||||
} else if (newLuxLevel < 0) {
|
||||
luxLevel = 0;
|
||||
} else {
|
||||
luxLevel = newLuxLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t _readLux(struct GBALuminanceSource* lux) {
|
||||
UNUSED(lux);
|
||||
int value = 0x16;
|
||||
if (luxLevel > 0) {
|
||||
value += GBA_LUX_LEVELS[luxLevel - 1];
|
||||
}
|
||||
return 0xFF - value;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/* 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 "gles2.h"
|
||||
|
||||
#include "gba/video.h"
|
||||
|
||||
static const char* const _vertexShader =
|
||||
"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.46875, -0.3125);\n"
|
||||
"}";
|
||||
|
||||
static const char* const _fragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = texture2D(tex, texCoord);\n"
|
||||
" color.a = 1.;\n"
|
||||
" gl_FragColor = color;"
|
||||
"}";
|
||||
|
||||
static const GLfloat _vertices[] = {
|
||||
-1.f, -1.f,
|
||||
-1.f, 1.f,
|
||||
1.f, 1.f,
|
||||
1.f, -1.f,
|
||||
};
|
||||
|
||||
static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
||||
UNUSED(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);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void GBAGLES2ContextDeinit(struct VideoBackend* v) {
|
||||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glDeleteTextures(1, &context->tex);
|
||||
}
|
||||
|
||||
static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) {
|
||||
int drawW = w;
|
||||
int drawH = h;
|
||||
if (v->lockAspectRatio) {
|
||||
if (w * 2 > h * 3) {
|
||||
drawW = h * 3 / 2;
|
||||
} else if (w * 2 < h * 3) {
|
||||
drawH = w * 2 / 3;
|
||||
}
|
||||
}
|
||||
glViewport(0, 0, 240, 160);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
}
|
||||
|
||||
static void GBAGLES2ContextClear(struct VideoBackend* v) {
|
||||
UNUSED(v);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
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);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
|
||||
struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBAGLES2ContextCreate(struct GBAGLES2Context* context) {
|
||||
context->d.init = GBAGLES2ContextInit;
|
||||
context->d.deinit = GBAGLES2ContextDeinit;
|
||||
context->d.resized = GBAGLES2ContextResized;
|
||||
context->d.swap = 0;
|
||||
context->d.clear = GBAGLES2ContextClear;
|
||||
context->d.postFrame = GBAGLES2ContextPostFrame;
|
||||
context->d.drawFrame = GBAGLES2ContextDrawFrame;
|
||||
context->d.setMessage = 0;
|
||||
context->d.clearMessage = 0;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* 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 GLES2_H
|
||||
#define GLES2_H
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "platform/video-backend.h"
|
||||
|
||||
struct GBAGLES2Context {
|
||||
struct VideoBackend d;
|
||||
|
||||
GLuint tex;
|
||||
GLuint fragmentShader;
|
||||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
GLuint bufferObject;
|
||||
GLuint texLocation;
|
||||
GLuint positionLocation;
|
||||
};
|
||||
|
||||
void GBAGLES2ContextCreate(struct GBAGLES2Context*);
|
||||
|
||||
#endif
|
|
@ -186,7 +186,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
|
|||
}
|
||||
}
|
||||
GBASyncWaitFrameEnd(&context->sync);
|
||||
if (*frames == duration) {
|
||||
if (duration > 0 && *frames == duration) {
|
||||
_GBAPerfShutdown(0);
|
||||
}
|
||||
if (_dispatchExiting) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#ifdef __FreeBSD__
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
|
@ -79,7 +79,7 @@ static inline int ThreadJoin(Thread thread) {
|
|||
static inline int ThreadSetName(const char* name) {
|
||||
#ifdef __APPLE__
|
||||
return pthread_setname_np(name);
|
||||
#elif defined(__FreeBSD__)
|
||||
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
return 0;
|
||||
#else
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* 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 "AboutScreen.h"
|
||||
|
||||
#include "util/version.h"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AboutScreen::AboutScreen(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
QPixmap logo(":/res/mgba-1024.png");
|
||||
logo = logo.scaled(m_ui.logo->minimumSize() * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
logo.setDevicePixelRatio(devicePixelRatio());
|
||||
m_ui.logo->setPixmap(logo);
|
||||
|
||||
m_ui.projectName->setText(QLatin1String(projectName));
|
||||
m_ui.projectVersion->setText(QLatin1String(projectVersion));
|
||||
QString gitInfo = m_ui.gitInfo->text();
|
||||
gitInfo.replace("{gitBranch}", QLatin1String(gitBranch));
|
||||
gitInfo.replace("{gitCommit}", QLatin1String(gitCommit));
|
||||
m_ui.gitInfo->setText(gitInfo);
|
||||
QString description = m_ui.description->text();
|
||||
description.replace("{projectName}", QLatin1String(projectName));
|
||||
m_ui.description->setText(description);
|
||||
QString extraLinks = m_ui.extraLinks->text();
|
||||
extraLinks.replace("{gitBranch}", QLatin1String(gitBranch));
|
||||
m_ui.extraLinks->setText(extraLinks);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_ABOUT_SCREEN
|
||||
#define QGBA_ABOUT_SCREEN
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "ui_AboutScreen.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class AboutScreen : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AboutScreen(QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
Ui::AboutScreen m_ui;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,177 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AboutScreen</class>
|
||||
<widget class="QWidget" name="AboutScreen">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>565</width>
|
||||
<height>268</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>About</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="projectName">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>36</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>{projectName}</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="4">
|
||||
<widget class="QLabel" name="copyright">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>© 2013 – 2015 Jeffrey Pfau — Game Boy Advance is a registered trademark of Nintendo Co., Ltd.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="projectVersion">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>{projectVersion}</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="6">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="logo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>256</width>
|
||||
<height>192</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>{logo}</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="text">
|
||||
<string>{projectName} is an open-source Game Boy Advance emulator</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="extraLinks">
|
||||
<property name="text">
|
||||
<string><a href="http://mgba.io/">Website</a> • <a href="https://forumsmgba.io/">Forums / Support</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> • <a href="https://github.com/mgba-emu/mgba/blob/{gitBranch}/LICENSE">License</a></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="gitInfo">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt></string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -38,10 +38,10 @@ AudioProcessor* AudioProcessor::create() {
|
|||
#endif
|
||||
|
||||
default:
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
return new AudioProcessorQt();
|
||||
#else
|
||||
#ifdef BUILD_SDL
|
||||
return new AudioProcessorSDL();
|
||||
#else
|
||||
return new AudioProcessorQt();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ public slots:
|
|||
virtual void setBufferSamples(int samples) = 0;
|
||||
virtual void inputParametersChanged() = 0;
|
||||
|
||||
virtual unsigned sampleRate() const = 0;
|
||||
|
||||
protected:
|
||||
GBAThread* input() { return m_context; }
|
||||
|
||||
|
|
|
@ -83,3 +83,10 @@ void AudioProcessorQt::inputParametersChanged() {
|
|||
m_device->setFormat(m_audioOutput->format());
|
||||
}
|
||||
}
|
||||
|
||||
unsigned AudioProcessorQt::sampleRate() const {
|
||||
if (!m_audioOutput) {
|
||||
return 0;
|
||||
}
|
||||
return m_audioOutput->format().sampleRate();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ public slots:
|
|||
virtual void setBufferSamples(int samples);
|
||||
virtual void inputParametersChanged();
|
||||
|
||||
virtual unsigned sampleRate() const override;
|
||||
|
||||
private:
|
||||
QAudioOutput* m_audioOutput;
|
||||
AudioDevice* m_device;
|
||||
|
|
|
@ -54,3 +54,7 @@ void AudioProcessorSDL::setBufferSamples(int samples) {
|
|||
|
||||
void AudioProcessorSDL::inputParametersChanged() {
|
||||
}
|
||||
|
||||
unsigned AudioProcessorSDL::sampleRate() const {
|
||||
return m_audio.obtainedSpec.freq;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ public slots:
|
|||
virtual void setBufferSamples(int samples);
|
||||
virtual void inputParametersChanged();
|
||||
|
||||
virtual unsigned sampleRate() const override;
|
||||
|
||||
private:
|
||||
GBASDLAudio m_audio;
|
||||
};
|
||||
|
|
|
@ -28,17 +28,31 @@ endif()
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
find_package(Qt5Multimedia)
|
||||
if(NOT WIN32 OR NOT BUILD_SDL)
|
||||
find_package(Qt5Multimedia)
|
||||
endif()
|
||||
find_package(Qt5OpenGL)
|
||||
find_package(Qt5Widgets)
|
||||
|
||||
if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
|
||||
if(NOT BUILD_GL AND NOT BUILD_GLES2)
|
||||
message(WARNING "OpenGL is required to build the Qt port")
|
||||
set(BUILD_QT OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND)
|
||||
message(WARNING "Cannot find Qt modules")
|
||||
set(BUILD_QT OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(BUILD_GL)
|
||||
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
|
||||
endif()
|
||||
|
||||
if(BUILD_GLES2)
|
||||
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c)
|
||||
endif()
|
||||
|
||||
get_target_property(QT_TYPE Qt5::Core TYPE)
|
||||
if(QT_TYPE STREQUAL STATIC_LIBRARY)
|
||||
|
@ -47,6 +61,7 @@ if(QT_TYPE STREQUAL STATIC_LIBRARY)
|
|||
endif()
|
||||
|
||||
set(SOURCE_FILES
|
||||
AboutScreen.cpp
|
||||
AudioProcessor.cpp
|
||||
CheatsModel.cpp
|
||||
CheatsView.cpp
|
||||
|
@ -61,6 +76,7 @@ set(SOURCE_FILES
|
|||
GamepadAxisEvent.cpp
|
||||
GamepadButtonEvent.cpp
|
||||
InputController.cpp
|
||||
InputProfile.cpp
|
||||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogController.cpp
|
||||
|
@ -82,6 +98,7 @@ set(SOURCE_FILES
|
|||
VideoView.cpp)
|
||||
|
||||
qt5_wrap_ui(UI_FILES
|
||||
AboutScreen.ui
|
||||
CheatsView.ui
|
||||
GIFView.ui
|
||||
LoadSaveState.ui
|
||||
|
@ -141,7 +158,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR
|
|||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
|
||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}-qt
|
||||
|
|
|
@ -115,8 +115,8 @@ ConfigController::ConfigController(QObject* parent)
|
|||
m_opts.rewindBufferCapacity = 0;
|
||||
m_opts.useBios = true;
|
||||
m_opts.suspendScreensaver = true;
|
||||
GBAConfigLoadDefaults(&m_config, &m_opts);
|
||||
GBAConfigLoad(&m_config);
|
||||
GBAConfigLoadDefaults(&m_config, &m_opts);
|
||||
GBAConfigMap(&m_config, &m_opts);
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,11 @@ ConfigController::~ConfigController() {
|
|||
}
|
||||
|
||||
bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[]) {
|
||||
return ::parseArguments(args, &m_config, argc, argv, 0);
|
||||
if (::parseArguments(args, &m_config, argc, argv, 0)) {
|
||||
GBAConfigMap(&m_config, &m_opts);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigOption* ConfigController::addOption(const char* key) {
|
||||
|
@ -258,3 +262,19 @@ void ConfigController::write() {
|
|||
GBAConfigSave(&m_config);
|
||||
m_settings->sync();
|
||||
}
|
||||
|
||||
void ConfigController::makePortable() {
|
||||
GBAConfigMakePortable(&m_config);
|
||||
|
||||
char path[PATH_MAX];
|
||||
GBAConfigDirectory(path, sizeof(path));
|
||||
QString fileName(path);
|
||||
fileName.append(QDir::separator());
|
||||
fileName.append("qt.ini");
|
||||
QSettings* settings2 = new QSettings(fileName, QSettings::IniFormat, this);
|
||||
for (const auto& key : m_settings->allKeys()) {
|
||||
settings2->setValue(key, m_settings->value(key));
|
||||
}
|
||||
delete m_settings;
|
||||
m_settings = settings2;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ public slots:
|
|||
void setOption(const char* key, const QVariant& value);
|
||||
void setQtOption(const QString& key, const QVariant& value, const QString& group = QString());
|
||||
|
||||
void makePortable();
|
||||
void write();
|
||||
|
||||
private:
|
||||
|
|
|
@ -51,6 +51,10 @@ Display::Display(QWidget* parent)
|
|||
{
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
connect(&m_mouseTimer, SIGNAL(timeout()), this, SIGNAL(hideCursor()));
|
||||
m_mouseTimer.setSingleShot(true);
|
||||
m_mouseTimer.setInterval(MOUSE_DISAPPEAR_TIMER);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void Display::resizeEvent(QResizeEvent*) {
|
||||
|
@ -68,4 +72,13 @@ void Display::filter(bool filter) {
|
|||
|
||||
void Display::showMessage(const QString& message) {
|
||||
m_messagePainter.showMessage(message);
|
||||
if (!isDrawing()) {
|
||||
forceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Display::mouseMoveEvent(QMouseEvent*) {
|
||||
emit showCursor();
|
||||
m_mouseTimer.stop();
|
||||
m_mouseTimer.start();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@ public:
|
|||
bool isAspectRatioLocked() const { return m_lockAspectRatio; }
|
||||
bool isFiltered() const { return m_filter; }
|
||||
|
||||
virtual bool isDrawing() const = 0;
|
||||
|
||||
signals:
|
||||
void showCursor();
|
||||
void hideCursor();
|
||||
|
||||
public slots:
|
||||
virtual void startDrawing(GBAThread* context) = 0;
|
||||
virtual void stopDrawing() = 0;
|
||||
|
@ -47,15 +53,19 @@ public slots:
|
|||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent*);
|
||||
virtual void mouseMoveEvent(QMouseEvent*) override;
|
||||
|
||||
MessagePainter* messagePainter() { return &m_messagePainter; }
|
||||
|
||||
|
||||
private:
|
||||
static Driver s_driver;
|
||||
static const int MOUSE_DISAPPEAR_TIMER = 1000;
|
||||
|
||||
MessagePainter m_messagePainter;
|
||||
bool m_lockAspectRatio;
|
||||
bool m_filter;
|
||||
QTimer m_mouseTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -16,11 +16,14 @@ using namespace QGBA;
|
|||
|
||||
DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
|
||||
: Display(parent)
|
||||
, m_isDrawing(false)
|
||||
, m_gl(new EmptyGLWidget(format, this))
|
||||
, m_painter(new PainterGL(m_gl))
|
||||
, m_drawThread(nullptr)
|
||||
, m_context(nullptr)
|
||||
{
|
||||
m_gl->setMouseTracking(true);
|
||||
m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
|
||||
}
|
||||
|
||||
DisplayGL::~DisplayGL() {
|
||||
|
@ -31,6 +34,7 @@ void DisplayGL::startDrawing(GBAThread* thread) {
|
|||
if (m_drawThread) {
|
||||
return;
|
||||
}
|
||||
m_isDrawing = true;
|
||||
m_painter->setContext(thread);
|
||||
m_painter->setMessagePainter(messagePainter());
|
||||
m_context = thread;
|
||||
|
@ -53,6 +57,7 @@ void DisplayGL::startDrawing(GBAThread* thread) {
|
|||
|
||||
void DisplayGL::stopDrawing() {
|
||||
if (m_drawThread) {
|
||||
m_isDrawing = false;
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
|
@ -67,6 +72,7 @@ void DisplayGL::stopDrawing() {
|
|||
|
||||
void DisplayGL::pauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
m_isDrawing = false;
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
|
@ -79,6 +85,7 @@ void DisplayGL::pauseDrawing() {
|
|||
|
||||
void DisplayGL::unpauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
m_isDrawing = true;
|
||||
if (GBAThreadIsActive(m_context)) {
|
||||
GBAThreadInterrupt(m_context);
|
||||
}
|
||||
|
@ -133,7 +140,11 @@ PainterGL::PainterGL(QGLWidget* parent)
|
|||
, m_context(nullptr)
|
||||
, m_messagePainter(nullptr)
|
||||
{
|
||||
#ifdef BUILD_GL
|
||||
GBAGLContextCreate(&m_backend);
|
||||
#elif defined(BUILD_GLES2)
|
||||
GBAGLES2ContextCreate(&m_backend);
|
||||
#endif
|
||||
m_backend.d.swap = [](VideoBackend* v) {
|
||||
PainterGL* painter = static_cast<PainterGL*>(v->user);
|
||||
painter->m_gl->swapBuffers();
|
||||
|
|
|
@ -9,11 +9,16 @@
|
|||
#include "Display.h"
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <QMouseEvent>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
extern "C" {
|
||||
#ifdef BUILD_GL
|
||||
#include "platform/opengl/gl.h"
|
||||
#elif defined(BUILD_GLES2)
|
||||
#include "platform/opengl/gles2.h"
|
||||
#endif
|
||||
}
|
||||
|
||||
struct GBAThread;
|
||||
|
@ -27,6 +32,7 @@ public:
|
|||
protected:
|
||||
void paintEvent(QPaintEvent*) override {}
|
||||
void resizeEvent(QResizeEvent*) override {}
|
||||
void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); }
|
||||
};
|
||||
|
||||
class PainterGL;
|
||||
|
@ -37,6 +43,8 @@ public:
|
|||
DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
|
||||
~DisplayGL();
|
||||
|
||||
bool isDrawing() const override { return m_isDrawing; }
|
||||
|
||||
public slots:
|
||||
void startDrawing(GBAThread* context) override;
|
||||
void stopDrawing() override;
|
||||
|
@ -54,6 +62,7 @@ protected:
|
|||
private:
|
||||
void resizePainter();
|
||||
|
||||
bool m_isDrawing;
|
||||
QGLWidget* m_gl;
|
||||
PainterGL* m_painter;
|
||||
QThread* m_drawThread;
|
||||
|
@ -88,7 +97,11 @@ private:
|
|||
QGLWidget* m_gl;
|
||||
bool m_active;
|
||||
GBAThread* m_context;
|
||||
#ifdef BUILD_GL
|
||||
GBAGLContext m_backend;
|
||||
#elif defined(BUILD_GLES2)
|
||||
GBAGLES2Context m_backend;
|
||||
#endif
|
||||
QSize m_size;
|
||||
MessagePainter* m_messagePainter;
|
||||
};
|
||||
|
|
|
@ -15,11 +15,13 @@ using namespace QGBA;
|
|||
|
||||
DisplayQt::DisplayQt(QWidget* parent)
|
||||
: Display(parent)
|
||||
, m_isDrawing(false)
|
||||
, m_backing(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void DisplayQt::startDrawing(GBAThread*) {
|
||||
m_isDrawing = true;
|
||||
}
|
||||
|
||||
void DisplayQt::lockAspectRatio(bool lock) {
|
||||
|
|
|
@ -21,11 +21,13 @@ Q_OBJECT
|
|||
public:
|
||||
DisplayQt(QWidget* parent = nullptr);
|
||||
|
||||
bool isDrawing() const override { return m_isDrawing; }
|
||||
|
||||
public slots:
|
||||
void startDrawing(GBAThread* context) override;
|
||||
void stopDrawing() override {}
|
||||
void pauseDrawing() override {}
|
||||
void unpauseDrawing() override {}
|
||||
void stopDrawing() override { m_isDrawing = false; }
|
||||
void pauseDrawing() override { m_isDrawing = false; }
|
||||
void unpauseDrawing() override { m_isDrawing = true; }
|
||||
void forceDraw() override { update(); }
|
||||
void lockAspectRatio(bool lock) override;
|
||||
void filter(bool filter) override;
|
||||
|
@ -35,6 +37,7 @@ protected:
|
|||
virtual void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
bool m_isDrawing;
|
||||
QImage m_backing;
|
||||
};
|
||||
|
||||
|
|
|
@ -45,31 +45,32 @@ GBAApp::GBAApp(int& argc, char* argv[])
|
|||
Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("displayDriver").toInt()));
|
||||
}
|
||||
|
||||
GBAArguments args;
|
||||
bool loaded = m_configController.parseArguments(&args, argc, argv);
|
||||
if (loaded && args.showHelp) {
|
||||
usage(argv[0], 0);
|
||||
::exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
Window* w = new Window(&m_configController);
|
||||
connect(w, &Window::destroyed, [this]() {
|
||||
m_windows[0] = nullptr;
|
||||
});
|
||||
m_windows[0] = w;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
|
||||
GBAArguments args;
|
||||
if (m_configController.parseArguments(&args, argc, argv)) {
|
||||
if (loaded) {
|
||||
w->argumentsPassed(&args);
|
||||
} else {
|
||||
w->loadConfig();
|
||||
}
|
||||
freeArguments(&args);
|
||||
w->show();
|
||||
|
||||
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
|
||||
w->controller()->reloadAudioDriver();
|
||||
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
#ifdef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GBAApp::event(QEvent* event) {
|
||||
|
@ -91,14 +92,9 @@ Window* GBAApp::newWindow() {
|
|||
});
|
||||
m_windows[windowId] = w;
|
||||
w->setAttribute(Qt::WA_DeleteOnClose);
|
||||
#ifndef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
w->loadConfig();
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
#ifdef Q_OS_MAC
|
||||
w->show();
|
||||
#endif
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
return w;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "GBAKeyEditor.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
|
@ -17,13 +18,14 @@
|
|||
using namespace QGBA;
|
||||
|
||||
const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247;
|
||||
const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
|
||||
const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
|
||||
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
|
||||
const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432;
|
||||
const qreal GBAKeyEditor::DPAD_WIDTH = 0.12;
|
||||
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12;
|
||||
|
||||
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_profileSelect(nullptr)
|
||||
, m_clear(nullptr)
|
||||
, m_type(type)
|
||||
, m_profile(profile)
|
||||
, m_controller(controller)
|
||||
|
@ -32,6 +34,7 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
|||
setMinimumSize(300, 300);
|
||||
|
||||
const GBAInputMap* map = controller->map();
|
||||
controller->stealFocus(this);
|
||||
|
||||
m_keyDU = new KeyEditor(this);
|
||||
m_keyDD = new KeyEditor(this);
|
||||
|
@ -64,31 +67,36 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
|||
m_controller->loadProfile(m_type, m_profile);
|
||||
refresh();
|
||||
});
|
||||
|
||||
m_clear = new QWidget(this);
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
m_clear->setLayout(layout);
|
||||
layout->setSpacing(6);
|
||||
|
||||
QPushButton* clearButton = new QPushButton(tr("Clear Button"));
|
||||
layout->addWidget(clearButton);
|
||||
connect(clearButton, &QAbstractButton::pressed, [this]() {
|
||||
if (!findFocus()) {
|
||||
return;
|
||||
}
|
||||
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
|
||||
(*m_currentKey)->clearButton();
|
||||
(*m_currentKey)->blockSignals(signalsBlocked);
|
||||
});
|
||||
|
||||
QPushButton* clearAxis = new QPushButton(tr("Clear Analog"));
|
||||
layout->addWidget(clearAxis);
|
||||
connect(clearAxis, &QAbstractButton::pressed, [this]() {
|
||||
if (!findFocus()) {
|
||||
return;
|
||||
}
|
||||
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
|
||||
(*m_currentKey)->clearAxis();
|
||||
(*m_currentKey)->blockSignals(signalsBlocked);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyDD, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyDL, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyDR, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keySelect, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyStart, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyA, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyB, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyL, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(m_keyR, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
|
||||
connect(m_keyDU, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyDD, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyDL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyDR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keySelect, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyStart, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyA, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyB, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
connect(m_keyR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
|
||||
m_buttons = new QWidget(this);
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
m_buttons->setLayout(layout);
|
||||
|
@ -115,6 +123,11 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
|
|||
m_keyR
|
||||
};
|
||||
|
||||
for (auto& key : m_keyOrder) {
|
||||
connect(key, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
connect(key, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
|
||||
}
|
||||
|
||||
m_currentKey = m_keyOrder.end();
|
||||
|
||||
m_background.load(":/res/keymap.qpic");
|
||||
|
@ -135,13 +148,17 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
|
|||
setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y);
|
||||
setLocation(m_keySelect, 0.415, 0.93);
|
||||
setLocation(m_keyStart, 0.585, 0.93);
|
||||
setLocation(m_keyA, 0.826, 0.451);
|
||||
setLocation(m_keyB, 0.667, 0.490);
|
||||
setLocation(m_keyA, 0.826, 0.475);
|
||||
setLocation(m_keyB, 0.667, 0.514);
|
||||
setLocation(m_keyL, 0.1, 0.1);
|
||||
setLocation(m_keyR, 0.9, 0.1);
|
||||
|
||||
if (m_profileSelect) {
|
||||
setLocation(m_profileSelect, 0.5, 0.7);
|
||||
setLocation(m_profileSelect, 0.5, 0.67);
|
||||
}
|
||||
|
||||
if (m_clear) {
|
||||
setLocation(m_clear, 0.5, 0.77);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +168,19 @@ void GBAKeyEditor::paintEvent(QPaintEvent* event) {
|
|||
painter.drawPicture(0, 0, m_background);
|
||||
}
|
||||
|
||||
void GBAKeyEditor::closeEvent(QCloseEvent*) {
|
||||
m_controller->releaseFocus(this);
|
||||
}
|
||||
|
||||
bool GBAKeyEditor::event(QEvent* event) {
|
||||
if (event->type() == QEvent::WindowActivate) {
|
||||
m_controller->stealFocus(this);
|
||||
} else if (event->type() == QEvent::WindowDeactivate) {
|
||||
m_controller->releaseFocus(this);
|
||||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void GBAKeyEditor::setNext() {
|
||||
findFocus();
|
||||
|
||||
|
@ -167,6 +197,10 @@ void GBAKeyEditor::setNext() {
|
|||
}
|
||||
|
||||
void GBAKeyEditor::save() {
|
||||
#ifdef BUILD_SDL
|
||||
m_controller->unbindAllAxes(m_type);
|
||||
#endif
|
||||
|
||||
bindKey(m_keyDU, GBA_KEY_UP);
|
||||
bindKey(m_keyDD, GBA_KEY_DOWN);
|
||||
bindKey(m_keyDL, GBA_KEY_LEFT);
|
||||
|
@ -239,14 +273,11 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) {
|
|||
|
||||
void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
|
||||
#ifdef BUILD_SDL
|
||||
if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) {
|
||||
m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key);
|
||||
} else {
|
||||
#endif
|
||||
m_controller->bindKey(m_type, keyEditor->value(), key);
|
||||
#ifdef BUILD_SDL
|
||||
if (m_type == SDL_BINDING_BUTTON) {
|
||||
m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key);
|
||||
}
|
||||
#endif
|
||||
m_controller->bindKey(m_type, keyEditor->value(), key);
|
||||
}
|
||||
|
||||
bool GBAKeyEditor::findFocus() {
|
||||
|
|
|
@ -35,6 +35,8 @@ public slots:
|
|||
protected:
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private slots:
|
||||
void setNext();
|
||||
|
@ -64,6 +66,7 @@ private:
|
|||
KeyEditor* keyById(GBAKey);
|
||||
|
||||
QComboBox* m_profileSelect;
|
||||
QWidget* m_clear;
|
||||
QWidget* m_buttons;
|
||||
KeyEditor* m_keyDU;
|
||||
KeyEditor* m_keyDD;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "AudioProcessor.h"
|
||||
#include "InputController.h"
|
||||
#include "LogController.h"
|
||||
#include "MultiplayerController.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
|
@ -28,8 +29,6 @@ extern "C" {
|
|||
using namespace QGBA;
|
||||
using namespace std;
|
||||
|
||||
const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };
|
||||
|
||||
GameController::GameController(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_drawContext(new uint32_t[256 * 256])
|
||||
|
@ -48,9 +47,13 @@ GameController::GameController(QObject* parent)
|
|||
, m_turboForced(false)
|
||||
, m_turboSpeed(-1)
|
||||
, m_wasPaused(false)
|
||||
, m_audioChannels{ true, true, true, true, true, true }
|
||||
, m_videoLayers{ true, true, true, true, true }
|
||||
, m_inputController(nullptr)
|
||||
, m_multiplayer(nullptr)
|
||||
, m_stateSlot(1)
|
||||
, m_backupLoadState(nullptr)
|
||||
, m_backupSaveState(nullptr)
|
||||
{
|
||||
m_renderer = new GBAVideoSoftwareRenderer;
|
||||
GBAVideoSoftwareRendererCreate(m_renderer);
|
||||
|
@ -89,7 +92,25 @@ GameController::GameController(QObject* parent)
|
|||
context->gba->rtcSource = &controller->m_rtc.d;
|
||||
context->gba->rumble = controller->m_inputController->rumble();
|
||||
context->gba->rotationSource = controller->m_inputController->rotationSource();
|
||||
context->gba->audio.forceDisableCh[0] = !controller->m_audioChannels[0];
|
||||
context->gba->audio.forceDisableCh[1] = !controller->m_audioChannels[1];
|
||||
context->gba->audio.forceDisableCh[2] = !controller->m_audioChannels[2];
|
||||
context->gba->audio.forceDisableCh[3] = !controller->m_audioChannels[3];
|
||||
context->gba->audio.forceDisableChA = !controller->m_audioChannels[4];
|
||||
context->gba->audio.forceDisableChB = !controller->m_audioChannels[5];
|
||||
context->gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0];
|
||||
context->gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1];
|
||||
context->gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2];
|
||||
context->gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3];
|
||||
context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4];
|
||||
controller->m_fpsTarget = context->fpsTarget;
|
||||
|
||||
if (GBALoadState(context, context->stateDir, 0)) {
|
||||
VFile* vf = GBAGetState(context->gba, context->stateDir, 0, true);
|
||||
if (vf) {
|
||||
vf->truncate(vf, 0);
|
||||
}
|
||||
}
|
||||
controller->gameStarted(context);
|
||||
};
|
||||
|
||||
|
@ -100,19 +121,33 @@ GameController::GameController(QObject* parent)
|
|||
|
||||
m_threadContext.frameCallback = [](GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
|
||||
GBAThreadPauseFromThread(context);
|
||||
controller->gamePaused(&controller->m_threadContext);
|
||||
}
|
||||
if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) {
|
||||
controller->frameAvailable(controller->m_drawContext);
|
||||
} else {
|
||||
controller->frameAvailable(nullptr);
|
||||
}
|
||||
if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
|
||||
GBAThreadPauseFromThread(context);
|
||||
controller->gamePaused(&controller->m_threadContext);
|
||||
}
|
||||
};
|
||||
|
||||
m_threadContext.stopCallback = [](GBAThread* context) {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
if (!GBASaveState(context, context->stateDir, 0, true)) {
|
||||
return false;
|
||||
}
|
||||
QMetaObject::invokeMethod(controller, "closeGame");
|
||||
return true;
|
||||
};
|
||||
|
||||
m_threadContext.logHandler = [](GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
|
||||
static const char* stubMessage = "Stub software interrupt";
|
||||
static const char* stubMessage = "Stub software interrupt: %02X";
|
||||
static const char* savestateMessage = "State %i loaded";
|
||||
static const char* savestateFailedMessage = "State %i failed to load";
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
@ -123,6 +158,25 @@ GameController::GameController(QObject* parent)
|
|||
int immediate = va_arg(argc, int);
|
||||
va_end(argc);
|
||||
controller->unimplementedBiosCall(immediate);
|
||||
} else if (level == GBA_LOG_STATUS) {
|
||||
// Slot 0 is reserved for suspend points
|
||||
if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
|
||||
va_list argc;
|
||||
va_copy(argc, args);
|
||||
int slot = va_arg(argc, int);
|
||||
va_end(argc);
|
||||
if (slot == 0) {
|
||||
format = "Loaded suspend state";
|
||||
}
|
||||
} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) {
|
||||
va_list argc;
|
||||
va_copy(argc, args);
|
||||
int slot = va_arg(argc, int);
|
||||
va_end(argc);
|
||||
if (slot == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (level == GBA_LOG_FATAL) {
|
||||
QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
|
||||
|
@ -162,6 +216,7 @@ GameController::~GameController() {
|
|||
GBACheatDeviceDestroy(&m_cheatDevice);
|
||||
delete m_renderer;
|
||||
delete[] m_drawContext;
|
||||
delete m_backupLoadState;
|
||||
}
|
||||
|
||||
void GameController::setMultiplayerController(MultiplayerController* controller) {
|
||||
|
@ -284,6 +339,7 @@ void GameController::openGame(bool biosOnly) {
|
|||
}
|
||||
|
||||
m_inputController->recalibrateAxes();
|
||||
memset(m_drawContext, 0xF8, 1024 * 256);
|
||||
|
||||
if (!GBAThreadStart(&m_threadContext)) {
|
||||
m_gameOpen = false;
|
||||
|
@ -409,8 +465,7 @@ void GameController::setPaused(bool paused) {
|
|||
return;
|
||||
}
|
||||
if (paused) {
|
||||
GBAThreadPause(&m_threadContext);
|
||||
emit gamePaused(&m_threadContext);
|
||||
m_pauseAfterFrame.testAndSetRelaxed(false, true);
|
||||
} else {
|
||||
GBAThreadUnpause(&m_threadContext);
|
||||
emit gameUnpaused(&m_threadContext);
|
||||
|
@ -475,7 +530,9 @@ void GameController::startRewinding() {
|
|||
return;
|
||||
}
|
||||
m_wasPaused = isPaused();
|
||||
bool signalsBlocked = blockSignals(true);
|
||||
setPaused(true);
|
||||
blockSignals(signalsBlocked);
|
||||
m_rewindTimer.start();
|
||||
}
|
||||
|
||||
|
@ -484,7 +541,9 @@ void GameController::stopRewinding() {
|
|||
return;
|
||||
}
|
||||
m_rewindTimer.stop();
|
||||
bool signalsBlocked = blockSignals(true);
|
||||
setPaused(m_wasPaused);
|
||||
blockSignals(signalsBlocked);
|
||||
}
|
||||
|
||||
void GameController::keyPressed(int key) {
|
||||
|
@ -532,6 +591,49 @@ void GameController::setAudioBufferSamples(int samples) {
|
|||
QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
|
||||
}
|
||||
|
||||
void GameController::setAudioChannelEnabled(int channel, bool enable) {
|
||||
if (channel > 5 || channel < 0) {
|
||||
return;
|
||||
}
|
||||
m_audioChannels[channel] = enable;
|
||||
if (m_gameOpen) {
|
||||
switch (channel) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
m_threadContext.gba->audio.forceDisableCh[channel] = !enable;
|
||||
break;
|
||||
case 4:
|
||||
m_threadContext.gba->audio.forceDisableChA = !enable;
|
||||
break;
|
||||
case 5:
|
||||
m_threadContext.gba->audio.forceDisableChB = !enable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::setVideoLayerEnabled(int layer, bool enable) {
|
||||
if (layer > 4 || layer < 0) {
|
||||
return;
|
||||
}
|
||||
m_videoLayers[layer] = enable;
|
||||
if (m_gameOpen) {
|
||||
switch (layer) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
m_threadContext.gba->video.renderer->disableBG[layer] = !enable;
|
||||
break;
|
||||
case 4:
|
||||
m_threadContext.gba->video.renderer->disableOBJ = !enable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::setFPSTarget(float fps) {
|
||||
threadInterrupt();
|
||||
m_fpsTarget = fps;
|
||||
|
@ -556,11 +658,16 @@ void GameController::setUseBIOS(bool use) {
|
|||
}
|
||||
|
||||
void GameController::loadState(int slot) {
|
||||
if (slot > 0) {
|
||||
if (slot > 0 && slot != m_stateSlot) {
|
||||
m_stateSlot = slot;
|
||||
m_backupSaveState.clear();
|
||||
}
|
||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
if (!controller->m_backupLoadState) {
|
||||
controller->m_backupLoadState = new GBASerializedState;
|
||||
}
|
||||
GBASerialize(context->gba, controller->m_backupLoadState);
|
||||
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
|
||||
controller->frameAvailable(controller->m_drawContext);
|
||||
controller->stateLoaded(context);
|
||||
|
@ -574,10 +681,50 @@ void GameController::saveState(int slot) {
|
|||
}
|
||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, false);
|
||||
if (vf) {
|
||||
controller->m_backupSaveState.resize(vf->size(vf));
|
||||
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
||||
vf->close(vf);
|
||||
}
|
||||
GBASaveState(context, context->stateDir, controller->m_stateSlot, true);
|
||||
});
|
||||
}
|
||||
|
||||
void GameController::loadBackupState() {
|
||||
if (!m_backupLoadState) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
if (GBADeserialize(context->gba, controller->m_backupLoadState)) {
|
||||
GBALog(context->gba, GBA_LOG_STATUS, "Undid state load");
|
||||
controller->frameAvailable(controller->m_drawContext);
|
||||
controller->stateLoaded(context);
|
||||
}
|
||||
delete controller->m_backupLoadState;
|
||||
controller->m_backupLoadState = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void GameController::saveBackupState() {
|
||||
if (m_backupSaveState.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, true);
|
||||
if (vf) {
|
||||
vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
|
||||
vf->close(vf);
|
||||
GBALog(context->gba, GBA_LOG_STATUS, "Undid state save");
|
||||
}
|
||||
controller->m_backupSaveState.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void GameController::setVideoSync(bool set) {
|
||||
m_videoSync = set;
|
||||
if (!m_turbo) {
|
||||
|
@ -701,7 +848,7 @@ void GameController::setLuminanceValue(uint8_t value) {
|
|||
value = std::max<int>(value - 0x16, 0);
|
||||
m_luxLevel = 10;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (value < LUX_LEVELS[i]) {
|
||||
if (value < GBA_LUX_LEVELS[i]) {
|
||||
m_luxLevel = i;
|
||||
break;
|
||||
}
|
||||
|
@ -713,7 +860,7 @@ void GameController::setLuminanceLevel(int level) {
|
|||
int value = 0x16;
|
||||
level = std::max(0, std::min(10, level));
|
||||
if (level > 0) {
|
||||
value += LUX_LEVELS[level - 1];
|
||||
value += GBA_LUX_LEVELS[level - 1];
|
||||
}
|
||||
setLuminanceValue(value);
|
||||
}
|
||||
|
@ -746,7 +893,7 @@ void GameController::redoSamples(int samples) {
|
|||
if (m_threadContext.gba) {
|
||||
sampleRate = m_threadContext.gba->audio.sampleRate;
|
||||
}
|
||||
ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100);
|
||||
ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, m_audioProcess->sampleRate());
|
||||
m_threadContext.audioBuffers = ceil(samples / ratio);
|
||||
#else
|
||||
m_threadContext.audioBuffers = samples;
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
void threadContinue();
|
||||
|
||||
bool isPaused();
|
||||
bool isLoaded() { return m_gameOpen; }
|
||||
bool isLoaded() { return m_gameOpen && GBAThreadIsActive(&m_threadContext); }
|
||||
|
||||
bool audioSync() const { return m_audioSync; }
|
||||
bool videoSync() const { return m_videoSync; }
|
||||
|
@ -72,6 +72,8 @@ public:
|
|||
|
||||
void setOptions(const GBAOptions*);
|
||||
|
||||
int stateSlot() const { return m_stateSlot; }
|
||||
|
||||
#ifdef USE_GDB_STUB
|
||||
ARMDebugger* debugger();
|
||||
void setDebugger(ARMDebugger*);
|
||||
|
@ -117,9 +119,13 @@ public slots:
|
|||
void keyReleased(int key);
|
||||
void clearKeys();
|
||||
void setAudioBufferSamples(int samples);
|
||||
void setAudioChannelEnabled(int channel, bool enable = true);
|
||||
void setVideoLayerEnabled(int layer, bool enable = true);
|
||||
void setFPSTarget(float fps);
|
||||
void loadState(int slot = 0);
|
||||
void saveState(int slot = 0);
|
||||
void loadBackupState();
|
||||
void saveBackupState();
|
||||
void setVideoSync(bool);
|
||||
void setAudioSync(bool);
|
||||
void setFrameskip(int);
|
||||
|
@ -191,7 +197,12 @@ private:
|
|||
QTimer m_rewindTimer;
|
||||
bool m_wasPaused;
|
||||
|
||||
bool m_audioChannels[6];
|
||||
bool m_videoLayers[5];
|
||||
|
||||
int m_stateSlot;
|
||||
GBASerializedState* m_backupLoadState;
|
||||
QByteArray m_backupSaveState;
|
||||
|
||||
InputController* m_inputController;
|
||||
MultiplayerController* m_multiplayer;
|
||||
|
@ -203,8 +214,6 @@ private:
|
|||
uint8_t m_luxValue;
|
||||
int m_luxLevel;
|
||||
|
||||
static const int LUX_LEVELS[10];
|
||||
|
||||
GBARTCGenericSource m_rtc;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "ConfigController.h"
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputProfile.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTimer>
|
||||
|
@ -24,7 +25,7 @@ int InputController::s_sdlInited = 0;
|
|||
GBASDLEvents InputController::s_sdlEvents;
|
||||
#endif
|
||||
|
||||
InputController::InputController(int playerId, QObject* parent)
|
||||
InputController::InputController(int playerId, QWidget* topLevel, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_playerId(playerId)
|
||||
, m_config(nullptr)
|
||||
|
@ -33,6 +34,8 @@ InputController::InputController(int playerId, QObject* parent)
|
|||
, m_playerAttached(false)
|
||||
#endif
|
||||
, m_allowOpposing(false)
|
||||
, m_topLevel(topLevel)
|
||||
, m_focusParent(topLevel)
|
||||
{
|
||||
GBAInputMapInit(&m_inputMap);
|
||||
|
||||
|
@ -106,8 +109,15 @@ void InputController::loadConfiguration(uint32_t type) {
|
|||
}
|
||||
|
||||
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
||||
GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
||||
bool loaded = GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
||||
recalibrateAxes();
|
||||
if (!loaded) {
|
||||
const InputProfile* ip = InputProfile::findProfile(profile);
|
||||
if (ip) {
|
||||
ip->apply(this);
|
||||
}
|
||||
}
|
||||
emit profileLoaded(profile);
|
||||
}
|
||||
|
||||
void InputController::saveConfiguration() {
|
||||
|
@ -198,9 +208,11 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device)
|
|||
|
||||
GBARumble* InputController::rumble() {
|
||||
#ifdef BUILD_SDL
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
if (m_playerAttached) {
|
||||
return &m_sdlPlayer.rumble.d;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -403,6 +415,10 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct
|
|||
GBAInputBindAxis(&m_inputMap, type, axis, &description);
|
||||
}
|
||||
|
||||
void InputController::unbindAllAxes(uint32_t type) {
|
||||
GBAInputUnbindAllAxes(&m_inputMap, type);
|
||||
}
|
||||
|
||||
void InputController::testGamepad(int type) {
|
||||
auto activeAxes = activeGamepadAxes(type);
|
||||
auto oldAxes = m_activeAxes;
|
||||
|
@ -424,7 +440,7 @@ void InputController::testGamepad(int type) {
|
|||
if (newlyAboveThreshold) {
|
||||
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
|
||||
postPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
sendGamepadEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
clearPendingEvent(event->gbaKey());
|
||||
}
|
||||
|
@ -433,7 +449,7 @@ void InputController::testGamepad(int type) {
|
|||
for (auto axis : oldAxes) {
|
||||
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
|
||||
clearPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
sendGamepadEvent(event);
|
||||
}
|
||||
|
||||
if (!QApplication::focusWidget()) {
|
||||
|
@ -446,7 +462,7 @@ void InputController::testGamepad(int type) {
|
|||
for (int button : activeButtons) {
|
||||
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
|
||||
postPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
sendGamepadEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
clearPendingEvent(event->gbaKey());
|
||||
}
|
||||
|
@ -454,10 +470,23 @@ void InputController::testGamepad(int type) {
|
|||
for (int button : oldButtons) {
|
||||
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
|
||||
clearPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
sendGamepadEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void InputController::sendGamepadEvent(QEvent* event) {
|
||||
QWidget* focusWidget = nullptr;
|
||||
if (m_focusParent) {
|
||||
focusWidget = m_focusParent->focusWidget();
|
||||
if (!focusWidget) {
|
||||
focusWidget = m_focusParent;
|
||||
}
|
||||
} else {
|
||||
focusWidget = QApplication::focusWidget();
|
||||
}
|
||||
QApplication::sendEvent(focusWidget, event);
|
||||
}
|
||||
|
||||
void InputController::postPendingEvent(GBAKey key) {
|
||||
m_pendingEvents.insert(key);
|
||||
}
|
||||
|
@ -470,16 +499,36 @@ bool InputController::hasPendingEvent(GBAKey key) const {
|
|||
return m_pendingEvents.contains(key);
|
||||
}
|
||||
|
||||
#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
void InputController::suspendScreensaver() {
|
||||
#ifdef BUILD_SDL
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
GBASDLSuspendScreensaver(&s_sdlEvents);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::resumeScreensaver() {
|
||||
#ifdef BUILD_SDL
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
GBASDLResumeScreensaver(&s_sdlEvents);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::setScreensaverSuspendable(bool suspendable) {
|
||||
#ifdef BUILD_SDL
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::stealFocus(QWidget* focus) {
|
||||
m_focusParent = focus;
|
||||
}
|
||||
|
||||
void InputController::releaseFocus(QWidget* focus) {
|
||||
if (focus == m_focusParent) {
|
||||
m_focusParent = m_topLevel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ Q_OBJECT
|
|||
public:
|
||||
static const uint32_t KEYBOARD = 0x51545F4B;
|
||||
|
||||
InputController(int playerId = 0, QObject* parent = nullptr);
|
||||
InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
|
||||
~InputController();
|
||||
|
||||
void setConfiguration(ConfigController* config);
|
||||
|
@ -60,6 +60,7 @@ public:
|
|||
void recalibrateAxes();
|
||||
|
||||
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
|
||||
void unbindAllAxes(uint32_t type);
|
||||
|
||||
QStringList connectedGamepads(uint32_t type) const;
|
||||
int gamepad(uint32_t type) const;
|
||||
|
@ -74,28 +75,35 @@ public:
|
|||
float gyroSensitivity() const;
|
||||
void setGyroSensitivity(float sensitivity);
|
||||
|
||||
void stealFocus(QWidget* focus);
|
||||
void releaseFocus(QWidget* focus);
|
||||
|
||||
GBARumble* rumble();
|
||||
GBARotationSource* rotationSource();
|
||||
|
||||
signals:
|
||||
void profileLoaded(const QString& profile);
|
||||
|
||||
public slots:
|
||||
void testGamepad(int type);
|
||||
|
||||
#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
// TODO: Move these to somewhere that makes sense
|
||||
void suspendScreensaver();
|
||||
void resumeScreensaver();
|
||||
void setScreensaverSuspendable(bool);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void postPendingEvent(GBAKey);
|
||||
void clearPendingEvent(GBAKey);
|
||||
bool hasPendingEvent(GBAKey) const;
|
||||
void sendGamepadEvent(QEvent*);
|
||||
|
||||
GBAInputMap m_inputMap;
|
||||
ConfigController* m_config;
|
||||
int m_playerId;
|
||||
bool m_allowOpposing;
|
||||
QWidget* m_topLevel;
|
||||
QWidget* m_focusParent;
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
static int s_sdlInited;
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/* 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 "InputProfile.h"
|
||||
|
||||
#include "InputController.h"
|
||||
|
||||
#include <QRegExp>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
const InputProfile InputProfile::s_defaultMaps[] = {
|
||||
{
|
||||
"XInput Controller #\\d+", // XInput (Windows)
|
||||
(int[GBA_KEY_MAX]) {
|
||||
/*keyA */ 11,
|
||||
/*keyB */ 10,
|
||||
/*keySelect */ 5,
|
||||
/*keyStart */ 4,
|
||||
/*keyRight */ 3,
|
||||
/*keyLeft */ 2,
|
||||
/*keyUp */ 0,
|
||||
/*keyDown */ 1,
|
||||
/*keyR */ 9,
|
||||
/*keyL */ 8
|
||||
},
|
||||
(ShortcutButton[]) {
|
||||
{"loadState", 12},
|
||||
{"saveState", 13},
|
||||
{}
|
||||
},
|
||||
(ShortcutAxis[]) {
|
||||
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 4},
|
||||
{}
|
||||
}
|
||||
},
|
||||
{
|
||||
"(Microsoft X-Box 360 pad|Xbox Gamepad \\(userspace driver\\))", // Linux
|
||||
(int[GBA_KEY_MAX]) {
|
||||
/*keyA */ 1,
|
||||
/*keyB */ 0,
|
||||
/*keySelect */ 6,
|
||||
/*keyStart */ 7,
|
||||
/*keyRight */ -1,
|
||||
/*keyLeft */ -1,
|
||||
/*keyUp */ -1,
|
||||
/*keyDown */ -1,
|
||||
/*keyR */ 5,
|
||||
/*keyL */ 4
|
||||
},
|
||||
(ShortcutButton[]) {
|
||||
{"loadState", 2},
|
||||
{"saveState", 3},
|
||||
{}
|
||||
},
|
||||
(ShortcutAxis[]) {
|
||||
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||
{}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Controller", // The Xbox 360 controller drivers on OS X are vague...
|
||||
(int[GBA_KEY_MAX]) {
|
||||
/*keyA */ 1,
|
||||
/*keyB */ 0,
|
||||
/*keySelect */ 9,
|
||||
/*keyStart */ 8,
|
||||
/*keyRight */ 14,
|
||||
/*keyLeft */ 13,
|
||||
/*keyUp */ 11,
|
||||
/*keyDown */ 12,
|
||||
/*keyR */ 5,
|
||||
/*keyL */ 4
|
||||
},
|
||||
(ShortcutButton[]) {
|
||||
{"loadState", 2},
|
||||
{"saveState", 3},
|
||||
{}
|
||||
},
|
||||
(ShortcutAxis[]) {
|
||||
{"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
{"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||
{}
|
||||
}
|
||||
},
|
||||
{
|
||||
"PLAYSTATION\\(R\\)3 Controller", // DualShock 3 (OS X)
|
||||
(int[GBA_KEY_MAX]) {
|
||||
/*keyA */ 13,
|
||||
/*keyB */ 14,
|
||||
/*keySelect */ 0,
|
||||
/*keyStart */ 3,
|
||||
/*keyRight */ 5,
|
||||
/*keyLeft */ 7,
|
||||
/*keyUp */ 4,
|
||||
/*keyDown */ 6,
|
||||
/*keyR */ 11,
|
||||
/*keyL */ 10
|
||||
},
|
||||
(ShortcutButton[]) {
|
||||
{"loadState", 15},
|
||||
{"saveState", 12},
|
||||
{"holdFastForward", 9},
|
||||
{"holdRewind", 8},
|
||||
{}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Wiimote \\(..-..-..-..-..-..\\)", // WJoy (OS X)
|
||||
(int[GBA_KEY_MAX]) {
|
||||
/*keyA */ 15,
|
||||
/*keyB */ 16,
|
||||
/*keySelect */ 7,
|
||||
/*keyStart */ 6,
|
||||
/*keyRight */ 14,
|
||||
/*keyLeft */ 13,
|
||||
/*keyUp */ 11,
|
||||
/*keyDown */ 12,
|
||||
/*keyR */ 20,
|
||||
/*keyL */ 19
|
||||
},
|
||||
(ShortcutButton[]) {
|
||||
{"loadState", 18},
|
||||
{"saveState", 17},
|
||||
{"holdFastForward", 22},
|
||||
{"holdRewind", 21},
|
||||
{}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
constexpr InputProfile::InputProfile(const char* name,
|
||||
int keys[GBA_KEY_MAX],
|
||||
const ShortcutButton* shortcutButtons,
|
||||
const ShortcutAxis* shortcutAxes,
|
||||
AxisValue axes[GBA_KEY_MAX],
|
||||
const struct Coord& tiltAxis,
|
||||
const struct Coord& gyroAxis,
|
||||
float gyroSensitivity)
|
||||
: m_profileName(name)
|
||||
, m_keys {
|
||||
keys[GBA_KEY_A],
|
||||
keys[GBA_KEY_B],
|
||||
keys[GBA_KEY_SELECT],
|
||||
keys[GBA_KEY_START],
|
||||
keys[GBA_KEY_RIGHT],
|
||||
keys[GBA_KEY_LEFT],
|
||||
keys[GBA_KEY_UP],
|
||||
keys[GBA_KEY_DOWN],
|
||||
keys[GBA_KEY_R],
|
||||
keys[GBA_KEY_L]
|
||||
}
|
||||
, m_shortcutButtons(shortcutButtons)
|
||||
, m_shortcutAxes(shortcutAxes)
|
||||
, m_axes {
|
||||
axes[GBA_KEY_A],
|
||||
axes[GBA_KEY_B],
|
||||
axes[GBA_KEY_SELECT],
|
||||
axes[GBA_KEY_START],
|
||||
axes[GBA_KEY_RIGHT],
|
||||
axes[GBA_KEY_LEFT],
|
||||
axes[GBA_KEY_UP],
|
||||
axes[GBA_KEY_DOWN],
|
||||
axes[GBA_KEY_R],
|
||||
axes[GBA_KEY_L]
|
||||
}
|
||||
, m_tiltAxis(tiltAxis)
|
||||
, m_gyroAxis(gyroAxis)
|
||||
, m_gyroSensitivity(gyroSensitivity)
|
||||
{
|
||||
}
|
||||
|
||||
const InputProfile* InputProfile::findProfile(const QString& name) {
|
||||
for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) {
|
||||
QRegExp re(s_defaultMaps[i].m_profileName);
|
||||
if (re.exactMatch(name)) {
|
||||
return &s_defaultMaps[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InputProfile::apply(InputController* controller) const {
|
||||
for (size_t i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
#ifdef BUILD_SDL
|
||||
controller->bindKey(SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i));
|
||||
controller->bindAxis(SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i));
|
||||
#endif
|
||||
}
|
||||
controller->registerTiltAxisX(m_tiltAxis.x);
|
||||
controller->registerTiltAxisY(m_tiltAxis.y);
|
||||
controller->registerGyroAxisX(m_gyroAxis.x);
|
||||
controller->registerGyroAxisY(m_gyroAxis.y);
|
||||
controller->setGyroSensitivity(m_gyroSensitivity);
|
||||
}
|
||||
|
||||
bool InputProfile::lookupShortcutButton(const QString& shortcutName, int* button) const {
|
||||
for (size_t i = 0; m_shortcutButtons[i].shortcut; ++i) {
|
||||
const ShortcutButton& shortcut = m_shortcutButtons[i];
|
||||
if (QLatin1String(shortcut.shortcut) == shortcutName) {
|
||||
*button = shortcut.button;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputProfile::lookupShortcutAxis(const QString& shortcutName, int* axis, GamepadAxisEvent::Direction* direction) const {
|
||||
for (size_t i = 0; m_shortcutAxes[i].shortcut; ++i) {
|
||||
const ShortcutAxis& shortcut = m_shortcutAxes[i];
|
||||
if (QLatin1String(shortcut.shortcut) == shortcutName) {
|
||||
*axis = shortcut.axis;
|
||||
*direction = shortcut.direction;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_PROFILE
|
||||
#define QGBA_INPUT_PROFILE
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba/interface.h"
|
||||
}
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class InputController;
|
||||
|
||||
class InputProfile {
|
||||
public:
|
||||
static const InputProfile* findProfile(const QString& name);
|
||||
|
||||
void apply(InputController*) const;
|
||||
bool lookupShortcutButton(const QString& shortcut, int* button) const;
|
||||
bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const;
|
||||
|
||||
private:
|
||||
struct Coord {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
struct AxisValue {
|
||||
GamepadAxisEvent::Direction direction;
|
||||
int axis;
|
||||
};
|
||||
|
||||
struct ShortcutButton {
|
||||
const char* shortcut;
|
||||
int button;
|
||||
};
|
||||
|
||||
struct ShortcutAxis {
|
||||
const char* shortcut;
|
||||
GamepadAxisEvent::Direction direction;
|
||||
int axis;
|
||||
};
|
||||
|
||||
constexpr InputProfile(const char* name,
|
||||
int keys[GBA_KEY_MAX],
|
||||
const ShortcutButton* shortcutButtons = (ShortcutButton[]) {{}},
|
||||
const ShortcutAxis* shortcutAxes = (ShortcutAxis[]) {{}},
|
||||
AxisValue axes[GBA_KEY_MAX] = (AxisValue[GBA_KEY_MAX]) {
|
||||
{}, {}, {}, {},
|
||||
{ GamepadAxisEvent::Direction::POSITIVE, 0 },
|
||||
{ GamepadAxisEvent::Direction::NEGATIVE, 0 },
|
||||
{ GamepadAxisEvent::Direction::NEGATIVE, 1 },
|
||||
{ GamepadAxisEvent::Direction::POSITIVE, 1 },
|
||||
{}, {}},
|
||||
const struct Coord& tiltAxis = { 2, 3 },
|
||||
const struct Coord& gyroAxis = { 0, 1 },
|
||||
float gyroSensitivity = 2e+09f);
|
||||
|
||||
static const InputProfile s_defaultMaps[];
|
||||
|
||||
const char* m_profileName;
|
||||
const int m_keys[GBA_KEY_MAX];
|
||||
const AxisValue m_axes[GBA_KEY_MAX];
|
||||
const ShortcutButton* m_shortcutButtons;
|
||||
const ShortcutAxis* m_shortcutAxes;
|
||||
Coord m_tiltAxis;
|
||||
Coord m_gyroAxis;
|
||||
float m_gyroSensitivity;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -15,22 +15,20 @@ using namespace QGBA;
|
|||
KeyEditor::KeyEditor(QWidget* parent)
|
||||
: QLineEdit(parent)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_key(-1)
|
||||
, m_axis(-1)
|
||||
, m_button(false)
|
||||
{
|
||||
setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
void KeyEditor::setValue(int key) {
|
||||
m_key = key;
|
||||
if (m_button) {
|
||||
if (key < 0) {
|
||||
clear();
|
||||
} else {
|
||||
setText(QString::number(key));
|
||||
}
|
||||
updateButtonText();
|
||||
} else {
|
||||
setText(QKeySequence(key).toString(QKeySequence::NativeText));
|
||||
}
|
||||
m_key = key;
|
||||
emit valueChanged(key);
|
||||
}
|
||||
|
||||
|
@ -41,18 +39,30 @@ void KeyEditor::setValueKey(int key) {
|
|||
|
||||
void KeyEditor::setValueButton(int button) {
|
||||
m_button = true;
|
||||
m_direction = GamepadAxisEvent::NEUTRAL;
|
||||
setValue(button);
|
||||
}
|
||||
|
||||
void KeyEditor::setValueAxis(int axis, int32_t value) {
|
||||
m_button = true;
|
||||
m_key = axis;
|
||||
m_axis = axis;
|
||||
m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE;
|
||||
setText((value < 0 ? "-" : "+") + QString::number(axis));
|
||||
updateButtonText();
|
||||
emit axisChanged(axis, m_direction);
|
||||
}
|
||||
|
||||
void KeyEditor::clearButton() {
|
||||
m_button = true;
|
||||
setValue(-1);
|
||||
}
|
||||
|
||||
void KeyEditor::clearAxis() {
|
||||
m_button = true;
|
||||
m_axis = -1;
|
||||
m_direction = GamepadAxisEvent::NEUTRAL;
|
||||
updateButtonText();
|
||||
emit axisChanged(m_axis, m_direction);
|
||||
}
|
||||
|
||||
QSize KeyEditor::sizeHint() const {
|
||||
QSize hint = QLineEdit::sizeHint();
|
||||
hint.setWidth(40);
|
||||
|
@ -85,3 +95,18 @@ bool KeyEditor::event(QEvent* event) {
|
|||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void KeyEditor::updateButtonText() {
|
||||
QStringList text;
|
||||
if (m_key >= 0) {
|
||||
text.append(QString::number(m_key));
|
||||
}
|
||||
if (m_direction != GamepadAxisEvent::NEUTRAL) {
|
||||
text.append((m_direction == GamepadAxisEvent::NEGATIVE ? "-" : "+") + QString::number(m_axis));
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
setText(tr("---"));
|
||||
} else {
|
||||
setText(text.join("/"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
int value() const { return m_key; }
|
||||
|
||||
GamepadAxisEvent::Direction direction() const { return m_direction; }
|
||||
int axis() const { return m_axis; }
|
||||
|
||||
virtual QSize sizeHint() const override;
|
||||
|
||||
|
@ -28,6 +29,8 @@ public slots:
|
|||
void setValueKey(int key);
|
||||
void setValueButton(int button);
|
||||
void setValueAxis(int axis, int32_t value);
|
||||
void clearButton();
|
||||
void clearAxis();
|
||||
|
||||
signals:
|
||||
void valueChanged(int key);
|
||||
|
@ -38,7 +41,10 @@ protected:
|
|||
virtual bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void updateButtonText();
|
||||
|
||||
int m_key;
|
||||
int m_axis;
|
||||
bool m_button;
|
||||
GamepadAxisEvent::Direction m_direction;
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ using namespace QGBA;
|
|||
LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_controller(controller)
|
||||
, m_currentFocus(0)
|
||||
, m_currentFocus(controller->stateSlot() - 1)
|
||||
, m_mode(LoadSave::LOAD)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
@ -45,6 +45,13 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
|
|||
connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
|
||||
}
|
||||
|
||||
if (m_currentFocus >= 9) {
|
||||
m_currentFocus = 0;
|
||||
}
|
||||
if (m_currentFocus < 0) {
|
||||
m_currentFocus = 0;
|
||||
}
|
||||
|
||||
QAction* escape = new QAction(this);
|
||||
escape->connect(escape, SIGNAL(triggered()), this, SLOT(close()));
|
||||
escape->setShortcut(QKeySequence("Esc"));
|
||||
|
|
|
@ -39,6 +39,7 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
|
|||
connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||
connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||
connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||
connect(m_ui.hwGBPlayer, SIGNAL(clicked()), this, SLOT(updateOverrides()));
|
||||
|
||||
connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride()));
|
||||
|
||||
|
@ -80,6 +81,9 @@ void OverrideView::updateOverrides() {
|
|||
m_override.hardware |= HW_RUMBLE;
|
||||
}
|
||||
}
|
||||
if (m_ui.hwGBPlayer->isChecked()) {
|
||||
m_override.hardware |= HW_GB_PLAYER_DETECTION;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
uint32_t parsedIdleLoop = m_ui.idleLoop->text().toInt(&ok, 16);
|
||||
|
@ -115,6 +119,7 @@ void OverrideView::gameStarted(GBAThread* thread) {
|
|||
m_ui.hwLight->setChecked(thread->gba->memory.hw.devices & HW_LIGHT_SENSOR);
|
||||
m_ui.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT);
|
||||
m_ui.hwRumble->setChecked(thread->gba->memory.hw.devices & HW_RUMBLE);
|
||||
m_ui.hwGBPlayer->setChecked(thread->gba->memory.hw.devices & HW_GB_PLAYER_DETECTION);
|
||||
|
||||
if (thread->gba->idleLoop != IDLE_LOOP_NONE) {
|
||||
m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16));
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>409</width>
|
||||
<height>228</height>
|
||||
<width>401</width>
|
||||
<height>203</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -23,13 +23,19 @@
|
|||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item row="2" column="1">
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -44,7 +50,135 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="3">
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Save type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="savetype">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Autodetect</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>SRAM</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Flash 512kb</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Flash 1Mb</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>EEPROM</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Idle loop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="idleLoop"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="hwGBPlayer">
|
||||
<property name="text">
|
||||
<string>Game Boy Player features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" rowspan="3">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string/>
|
||||
|
@ -113,83 +247,6 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Save type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="savetype">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Autodetect</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>SRAM</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Flash 512kb</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Flash 1Mb</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>EEPROM</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Idle loop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="idleLoop"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
|
|
@ -36,6 +36,19 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
|
|||
loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||
loadSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
||||
|
||||
double fastForwardRatio = loadSetting("fastForwardRatio").toDouble();
|
||||
if (fastForwardRatio <= 0) {
|
||||
m_ui.fastForwardUnbounded->setChecked(true);
|
||||
m_ui.fastForwardRatio->setEnabled(false);
|
||||
} else {
|
||||
m_ui.fastForwardUnbounded->setChecked(false);
|
||||
m_ui.fastForwardRatio->setEnabled(true);
|
||||
m_ui.fastForwardRatio->setValue(fastForwardRatio);
|
||||
}
|
||||
connect(m_ui.fastForwardUnbounded, &QAbstractButton::toggled, [this](bool checked) {
|
||||
m_ui.fastForwardRatio->setEnabled(!checked);
|
||||
});
|
||||
|
||||
QString idleOptimization = loadSetting("idleOptimization");
|
||||
if (idleOptimization == "ignore") {
|
||||
m_ui.idleOptimization->setCurrentIndex(0);
|
||||
|
@ -103,6 +116,12 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||
saveSetting("suspendScreensaver", m_ui.suspendScreensaver);
|
||||
|
||||
if (m_ui.fastForwardUnbounded->isChecked()) {
|
||||
saveSetting("fastForwardRatio", "-1");
|
||||
} else {
|
||||
saveSetting("fastForwardRatio", m_ui.fastForwardRatio);
|
||||
}
|
||||
|
||||
switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) {
|
||||
case IDLE_LOOP_IGNORE:
|
||||
saveSetting("idleOptimization", "ignore");
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>698</width>
|
||||
<height>366</height>
|
||||
<width>707</width>
|
||||
<height>420</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -380,21 +380,21 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="allowOpposingDirections">
|
||||
<property name="text">
|
||||
<string>Allow opposing input directions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="suspendScreensaver">
|
||||
<property name="text">
|
||||
<string>Suspend screensaver</string>
|
||||
|
@ -404,14 +404,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Idle loops</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="idleOptimization">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
@ -430,6 +430,52 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QDoubleSpinBox" name="fastForwardRatio">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>20.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Fast forward speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="fastForwardUnbounded">
|
||||
<property name="text">
|
||||
<string>Unbounded</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "ConfigController.h"
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputProfile.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QKeyEvent>
|
||||
|
@ -18,6 +19,7 @@ ShortcutController::ShortcutController(QObject* parent)
|
|||
: QAbstractItemModel(parent)
|
||||
, m_rootMenu(nullptr)
|
||||
, m_config(nullptr)
|
||||
, m_profile(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -239,6 +241,9 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) {
|
|||
}
|
||||
if (m_config) {
|
||||
m_config->setQtOption(item->name(), button, BUTTON_SECTION);
|
||||
if (!m_profileName.isNull()) {
|
||||
m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
|
||||
}
|
||||
}
|
||||
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||
createIndex(index.row(), 2, index.internalPointer()));
|
||||
|
@ -272,6 +277,9 @@ void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadA
|
|||
d = '-';
|
||||
}
|
||||
m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
|
||||
if (!m_profileName.isNull()) {
|
||||
m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
|
||||
}
|
||||
}
|
||||
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||
createIndex(index.row(), 2, index.internalPointer()));
|
||||
|
@ -365,6 +373,9 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
|
|||
}
|
||||
|
||||
void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return;
|
||||
}
|
||||
QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
|
||||
if (!shortcut.isNull()) {
|
||||
QKeySequence keySequence(shortcut.toString());
|
||||
|
@ -377,19 +388,45 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
|||
}
|
||||
item->setShortcut(keySequence);
|
||||
}
|
||||
QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION);
|
||||
if (!button.isNull()) {
|
||||
int oldButton = item->button();
|
||||
item->setButton(button.toInt());
|
||||
if (oldButton >= 0) {
|
||||
m_buttons.take(oldButton);
|
||||
loadGamepadShortcuts(item);
|
||||
}
|
||||
|
||||
void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return;
|
||||
}
|
||||
QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
|
||||
int oldButton = item->button();
|
||||
if (oldButton >= 0) {
|
||||
m_buttons.take(oldButton);
|
||||
item->setButton(-1);
|
||||
}
|
||||
if (button.isNull() && m_profile) {
|
||||
int buttonInt;
|
||||
if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
|
||||
button = buttonInt;
|
||||
}
|
||||
}
|
||||
if (!button.isNull()) {
|
||||
item->setButton(button.toInt());
|
||||
m_buttons[button.toInt()] = item;
|
||||
}
|
||||
QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION);
|
||||
|
||||
QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
|
||||
int oldAxis = item->axis();
|
||||
GamepadAxisEvent::Direction oldDirection = item->direction();
|
||||
if (oldAxis >= 0) {
|
||||
m_axes.take(qMakePair(oldAxis, oldDirection));
|
||||
item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
|
||||
}
|
||||
if (axis.isNull() && m_profile) {
|
||||
int axisInt;
|
||||
GamepadAxisEvent::Direction direction;
|
||||
if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
|
||||
axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
|
||||
}
|
||||
}
|
||||
if (!axis.isNull()) {
|
||||
int oldAxis = item->axis();
|
||||
GamepadAxisEvent::Direction oldDirection = item->direction();
|
||||
QString axisDesc = axis.toString();
|
||||
if (axisDesc.size() >= 2) {
|
||||
GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
|
||||
|
@ -403,9 +440,6 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) {
|
|||
int axis = axisDesc.mid(1).toInt(&ok);
|
||||
if (ok) {
|
||||
item->setAxis(axis, direction);
|
||||
if (oldAxis >= 0) {
|
||||
m_axes.take(qMakePair(oldAxis, oldDirection));
|
||||
}
|
||||
m_axes[qMakePair(axis, direction)] = item;
|
||||
}
|
||||
}
|
||||
|
@ -432,6 +466,21 @@ QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
|
|||
return QKeySequence(modifier + key);
|
||||
}
|
||||
|
||||
void ShortcutController::loadProfile(const QString& profile) {
|
||||
m_profileName = profile;
|
||||
m_profile = InputProfile::findProfile(profile);
|
||||
onSubitems(&m_rootMenu, [this](ShortcutItem* item) {
|
||||
loadGamepadShortcuts(item);
|
||||
});
|
||||
}
|
||||
|
||||
void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) {
|
||||
for (ShortcutItem& subitem : item->items()) {
|
||||
func(&subitem);
|
||||
onSubitems(&subitem, func);
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent)
|
||||
: m_action(action)
|
||||
, m_shortcut(action->shortcut())
|
||||
|
|
|
@ -21,6 +21,7 @@ class QString;
|
|||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
class InputProfile;
|
||||
|
||||
class ShortcutController : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
@ -29,6 +30,8 @@ private:
|
|||
constexpr static const char* const KEY_SECTION = "shortcutKey";
|
||||
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
|
||||
constexpr static const char* const AXIS_SECTION = "shortcutAxis";
|
||||
constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton.";
|
||||
constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis.";
|
||||
|
||||
class ShortcutItem {
|
||||
public:
|
||||
|
@ -84,6 +87,7 @@ public:
|
|||
ShortcutController(QObject* parent = nullptr);
|
||||
|
||||
void setConfigController(ConfigController* controller);
|
||||
void setProfile(const QString& profile);
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
@ -99,6 +103,8 @@ public:
|
|||
const QKeySequence& shortcut, const QString& visibleName, const QString& name);
|
||||
void addMenu(QMenu* menu, QMenu* parent = nullptr);
|
||||
|
||||
QAction* getAction(const QString& name);
|
||||
|
||||
QKeySequence shortcutAt(const QModelIndex& index) const;
|
||||
bool isMenuAt(const QModelIndex& index) const;
|
||||
|
||||
|
@ -111,6 +117,9 @@ public:
|
|||
|
||||
static QKeySequence keyEventToSequence(const QKeyEvent*);
|
||||
|
||||
public slots:
|
||||
void loadProfile(const QString& profile);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
|
@ -118,6 +127,8 @@ private:
|
|||
ShortcutItem* itemAt(const QModelIndex& index);
|
||||
const ShortcutItem* itemAt(const QModelIndex& index) const;
|
||||
void loadShortcuts(ShortcutItem*);
|
||||
void loadGamepadShortcuts(ShortcutItem*);
|
||||
void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func);
|
||||
|
||||
ShortcutItem m_rootMenu;
|
||||
QMap<QMenu*, ShortcutItem*> m_menuMap;
|
||||
|
@ -125,6 +136,8 @@ private:
|
|||
QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes;
|
||||
QMap<QKeySequence, ShortcutItem*> m_heldKeys;
|
||||
ConfigController* m_config;
|
||||
QString m_profileName;
|
||||
const InputProfile* m_profile;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "ShortcutView.h"
|
||||
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputController.h"
|
||||
#include "ShortcutController.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
@ -15,6 +16,7 @@ using namespace QGBA;
|
|||
ShortcutView::ShortcutView(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_controller(nullptr)
|
||||
, m_input(nullptr)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.keyEdit->setValueButton(-1);
|
||||
|
@ -32,6 +34,14 @@ void ShortcutView::setController(ShortcutController* controller) {
|
|||
m_ui.shortcutTable->setModel(controller);
|
||||
}
|
||||
|
||||
void ShortcutView::setInputController(InputController* controller) {
|
||||
if (m_input) {
|
||||
m_input->releaseFocus(this);
|
||||
}
|
||||
m_input = controller;
|
||||
m_input->stealFocus(this);
|
||||
}
|
||||
|
||||
bool ShortcutView::eventFilter(QObject*, QEvent* event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
|
@ -111,3 +121,20 @@ void ShortcutView::updateAxis(int axis, int direction) {
|
|||
m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis,
|
||||
static_cast<GamepadAxisEvent::Direction>(direction));
|
||||
}
|
||||
|
||||
void ShortcutView::closeEvent(QCloseEvent*) {
|
||||
if (m_input) {
|
||||
m_input->releaseFocus(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShortcutView::event(QEvent* event) {
|
||||
if (m_input) {
|
||||
if (event->type() == QEvent::WindowActivate) {
|
||||
m_input->stealFocus(this);
|
||||
} else if (event->type() == QEvent::WindowDeactivate) {
|
||||
m_input->releaseFocus(this);
|
||||
}
|
||||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace QGBA {
|
||||
|
||||
class InputController;
|
||||
class ShortcutController;
|
||||
|
||||
class ShortcutView : public QWidget {
|
||||
|
@ -23,9 +24,12 @@ public:
|
|||
ShortcutView(QWidget* parent = nullptr);
|
||||
|
||||
void setController(ShortcutController* controller);
|
||||
void setInputController(InputController* input);
|
||||
|
||||
protected:
|
||||
virtual bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private slots:
|
||||
void load(const QModelIndex&);
|
||||
|
@ -38,6 +42,7 @@ private:
|
|||
Ui::ShortcutView m_ui;
|
||||
|
||||
ShortcutController* m_controller;
|
||||
InputController* m_input;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QPainter>
|
||||
#include <QStackedLayout>
|
||||
|
||||
#include "AboutScreen.h"
|
||||
#include "CheatsView.h"
|
||||
#include "ConfigController.h"
|
||||
#include "Display.h"
|
||||
|
@ -40,8 +41,8 @@ extern "C" {
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
#ifdef __WIN32
|
||||
// This is a macro everywhere except MinGW, it seems
|
||||
#if defined(__WIN32) || defined(__OpenBSD__)
|
||||
// This is a macro everywhere except MinGW and OpenBSD, it seems
|
||||
using std::isnan;
|
||||
#endif
|
||||
|
||||
|
@ -53,7 +54,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
, m_screenWidget(new WindowBackground())
|
||||
, m_logo(":/res/mgba-1024.png")
|
||||
, m_config(config)
|
||||
, m_inputController(playerId)
|
||||
, m_inputController(playerId, this)
|
||||
#ifdef USE_FFMPEG
|
||||
, m_videoView(nullptr)
|
||||
#endif
|
||||
|
@ -128,6 +129,15 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
|
||||
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
|
||||
connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS()));
|
||||
connect(m_display, &Display::hideCursor, [this]() {
|
||||
if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display) {
|
||||
m_screenWidget->setCursor(Qt::BlankCursor);
|
||||
}
|
||||
});
|
||||
connect(m_display, &Display::showCursor, [this]() {
|
||||
m_screenWidget->unsetCursor();
|
||||
});
|
||||
connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&)));
|
||||
|
||||
m_log.setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS);
|
||||
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
|
||||
|
@ -162,6 +172,7 @@ void Window::argumentsPassed(GBAArguments* args) {
|
|||
|
||||
void Window::resizeFrame(int width, int height) {
|
||||
QSize newSize(width, height);
|
||||
m_screenWidget->setSizeHint(newSize);
|
||||
newSize -= m_screenWidget->size();
|
||||
newSize += size();
|
||||
resize(newSize);
|
||||
|
@ -296,6 +307,7 @@ void Window::openSettingsWindow() {
|
|||
SettingsView* settingsWindow = new SettingsView(m_config);
|
||||
connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&)));
|
||||
connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver()));
|
||||
connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart()));
|
||||
openView(settingsWindow);
|
||||
}
|
||||
|
||||
|
@ -305,6 +317,7 @@ void Window::openShortcutWindow() {
|
|||
#endif
|
||||
ShortcutView* shortcutView = new ShortcutView();
|
||||
shortcutView->setController(m_shortcutController);
|
||||
shortcutView->setInputController(&m_inputController);
|
||||
openView(shortcutView);
|
||||
}
|
||||
|
||||
|
@ -333,6 +346,11 @@ void Window::openMemoryWindow() {
|
|||
openView(memoryWindow);
|
||||
}
|
||||
|
||||
void Window::openAboutScreen() {
|
||||
AboutScreen* about = new AboutScreen();
|
||||
openView(about);
|
||||
}
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
void Window::openGamepadWindow() {
|
||||
const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON);
|
||||
|
@ -409,14 +427,34 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
|
|||
event->accept();
|
||||
}
|
||||
|
||||
void Window::resizeEvent(QResizeEvent*) {
|
||||
void Window::resizeEvent(QResizeEvent* event) {
|
||||
if (!isFullScreen()) {
|
||||
m_config->setOption("height", m_screenWidget->height());
|
||||
m_config->setOption("width", m_screenWidget->width());
|
||||
}
|
||||
|
||||
int factor = 0;
|
||||
if (event->size().width() % VIDEO_HORIZONTAL_PIXELS == 0 && event->size().height() % VIDEO_VERTICAL_PIXELS == 0 &&
|
||||
event->size().width() / VIDEO_HORIZONTAL_PIXELS == event->size().height() / VIDEO_VERTICAL_PIXELS) {
|
||||
factor = event->size().width() / VIDEO_HORIZONTAL_PIXELS;
|
||||
}
|
||||
for (QMap<int, QAction*>::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) {
|
||||
bool enableSignals = iter.value()->blockSignals(true);
|
||||
if (iter.key() == factor) {
|
||||
iter.value()->setChecked(true);
|
||||
} else {
|
||||
iter.value()->setChecked(false);
|
||||
}
|
||||
iter.value()->blockSignals(enableSignals);
|
||||
}
|
||||
|
||||
m_config->setOption("fullscreen", isFullScreen());
|
||||
}
|
||||
|
||||
void Window::showEvent(QShowEvent* event) {
|
||||
resizeFrame(m_screenWidget->sizeHint().width(), m_screenWidget->sizeHint().height());
|
||||
}
|
||||
|
||||
void Window::closeEvent(QCloseEvent* event) {
|
||||
emit shutdown();
|
||||
m_config->setQtOption("windowPos", pos());
|
||||
|
@ -424,6 +462,10 @@ void Window::closeEvent(QCloseEvent* event) {
|
|||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
void Window::focusInEvent(QFocusEvent*) {
|
||||
m_display->forceDraw();
|
||||
}
|
||||
|
||||
void Window::focusOutEvent(QFocusEvent*) {
|
||||
m_controller->setTurbo(false, false);
|
||||
m_controller->stopRewinding();
|
||||
|
@ -464,7 +506,6 @@ void Window::enterFullScreen() {
|
|||
return;
|
||||
}
|
||||
showFullScreen();
|
||||
setCursor(Qt::BlankCursor);
|
||||
#ifndef Q_OS_MAC
|
||||
if (m_controller->isLoaded() && !m_controller->isPaused()) {
|
||||
menuBar()->hide();
|
||||
|
@ -476,7 +517,7 @@ void Window::exitFullScreen() {
|
|||
if (!isFullScreen()) {
|
||||
return;
|
||||
}
|
||||
unsetCursor();
|
||||
m_screenWidget->unsetCursor();
|
||||
menuBar()->show();
|
||||
showNormal();
|
||||
}
|
||||
|
@ -504,6 +545,7 @@ void Window::gameStarted(GBAThread* context) {
|
|||
action->setDisabled(false);
|
||||
}
|
||||
if (context->fname) {
|
||||
setWindowFilePath(context->fname);
|
||||
appendMRU(context->fname);
|
||||
}
|
||||
updateTitle();
|
||||
|
@ -523,10 +565,12 @@ void Window::gameStopped() {
|
|||
foreach (QAction* action, m_gameActions) {
|
||||
action->setDisabled(true);
|
||||
}
|
||||
setWindowFilePath(QString());
|
||||
updateTitle();
|
||||
detachWidget(m_display);
|
||||
m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height());
|
||||
m_screenWidget->setPixmap(m_logo);
|
||||
m_screenWidget->unsetCursor();
|
||||
|
||||
m_fpsTimer.stop();
|
||||
}
|
||||
|
@ -561,6 +605,23 @@ void Window::unimplementedBiosCall(int call) {
|
|||
fail->show();
|
||||
}
|
||||
|
||||
void Window::tryMakePortable() {
|
||||
QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"),
|
||||
tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"),
|
||||
QMessageBox::Yes | QMessageBox::Cancel, this, Qt::Sheet);
|
||||
confirm->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(confirm->button(QMessageBox::Yes), SIGNAL(clicked()), m_config, SLOT(makePortable()));
|
||||
confirm->show();
|
||||
}
|
||||
|
||||
void Window::mustRestart() {
|
||||
QMessageBox* dialog = new QMessageBox(QMessageBox::Warning, tr("Restart needed"),
|
||||
tr("Some changes will not take effect until the emulator is restarted."),
|
||||
QMessageBox::Ok, this, Qt::Sheet);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void Window::recordFrame() {
|
||||
m_frameList.append(QDateTime::currentDateTime());
|
||||
while (m_frameList.count() > FRAME_LIST_SIZE) {
|
||||
|
@ -642,6 +703,10 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
fileMenu->addSeparator();
|
||||
|
||||
addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable");
|
||||
|
||||
fileMenu->addSeparator();
|
||||
|
||||
QAction* loadState = new QAction(tr("&Load state"), fileMenu);
|
||||
loadState->setShortcut(tr("F10"));
|
||||
connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); });
|
||||
|
@ -672,6 +737,21 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
quickLoadMenu->addSeparator();
|
||||
quickSaveMenu->addSeparator();
|
||||
|
||||
QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu);
|
||||
undoLoadState->setShortcut(tr("F11"));
|
||||
connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState()));
|
||||
m_gameActions.append(undoLoadState);
|
||||
addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState");
|
||||
|
||||
QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu);
|
||||
undoSaveState->setShortcut(tr("Shift+F11"));
|
||||
connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState()));
|
||||
m_gameActions.append(undoSaveState);
|
||||
addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState");
|
||||
|
||||
quickLoadMenu->addSeparator();
|
||||
quickSaveMenu->addSeparator();
|
||||
|
||||
int i;
|
||||
for (i = 1; i < 10; ++i) {
|
||||
quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
|
||||
|
@ -705,6 +785,14 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
});
|
||||
addControlledAction(fileMenu, multiWindow, "multiWindow");
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
fileMenu->addSeparator();
|
||||
#endif
|
||||
|
||||
QAction* about = new QAction(tr("About"), fileMenu);
|
||||
connect(about, SIGNAL(triggered()), this, SLOT(openAboutScreen()));
|
||||
fileMenu->addAction(about);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");
|
||||
#endif
|
||||
|
@ -850,10 +938,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_shortcutController->addMenu(frameMenu, avMenu);
|
||||
for (int i = 1; i <= 6; ++i) {
|
||||
QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu);
|
||||
setSize->setCheckable(true);
|
||||
connect(setSize, &QAction::triggered, [this, i]() {
|
||||
showNormal();
|
||||
resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i);
|
||||
});
|
||||
m_frameSizes[i] = setSize;
|
||||
addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i)));
|
||||
}
|
||||
QKeySequence fullscreenKeys;
|
||||
|
@ -933,14 +1023,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
QAction* recordOutput = new QAction(tr("Record output..."), avMenu);
|
||||
recordOutput->setShortcut(tr("F11"));
|
||||
connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow()));
|
||||
addControlledAction(avMenu, recordOutput, "recordOutput");
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu);
|
||||
recordGIF->setShortcut(tr("Shift+F11"));
|
||||
connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow()));
|
||||
addControlledAction(avMenu, recordGIF, "recordGIF");
|
||||
#endif
|
||||
|
@ -952,16 +1040,14 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers);
|
||||
enableBg->setCheckable(true);
|
||||
enableBg->setChecked(true);
|
||||
connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; });
|
||||
m_gameActions.append(enableBg);
|
||||
connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->setVideoLayerEnabled(i, enable); });
|
||||
addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i));
|
||||
}
|
||||
|
||||
QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers);
|
||||
enableObj->setCheckable(true);
|
||||
enableObj->setChecked(true);
|
||||
connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; });
|
||||
m_gameActions.append(enableObj);
|
||||
connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); });
|
||||
addControlledAction(videoLayers, enableObj, "enableOBJ");
|
||||
|
||||
QMenu* audioChannels = avMenu->addMenu(tr("Audio channels"));
|
||||
|
@ -970,23 +1056,20 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels);
|
||||
enableCh->setCheckable(true);
|
||||
enableCh->setChecked(true);
|
||||
connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; });
|
||||
m_gameActions.append(enableCh);
|
||||
connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(i, enable); });
|
||||
addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1));
|
||||
}
|
||||
|
||||
QAction* enableChA = new QAction(tr("Channel A"), audioChannels);
|
||||
enableChA->setCheckable(true);
|
||||
enableChA->setChecked(true);
|
||||
connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; });
|
||||
m_gameActions.append(enableChA);
|
||||
connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(4, enable); });
|
||||
addControlledAction(audioChannels, enableChA, QString("enableChA"));
|
||||
|
||||
QAction* enableChB = new QAction(tr("Channel B"), audioChannels);
|
||||
enableChB->setCheckable(true);
|
||||
enableChB->setChecked(true);
|
||||
connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; });
|
||||
m_gameActions.append(enableChB);
|
||||
connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); });
|
||||
addControlledAction(audioChannels, enableChB, QString("enableChB"));
|
||||
|
||||
QMenu* toolsMenu = menubar->addMenu(tr("&Tools"));
|
||||
|
@ -1088,6 +1171,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
void Window::attachWidget(QWidget* widget) {
|
||||
m_screenWidget->layout()->addWidget(widget);
|
||||
unsetCursor();
|
||||
static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
|
||||
}
|
||||
|
||||
|
@ -1169,10 +1253,10 @@ void WindowBackground::paintEvent(QPaintEvent*) {
|
|||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||
QSize s = size();
|
||||
QSize ds = s;
|
||||
if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) {
|
||||
ds.setWidth(s.height() * m_aspectWidth / m_aspectHeight);
|
||||
} else if (s.width() * m_aspectHeight < s.height() * m_aspectWidth) {
|
||||
ds.setHeight(s.width() * m_aspectHeight / m_aspectWidth);
|
||||
if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
|
||||
ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
|
||||
} else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
|
||||
ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
|
|
|
@ -82,6 +82,8 @@ public slots:
|
|||
void openPaletteWindow();
|
||||
void openMemoryWindow();
|
||||
|
||||
void openAboutScreen();
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
void openGamepadWindow();
|
||||
#endif
|
||||
|
@ -102,7 +104,9 @@ protected:
|
|||
virtual void keyPressEvent(QKeyEvent* event) override;
|
||||
virtual void keyReleaseEvent(QKeyEvent* event) override;
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual void showEvent(QShowEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
virtual void focusInEvent(QFocusEvent*) override;
|
||||
virtual void focusOutEvent(QFocusEvent*) override;
|
||||
virtual void dragEnterEvent(QDragEnterEvent*) override;
|
||||
virtual void dropEvent(QDropEvent*) override;
|
||||
|
@ -115,6 +119,9 @@ private slots:
|
|||
void gameFailed();
|
||||
void unimplementedBiosCall(int);
|
||||
|
||||
void tryMakePortable();
|
||||
void mustRestart();
|
||||
|
||||
void recordFrame();
|
||||
void showFPS();
|
||||
|
||||
|
@ -141,6 +148,7 @@ private:
|
|||
GameController* m_controller;
|
||||
Display* m_display;
|
||||
QList<QAction*> m_gameActions;
|
||||
QMap<int, QAction*> m_frameSizes;
|
||||
LogController m_log;
|
||||
LogView* m_logView;
|
||||
LoadSaveState* m_stateWindow;
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
#include <QtPlugin>
|
||||
#ifdef _WIN32
|
||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
Q_IMPORT_PLUGIN(QWindowsAudioPlugin);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QGBA::GBAApp application(argc, argv);
|
||||
|
|
|
@ -51,13 +51,16 @@ set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c)
|
|||
|
||||
if(BUILD_RASPI)
|
||||
add_definitions(-DBUILD_RASPI)
|
||||
set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c)
|
||||
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
||||
list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c)
|
||||
set(OPENGLES2_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
||||
set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline")
|
||||
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC})
|
||||
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC})
|
||||
set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
|
||||
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi)
|
||||
unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND
|
||||
endif()
|
||||
|
||||
if(BUILD_PANDORA)
|
||||
|
@ -66,13 +69,18 @@ else()
|
|||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)
|
||||
if(BUILD_GL)
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
|
||||
list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
|
||||
list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
|
||||
include_directories(${OPENGL_INCLUDE_DIR})
|
||||
endif()
|
||||
if(BUILD_GLES2)
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c)
|
||||
list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
|
||||
include_directories(${OPENGLES2_INCLUDE_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
|
||||
set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
|
||||
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
|
||||
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "main.h"
|
||||
|
||||
static const char* _vertexShader =
|
||||
"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.46875, -0.3125);\n"
|
||||
"}";
|
||||
|
||||
static const char* _fragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = texture2D(tex, texCoord);\n"
|
||||
" color.a = 1.;\n"
|
||||
" gl_FragColor = color;"
|
||||
"}";
|
||||
|
||||
static const GLfloat _vertices[] = {
|
||||
-1.f, -1.f,
|
||||
-1.f, 1.f,
|
||||
1.f, 1.f,
|
||||
1.f, -1.f,
|
||||
};
|
||||
|
||||
bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
bcm_host_init();
|
||||
renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
int major, minor;
|
||||
if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) {
|
||||
printf("Failed to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) {
|
||||
printf("Failed to get GLES API");
|
||||
return false;
|
||||
}
|
||||
|
||||
const EGLint requestConfig[] = {
|
||||
EGL_RED_SIZE, 5,
|
||||
EGL_GREEN_SIZE, 5,
|
||||
EGL_BLUE_SIZE, 5,
|
||||
EGL_ALPHA_SIZE, 1,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLConfig config;
|
||||
EGLint numConfigs;
|
||||
|
||||
if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) {
|
||||
printf("Failed to choose EGL config\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const EGLint contextAttributes[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
int dispWidth = 240, dispHeight = 160, adjWidth;
|
||||
renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes);
|
||||
graphics_get_display_size(0, &dispWidth, &dispHeight);
|
||||
adjWidth = dispHeight / 2 * 3;
|
||||
|
||||
DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0);
|
||||
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
|
||||
|
||||
VC_RECT_T destRect = {
|
||||
.x = (dispWidth - adjWidth) / 2,
|
||||
.y = 0,
|
||||
.width = adjWidth,
|
||||
.height = dispHeight
|
||||
};
|
||||
|
||||
VC_RECT_T srcRect = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 240 << 16,
|
||||
.height = 160 << 16
|
||||
};
|
||||
|
||||
DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0);
|
||||
vc_dispmanx_update_submit_sync(update);
|
||||
|
||||
renderer->window.element = element;
|
||||
renderer->window.width = dispWidth;
|
||||
renderer->window.height = dispHeight;
|
||||
|
||||
renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0);
|
||||
if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderer->d.outputBuffer = memalign(16, 256 * 256 * 4);
|
||||
renderer->d.outputBufferStride = 256;
|
||||
glGenTextures(1, &renderer->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->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);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
renderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
renderer->vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
renderer->program = glCreateProgram();
|
||||
|
||||
glShaderSource(renderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0);
|
||||
glShaderSource(renderer->vertexShader, 1, (const GLchar**) &_vertexShader, 0);
|
||||
glAttachShader(renderer->program, renderer->vertexShader);
|
||||
glAttachShader(renderer->program, renderer->fragmentShader);
|
||||
char log[1024];
|
||||
glCompileShader(renderer->fragmentShader);
|
||||
glCompileShader(renderer->vertexShader);
|
||||
glGetShaderInfoLog(renderer->fragmentShader, 1024, 0, log);
|
||||
glGetShaderInfoLog(renderer->vertexShader, 1024, 0, log);
|
||||
glLinkProgram(renderer->program);
|
||||
glGetProgramInfoLog(renderer->program, 1024, 0, log);
|
||||
printf("%s\n", log);
|
||||
renderer->texLocation = glGetUniformLocation(renderer->program, "tex");
|
||||
renderer->positionLocation = glGetAttribLocation(renderer->program, "position");
|
||||
glClearColor(1.f, 0.f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
|
||||
SDL_Event event;
|
||||
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
}
|
||||
|
||||
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
|
||||
glViewport(0, 0, 240, 160);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUseProgram(renderer->program);
|
||||
glUniform1i(renderer->texLocation, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer);
|
||||
glVertexAttribPointer(renderer->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
|
||||
glEnableVertexAttribArray(renderer->positionLocation);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glUseProgram(0);
|
||||
eglSwapBuffers(renderer->display, renderer->surface);
|
||||
}
|
||||
GBASyncWaitFrameEnd(&context->sync);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
|
||||
eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglDestroySurface(renderer->display, renderer->surface);
|
||||
eglDestroyContext(renderer->display, renderer->context);
|
||||
eglTerminate(renderer->display);
|
||||
bcm_host_deinit();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "main.h"
|
||||
|
||||
void GBASDLGLCommonSwap(struct VideoBackend* context) {
|
||||
struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GL_SwapWindow(renderer->window);
|
||||
#else
|
||||
UNUSED(renderer);
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer) {
|
||||
#ifndef COLOR_16_BIT
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||
#ifdef COLOR_5_6_5
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
|
||||
#endif
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
|
||||
renderer->glCtx = SDL_GL_CreateContext(renderer->window);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
renderer->player.window = renderer->window;
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
|
||||
#ifdef COLOR_16_BIT
|
||||
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL);
|
||||
#else
|
||||
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
|
||||
#endif
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef SDL_GL_COMMON_H
|
||||
#define SDL_GL_COMMON_H
|
||||
#include "main.h"
|
||||
|
||||
void GBASDLGLCommonSwap(struct VideoBackend* context);
|
||||
void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer);
|
||||
|
||||
#endif
|
|
@ -5,18 +5,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "main.h"
|
||||
|
||||
#include "gl-common.h"
|
||||
|
||||
#include "gba/supervisor/thread.h"
|
||||
#include "platform/opengl/gl.h"
|
||||
|
||||
static void _sdlSwap(struct VideoBackend* context) {
|
||||
struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GL_SwapWindow(renderer->window);
|
||||
#else
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _doViewport(int w, int h, struct VideoBackend* v) {
|
||||
v->resized(v, w, h);
|
||||
v->clear(v);
|
||||
|
@ -35,34 +28,7 @@ void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) {
|
|||
}
|
||||
|
||||
bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
#ifndef COLOR_16_BIT
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||
#ifdef COLOR_5_6_5
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
|
||||
#endif
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
|
||||
renderer->glCtx = SDL_GL_CreateContext(renderer->window);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
renderer->player.window = renderer->window;
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
|
||||
#ifdef COLOR_16_BIT
|
||||
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL);
|
||||
#else
|
||||
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
|
||||
#endif
|
||||
#endif
|
||||
GBASDLGLCommonInit(renderer);
|
||||
|
||||
renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL);
|
||||
renderer->d.outputBufferStride = 256;
|
||||
|
@ -71,7 +37,7 @@ bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) {
|
|||
renderer->gl.d.user = renderer;
|
||||
renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio;
|
||||
renderer->gl.d.filter = renderer->filter;
|
||||
renderer->gl.d.swap = _sdlSwap;
|
||||
renderer->gl.d.swap = GBASDLGLCommonSwap;
|
||||
renderer->gl.d.init(&renderer->gl.d, 0);
|
||||
|
||||
_doViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d);
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/* 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 "main.h"
|
||||
|
||||
#include "gl-common.h"
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
static bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer);
|
||||
static void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
|
||||
static void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer);
|
||||
|
||||
void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer) {
|
||||
renderer->init = GBASDLGLES2Init;
|
||||
renderer->deinit = GBASDLGLES2Deinit;
|
||||
renderer->runloop = GBASDLGLES2Runloop;
|
||||
}
|
||||
|
||||
bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer) {
|
||||
#ifdef BUILD_RASPI
|
||||
bcm_host_init();
|
||||
renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
int major, minor;
|
||||
if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) {
|
||||
printf("Failed to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) {
|
||||
printf("Failed to get GLES API");
|
||||
return false;
|
||||
}
|
||||
|
||||
const EGLint requestConfig[] = {
|
||||
EGL_RED_SIZE, 5,
|
||||
EGL_GREEN_SIZE, 5,
|
||||
EGL_BLUE_SIZE, 5,
|
||||
EGL_ALPHA_SIZE, 1,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLConfig config;
|
||||
EGLint numConfigs;
|
||||
|
||||
if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) {
|
||||
printf("Failed to choose EGL config\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const EGLint contextAttributes[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
int dispWidth = 240, dispHeight = 160, adjWidth;
|
||||
renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes);
|
||||
graphics_get_display_size(0, &dispWidth, &dispHeight);
|
||||
adjWidth = dispHeight / 2 * 3;
|
||||
|
||||
DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0);
|
||||
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
|
||||
|
||||
VC_RECT_T destRect = {
|
||||
.x = (dispWidth - adjWidth) / 2,
|
||||
.y = 0,
|
||||
.width = adjWidth,
|
||||
.height = dispHeight
|
||||
};
|
||||
|
||||
VC_RECT_T srcRect = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 240 << 16,
|
||||
.height = 160 << 16
|
||||
};
|
||||
|
||||
DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0);
|
||||
vc_dispmanx_update_submit_sync(update);
|
||||
|
||||
renderer->window.element = element;
|
||||
renderer->window.width = dispWidth;
|
||||
renderer->window.height = dispHeight;
|
||||
|
||||
renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0);
|
||||
if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
GBASDLGLCommonInit(renderer);
|
||||
#endif
|
||||
|
||||
renderer->d.outputBuffer = memalign(16, 256 * 256 * 4);
|
||||
renderer->d.outputBufferStride = 256;
|
||||
|
||||
GBAGLES2ContextCreate(&renderer->gl);
|
||||
renderer->gl.d.user = renderer;
|
||||
renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio;
|
||||
renderer->gl.d.filter = renderer->filter;
|
||||
renderer->gl.d.swap = GBASDLGLCommonSwap;
|
||||
renderer->gl.d.init(&renderer->gl.d, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
|
||||
SDL_Event event;
|
||||
struct VideoBackend* v = &renderer->gl.d;
|
||||
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
}
|
||||
|
||||
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
|
||||
v->postFrame(v, renderer->d.outputBuffer);
|
||||
}
|
||||
v->drawFrame(v);
|
||||
GBASyncWaitFrameEnd(&context->sync);
|
||||
#ifdef BUILD_RASPI
|
||||
eglSwapBuffers(renderer->display, renderer->surface);
|
||||
#else
|
||||
v->swap(v);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer) {
|
||||
if (renderer->gl.d.deinit) {
|
||||
renderer->gl.d.deinit(&renderer->gl.d);
|
||||
}
|
||||
#ifdef BUILD_RASPI
|
||||
eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglDestroySurface(renderer->display, renderer->surface);
|
||||
eglDestroyContext(renderer->display, renderer->context);
|
||||
eglTerminate(renderer->display);
|
||||
bcm_host_deinit();
|
||||
#elif SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GL_DeleteContext(renderer->glCtx);
|
||||
#endif
|
||||
free(renderer->d.outputBuffer);
|
||||
}
|
|
@ -86,6 +86,8 @@ int main(int argc, char** argv) {
|
|||
|
||||
#ifdef BUILD_GL
|
||||
GBASDLGLCreate(&renderer);
|
||||
#elif defined(BUILD_GLES2)
|
||||
GBASDLGLES2Create(&renderer);
|
||||
#else
|
||||
GBASDLSWCreate(&renderer);
|
||||
#endif
|
||||
|
|
|
@ -20,13 +20,16 @@
|
|||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#include <SDL/SDL.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <bcm_host.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_GLES2
|
||||
#include "platform/opengl/gles2.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_PIXMAN
|
||||
#include <pixman.h>
|
||||
#endif
|
||||
|
@ -57,6 +60,8 @@ struct SDLSoftwareRenderer {
|
|||
|
||||
#ifdef BUILD_GL
|
||||
struct GBAGLContext gl;
|
||||
#elif BUILD_GLES2
|
||||
struct GBAGLES2Context gl;
|
||||
#endif
|
||||
|
||||
#ifdef USE_PIXMAN
|
||||
|
@ -69,13 +74,6 @@ struct SDLSoftwareRenderer {
|
|||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
EGL_DISPMANX_WINDOW_T window;
|
||||
GLuint tex;
|
||||
GLuint fragmentShader;
|
||||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
GLuint bufferObject;
|
||||
GLuint texLocation;
|
||||
GLuint positionLocation;
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_PANDORA
|
||||
|
@ -90,4 +88,8 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer);
|
|||
#ifdef BUILD_GL
|
||||
void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer);
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_GLES2
|
||||
void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer);
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#endif
|
||||
|
||||
#define GYRO_STEPS 100
|
||||
#define RUMBLE_PWM 35
|
||||
#define RUMBLE_PWM 20
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
static void _GBASDLSetRumble(struct GBARumble* rumble, int enable);
|
||||
|
|
|
@ -25,6 +25,7 @@ static void GBAWiiFrame(void);
|
|||
static bool GBAWiiLoadGame(const char* path);
|
||||
|
||||
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _audioDMA(void);
|
||||
|
||||
static struct GBA gba;
|
||||
static struct ARMCore cpu;
|
||||
|
@ -41,9 +42,18 @@ static GXTexObj tex;
|
|||
static void* framebuffer[2];
|
||||
static int whichFb = 0;
|
||||
|
||||
static struct GBAStereoSample audioBuffer[2][SAMPLES] __attribute__ ((__aligned__(32)));
|
||||
static size_t audioBufferSize = 0;
|
||||
static int currentAudioBuffer = 0;
|
||||
|
||||
int main() {
|
||||
VIDEO_Init();
|
||||
PAD_Init();
|
||||
AUDIO_Init(0);
|
||||
AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
|
||||
AUDIO_RegisterDMACallback(_audioDMA);
|
||||
|
||||
memset(audioBuffer, 0, sizeof(audioBuffer));
|
||||
|
||||
#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
|
||||
#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
|
||||
|
@ -140,8 +150,8 @@ int main() {
|
|||
GBAAudioResizeBuffer(&gba.audio, SAMPLES);
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 44100);
|
||||
blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 44100);
|
||||
blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 48000);
|
||||
blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
|
||||
#endif
|
||||
|
||||
if (!GBAWiiLoadGame("/rom.gba")) {
|
||||
|
@ -149,6 +159,21 @@ int main() {
|
|||
}
|
||||
|
||||
while (true) {
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
int available = blip_samples_avail(gba.audio.left);
|
||||
if (available + audioBufferSize > SAMPLES) {
|
||||
available = SAMPLES - audioBufferSize;
|
||||
}
|
||||
if (available > 0) {
|
||||
blip_read_samples(gba.audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
|
||||
blip_read_samples(gba.audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
|
||||
audioBufferSize += available;
|
||||
}
|
||||
if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
|
||||
_audioDMA();
|
||||
AUDIO_StartDMA();
|
||||
}
|
||||
#endif
|
||||
PAD_ScanPads();
|
||||
u16 padkeys = PAD_ButtonsHeld(0);
|
||||
int keys = 0;
|
||||
|
@ -284,3 +309,13 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
UNUSED(renderer);
|
||||
GBAWiiFrame();
|
||||
}
|
||||
|
||||
static void _audioDMA(void) {
|
||||
if (!audioBufferSize) {
|
||||
return;
|
||||
}
|
||||
currentAudioBuffer = !currentAudioBuffer;
|
||||
DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
|
||||
AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
|
||||
audioBufferSize = 0;
|
||||
}
|
||||
|
|
|
@ -100,6 +100,10 @@ void ConfigurationClearValue(struct Configuration* configuration, const char* se
|
|||
HashTableRemove(currentSection, key);
|
||||
}
|
||||
|
||||
bool ConfigurationHasSection(const struct Configuration* configuration, const char* section) {
|
||||
return HashTableLookup(&configuration->sections, section);
|
||||
}
|
||||
|
||||
const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
|
||||
const struct Table* currentSection = &configuration->root;
|
||||
if (section) {
|
||||
|
|
|
@ -23,6 +23,7 @@ void ConfigurationSetIntValue(struct Configuration*, const char* section, const
|
|||
void ConfigurationSetUIntValue(struct Configuration*, const char* section, const char* key, unsigned value);
|
||||
void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
|
||||
|
||||
bool ConfigurationHasSection(const struct Configuration*, const char* section);
|
||||
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key);
|
||||
|
||||
void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);
|
||||
|
|
|
@ -15,11 +15,14 @@ int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) {
|
|||
int res = snprintf(str, size, "%*.g", FLT_DIG, f);
|
||||
uselocale(old);
|
||||
return res;
|
||||
#else
|
||||
#elif defined(HAVE_SETLOCALE)
|
||||
char* old = setlocale(LC_NUMERIC, locale);
|
||||
int res = snprintf(str, size, "%*.g", FLT_DIG, f);
|
||||
setlocale(LC_NUMERIC, old);
|
||||
return res;
|
||||
#else
|
||||
UNUSED(locale);
|
||||
return snprintf(str, size, "%*.g", FLT_DIG, f);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -30,11 +33,14 @@ float strtof_l(const char* restrict str, char** restrict end, locale_t locale) {
|
|||
float res = strtof(str, end);
|
||||
uselocale(old);
|
||||
return res;
|
||||
#else
|
||||
#elif defined(HAVE_SETLOCALE)
|
||||
char* old = setlocale(LC_NUMERIC, locale);
|
||||
float res = strtof(str, end);
|
||||
setlocale(LC_NUMERIC, old);
|
||||
return res;
|
||||
#else
|
||||
UNUSED(locale);
|
||||
return strtof(str, end);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -39,6 +39,7 @@ struct VFile {
|
|||
void (*unmap)(struct VFile* vf, void* memory, size_t size);
|
||||
void (*truncate)(struct VFile* vf, size_t size);
|
||||
ssize_t (*size)(struct VFile* vf);
|
||||
bool (*sync)(struct VFile* vf, const void* buffer, size_t size);
|
||||
};
|
||||
|
||||
struct VDirEntry {
|
||||
|
|
|
@ -30,6 +30,7 @@ static void* _vfdMap(struct VFile* vf, size_t size, int flags);
|
|||
static void _vfdUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void _vfdTruncate(struct VFile* vf, size_t size);
|
||||
static ssize_t _vfdSize(struct VFile* vf);
|
||||
static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size);
|
||||
|
||||
struct VFile* VFileOpenFD(const char* path, int flags) {
|
||||
if (!path) {
|
||||
|
@ -66,6 +67,7 @@ struct VFile* VFileFromFD(int fd) {
|
|||
vfd->d.unmap = _vfdUnmap;
|
||||
vfd->d.truncate = _vfdTruncate;
|
||||
vfd->d.size = _vfdSize;
|
||||
vfd->d.sync = _vfdSync;
|
||||
|
||||
return &vfd->d;
|
||||
}
|
||||
|
@ -166,3 +168,14 @@ static ssize_t _vfdSize(struct VFile* vf) {
|
|||
}
|
||||
return stat.st_size;
|
||||
}
|
||||
|
||||
static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) {
|
||||
UNUSED(buffer);
|
||||
UNUSED(size);
|
||||
struct VFileFD* vfd = (struct VFileFD*) vf;
|
||||
#ifndef _WIN32
|
||||
return fsync(vfd->fd) == 0;
|
||||
#else
|
||||
return FlushFileBuffers((HANDLE) _get_osfhandle(vfd->fd));
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ static void* _vffMap(struct VFile* vf, size_t size, int flags);
|
|||
static void _vffUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void _vffTruncate(struct VFile* vf, size_t size);
|
||||
static ssize_t _vffSize(struct VFile* vf);
|
||||
static bool _vffSync(struct VFile* vf, const void* buffer, size_t size);
|
||||
|
||||
struct VFile* VFileFOpen(const char* path, const char* mode) {
|
||||
if (!path && !mode) {
|
||||
|
@ -57,6 +58,7 @@ struct VFile* VFileFromFILE(FILE* file) {
|
|||
vff->d.unmap = _vffUnmap;
|
||||
vff->d.truncate = _vffTruncate;
|
||||
vff->d.size = _vffSize;
|
||||
vff->d.sync = _vffSync;
|
||||
|
||||
return &vff->d;
|
||||
}
|
||||
|
@ -140,3 +142,14 @@ static ssize_t _vffSize(struct VFile* vf) {
|
|||
fseek(vff->file, pos, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool _vffSync(struct VFile* vf, const void* buffer, size_t size) {
|
||||
struct VFileFILE* vff = (struct VFileFILE*) vf;
|
||||
if (buffer && size) {
|
||||
long pos = ftell(vff->file);
|
||||
fseek(vff->file, 0, SEEK_SET);
|
||||
fwrite(buffer, size, 1, vff->file);
|
||||
fseek(vff->file, pos, SEEK_SET);
|
||||
}
|
||||
return fflush(vff->file) == 0;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ static void* _vf7zMap(struct VFile* vf, size_t size, int flags);
|
|||
static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void _vf7zTruncate(struct VFile* vf, size_t size);
|
||||
static ssize_t _vf7zSize(struct VFile* vf);
|
||||
static bool _vf7zSync(struct VFile* vf, const void* buffer, size_t size);
|
||||
|
||||
static bool _vd7zClose(struct VDir* vd);
|
||||
static void _vd7zRewind(struct VDir* vd);
|
||||
|
@ -94,6 +95,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) {
|
|||
SzArEx_Init(&vd->db);
|
||||
SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp);
|
||||
if (res != SZ_OK) {
|
||||
File_Close(&vd->archiveStream.file);
|
||||
free(vd);
|
||||
return 0;
|
||||
}
|
||||
|
@ -114,6 +116,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) {
|
|||
bool _vf7zClose(struct VFile* vf) {
|
||||
struct VFile7z* vf7z = (struct VFile7z*) vf;
|
||||
IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer);
|
||||
File_Close(&vf7z->vd->archiveStream.file);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -291,6 +294,7 @@ struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) {
|
|||
vf->d.unmap = _vf7zUnmap;
|
||||
vf->d.truncate = _vf7zTruncate;
|
||||
vf->d.size = _vf7zSize;
|
||||
vf->d.sync = _vf7zSync;
|
||||
|
||||
return &vf->d;
|
||||
}
|
||||
|
@ -308,4 +312,11 @@ const char* _vde7zName(struct VDirEntry* vde) {
|
|||
return vde7z->utf8;
|
||||
}
|
||||
|
||||
bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) {
|
||||
UNUSED(vf);
|
||||
UNUSED(memory);
|
||||
UNUSED(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue