mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-08 19:53:40 -04:00
Merge branch 'master' into COMBO_BUTTON
This commit is contained in:
commit
3ef66be317
223 changed files with 21336 additions and 11169 deletions
|
|
@ -8,7 +8,7 @@ REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
|||
# Determine the name of the release being built.
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
||||
REV_NAME="azahar-$GITHUB_REF_NAME-$OS-$TARGET"
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
|
||||
else
|
||||
RELEASE_NAME=azahar-head
|
||||
fi
|
||||
|
|
|
|||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
path: artifacts/
|
||||
|
||||
macos:
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-26-intel') || 'macos-26' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
|
|||
172
.github/workflows/libretro.yml
vendored
Normal file
172
.github/workflows/libretro.yml
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
name: citra-libretro
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: android
|
||||
TARGET: arm64-v8a
|
||||
API_LEVEL: 21
|
||||
ANDROID_NDK_VERSION: 26.2.11394342
|
||||
ANDROID_ABI: arm64-v8a
|
||||
BUILD_DIR: build/android-arm64-v8a
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set tag name
|
||||
run: |
|
||||
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
||||
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
fi
|
||||
echo $GIT_TAG_NAME
|
||||
- name: Update Android SDK CMake version
|
||||
run: |
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
|
||||
- name: Build
|
||||
run: |
|
||||
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
zip -j -9 azahar-libretro-android-arm64.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
linux:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: linux
|
||||
TARGET: x86_64
|
||||
BUILD_DIR: build/linux-x86_64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
zip -j -9 azahar-libretro-linux-x86_64.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
windows:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OS: windows
|
||||
TARGET: x86_64
|
||||
BUILD_DIR: build/windows-x86_64
|
||||
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
|
||||
CMAKE: x86_64-w64-mingw32.static-cmake
|
||||
IMAGE: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build in cross-container
|
||||
run: |
|
||||
docker pull $IMAGE
|
||||
docker run --rm --user root \
|
||||
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
|
||||
-w "${GITHUB_WORKSPACE}" \
|
||||
$IMAGE \
|
||||
bash -lc "\
|
||||
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
|
||||
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)"
|
||||
zip -j -9 azahar-libretro-windows-x86_64.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
macos:
|
||||
runs-on: macos-26
|
||||
strategy:
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
env:
|
||||
OS: macos
|
||||
TARGET: ${{ matrix.target }}
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
BUILD_DIR: build/osx-${{ matrix.target }}
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: brew install spirv-tools
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
zip -j -9 azahar-libretro-macos-$TARGET.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
ios:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: ios
|
||||
TARGET: arm64
|
||||
BUILD_DIR: build/ios-arm64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
zip -j -9 azahar-libretro-ios.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
tvos:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: tvos
|
||||
TARGET: arm64
|
||||
BUILD_DIR: build/tvos-arm64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
zip -j -9 azahar-libretro-tvos.zip ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.*
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -56,3 +56,7 @@ repo/
|
|||
.ccache/
|
||||
node_modules/
|
||||
VULKAN_SDK/
|
||||
|
||||
# Version info files
|
||||
GIT-COMMIT
|
||||
GIT-TAG
|
||||
|
|
|
|||
133
.gitlab-ci.yml
Normal file
133
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
.core-defs:
|
||||
variables:
|
||||
JNI_PATH: .
|
||||
CORENAME: azahar
|
||||
API_LEVEL: 21
|
||||
BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_TESTS=OFF
|
||||
CORE_ARGS: ${BASE_CORE_ARGS}
|
||||
EXTRA_PATH: bin/Release
|
||||
|
||||
variables:
|
||||
STATIC_RETROARCH_BRANCH: master
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Inclusion templates, required for the build to work
|
||||
include:
|
||||
################################## DESKTOPS ############################## ##
|
||||
# Windows 64-bit
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/windows-cmake-mingw.yml'
|
||||
|
||||
# Linux 64-bit
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/linux-cmake.yml'
|
||||
|
||||
# MacOS x86_64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/osx-cmake-x86.yml'
|
||||
|
||||
# MacOS ARM64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/osx-cmake-arm64.yml'
|
||||
|
||||
################################## CELLULAR ############################## ##
|
||||
# Android
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/android-cmake.yml'
|
||||
|
||||
# iOS
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/ios-cmake.yml'
|
||||
|
||||
# tvOS
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/tvos-cmake.yml'
|
||||
|
||||
################################## CONSOLES ############################## ##
|
||||
|
||||
# Stages for building
|
||||
stages:
|
||||
- build-prepare
|
||||
- build-shared
|
||||
- build-static
|
||||
|
||||
##############################################################################
|
||||
#################################### STAGES ##################################
|
||||
##############################################################################
|
||||
#
|
||||
################################### DESKTOPS #################################
|
||||
# Windows 64-bit
|
||||
libretro-build-windows-x64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-windows-cmake-x86_64
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF -G Ninja
|
||||
|
||||
# Linux 64-bit
|
||||
libretro-build-linux-x64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-linux-cmake-x86_64
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF
|
||||
CC: /usr/bin/gcc-12
|
||||
CXX: /usr/bin/g++-12
|
||||
|
||||
# MacOS x86_64
|
||||
libretro-build-osx-x64:
|
||||
tags:
|
||||
- mac-apple-silicon
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-osx-cmake-x86_64
|
||||
|
||||
# MacOS ARM64
|
||||
libretro-build-osx-arm64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-osx-cmake-arm64
|
||||
variables:
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
|
||||
################################### CELLULAR #################################
|
||||
# Android ARMv8a
|
||||
android-arm64-v8a:
|
||||
extends:
|
||||
- .libretro-android-cmake-arm64-v8a
|
||||
- .core-defs
|
||||
variables:
|
||||
ANDROID_NDK_VERSION: 26.2.11394342
|
||||
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
||||
LIBNAME: ${CORENAME}_libretro.so
|
||||
artifacts:
|
||||
paths:
|
||||
- $LIBNAME
|
||||
|
||||
# iOS arm64
|
||||
libretro-build-ios-arm64:
|
||||
extends:
|
||||
- .libretro-ios-cmake-arm64
|
||||
- .core-defs
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
IOS_MINVER: "14.0"
|
||||
EXTRA_PATH: bin/RelWithDebInfo
|
||||
|
||||
# tvOS arm64
|
||||
libretro-build-tvos-arm64:
|
||||
extends:
|
||||
- .libretro-tvos-cmake-arm64
|
||||
- .core-defs
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
MINVER: "14.0"
|
||||
EXTRA_PATH: bin/RelWithDebInfo
|
||||
|
||||
################################### CONSOLES #################################
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -103,3 +103,6 @@
|
|||
[submodule "externals/xxHash"]
|
||||
path = externals/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
[submodule "externals/libretro-common"]
|
||||
path = externals/libretro-common/libretro-common
|
||||
url = https://github.com/libretro/libretro-common.git
|
||||
|
|
|
|||
|
|
@ -17,20 +17,23 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
|||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
include(FindPkgConfig)
|
||||
|
||||
project(citra LANGUAGES C CXX ASM)
|
||||
# must be invoked after project() command when using CMAKE_TOOLCHAIN_FILE
|
||||
include(FindPkgConfig)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
|
||||
|
||||
# Some submodules like to pick their own default build type if not specified.
|
||||
# Make sure we default to Release build type always, unless the generator has custom types.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (APPLE AND NOT ENABLE_LIBRETRO)
|
||||
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
|
|
@ -90,8 +93,18 @@ else()
|
|||
set(DEFAULT_ENABLE_OPENGL ON)
|
||||
endif()
|
||||
|
||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
|
||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||
set(_USER_SET_OPTIONS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
if(DEFINED ${_opt})
|
||||
list(APPEND _USER_SET_OPTIONS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
option(ENABLE_SDL2 "Enable using SDL2" ON)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF)
|
||||
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
||||
|
||||
# Set bundled qt as dependent options.
|
||||
|
|
@ -130,6 +143,31 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
|
|||
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
|
||||
|
||||
# Handle incompatible options for libretro builds
|
||||
if(ENABLE_LIBRETRO)
|
||||
# Check for explicitly-set conflicting options
|
||||
set(_CONFLICTS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
list(FIND _USER_SET_OPTIONS ${_opt} _idx)
|
||||
if(NOT _idx EQUAL -1 AND ${_opt})
|
||||
list(APPEND _CONFLICTS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(_CONFLICTS)
|
||||
string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}")
|
||||
message(FATAL_ERROR
|
||||
"ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n"
|
||||
"These options were explicitly enabled but are not supported for libretro builds.\n"
|
||||
"Remove these options or set them to OFF.")
|
||||
endif()
|
||||
|
||||
# Force disable incompatible options (handles defaulted-on options)
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Pass the following values to C++ land
|
||||
if (ENABLE_QT)
|
||||
add_definitions(-DENABLE_QT)
|
||||
|
|
@ -143,9 +181,6 @@ endif()
|
|||
if (ENABLE_SDL2)
|
||||
add_definitions(-DENABLE_SDL2)
|
||||
endif()
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_definitions(-DENABLE_SDL2_FRONTEND)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
|
||||
message(STATUS "SSE4.2 enabled for x86_64")
|
||||
|
|
@ -300,6 +335,9 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
|
|||
# set up output paths for executable binaries
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
|
@ -359,7 +397,7 @@ if (APPLE)
|
|||
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||
if (NOT USE_SYSTEM_MOLTENVK)
|
||||
download_moltenvk()
|
||||
endif()
|
||||
|
|
@ -513,8 +551,6 @@ if (NOT ANDROID AND NOT IOS)
|
|||
include(BundleTarget)
|
||||
if (ENABLE_QT)
|
||||
qt_bundle_target(citra_meta)
|
||||
elseif (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra_meta)
|
||||
endif()
|
||||
if (ENABLE_ROOM_STANDALONE)
|
||||
bundle_target(citra_room_standalone)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
macro(generate_build_info)
|
||||
find_package(Git QUIET)
|
||||
|
||||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
|
|
@ -6,9 +8,14 @@ macro(generate_build_info)
|
|||
endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
|
||||
|
||||
if (EXISTS "${SRC_DIR}/.git/objects")
|
||||
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
|
||||
# Find the package here with the known path so that the GetGit commands can find it as well
|
||||
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
||||
|
||||
|
|
@ -17,12 +24,6 @@ macro(generate_build_info)
|
|||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||
# unified source archive
|
||||
file(READ "${SRC_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
else()
|
||||
# self-packed archive?
|
||||
set(GIT_REV "UNKNOWN")
|
||||
|
|
@ -39,8 +40,8 @@ macro(generate_build_info)
|
|||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||
endif()
|
||||
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
|
||||
string(STRIP ${GIT_TAG} GIT_TAG)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
|
||||
|
||||
include(GenerateBuildInfo)
|
||||
generate_build_info()
|
||||
|
||||
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
||||
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
|
||||
set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||
|
|
@ -47,4 +48,4 @@ foreach (F IN LISTS HASH_FILES)
|
|||
set(COMBINED "${COMBINED}${TMP}")
|
||||
endforeach()
|
||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
|
|
|
|||
275
CMakeModules/GenerateSettingKeys.cmake
Normal file
275
CMakeModules/GenerateSettingKeys.cmake
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
## This file should be the *only place* where setting keys exist as strings.
|
||||
# All references to setting strings should be derived from the
|
||||
# `setting_keys.h` and `jni_setting_keys.cpp` files generated here.
|
||||
|
||||
# !!! Changes made here should be mirrored to SettingKeys.kt if used on Android
|
||||
|
||||
# Shared setting keys (multi-platform)
|
||||
foreach(KEY IN ITEMS
|
||||
"use_artic_base_controller"
|
||||
"enable_gamemode"
|
||||
"use_cpu_jit"
|
||||
"cpu_clock_percentage"
|
||||
"is_new_3ds"
|
||||
"lle_applets"
|
||||
"deterministic_async_operations"
|
||||
"enable_required_online_lle_modules"
|
||||
"use_virtual_sd"
|
||||
"use_custom_storage"
|
||||
"compress_cia_installs"
|
||||
"region_value"
|
||||
"init_clock"
|
||||
"init_time"
|
||||
"init_time_offset"
|
||||
"init_ticks_type"
|
||||
"init_ticks_override"
|
||||
"plugin_loader"
|
||||
"allow_plugin_loader"
|
||||
"steps_per_hour"
|
||||
"apply_region_free_patch"
|
||||
"graphics_api"
|
||||
"physical_device"
|
||||
"use_gles"
|
||||
"renderer_debug"
|
||||
"dump_command_buffers"
|
||||
"spirv_shader_gen"
|
||||
"disable_spirv_optimizer"
|
||||
"async_shader_compilation"
|
||||
"async_presentation"
|
||||
"use_hw_shader"
|
||||
"use_disk_shader_cache"
|
||||
"shaders_accurate_mul"
|
||||
"use_vsync"
|
||||
"use_display_refresh_rate_detection"
|
||||
"use_shader_jit"
|
||||
"resolution_factor"
|
||||
"frame_limit"
|
||||
"turbo_limit"
|
||||
"texture_filter"
|
||||
"texture_sampling"
|
||||
"delay_game_render_thread_us"
|
||||
"layout_option"
|
||||
"swap_screen"
|
||||
"upright_screen"
|
||||
"secondary_display_layout"
|
||||
"large_screen_proportion"
|
||||
"screen_gap"
|
||||
"small_screen_position"
|
||||
"custom_top_x"
|
||||
"custom_top_y"
|
||||
"custom_top_width"
|
||||
"custom_top_height"
|
||||
"custom_bottom_x"
|
||||
"custom_bottom_y"
|
||||
"custom_bottom_width"
|
||||
"custom_bottom_height"
|
||||
"custom_second_layer_opacity"
|
||||
"aspect_ratio"
|
||||
"screen_top_stretch"
|
||||
"screen_top_leftright_padding"
|
||||
"screen_top_topbottom_padding"
|
||||
"screen_bottom_stretch"
|
||||
"screen_bottom_leftright_padding"
|
||||
"screen_bottom_topbottom_padding"
|
||||
"portrait_layout_option"
|
||||
"custom_portrait_top_x"
|
||||
"custom_portrait_top_y"
|
||||
"custom_portrait_top_width"
|
||||
"custom_portrait_top_height"
|
||||
"custom_portrait_bottom_x"
|
||||
"custom_portrait_bottom_y"
|
||||
"custom_portrait_bottom_width"
|
||||
"custom_portrait_bottom_height"
|
||||
"bg_red"
|
||||
"bg_green"
|
||||
"bg_blue"
|
||||
"render_3d"
|
||||
"factor_3d"
|
||||
"swap_eyes_3d"
|
||||
"render_3d_which_display"
|
||||
"mono_render_option"
|
||||
"cardboard_screen_size"
|
||||
"cardboard_x_shift"
|
||||
"cardboard_y_shift"
|
||||
"filter_mode"
|
||||
"pp_shader_name"
|
||||
"anaglyph_shader_name"
|
||||
"dump_textures"
|
||||
"custom_textures"
|
||||
"preload_textures"
|
||||
"async_custom_loading"
|
||||
"disable_right_eye_render"
|
||||
"audio_emulation"
|
||||
"enable_audio_stretching"
|
||||
"enable_realtime_audio"
|
||||
"volume"
|
||||
"output_type"
|
||||
"output_device"
|
||||
"input_type"
|
||||
"input_device"
|
||||
"delay_start_for_lle_modules"
|
||||
"use_gdbstub"
|
||||
"gdbstub_port"
|
||||
"instant_debug_log"
|
||||
"enable_rpc_server"
|
||||
"log_filter"
|
||||
"log_regex_filter"
|
||||
"use_integer_scaling"
|
||||
"layouts_to_cycle"
|
||||
"camera_inner_flip"
|
||||
"camera_outer_left_flip"
|
||||
"camera_outer_right_flip"
|
||||
"camera_inner_name"
|
||||
"camera_inner_config"
|
||||
"camera_outer_left_name"
|
||||
"camera_outer_left_config"
|
||||
"camera_outer_right_name"
|
||||
"camera_outer_right_config"
|
||||
"video_encoder"
|
||||
"video_encoder_options"
|
||||
"video_bitrate"
|
||||
"audio_encoder"
|
||||
"audio_encoder_options"
|
||||
"audio_bitrate"
|
||||
"last_artic_base_addr"
|
||||
"motion_device"
|
||||
"touch_device"
|
||||
"udp_input_address"
|
||||
"udp_input_port"
|
||||
"udp_pad_index"
|
||||
"record_frame_times"
|
||||
"language" # FIXME: DUPLICATE KEY (libretro equivalent: language_value)
|
||||
"web_api_url"
|
||||
"citra_username"
|
||||
"citra_token"
|
||||
)
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
if (ANDROID)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
|
||||
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Qt exclusive setting keys
|
||||
# Note: A lot of these are very generic because our Qt settings are currently put under groups:
|
||||
# E.g. UILayout\geometry
|
||||
# TODO: We should probably get rid of these groups and use complete keys at some point. -OS
|
||||
# FIXME: Some of these settings don't use the standard snake_case. When we can migrate, address that. -OS
|
||||
if (ENABLE_QT)
|
||||
foreach(KEY IN ITEMS
|
||||
"nickname"
|
||||
"ip"
|
||||
"port"
|
||||
"room_nickname"
|
||||
"room_name"
|
||||
"room_port"
|
||||
"host_type"
|
||||
"max_player"
|
||||
"room_description"
|
||||
"multiplayer_filter_text"
|
||||
"multiplayer_filter_games_owned"
|
||||
"multiplayer_filter_hide_empty"
|
||||
"multiplayer_filter_hide_full"
|
||||
"username_ban_list"
|
||||
"username"
|
||||
"ip_ban_list"
|
||||
"romsPath"
|
||||
"symbolsPath"
|
||||
"movieRecordPath"
|
||||
"moviePlaybackPath"
|
||||
"videoDumpingPath"
|
||||
"gameListRootDir"
|
||||
"gameListDeepScan"
|
||||
"path"
|
||||
"deep_scan"
|
||||
"expanded"
|
||||
"recentFiles"
|
||||
"output_format"
|
||||
"format_options"
|
||||
"theme"
|
||||
"program_id"
|
||||
"geometry"
|
||||
"state"
|
||||
"geometryRenderWindow"
|
||||
"gameListHeaderState"
|
||||
"microProfileDialogGeometry"
|
||||
"name"
|
||||
"bind"
|
||||
"profile"
|
||||
"use_touch_from_button"
|
||||
"touch_from_button_map"
|
||||
"touch_from_button_maps" # Why are these two so similar? Basically typo bait
|
||||
"nand_directory"
|
||||
"sdmc_directory"
|
||||
"game_id"
|
||||
"KeySeq"
|
||||
"gamedirs"
|
||||
"libvorbis"
|
||||
"Context"
|
||||
"favorites"
|
||||
)
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Android exclusive setting keys (standalone app only, not Android libretro)
|
||||
if (ANDROID)
|
||||
foreach(KEY IN ITEMS
|
||||
"expand_to_cutout_area"
|
||||
"performance_overlay_enable"
|
||||
"performance_overlay_show_fps"
|
||||
"performance_overlay_show_frame_time"
|
||||
"performance_overlay_show_speed"
|
||||
"performance_overlay_show_app_ram_usage"
|
||||
"performance_overlay_show_available_ram"
|
||||
"performance_overlay_show_battery_temp"
|
||||
"performance_overlay_background"
|
||||
"use_frame_limit" # FIXME: DUPLICATE KEY (shared equivalent: frame_limit)
|
||||
"android_hide_images"
|
||||
"screen_orientation"
|
||||
"performance_overlay_position"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
|
||||
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Libretro exclusive setting keys
|
||||
if (ENABLE_LIBRETRO)
|
||||
foreach(KEY IN ITEMS
|
||||
"language_value"
|
||||
"swap_screen_mode"
|
||||
"use_libretro_save_path"
|
||||
"analog_function"
|
||||
"analog_deadzone"
|
||||
"enable_mouse_touchscreen"
|
||||
"enable_touch_touchscreen"
|
||||
"render_touchscreen"
|
||||
"enable_motion"
|
||||
"motion_sensitivity"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Trim trailing comma and newline from SETTING_KEY_LIST
|
||||
string(LENGTH "${SETTING_KEY_LIST}" SETTING_KEY_LIST_LENGTH)
|
||||
math(EXPR SETTING_KEY_LIST_NEW_LENGTH "${SETTING_KEY_LIST_LENGTH} - 1")
|
||||
string(SUBSTRING "${SETTING_KEY_LIST}" 0 ${SETTING_KEY_LIST_NEW_LENGTH} SETTING_KEY_LIST)
|
||||
|
||||
# Configure files
|
||||
configure_file("common/setting_keys.h.in" "common/setting_keys.h" @ONLY)
|
||||
if (ENABLE_QT)
|
||||
configure_file("citra_qt/setting_qkeys.h.in" "citra_qt/setting_qkeys.h" @ONLY)
|
||||
endif()
|
||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
||||
configure_file("android/app/src/main/jni/jni_setting_keys.cpp.in" "android/app/src/main/jni/jni_setting_keys.cpp" @ONLY)
|
||||
endif()
|
||||
637
dist/languages/ca_ES_valencia.ts
vendored
637
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/da_DK.ts
vendored
637
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/de.ts
vendored
637
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/el.ts
vendored
637
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/es_ES.ts
vendored
637
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/fi.ts
vendored
637
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load diff
655
dist/languages/fr.ts
vendored
655
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/hu_HU.ts
vendored
637
dist/languages/hu_HU.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/id.ts
vendored
637
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load diff
689
dist/languages/it.ts
vendored
689
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/ja_JP.ts
vendored
637
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/ko_KR.ts
vendored
637
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/lt_LT.ts
vendored
637
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/nb.ts
vendored
637
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/nl.ts
vendored
637
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/pl_PL.ts
vendored
637
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load diff
641
dist/languages/pt_BR.ts
vendored
641
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/ro_RO.ts
vendored
637
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/ru_RU.ts
vendored
637
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load diff
641
dist/languages/sv.ts
vendored
641
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/tr_TR.ts
vendored
637
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/vi_VN.ts
vendored
637
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/zh_CN.ts
vendored
637
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
637
dist/languages/zh_TW.ts
vendored
637
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
27
externals/CMakeLists.txt
vendored
27
externals/CMakeLists.txt
vendored
|
|
@ -50,15 +50,17 @@ else()
|
|||
endif()
|
||||
|
||||
# Catch2
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
if (ENABLE_TESTS)
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
|
||||
# Crypto++
|
||||
if(USE_SYSTEM_CRYPTOPP)
|
||||
|
|
@ -292,6 +294,15 @@ if (USE_DISCORD_PRESENCE)
|
|||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
# LibRetro
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_library(libretro INTERFACE)
|
||||
target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include)
|
||||
if (ANDROID)
|
||||
add_subdirectory(libretro-common EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# JSON
|
||||
add_library(json-headers INTERFACE)
|
||||
if (USE_SYSTEM_JSON)
|
||||
|
|
|
|||
2
externals/boost
vendored
2
externals/boost
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||
Subproject commit f9b15f673a688982f78a5f63a49a27275b318e5f
|
||||
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
add_library(libretro_common STATIC
|
||||
libretro-common/compat/compat_posix_string.c
|
||||
libretro-common/compat/fopen_utf8.c
|
||||
libretro-common/encodings/encoding_utf.c
|
||||
libretro-common/compat/compat_strl.c
|
||||
libretro-common/file/file_path.c
|
||||
libretro-common/streams/file_stream.c
|
||||
libretro-common/streams/file_stream_transforms.c
|
||||
libretro-common/string/stdstring.c
|
||||
libretro-common/time/rtime.c
|
||||
libretro-common/vfs/vfs_implementation.c
|
||||
)
|
||||
target_include_directories(libretro_common PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include
|
||||
)
|
||||
1
externals/libretro-common/libretro-common
vendored
Submodule
1
externals/libretro-common/libretro-common
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# Enable modules to include each other's files
|
||||
include_directories(.)
|
||||
|
||||
include(GenerateSettingKeys)
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
|
@ -110,10 +112,14 @@ else()
|
|||
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
||||
-Wno-unused-command-line-argument
|
||||
# Build fortification options
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-protector-strong
|
||||
-fstack-clash-protection
|
||||
)
|
||||
if (NOT ENABLE_LIBRETRO)
|
||||
add_compile_options(
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-clash-protection
|
||||
)
|
||||
endif()
|
||||
|
||||
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
|
||||
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
|
||||
|
|
@ -189,18 +195,18 @@ if (ENABLE_TESTS)
|
|||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_subdirectory(citra_sdl)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(citra_qt)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
|
||||
if (ENABLE_QT) # Or any other hypothetical future frontends
|
||||
add_subdirectory(citra_meta)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_subdirectory(citra_libretro)
|
||||
endif()
|
||||
|
||||
if (ENABLE_ROOM)
|
||||
add_subdirectory(citra_room)
|
||||
endif()
|
||||
|
|
@ -209,7 +215,7 @@ if (ENABLE_ROOM_STANDALONE)
|
|||
add_subdirectory(citra_room_standalone)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
||||
add_subdirectory(android/app/src/main/jni)
|
||||
target_include_directories(citra-android PRIVATE android/app/src/main)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -267,36 +267,28 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
val button =
|
||||
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||
val action: Int = when (event.action) {
|
||||
when (event.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
hotkeyUtility.handleHotkey(button)
|
||||
|
||||
// On some devices, the back gesture / button press is not intercepted by androidx
|
||||
// and fails to open the emulation menu. So we're stuck running deprecated code to
|
||||
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// If the hotkey is pressed, we don't want to open the drawer
|
||||
if (!hotkeyUtility.HotkeyIsPressed) {
|
||||
if (!hotkeyUtility.hotkeyIsPressed) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
return hotkeyUtility.handleKeyPress(event)
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> {
|
||||
hotkeyUtility.HotkeyIsPressed = false
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
return hotkeyUtility.handleKeyRelease(event)
|
||||
}
|
||||
else -> {
|
||||
return false;
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
val input = event.device
|
||||
?: // Controller was disconnected
|
||||
return false
|
||||
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
|
||||
}
|
||||
|
||||
private fun onAmiiboSelected(selectedFile: String) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import org.citra.citra_emu.NativeLibrary
|
|||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
|
|
@ -31,8 +32,16 @@ class ScreenAdjustmentUtil(
|
|||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
fun cycleLayouts() {
|
||||
val landscapeValues = context.resources.getIntArray(R.array.landscapeValues)
|
||||
|
||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
|
||||
val landscapeValues =
|
||||
if (landscapeLayoutsToCycle.isNotEmpty())
|
||||
landscapeLayoutsToCycle.toIntArray()
|
||||
else context.resources.getIntArray(
|
||||
R.array.landscapeValues
|
||||
)
|
||||
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||
|
||||
if (NativeLibrary.isPortraitMode) {
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ enum class Hotkey(val button: Int) {
|
|||
PAUSE_OR_RESUME(10004),
|
||||
QUICKSAVE(10005),
|
||||
QUICKLOAD(10006),
|
||||
TURBO_LIMIT(10007);
|
||||
TURBO_LIMIT(10007),
|
||||
ENABLE(10008);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,50 +5,140 @@
|
|||
package org.citra.citra_emu.features.hotkeys
|
||||
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class HotkeyUtility(
|
||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||
private val context: Context) {
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||
var HotkeyIsPressed = false
|
||||
private var hotkeyIsEnabled = false
|
||||
var hotkeyIsPressed = false
|
||||
private val currentlyPressedButtons = mutableSetOf<Int>()
|
||||
|
||||
fun handleKeyPress(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val enableButton =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
.getString(Settings.HOTKEY_ENABLE, "")
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
hotkeyIsEnabled = hotkeyIsEnabled || enableButton == "" || thisKeyIsEnableButton
|
||||
|
||||
// Now process all internal buttons associated with this keypress
|
||||
for (button in buttonSet) {
|
||||
currentlyPressedButtons.add(button)
|
||||
//option 1 - this is the enable command, which was already handled
|
||||
if (button == Hotkey.ENABLE.button) {
|
||||
handled = true
|
||||
}
|
||||
// option 2 - this is a different hotkey command
|
||||
else if (hotkeyButtons.contains(button)) {
|
||||
if (hotkeyIsEnabled) {
|
||||
handled = handleHotkey(button) || handled
|
||||
}
|
||||
}
|
||||
// option 3 - this is a normal key
|
||||
else {
|
||||
// if this key press is ALSO associated with a hotkey that will process, skip
|
||||
// the normal key event.
|
||||
if (!thisKeyIsHotkey || !hotkeyIsEnabled) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
) || handled
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleKeyRelease(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
if (thisKeyIsEnableButton) {
|
||||
handled = true; hotkeyIsEnabled = false
|
||||
}
|
||||
|
||||
for (button in buttonSet) {
|
||||
// this is a hotkey button
|
||||
if (hotkeyButtons.contains(button)) {
|
||||
currentlyPressedButtons.remove(button)
|
||||
if (!currentlyPressedButtons.any { hotkeyButtons.contains(it) }) {
|
||||
// all hotkeys are no longer pressed
|
||||
hotkeyIsPressed = false
|
||||
}
|
||||
} else {
|
||||
// if this key ALSO sends a hotkey command that we already/will handle,
|
||||
// or if we did not register the press of this button, e.g. if this key
|
||||
// was also a hotkey pressed after enable, but released after enable button release, then
|
||||
// skip the normal key event
|
||||
if ((!thisKeyIsHotkey || !hotkeyIsEnabled) && currentlyPressedButtons.contains(
|
||||
button
|
||||
)
|
||||
) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
) || handled
|
||||
currentlyPressedButtons.remove(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleHotkey(bindedButton: Int): Boolean {
|
||||
if(hotkeyButtons.contains(bindedButton)) {
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if(wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
else -> {}
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
HotkeyIsPressed = true
|
||||
return true
|
||||
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if (wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
hotkeyIsPressed = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings
|
||||
|
||||
// This list should mirror the list in GenerateSettingKeys.cmake,
|
||||
// specifically the Shared and Android setting keys.
|
||||
@Suppress("KotlinJniMissingFunction", "FunctionName")
|
||||
object SettingKeys {
|
||||
// Shared
|
||||
external fun use_artic_base_controller(): String
|
||||
external fun use_cpu_jit(): String
|
||||
external fun cpu_clock_percentage(): String
|
||||
external fun is_new_3ds(): String
|
||||
external fun lle_applets(): String
|
||||
external fun deterministic_async_operations(): String
|
||||
external fun enable_required_online_lle_modules(): String
|
||||
external fun use_virtual_sd(): String
|
||||
external fun compress_cia_installs(): String
|
||||
external fun region_value(): String
|
||||
external fun init_clock(): String
|
||||
external fun init_time(): String
|
||||
external fun init_ticks_type(): String
|
||||
external fun init_ticks_override(): String
|
||||
external fun plugin_loader(): String
|
||||
external fun allow_plugin_loader(): String
|
||||
external fun steps_per_hour(): String
|
||||
external fun apply_region_free_patch(): String
|
||||
external fun graphics_api(): String
|
||||
external fun use_gles(): String
|
||||
external fun renderer_debug(): String
|
||||
external fun spirv_shader_gen(): String
|
||||
external fun disable_spirv_optimizer(): String
|
||||
external fun async_shader_compilation(): String
|
||||
external fun async_presentation(): String
|
||||
external fun use_hw_shader(): String
|
||||
external fun use_disk_shader_cache(): String
|
||||
external fun shaders_accurate_mul(): String
|
||||
external fun use_vsync(): String
|
||||
external fun use_shader_jit(): String
|
||||
external fun resolution_factor(): String
|
||||
external fun frame_limit(): String
|
||||
external fun turbo_limit(): String
|
||||
external fun texture_filter(): String
|
||||
external fun texture_sampling(): String
|
||||
external fun delay_game_render_thread_us(): String
|
||||
external fun layout_option(): String
|
||||
external fun swap_screen(): String
|
||||
external fun upright_screen(): String
|
||||
external fun secondary_display_layout(): String
|
||||
external fun large_screen_proportion(): String
|
||||
external fun screen_gap(): String
|
||||
external fun small_screen_position(): String
|
||||
external fun custom_top_x(): String
|
||||
external fun custom_top_y(): String
|
||||
external fun custom_top_width(): String
|
||||
external fun custom_top_height(): String
|
||||
external fun custom_bottom_x(): String
|
||||
external fun custom_bottom_y(): String
|
||||
external fun custom_bottom_width(): String
|
||||
external fun custom_bottom_height(): String
|
||||
external fun custom_second_layer_opacity(): String
|
||||
external fun aspect_ratio(): String
|
||||
external fun portrait_layout_option(): String
|
||||
external fun custom_portrait_top_x(): String
|
||||
external fun custom_portrait_top_y(): String
|
||||
external fun custom_portrait_top_width(): String
|
||||
external fun custom_portrait_top_height(): String
|
||||
external fun custom_portrait_bottom_x(): String
|
||||
external fun custom_portrait_bottom_y(): String
|
||||
external fun custom_portrait_bottom_width(): String
|
||||
external fun custom_portrait_bottom_height(): String
|
||||
external fun bg_red(): String
|
||||
external fun bg_green(): String
|
||||
external fun bg_blue(): String
|
||||
external fun render_3d(): String
|
||||
external fun factor_3d(): String
|
||||
external fun swap_eyes_3d(): String
|
||||
external fun render_3d_which_display(): String
|
||||
external fun cardboard_screen_size(): String
|
||||
external fun cardboard_x_shift(): String
|
||||
external fun cardboard_y_shift(): String
|
||||
external fun filter_mode(): String
|
||||
external fun pp_shader_name(): String
|
||||
external fun anaglyph_shader_name(): String
|
||||
external fun dump_textures(): String
|
||||
external fun custom_textures(): String
|
||||
external fun preload_textures(): String
|
||||
external fun async_custom_loading(): String
|
||||
external fun disable_right_eye_render(): String
|
||||
external fun audio_emulation(): String
|
||||
external fun enable_audio_stretching(): String
|
||||
external fun enable_realtime_audio(): String
|
||||
external fun volume(): String
|
||||
external fun output_type(): String
|
||||
external fun output_device(): String
|
||||
external fun input_type(): String
|
||||
external fun input_device(): String
|
||||
external fun delay_start_for_lle_modules(): String
|
||||
external fun use_gdbstub(): String
|
||||
external fun gdbstub_port(): String
|
||||
external fun instant_debug_log(): String
|
||||
external fun enable_rpc_server(): String
|
||||
external fun log_filter(): String
|
||||
external fun log_regex_filter(): String
|
||||
external fun use_integer_scaling(): String
|
||||
external fun layouts_to_cycle(): String
|
||||
external fun camera_inner_flip(): String
|
||||
external fun camera_outer_left_flip(): String
|
||||
external fun camera_outer_right_flip(): String
|
||||
external fun camera_inner_name(): String
|
||||
external fun camera_inner_config(): String
|
||||
external fun camera_outer_left_name(): String
|
||||
external fun camera_outer_left_config(): String
|
||||
external fun camera_outer_right_name(): String
|
||||
external fun camera_outer_right_config(): String
|
||||
external fun last_artic_base_addr(): String
|
||||
external fun motion_device(): String
|
||||
external fun touch_device(): String
|
||||
external fun udp_input_address(): String
|
||||
external fun udp_input_port(): String
|
||||
external fun udp_pad_index(): String
|
||||
external fun record_frame_times(): String
|
||||
|
||||
// Android
|
||||
external fun expand_to_cutout_area(): String
|
||||
external fun performance_overlay_enable(): String
|
||||
external fun performance_overlay_show_fps(): String
|
||||
external fun performance_overlay_show_frame_time(): String
|
||||
external fun performance_overlay_show_speed(): String
|
||||
external fun performance_overlay_show_app_ram_usage(): String
|
||||
external fun performance_overlay_show_available_ram(): String
|
||||
external fun performance_overlay_show_battery_temp(): String
|
||||
external fun performance_overlay_background(): String
|
||||
external fun use_frame_limit(): String
|
||||
external fun android_hide_images(): String
|
||||
external fun screen_orientation(): String
|
||||
external fun performance_overlay_position(): String
|
||||
}
|
||||
|
|
@ -6,4 +6,4 @@ package org.citra.citra_emu.features.settings.model
|
|||
|
||||
interface AbstractListSetting<E> : AbstractSetting {
|
||||
var list: List<E>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,56 +4,58 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class BooleanSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
EXPAND_TO_CUTOUT_AREA("expand_to_cutout_area", Settings.SECTION_LAYOUT, false),
|
||||
SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
|
||||
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
|
||||
DISABLE_SPIRV_OPTIMIZER("disable_spirv_optimizer", Settings.SECTION_RENDERER, true),
|
||||
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
|
||||
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
|
||||
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
|
||||
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
|
||||
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
|
||||
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
|
||||
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED("performance_overlay_show_speed", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE("performance_overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM("performance_overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND("performance_overlay_background", Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
|
||||
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false),
|
||||
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true),
|
||||
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true),
|
||||
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false),
|
||||
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true),
|
||||
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false),
|
||||
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, false),
|
||||
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, true),
|
||||
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, false),
|
||||
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, true),
|
||||
ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, false),
|
||||
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, true),
|
||||
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, true),
|
||||
SHADER_JIT("use_shader_jit", Settings.SECTION_RENDERER, true),
|
||||
VSYNC("use_vsync", Settings.SECTION_RENDERER, false),
|
||||
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, true),
|
||||
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, false),
|
||||
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
|
||||
UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false),
|
||||
ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false),
|
||||
APPLY_REGION_FREE_PATCH("apply_region_free_patch", Settings.SECTION_SYSTEM, true),
|
||||
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
||||
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
||||
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
|
||||
DISABLE_SPIRV_OPTIMIZER(SettingKeys.disable_spirv_optimizer(), Settings.SECTION_RENDERER, true),
|
||||
PLUGIN_LOADER(SettingKeys.plugin_loader(), Settings.SECTION_SYSTEM, false),
|
||||
ALLOW_PLUGIN_LOADER(SettingKeys.allow_plugin_loader(), Settings.SECTION_SYSTEM, true),
|
||||
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
|
||||
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
|
||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
|
||||
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
|
||||
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
|
||||
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
|
||||
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
|
||||
SHADERS_ACCURATE_MUL(SettingKeys.shaders_accurate_mul(), Settings.SECTION_RENDERER, false),
|
||||
DISK_SHADER_CACHE(SettingKeys.use_disk_shader_cache(), Settings.SECTION_RENDERER, true),
|
||||
DUMP_TEXTURES(SettingKeys.dump_textures(), Settings.SECTION_UTILITY, false),
|
||||
CUSTOM_TEXTURES(SettingKeys.custom_textures(), Settings.SECTION_UTILITY, false),
|
||||
ASYNC_CUSTOM_LOADING(SettingKeys.async_custom_loading(), Settings.SECTION_UTILITY, true),
|
||||
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
|
||||
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
|
||||
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
|
||||
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
|
||||
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
||||
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
||||
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
|
||||
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
||||
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
|
||||
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
|
||||
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
|
||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
|
||||
ENABLE_COMBO_KEY("enable_combo_key", Settings.SECTION_CONTROLS, true);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class FloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f),
|
||||
EMPTY_SETTING("", "", 0.0f);
|
||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
enum class IntListSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: List<Int>,
|
||||
val canBeEmpty: Boolean = true
|
||||
) : AbstractListSetting<Int> {
|
||||
|
||||
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
||||
|
||||
private var backingList: List<Int> = defaultValue
|
||||
private var lastValidList : List<Int> = defaultValue
|
||||
|
||||
override var list: List<Int>
|
||||
get() = backingList
|
||||
set(value) {
|
||||
if (!canBeEmpty && value.isEmpty()) {
|
||||
backingList = lastValidList
|
||||
} else {
|
||||
backingList = value
|
||||
lastValidList = value
|
||||
}
|
||||
}
|
||||
|
||||
override val valueAsString: String
|
||||
get() = list.joinToString()
|
||||
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||
if (setting == this) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
|
||||
|
||||
fun from(key: String): IntListSetting? =
|
||||
values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,57 +4,59 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class IntSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Int
|
||||
) : AbstractIntSetting {
|
||||
FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
|
||||
EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
|
||||
INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
|
||||
CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
|
||||
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
|
||||
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
|
||||
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
|
||||
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
|
||||
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
|
||||
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
|
||||
SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
|
||||
LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||
LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT("secondary_display_layout",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
||||
PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||
PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||
PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||
AUDIO_INPUT_TYPE("input_type", Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
|
||||
TEXTURE_SAMPLING("texture_sampling", Settings.SECTION_RENDERER, 0),
|
||||
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
|
||||
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
|
||||
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
|
||||
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
||||
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
||||
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
|
||||
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 1),
|
||||
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
|
||||
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0),
|
||||
CARDBOARD_SCREEN_SIZE(SettingKeys.cardboard_screen_size(), Settings.SECTION_LAYOUT, 85),
|
||||
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
|
||||
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1),
|
||||
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
|
||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class ScaledFloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float,
|
||||
val scale: Int
|
||||
) : AbstractFloatSetting {
|
||||
AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
|
||||
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
get() = field * scale
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class Settings {
|
|||
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
|
||||
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
|
||||
const val SECTION_STORAGE = "Storage"
|
||||
const val SECTION_MISC = "Miscellaneous"
|
||||
|
||||
|
||||
const val KEY_BUTTON_A = "button_a"
|
||||
|
|
@ -137,6 +138,7 @@ class Settings {
|
|||
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
|
||||
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
|
||||
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
|
||||
const val HOTKEY_ENABLE = "hotkey_enable"
|
||||
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
|
||||
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
|
||||
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
||||
|
|
@ -206,6 +208,7 @@ class Settings {
|
|||
R.string.button_zr
|
||||
)
|
||||
val hotKeys = listOf(
|
||||
HOTKEY_ENABLE,
|
||||
HOTKEY_SCREEN_SWAP,
|
||||
HOTKEY_CYCLE_LAYOUT,
|
||||
HOTKEY_CLOSE_GAME,
|
||||
|
|
@ -215,6 +218,7 @@ class Settings {
|
|||
HOTKEY_TURBO_LIMIT
|
||||
)
|
||||
val hotkeyTitles = listOf(
|
||||
R.string.controller_hotkey_enable_button,
|
||||
R.string.emulation_swap_screens,
|
||||
R.string.emulation_cycle_landscape_layouts,
|
||||
R.string.emulation_close_game,
|
||||
|
|
@ -226,6 +230,7 @@ class Settings {
|
|||
|
||||
val comboSelection = mutableSetOf<String>()
|
||||
|
||||
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_MATERIAL_YOU = "MaterialYouTheme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class StringSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
INIT_TIME("init_time", Settings.SECTION_SYSTEM, "946731601"),
|
||||
CAMERA_INNER_NAME("camera_inner_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG("camera_inner_config", Settings.SECTION_CAMERA, "_front"),
|
||||
CAMERA_OUTER_LEFT_NAME("camera_outer_left_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_LEFT_CONFIG("camera_outer_left_config", Settings.SECTION_CAMERA, "_back"),
|
||||
CAMERA_OUTER_RIGHT_NAME("camera_outer_right_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back");
|
||||
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
||||
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
|
||||
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.content.SharedPreferences
|
|||
import android.view.InputDevice
|
||||
import android.view.InputDevice.MotionRange
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
|
|
@ -129,6 +130,7 @@ class InputBindingSetting(
|
|||
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||
Settings.HOTKEY_ENABLE -> Hotkey.ENABLE.button
|
||||
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
|
||||
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
|
||||
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
||||
|
|
@ -163,36 +165,40 @@ class InputBindingSetting(
|
|||
fun removeOldMapping() {
|
||||
// Try remove all possible keys we wrote for this setting
|
||||
val oldKey = preferences.getString(reverseKey, "")
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
if (oldKey != "") {
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
preferences.edit()
|
||||
.remove(abstractSetting.key) // Used for ui text
|
||||
.remove(oldKey) // Used for button mapping
|
||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||
.remove(oldKey + "_GuestButton") // Used for axis button
|
||||
.remove(oldKey + "_Inverted") // used for axis inversion
|
||||
.apply()
|
||||
.remove(reverseKey)
|
||||
val buttonCodes = try {
|
||||
preferences.getStringSet(oldKey, mutableSetOf<String>())!!.toMutableSet()
|
||||
} catch (e: ClassCastException) {
|
||||
// if this is an int pref, either old button or an axis, so just remove it
|
||||
preferences.edit().remove(oldKey).apply()
|
||||
return;
|
||||
}
|
||||
buttonCodes.remove(buttonCode.toString());
|
||||
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to write a gamepad button mapping for the setting.
|
||||
*/
|
||||
private fun writeButtonMapping(key: String) {
|
||||
private fun writeButtonMapping(keyEvent: KeyEvent) {
|
||||
val editor = preferences.edit()
|
||||
|
||||
// Remove mapping for another setting using this input
|
||||
val oldButtonCode = preferences.getInt(key, -1)
|
||||
if (oldButtonCode != -1) {
|
||||
val oldKey = getButtonKey(oldButtonCode)
|
||||
editor.remove(oldKey) // Only need to remove UI text setting, others will be overwritten
|
||||
}
|
||||
|
||||
val key = getInputButtonKey(keyEvent)
|
||||
// Pull in all codes associated with this key
|
||||
// Migrate from the old int preference if need be
|
||||
val buttonCodes = InputBindingSetting.getButtonSet(keyEvent)
|
||||
buttonCodes.add(buttonCode)
|
||||
// Cleanup old mapping for this setting
|
||||
removeOldMapping()
|
||||
|
||||
// Write new mapping
|
||||
editor.putInt(key, buttonCode)
|
||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
|
||||
|
||||
// Write next reverse mapping for future cleanup
|
||||
editor.putString(reverseKey, key)
|
||||
|
|
@ -230,9 +236,8 @@ class InputBindingSetting(
|
|||
}
|
||||
|
||||
val code = translateEventToKeyId(keyEvent)
|
||||
writeButtonMapping(getInputButtonKey(code))
|
||||
val uiString = "${keyEvent.device.name}: Button $code"
|
||||
value = uiString
|
||||
writeButtonMapping(keyEvent)
|
||||
value = "${keyEvent.device.name}: ${getButtonName(code)}"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,8 +264,7 @@ class InputBindingSetting(
|
|||
// use UP (-) to map vertical, but use RIGHT (+) to map horizontal
|
||||
val inverted = if (isHorizontalOrientation()) axisDir == '-' else axisDir == '+'
|
||||
writeAxisMapping(motionRange.axis, button, inverted)
|
||||
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
|
||||
value = uiString
|
||||
value = "Axis ${motionRange.axis}$axisDir"
|
||||
}
|
||||
|
||||
override val type = TYPE_INPUT_BINDING
|
||||
|
|
@ -268,6 +272,241 @@ class InputBindingSetting(
|
|||
companion object {
|
||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
||||
|
||||
private fun toTitleCase(raw: String): String =
|
||||
raw.replace("_", " ").lowercase()
|
||||
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
||||
|
||||
private const val BUTTON_NAME_L3 = "Button L3"
|
||||
private const val BUTTON_NAME_R3 = "Button R3"
|
||||
|
||||
private val buttonNameOverrides = mapOf(
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL to BUTTON_NAME_L3,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR to BUTTON_NAME_R3,
|
||||
LINUX_BTN_DPAD_UP to "Dpad Up",
|
||||
LINUX_BTN_DPAD_DOWN to "Dpad Down",
|
||||
LINUX_BTN_DPAD_LEFT to "Dpad Left",
|
||||
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
||||
)
|
||||
|
||||
fun getButtonName(keyCode: Int): String =
|
||||
buttonNameOverrides[keyCode]
|
||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||
|
||||
private data class DefaultButtonMapping(
|
||||
val settingKey: String,
|
||||
val hostKeyCode: Int,
|
||||
val guestButtonCode: Int
|
||||
)
|
||||
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
|
||||
private data class DefaultAxisMapping(
|
||||
val settingKey: String,
|
||||
val hostAxis: Int,
|
||||
val guestButton: Int,
|
||||
val orientation: Int,
|
||||
val inverted: Boolean
|
||||
)
|
||||
|
||||
private val xboxFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
private val nintendoFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
private val commonButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
|
||||
)
|
||||
|
||||
private val dpadButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
||||
)
|
||||
|
||||
private val stickAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
|
||||
)
|
||||
|
||||
private val dpadAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
|
||||
)
|
||||
|
||||
// Nintendo Switch Joy-Con specific mappings.
|
||||
// Joy-Cons connected via Bluetooth on Android have several quirks:
|
||||
// - They register as two separate InputDevices (left and right)
|
||||
// - Android's evdev translation swaps A<->B (BTN_EAST->BUTTON_B, BTN_SOUTH->BUTTON_A)
|
||||
// but does NOT swap X<->Y (BTN_NORTH->BUTTON_X, BTN_WEST->BUTTON_Y)
|
||||
// - D-pad buttons arrive as KEYCODE_UNKNOWN (0) with Linux BTN_DPAD_* scan codes
|
||||
// - Right stick uses AXIS_RX/AXIS_RY instead of AXIS_Z/AXIS_RZ
|
||||
private const val NINTENDO_VENDOR_ID = 0x057e
|
||||
|
||||
// Linux BTN_DPAD_* values (0x220-0x223). Joy-Con D-pad buttons arrive as
|
||||
// KEYCODE_UNKNOWN with these scan codes because Android's input layer doesn't
|
||||
// translate them to KEYCODE_DPAD_*. translateEventToKeyId() falls back to
|
||||
// the scan code in that case.
|
||||
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
|
||||
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
|
||||
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
|
||||
private const val LINUX_BTN_DPAD_RIGHT = 0x223 // 547
|
||||
|
||||
// Joy-Con face buttons: A/B are swapped by Android's evdev layer, but X/Y are not.
|
||||
// This is different from both the standard Xbox table (full swap) and the
|
||||
// Nintendo table (no swap).
|
||||
private val joyconFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
|
||||
private val joyconDpadButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
||||
)
|
||||
|
||||
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
|
||||
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to
|
||||
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
|
||||
private val joyconStickAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
|
||||
)
|
||||
|
||||
/**
|
||||
* Detects whether a device is a Nintendo Switch Joy-Con (as opposed to a
|
||||
* Pro Controller or other Nintendo device) by checking vendor ID + device
|
||||
* capabilities. Joy-Cons lack AXIS_HAT_X/Y and use AXIS_RX/RY for the
|
||||
* right stick, while the Pro Controller has standard HAT axes and Z/RZ.
|
||||
*/
|
||||
fun isJoyCon(device: InputDevice?): Boolean {
|
||||
if (device == null) return false
|
||||
if (device.vendorId != NINTENDO_VENDOR_ID) return false
|
||||
|
||||
// Pro Controllers have HAT_X/HAT_Y (D-pad) and Z/RZ (right stick).
|
||||
// Joy-Cons lack both: no HAT axes, right stick on RX/RY instead of Z/RZ.
|
||||
var hasHatAxes = false
|
||||
var hasStandardRightStick = false
|
||||
for (range in device.motionRanges) {
|
||||
when (range.axis) {
|
||||
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> hasHatAxes = true
|
||||
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> hasStandardRightStick = true
|
||||
}
|
||||
}
|
||||
return !hasHatAxes && !hasStandardRightStick
|
||||
}
|
||||
|
||||
private val allBindingKeys: Set<String> by lazy {
|
||||
(Settings.buttonKeys + Settings.triggerKeys +
|
||||
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
|
||||
Settings.dPadButtonKeys).toSet()
|
||||
}
|
||||
|
||||
fun clearAllBindings() {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val editor = prefs.edit()
|
||||
val allKeys = prefs.all.keys.toList()
|
||||
for (key in allKeys) {
|
||||
if (key.startsWith(INPUT_MAPPING_PREFIX) || key in allBindingKeys) {
|
||||
editor.remove(key)
|
||||
}
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun applyBindings(
|
||||
buttonMappings: List<DefaultButtonMapping>,
|
||||
axisMappings: List<DefaultAxisMapping>
|
||||
) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val editor = prefs.edit()
|
||||
buttonMappings.forEach { applyDefaultButtonMapping(editor, it) }
|
||||
axisMappings.forEach { applyDefaultAxisMapping(editor, it) }
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Joy-Con specific bindings: scan code D-pad, partial face button
|
||||
* swap, and AXIS_RX/RY right stick.
|
||||
*/
|
||||
fun applyJoyConBindings() {
|
||||
applyBindings(
|
||||
joyconFaceButtonMappings + commonButtonMappings + joyconDpadButtonMappings,
|
||||
joyconStickAxisMappings
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies auto-mapped bindings based on detected controller layout and d-pad type.
|
||||
*
|
||||
* @param isNintendoLayout true if the controller uses Nintendo face button layout
|
||||
* (A=east, B=south), false for Xbox layout (A=south, B=east)
|
||||
* @param useAxisDpad true if the d-pad should be mapped as axis (HAT_X/HAT_Y),
|
||||
* false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT)
|
||||
*/
|
||||
fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) {
|
||||
val faceButtons = if (isNintendoLayout) nintendoFaceButtonMappings else xboxFaceButtonMappings
|
||||
val buttonMappings = if (useAxisDpad) {
|
||||
faceButtons + commonButtonMappings
|
||||
} else {
|
||||
faceButtons + commonButtonMappings + dpadButtonMappings
|
||||
}
|
||||
val axisMappings = if (useAxisDpad) {
|
||||
stickAxisMappings + dpadAxisMappings
|
||||
} else {
|
||||
stickAxisMappings
|
||||
}
|
||||
applyBindings(buttonMappings, axisMappings)
|
||||
}
|
||||
|
||||
private fun applyDefaultButtonMapping(
|
||||
editor: SharedPreferences.Editor,
|
||||
mapping: DefaultButtonMapping
|
||||
) {
|
||||
val prefKey = getInputButtonKey(mapping.hostKeyCode)
|
||||
editor.putInt(prefKey, mapping.guestButtonCode)
|
||||
editor.putString(mapping.settingKey, getButtonName(mapping.hostKeyCode))
|
||||
editor.putString(
|
||||
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}",
|
||||
prefKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyDefaultAxisMapping(
|
||||
editor: SharedPreferences.Editor,
|
||||
mapping: DefaultAxisMapping
|
||||
) {
|
||||
val axisKey = getInputAxisKey(mapping.hostAxis)
|
||||
editor.putInt(getInputAxisOrientationKey(mapping.hostAxis), mapping.orientation)
|
||||
editor.putInt(getInputAxisButtonKey(mapping.hostAxis), mapping.guestButton)
|
||||
editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted)
|
||||
val dir = if (mapping.orientation == 0) '+' else '-'
|
||||
editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir")
|
||||
val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
|
||||
editor.putString(reverseKey, axisKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings key for the specified Citra button code.
|
||||
*/
|
||||
|
|
@ -291,19 +530,31 @@ class InputBindingSetting(
|
|||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||
else -> ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad button.
|
||||
*
|
||||
* Get the mutable set of int button values this key should map to given an event
|
||||
*/
|
||||
@Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys")
|
||||
fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}"
|
||||
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
|
||||
val key = getInputButtonKey(keyCode)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
var buttonCodes = try {
|
||||
preferences.getStringSet(key, mutableSetOf<String>())
|
||||
} catch (e: ClassCastException) {
|
||||
val prefInt = preferences.getInt(key, -1);
|
||||
val migratedSet = if (prefInt != -1) {
|
||||
mutableSetOf(prefInt.toString())
|
||||
} else {
|
||||
mutableSetOf<String>()
|
||||
}
|
||||
migratedSet
|
||||
}
|
||||
if (buttonCodes == null) buttonCodes = mutableSetOf<String>()
|
||||
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad button.
|
||||
*
|
||||
*/
|
||||
fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}"
|
||||
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
|
||||
|
||||
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
|
||||
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad axis.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
class MultiChoiceSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Int,
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: List<Int>? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_MULTI_CHOICE
|
||||
|
||||
val selectedValues: List<Int>
|
||||
get() {
|
||||
if (setting == null) {
|
||||
return defaultValue!!
|
||||
}
|
||||
try {
|
||||
val setting = setting as IntListSetting
|
||||
return setting.list
|
||||
}catch (_: ClassCastException) {
|
||||
}
|
||||
return defaultValue!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing list. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: List<Int>): IntListSetting {
|
||||
val intSetting = setting as IntListSetting
|
||||
intSetting.list = selection
|
||||
return intSetting
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
|
||||
class RunnableSetting(
|
||||
titleId: Int,
|
||||
|
|
@ -12,7 +13,11 @@ class RunnableSetting(
|
|||
val isRuntimeRunnable: Boolean,
|
||||
@DrawableRes val iconId: Int = 0,
|
||||
val runnable: () -> Unit,
|
||||
val value: (() -> String)? = null
|
||||
val value: (() -> String)? = null,
|
||||
val onLongClick: (() -> Boolean)? = null
|
||||
) : SettingsItem(null, titleId, descriptionId) {
|
||||
override val type = TYPE_RUNNABLE
|
||||
|
||||
override val isEditable: Boolean
|
||||
get() = if (EmulationActivity.isRunning()) isRuntimeRunnable else true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ abstract class SettingsItem(
|
|||
) {
|
||||
abstract val type: Int
|
||||
|
||||
val isEditable: Boolean
|
||||
open val isEditable: Boolean
|
||||
get() {
|
||||
if (!EmulationActivity.isRunning()) return true
|
||||
return setting?.isRuntimeEditable ?: false
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import org.citra.citra_emu.features.settings.model.AbstractMultiStringSetting
|
|||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
|
@ -50,6 +51,7 @@ import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
|||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringMultiChoiceSetting
|
||||
|
|
@ -59,7 +61,8 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting
|
|||
import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.StringMultiChoiceViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.StringMultiChoiceViewHolder // TODO: Remove and integrate within MultiChoiceViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.MultiChoiceViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
|
||||
|
|
@ -67,6 +70,7 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SliderViewHolder
|
|||
import org.citra.citra_emu.features.settings.ui.viewholder.StringInputViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SubmenuViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHolder
|
||||
import org.citra.citra_emu.fragments.AutoMapDialogFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
|
|
@ -77,7 +81,8 @@ import kotlin.math.roundToInt
|
|||
class SettingsAdapter(
|
||||
private val fragmentView: SettingsFragmentView,
|
||||
public val context: Context
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
||||
DialogInterface.OnMultiChoiceClickListener {
|
||||
private var settings: ArrayList<SettingsItem>? = null
|
||||
private var clickedItem: SettingsItem? = null
|
||||
private var clickedPosition: Int
|
||||
|
|
@ -109,6 +114,10 @@ class SettingsAdapter(
|
|||
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
||||
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SLIDER -> {
|
||||
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
|
@ -190,24 +199,35 @@ class SettingsAdapter(
|
|||
SettingsItem.TYPE_SLIDER -> {
|
||||
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SWITCH -> {
|
||||
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
||||
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
|
||||
}
|
||||
SettingsItem.TYPE_DATETIME_SETTING -> {
|
||||
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
|
||||
}
|
||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
||||
}
|
||||
SettingsItem.TYPE_STRING_INPUT -> {
|
||||
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
||||
|
||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
||||
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_MULTI_CHOICE -> {
|
||||
(oldItem as StringMultiChoiceSetting).isEnabled == (newItem as StringMultiChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_DATETIME_SETTING -> {
|
||||
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_INPUT -> {
|
||||
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
||||
}
|
||||
|
||||
else -> {
|
||||
oldItem == newItem
|
||||
}
|
||||
|
|
@ -226,7 +246,7 @@ class SettingsAdapter(
|
|||
|
||||
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
||||
if (fragmentView.activityView != null)
|
||||
// Reload the settings list to update the UI
|
||||
// Reload the settings list to update the UI
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +264,27 @@ class SettingsAdapter(
|
|||
onSingleChoiceClick(item)
|
||||
}
|
||||
|
||||
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
|
||||
clickedItem = item
|
||||
|
||||
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setMultiChoiceItems(item.choicesId, value, this)
|
||||
.setOnDismissListener {
|
||||
if (clickedPosition != -1) {
|
||||
notifyItemChanged(clickedPosition)
|
||||
clickedPosition = -1
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) {
|
||||
clickedPosition = position
|
||||
onMultiChoiceClick(item)
|
||||
}
|
||||
|
||||
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
|
||||
clickedItem = item
|
||||
dialog = context?.let {
|
||||
|
|
@ -372,14 +413,14 @@ class SettingsAdapter(
|
|||
sliderString = sliderProgress.roundToInt().toString()
|
||||
if (textSliderValue?.text.toString() != sliderString) {
|
||||
textSliderValue?.setText(sliderString)
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
||||
}
|
||||
} else {
|
||||
val currentText = textSliderValue?.text.toString()
|
||||
val currentTextValue = currentText.toFloat()
|
||||
if (currentTextValue != sliderProgress) {
|
||||
textSliderValue?.setText(sliderString)
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -459,6 +500,7 @@ class SettingsAdapter(
|
|||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is AbstractShortSetting -> {
|
||||
val value = getValueForSingleChoiceSelection(it, which).toShort()
|
||||
if (it.selectedValue.toShort() != value) {
|
||||
|
|
@ -466,6 +508,7 @@ class SettingsAdapter(
|
|||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
||||
}
|
||||
fragmentView?.putSetting(setting)
|
||||
|
|
@ -511,11 +554,12 @@ class SettingsAdapter(
|
|||
val setting = it.setSelectedValue(value)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val setting = it.setSelectedValue(sliderProgress)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
}
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
|
|
@ -531,7 +575,7 @@ class SettingsAdapter(
|
|||
fragmentView?.putSetting(setting)
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedItem = null
|
||||
|
|
@ -539,6 +583,21 @@ class SettingsAdapter(
|
|||
textInputValue = ""
|
||||
}
|
||||
|
||||
//onclick for multichoice
|
||||
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
|
||||
val mcsetting = clickedItem as? MultiChoiceSetting
|
||||
mcsetting?.let {
|
||||
val value = getValueForMultiChoiceSelection(it, which)
|
||||
if (it.selectedValues.contains(value) != isChecked) {
|
||||
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
||||
fragmentView?.putSetting(setting)
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
|
|
@ -598,26 +657,42 @@ class SettingsAdapter(
|
|||
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onClickAutoMap() {
|
||||
val activity = fragmentView.activityView as FragmentActivity
|
||||
AutoMapDialogFragment.newInstance {
|
||||
fragmentView.loadSettingsList()
|
||||
fragmentView.onSettingChanged()
|
||||
}.show(activity.supportFragmentManager, AutoMapDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onLongClickAutoMap(): Boolean {
|
||||
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
|
||||
InputBindingSetting.clearAllBindings()
|
||||
fragmentView.loadSettingsList()
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun onClickRegenerateConsoleId() {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.regenerate_console_id)
|
||||
.setMessage(R.string.regenerate_console_id_description)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
SystemSaveGame.regenerateConsoleId()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
|
||||
SystemSaveGame.regenerateConsoleId()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRegenerateMAC() {
|
||||
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
|
||||
SystemSaveGame.regenerateMac()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmationDialog(titleId: Int, messageId: Int, onConfirm: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.regenerate_mac_address)
|
||||
.setMessage(R.string.regenerate_mac_address_description)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
SystemSaveGame.regenerateMac()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> onConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
|
@ -643,6 +718,16 @@ class SettingsAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getValueForMultiChoiceSelection(item: MultiChoiceSetting, which: Int): Int {
|
||||
val valuesId = item.valuesId
|
||||
return if (valuesId > 0) {
|
||||
val valuesArray = context.resources.getIntArray(valuesId)
|
||||
valuesArray[which]
|
||||
} else {
|
||||
which
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
||||
val value = item.selectedValue
|
||||
val valuesId = item.valuesId
|
||||
|
|
@ -660,23 +745,22 @@ class SettingsAdapter(
|
|||
return -1
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun onStringMultiChoiceClick(item: StringMultiChoiceSetting) {
|
||||
clickedItem = item
|
||||
dialog = context?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(item.nameId)
|
||||
.setMultiChoiceItems(item.choices, item.selectValueIndices, this)
|
||||
.show()
|
||||
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
|
||||
val value = item.selectedValues;
|
||||
val valuesId = item.valuesId;
|
||||
if (valuesId > 0) {
|
||||
val valuesArray = context.resources.getIntArray(valuesId);
|
||||
val res = BooleanArray(valuesArray.size){false}
|
||||
for (index in valuesArray.indices) {
|
||||
if (value.contains(valuesArray[index])) {
|
||||
res[index] = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return BooleanArray(1){false};
|
||||
}
|
||||
|
||||
fun onStringMultiChoiceClick(item: StringMultiChoiceSetting, position: Int) {
|
||||
clickedPosition = position
|
||||
onStringMultiChoiceClick(item)
|
||||
}
|
||||
|
||||
|
||||
//TODO: I only added MultiChoice for fleshing out backend, debating whether to remove MultiChoiceSetting and related code for cleaning up
|
||||
override fun onClick(dialog: DialogInterface?, which: Int, is_checked: Boolean) {
|
||||
when (clickedItem) {
|
||||
|
|
@ -752,5 +836,4 @@ class SettingsAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import android.os.Build
|
|||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.serialization.builtins.IntArraySerializer
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.display.ScreenLayout
|
||||
|
|
@ -28,12 +29,14 @@ import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
|||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.HeaderSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.RunnableSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
||||
|
|
@ -798,6 +801,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
val buttons = settingsActivity.resources.getStringArray(R.array.n3dsButtons).take(10).toTypedArray()
|
||||
|
||||
sl.apply {
|
||||
add(
|
||||
RunnableSetting(
|
||||
R.string.controller_auto_map,
|
||||
R.string.controller_auto_map_description,
|
||||
true,
|
||||
R.drawable.ic_controller,
|
||||
{ settingsAdapter.onClickAutoMap() },
|
||||
onLongClick = { settingsAdapter.onLongClickAutoMap() }
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.generic_buttons))
|
||||
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
|
|
@ -833,7 +846,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_hotkeys))
|
||||
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
|
||||
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||
|
|
@ -931,6 +944,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
IntSetting.RESOLUTION_FACTOR.key,
|
||||
IntSetting.RESOLUTION_FACTOR.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_INTEGER_SCALING,
|
||||
R.string.use_integer_scaling,
|
||||
R.string.use_integer_scaling_description,
|
||||
BooleanSetting.USE_INTEGER_SCALING.key,
|
||||
BooleanSetting.USE_INTEGER_SCALING.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
|
|
@ -1181,6 +1203,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.UPRIGHT_SCREEN.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
MultiChoiceSetting(
|
||||
IntListSetting.LAYOUTS_TO_CYCLE,
|
||||
R.string.layouts_to_cycle,
|
||||
R.string.layouts_to_cycle_description,
|
||||
R.array.landscapeLayouts,
|
||||
R.array.landscapeLayoutValues,
|
||||
IntListSetting.LAYOUTS_TO_CYCLE.key,
|
||||
IntListSetting.LAYOUTS_TO_CYCLE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.PORTRAIT_SCREEN_LAYOUT,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SettingsItem
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = getTextSetting()
|
||||
|
||||
if (setting.isActive) {
|
||||
binding.textSettingName.alpha = 1f
|
||||
binding.textSettingDescription.alpha = 1f
|
||||
binding.textSettingValue.alpha = 1f
|
||||
} else {
|
||||
binding.textSettingName.alpha = 0.5f
|
||||
binding.textSettingDescription.alpha = 0.5f
|
||||
binding.textSettingValue.alpha = 0.5f
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTextSetting(): String {
|
||||
when (val item = setting) {
|
||||
is MultiChoiceSetting -> {
|
||||
val resMgr = binding.textSettingDescription.context.resources
|
||||
val values = resMgr.getIntArray(item.valuesId)
|
||||
var resList:List<String> = emptyList();
|
||||
values.forEachIndexed { i: Int, value: Int ->
|
||||
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
|
||||
resList = resList + resMgr.getStringArray(item.choicesId)[i];
|
||||
}
|
||||
}
|
||||
return resList.joinToString();
|
||||
}
|
||||
|
||||
else -> return ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
if (!setting.isEditable || !setting.isEnabled) {
|
||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||
return
|
||||
}
|
||||
|
||||
if (setting is MultiChoiceSetting) {
|
||||
adapter.onMultiChoiceClick(
|
||||
(setting as MultiChoiceSetting),
|
||||
bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
if (setting.isActive) {
|
||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
||||
} else {
|
||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,10 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
// no-op
|
||||
return true
|
||||
if (!setting.isEditable) {
|
||||
adapter.onClickDisabledSetting(true)
|
||||
return true
|
||||
}
|
||||
return setting.onLongClick?.invoke() ?: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ import org.citra.citra_emu.R
|
|||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.SettingSection
|
||||
|
|
@ -260,6 +261,11 @@ object SettingsFile {
|
|||
if (stringListSetting != null) {
|
||||
stringListSetting.list = value.split(", ").map { it }
|
||||
}
|
||||
|
||||
val intListSetting = IntListSetting.from(key)
|
||||
if (intListSetting != null) {
|
||||
intListSetting.list = value.split(", ").map { it.toInt() }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogAutoMapBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
||||
/**
|
||||
* Captures a single button press to detect controller layout (Xbox vs Nintendo)
|
||||
* and d-pad type (axis vs button), then applies the appropriate bindings.
|
||||
*/
|
||||
class AutoMapDialogFragment : BottomSheetDialogFragment() {
|
||||
private var _binding: DialogAutoMapBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var onComplete: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = DialogAutoMapBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
BottomSheetBehavior.from<View>(view.parent as View).state =
|
||||
BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
isCancelable = false
|
||||
view.requestFocus()
|
||||
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
|
||||
|
||||
binding.textTitle.setText(R.string.controller_auto_map)
|
||||
binding.textMessage.setText(R.string.auto_map_prompt)
|
||||
|
||||
binding.imageFaceButtons.setImageResource(R.drawable.automap_face_buttons)
|
||||
|
||||
dialog?.setOnKeyListener { _, _, event -> onKeyEvent(event) }
|
||||
|
||||
binding.buttonCancel.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.action != KeyEvent.ACTION_UP) return false
|
||||
|
||||
val keyCode = event.keyCode
|
||||
val device = event.device
|
||||
|
||||
// Check if this is a Nintendo Switch Joy-Con (not Pro Controller).
|
||||
// Joy-Cons have unique quirks: split devices, non-standard D-pad scan codes,
|
||||
// partial A/B swap but no X/Y swap from Android's evdev layer.
|
||||
val isJoyCon = InputBindingSetting.isJoyCon(device)
|
||||
|
||||
if (isJoyCon) {
|
||||
Log.info("[AutoMap] Detected Joy-Con - using Joy-Con mappings")
|
||||
InputBindingSetting.clearAllBindings()
|
||||
InputBindingSetting.applyJoyConBindings()
|
||||
onComplete?.invoke()
|
||||
dismiss()
|
||||
return true
|
||||
}
|
||||
|
||||
// For non-Joy-Con controllers, determine layout from which keycode arrives
|
||||
// for the east/right position.
|
||||
// The user is pressing the button in the "A" (east/right) position on the 3DS diamond.
|
||||
// Xbox layout: east position sends KEYCODE_BUTTON_B (97)
|
||||
// Nintendo layout: east position sends KEYCODE_BUTTON_A (96)
|
||||
val isNintendoLayout = when (keyCode) {
|
||||
KeyEvent.KEYCODE_BUTTON_A -> true
|
||||
KeyEvent.KEYCODE_BUTTON_B -> false
|
||||
else -> {
|
||||
// Unrecognized button - ignore and wait for a valid press
|
||||
Log.warning("[AutoMap] Ignoring unrecognized keycode $keyCode, waiting for A or B")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val layoutName = if (isNintendoLayout) "Nintendo" else "Xbox"
|
||||
Log.info("[AutoMap] Detected $layoutName layout (keyCode=$keyCode)")
|
||||
|
||||
val useAxisDpad = detectDpadType(device)
|
||||
|
||||
val dpadName = if (useAxisDpad) "axis" else "button"
|
||||
Log.info("[AutoMap] Detected $dpadName d-pad (device=${device?.name})")
|
||||
|
||||
InputBindingSetting.clearAllBindings()
|
||||
InputBindingSetting.applyAutoMapBindings(isNintendoLayout, useAxisDpad)
|
||||
|
||||
onComplete?.invoke()
|
||||
dismiss()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "AutoMapDialogFragment"
|
||||
|
||||
fun newInstance(
|
||||
onComplete: () -> Unit
|
||||
): AutoMapDialogFragment {
|
||||
val dialog = AutoMapDialogFragment()
|
||||
dialog.onComplete = onComplete
|
||||
return dialog
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for axis d-pad (HAT_X/HAT_Y), false for button d-pad (DPAD_UP/DOWN/LEFT/RIGHT).
|
||||
* Prefers axis when both are present. Defaults to axis if detection fails.
|
||||
*/
|
||||
private fun detectDpadType(device: InputDevice?): Boolean {
|
||||
if (device == null) return true
|
||||
|
||||
val hasAxisDpad = device.motionRanges.any {
|
||||
it.axis == MotionEvent.AXIS_HAT_X || it.axis == MotionEvent.AXIS_HAT_Y
|
||||
}
|
||||
|
||||
if (hasAxisDpad) return true
|
||||
|
||||
val dpadKeyCodes = intArrayOf(
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
)
|
||||
val hasButtonDpad = device.hasKeys(*dpadKeyCodes).any { it }
|
||||
|
||||
return !hasButtonDpad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import android.content.DialogInterface
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
|
|
@ -175,6 +176,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentEmulationBinding.inflate(inflater)
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_landscape_screen_layout).isVisible =
|
||||
CitraApplication.appContext.resources.configuration.orientation !=
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_portrait_screen_layout).isVisible =
|
||||
CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import org.citra.citra_emu.adapters.HomeSettingAdapter
|
|||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.model.Game
|
||||
|
|
@ -89,7 +89,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
{
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
||||
|
||||
inputBinding.editTextInput.setText(textInputValue)
|
||||
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||
|
|
@ -103,7 +103,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (textInputValue.isNotEmpty()) {
|
||||
preferences.edit()
|
||||
.putString("last_artic_base_addr", textInputValue)
|
||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
||||
.apply()
|
||||
val menu = Game(
|
||||
title = getString(R.string.artic_base),
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.citra.citra_emu.R
|
|||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
|
@ -177,7 +178,7 @@ class SystemFilesFragment : Fragment() {
|
|||
binding.buttonSetUpSystemFiles.setOnClickListener {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
||||
|
||||
val progressDialog = showProgressDialog(
|
||||
getText(R.string.setup_system_files),
|
||||
|
|
@ -274,7 +275,7 @@ class SystemFilesFragment : Fragment() {
|
|||
.setPositiveButton(android.R.string.ok) { diag, _ ->
|
||||
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
|
||||
preferences.edit()
|
||||
.putString("last_artic_base_addr", textInputValue)
|
||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
||||
.apply()
|
||||
val menu = Game(
|
||||
title = getString(R.string.artic_base),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ add_library(citra-android SHARED
|
|||
game_info.cpp
|
||||
id_cache.cpp
|
||||
id_cache.h
|
||||
jni_setting_keys.cpp
|
||||
native.cpp
|
||||
ndk_motion.cpp
|
||||
ndk_motion.h
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <INIReader.h>
|
||||
#include <boost/hana/string.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/setting_keys.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
|
|
@ -25,11 +28,11 @@
|
|||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "config.ini";
|
||||
android_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "config.ini";
|
||||
std::string ini_buffer;
|
||||
FileUtil::ReadFileToString(true, sdl2_config_loc, ini_buffer);
|
||||
FileUtil::ReadFileToString(true, android_config_loc, ini_buffer);
|
||||
if (!ini_buffer.empty()) {
|
||||
sdl2_config = std::make_unique<INIReader>(ini_buffer.c_str(), ini_buffer.size());
|
||||
android_config = std::make_unique<INIReader>(ini_buffer.c_str(), ini_buffer.size());
|
||||
}
|
||||
|
||||
Reload();
|
||||
|
|
@ -38,15 +41,15 @@ Config::Config() {
|
|||
Config::~Config() = default;
|
||||
|
||||
bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
||||
const std::string& location = this->sdl2_config_loc;
|
||||
if (sdl2_config == nullptr || sdl2_config->ParseError() < 0) {
|
||||
const std::string& location = this->android_config_loc;
|
||||
if (android_config == nullptr || android_config->ParseError() < 0) {
|
||||
if (retry) {
|
||||
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
|
||||
FileUtil::CreateFullPath(location);
|
||||
FileUtil::WriteStringToFile(true, location, default_contents);
|
||||
std::string ini_buffer;
|
||||
FileUtil::ReadFileToString(true, location, ini_buffer);
|
||||
sdl2_config =
|
||||
android_config =
|
||||
std::make_unique<INIReader>(ini_buffer.c_str(), ini_buffer.size()); // Reopen file
|
||||
|
||||
return LoadINI(default_contents, false);
|
||||
|
|
@ -77,7 +80,8 @@ static const std::array<int, Settings::NativeAnalog::NumAnalogs> default_analogs
|
|||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
||||
std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
|
||||
std::string setting_value =
|
||||
android_config->Get(group, setting.GetLabel(), setting.GetDefault());
|
||||
if (setting_value.empty()) {
|
||||
setting_value = setting.GetDefault();
|
||||
}
|
||||
|
|
@ -86,15 +90,15 @@ void Config::ReadSetting(const std::string& group, Settings::Setting<std::string
|
|||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
||||
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
||||
setting = android_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
||||
}
|
||||
|
||||
template <typename Type, bool ranged>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
||||
if constexpr (std::is_floating_point_v<Type>) {
|
||||
setting = sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault());
|
||||
setting = android_config->GetReal(group, setting.GetLabel(), setting.GetDefault());
|
||||
} else {
|
||||
setting = static_cast<Type>(sdl2_config->GetInteger(
|
||||
setting = static_cast<Type>(android_config->GetInteger(
|
||||
group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
|
||||
}
|
||||
}
|
||||
|
|
@ -103,30 +107,30 @@ void Config::ReadValues() {
|
|||
// Controls
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
std::string default_param = InputManager::GenerateButtonParamPackage(default_buttons[i]);
|
||||
Settings::values.current_input_profile.buttons[i] =
|
||||
sdl2_config->GetString("Controls", Settings::NativeButton::mapping[i], default_param);
|
||||
Settings::values.current_input_profile.buttons[i] = android_config->GetString(
|
||||
"Controls", Settings::NativeButton::mapping[i], default_param);
|
||||
if (Settings::values.current_input_profile.buttons[i].empty())
|
||||
Settings::values.current_input_profile.buttons[i] = default_param;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
std::string default_param = InputManager::GenerateAnalogParamPackage(default_analogs[i]);
|
||||
Settings::values.current_input_profile.analogs[i] =
|
||||
sdl2_config->GetString("Controls", Settings::NativeAnalog::mapping[i], default_param);
|
||||
Settings::values.current_input_profile.analogs[i] = android_config->GetString(
|
||||
"Controls", Settings::NativeAnalog::mapping[i], default_param);
|
||||
if (Settings::values.current_input_profile.analogs[i].empty())
|
||||
Settings::values.current_input_profile.analogs[i] = default_param;
|
||||
}
|
||||
|
||||
Settings::values.current_input_profile.motion_device = sdl2_config->GetString(
|
||||
Settings::values.current_input_profile.motion_device = android_config->GetString(
|
||||
"Controls", "motion_device",
|
||||
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0");
|
||||
Settings::values.current_input_profile.touch_device =
|
||||
sdl2_config->GetString("Controls", "touch_device", "engine:emu_window");
|
||||
Settings::values.current_input_profile.udp_input_address = sdl2_config->GetString(
|
||||
android_config->GetString("Controls", "touch_device", "engine:emu_window");
|
||||
Settings::values.current_input_profile.udp_input_address = android_config->GetString(
|
||||
"Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
|
||||
Settings::values.current_input_profile.udp_input_port =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
|
||||
InputCommon::CemuhookUDP::DEFAULT_PORT));
|
||||
static_cast<u16>(android_config->GetInteger("Controls", "udp_input_port",
|
||||
InputCommon::CemuhookUDP::DEFAULT_PORT));
|
||||
|
||||
ReadSetting("Controls", Settings::values.use_artic_base_controller);
|
||||
|
||||
|
|
@ -135,9 +139,9 @@ void Config::ReadValues() {
|
|||
ReadSetting("Core", Settings::values.cpu_clock_percentage);
|
||||
|
||||
// Renderer
|
||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", true);
|
||||
Settings::values.use_gles = android_config->GetBoolean("Renderer", "use_gles", true);
|
||||
Settings::values.shaders_accurate_mul =
|
||||
sdl2_config->GetBoolean("Renderer", "shaders_accurate_mul", false);
|
||||
android_config->GetBoolean("Renderer", "shaders_accurate_mul", false);
|
||||
ReadSetting("Renderer", Settings::values.graphics_api);
|
||||
ReadSetting("Renderer", Settings::values.async_presentation);
|
||||
ReadSetting("Renderer", Settings::values.async_shader_compilation);
|
||||
|
|
@ -152,7 +156,7 @@ void Config::ReadValues() {
|
|||
ReadSetting("Renderer", Settings::values.texture_sampling);
|
||||
ReadSetting("Renderer", Settings::values.turbo_limit);
|
||||
// Workaround to map Android setting for enabling the frame limiter to the format Citra expects
|
||||
if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) {
|
||||
if (android_config->GetBoolean("Renderer", "use_frame_limit", true)) {
|
||||
ReadSetting("Renderer", Settings::values.frame_limit);
|
||||
} else {
|
||||
Settings::values.frame_limit = 0;
|
||||
|
|
@ -166,8 +170,9 @@ void Config::ReadValues() {
|
|||
else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced)
|
||||
default_shader = "Horizontal (builtin)";
|
||||
Settings::values.pp_shader_name =
|
||||
sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
|
||||
android_config->GetString("Renderer", "pp_shader_name", default_shader);
|
||||
ReadSetting("Renderer", Settings::values.filter_mode);
|
||||
ReadSetting("Renderer", Settings::values.use_integer_scaling);
|
||||
|
||||
ReadSetting("Renderer", Settings::values.bg_red);
|
||||
ReadSetting("Renderer", Settings::values.bg_green);
|
||||
|
|
@ -180,18 +185,19 @@ void Config::ReadValues() {
|
|||
// Layout
|
||||
// Somewhat inelegant solution to ensure layout value is between 0 and 5 on read
|
||||
// since older config files may have other values
|
||||
int layoutInt = (int)sdl2_config->GetInteger(
|
||||
int layoutInt = (int)android_config->GetInteger(
|
||||
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen));
|
||||
if (layoutInt < 0 || layoutInt > 5) {
|
||||
layoutInt = static_cast<int>(Settings::LayoutOption::LargeScreen);
|
||||
}
|
||||
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layoutInt);
|
||||
Settings::values.screen_gap = static_cast<int>(sdl2_config->GetReal("Layout", "screen_gap", 0));
|
||||
Settings::values.screen_gap =
|
||||
static_cast<int>(android_config->GetReal("Layout", "screen_gap", 0));
|
||||
Settings::values.large_screen_proportion =
|
||||
static_cast<float>(sdl2_config->GetReal("Layout", "large_screen_proportion", 2.25));
|
||||
static_cast<float>(android_config->GetReal("Layout", "large_screen_proportion", 2.25));
|
||||
Settings::values.small_screen_position = static_cast<Settings::SmallScreenPosition>(
|
||||
sdl2_config->GetInteger("Layout", "small_screen_position",
|
||||
static_cast<int>(Settings::SmallScreenPosition::TopRight)));
|
||||
android_config->GetInteger("Layout", "small_screen_position",
|
||||
static_cast<int>(Settings::SmallScreenPosition::TopRight)));
|
||||
ReadSetting("Layout", Settings::values.screen_gap);
|
||||
ReadSetting("Layout", Settings::values.custom_top_x);
|
||||
ReadSetting("Layout", Settings::values.custom_top_y);
|
||||
|
|
@ -208,12 +214,12 @@ void Config::ReadValues() {
|
|||
ReadSetting("Layout", Settings::values.upright_screen);
|
||||
|
||||
Settings::values.portrait_layout_option =
|
||||
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
||||
static_cast<Settings::PortraitLayoutOption>(android_config->GetInteger(
|
||||
"Layout", "portrait_layout_option",
|
||||
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
||||
Settings::values.secondary_display_layout = static_cast<Settings::SecondaryDisplayLayout>(
|
||||
sdl2_config->GetInteger("Layout", "secondary_display_layout",
|
||||
static_cast<int>(Settings::SecondaryDisplayLayout::None)));
|
||||
android_config->GetInteger("Layout", Settings::HKeys::secondary_display_layout.c_str(),
|
||||
static_cast<int>(Settings::SecondaryDisplayLayout::None)));
|
||||
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
|
||||
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
||||
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
||||
|
|
@ -252,7 +258,8 @@ void Config::ReadValues() {
|
|||
ReadSetting("System", Settings::values.region_value);
|
||||
ReadSetting("System", Settings::values.init_clock);
|
||||
{
|
||||
std::string time = sdl2_config->GetString("System", "init_time", "946681277");
|
||||
std::string time =
|
||||
android_config->GetString("System", Settings::HKeys::init_time.c_str(), "946681277");
|
||||
try {
|
||||
Settings::values.init_time = std::stoll(time);
|
||||
} catch (...) {
|
||||
|
|
@ -267,24 +274,27 @@ void Config::ReadValues() {
|
|||
|
||||
// Camera
|
||||
using namespace Service::CAM;
|
||||
Settings::values.camera_name[OuterRightCamera] =
|
||||
sdl2_config->GetString("Camera", "camera_outer_right_name", "ndk");
|
||||
Settings::values.camera_config[OuterRightCamera] = sdl2_config->GetString(
|
||||
"Camera", "camera_outer_right_config", std::string{Camera::NDK::BackCameraPlaceholder});
|
||||
Settings::values.camera_name[OuterRightCamera] = android_config->GetString(
|
||||
"Camera", Settings::HKeys::camera_outer_right_name.c_str(), "ndk");
|
||||
Settings::values.camera_config[OuterRightCamera] =
|
||||
android_config->GetString("Camera", Settings::HKeys::camera_outer_right_config.c_str(),
|
||||
std::string{Camera::NDK::BackCameraPlaceholder});
|
||||
Settings::values.camera_flip[OuterRightCamera] =
|
||||
sdl2_config->GetInteger("Camera", "camera_outer_right_flip", 0);
|
||||
android_config->GetInteger("Camera", Settings::HKeys::camera_outer_right_flip.c_str(), 0);
|
||||
Settings::values.camera_name[InnerCamera] =
|
||||
sdl2_config->GetString("Camera", "camera_inner_name", "ndk");
|
||||
Settings::values.camera_config[InnerCamera] = sdl2_config->GetString(
|
||||
"Camera", "camera_inner_config", std::string{Camera::NDK::FrontCameraPlaceholder});
|
||||
android_config->GetString("Camera", Settings::HKeys::camera_inner_name.c_str(), "ndk");
|
||||
Settings::values.camera_config[InnerCamera] =
|
||||
android_config->GetString("Camera", Settings::HKeys::camera_inner_config.c_str(),
|
||||
std::string{Camera::NDK::FrontCameraPlaceholder});
|
||||
Settings::values.camera_flip[InnerCamera] =
|
||||
sdl2_config->GetInteger("Camera", "camera_inner_flip", 0);
|
||||
android_config->GetInteger("Camera", Settings::HKeys::camera_inner_flip.c_str(), 0);
|
||||
Settings::values.camera_name[OuterLeftCamera] =
|
||||
sdl2_config->GetString("Camera", "camera_outer_left_name", "ndk");
|
||||
Settings::values.camera_config[OuterLeftCamera] = sdl2_config->GetString(
|
||||
"Camera", "camera_outer_left_config", std::string{Camera::NDK::BackCameraPlaceholder});
|
||||
android_config->GetString("Camera", Settings::HKeys::camera_outer_left_name.c_str(), "ndk");
|
||||
Settings::values.camera_config[OuterLeftCamera] =
|
||||
android_config->GetString("Camera", Settings::HKeys::camera_outer_left_config.c_str(),
|
||||
std::string{Camera::NDK::BackCameraPlaceholder});
|
||||
Settings::values.camera_flip[OuterLeftCamera] =
|
||||
sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0);
|
||||
android_config->GetInteger("Camera", Settings::HKeys::camera_outer_left_flip.c_str(), 0);
|
||||
|
||||
// Miscellaneous
|
||||
ReadSetting("Miscellaneous", Settings::values.log_filter);
|
||||
|
|
@ -299,7 +309,7 @@ void Config::ReadValues() {
|
|||
|
||||
// Debugging
|
||||
Settings::values.record_frame_times =
|
||||
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
|
||||
android_config->GetBoolean("Debugging", Settings::HKeys::record_frame_times.c_str(), false);
|
||||
ReadSetting("Debugging", Settings::values.renderer_debug);
|
||||
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
||||
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
||||
|
|
@ -307,18 +317,34 @@ void Config::ReadValues() {
|
|||
ReadSetting("Debugging", Settings::values.enable_rpc_server);
|
||||
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);
|
||||
bool use_lle =
|
||||
android_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);
|
||||
Settings::values.lle_modules.emplace(service_module.name, use_lle);
|
||||
}
|
||||
|
||||
// Web Service
|
||||
NetSettings::values.web_api_url =
|
||||
sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org");
|
||||
NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", "");
|
||||
NetSettings::values.citra_token = sdl2_config->GetString("WebService", "citra_token", "");
|
||||
android_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org");
|
||||
NetSettings::values.citra_username =
|
||||
android_config->GetString("WebService", "citra_username", "");
|
||||
NetSettings::values.citra_token = android_config->GetString("WebService", "citra_token", "");
|
||||
}
|
||||
|
||||
void Config::Reload() {
|
||||
LoadINI(DefaultINI::sdl2_config_file);
|
||||
for (auto key = Settings::Keys::keys_array.begin(); key != Settings::Keys::keys_array.end();
|
||||
++key) {
|
||||
const auto key_declaration_string = std::string(*key) + " =";
|
||||
// FIXME: This code looks so ass when formatted by clang-format -OS
|
||||
if (std::ranges::find(DefaultINI::android_config_omitted_keys, *key) ==
|
||||
std::end(DefaultINI::android_config_omitted_keys) &&
|
||||
std::string(DefaultINI::android_config_default_file_content)
|
||||
.find(key_declaration_string) == std::string::npos) {
|
||||
ASSERT_MSG(false,
|
||||
"Validation of default content config failed: Missing or malformed key "
|
||||
"declaration {}",
|
||||
*key);
|
||||
}
|
||||
}
|
||||
LoadINI(DefaultINI::android_config_default_file_content);
|
||||
ReadValues();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ class INIReader;
|
|||
|
||||
class Config {
|
||||
private:
|
||||
std::unique_ptr<INIReader> sdl2_config;
|
||||
std::string sdl2_config_loc;
|
||||
std::unique_ptr<INIReader> android_config;
|
||||
std::string android_config_loc;
|
||||
|
||||
bool LoadINI(const std::string& default_contents = "", bool retry = true);
|
||||
void ReadValues();
|
||||
|
|
@ -26,7 +26,7 @@ public:
|
|||
|
||||
private:
|
||||
/**
|
||||
* Applies a value read from the sdl2_config to a Setting.
|
||||
* Applies a value read from the android_config to a Setting.
|
||||
*
|
||||
* @param group The name of the INI group
|
||||
* @param setting The yuzu setting to modify
|
||||
|
|
|
|||
|
|
@ -4,73 +4,61 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <boost/hana/string.hpp>
|
||||
#include "common/setting_keys.h"
|
||||
|
||||
namespace Keys = Settings::Keys;
|
||||
|
||||
namespace DefaultINI {
|
||||
|
||||
const char* sdl2_config_file = R"(
|
||||
// All of these setting keys are either not currently used by Android or are too niche to bother
|
||||
// documenting (people can contribute documentation if they care), or some other explained reason
|
||||
constexpr std::array android_config_omitted_keys = {
|
||||
Keys::enable_gamemode,
|
||||
Keys::use_custom_storage,
|
||||
Keys::init_time_offset,
|
||||
Keys::physical_device,
|
||||
Keys::use_gles, // Niche
|
||||
Keys::dump_command_buffers,
|
||||
Keys::use_display_refresh_rate_detection,
|
||||
Keys::screen_top_stretch,
|
||||
Keys::screen_top_leftright_padding,
|
||||
Keys::screen_top_topbottom_padding,
|
||||
Keys::screen_bottom_stretch,
|
||||
Keys::screen_bottom_leftright_padding,
|
||||
Keys::screen_bottom_topbottom_padding,
|
||||
Keys::mono_render_option,
|
||||
Keys::log_regex_filter, // Niche
|
||||
Keys::video_encoder,
|
||||
Keys::video_encoder_options,
|
||||
Keys::video_bitrate,
|
||||
Keys::audio_encoder,
|
||||
Keys::audio_encoder_options,
|
||||
Keys::audio_bitrate,
|
||||
Keys::last_artic_base_addr, // On Android, this value is stored as a "preference"
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Is this macro nasty? Yes.
|
||||
// Does it make the code way more readable? Also yes.
|
||||
#define DECLARE_KEY(KEY) +Settings::HKeys::KEY+BOOST_HANA_STRING(" =")+
|
||||
|
||||
static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
|
||||
[Controls]
|
||||
# The input devices and parameters for each 3DS native input
|
||||
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
|
||||
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
|
||||
|
||||
# for button input, the following devices are available:
|
||||
# - "keyboard" (default) for keyboard input. Required parameters:
|
||||
# - "code": the code of the key to bind
|
||||
# - "sdl" for joystick input using SDL. Required parameters:
|
||||
# - "joystick": the index of the joystick to bind
|
||||
# - "button"(optional): the index of the button to bind
|
||||
# - "hat"(optional): the index of the hat to bind as direction buttons
|
||||
# - "axis"(optional): the index of the axis to bind
|
||||
# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
|
||||
# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
|
||||
# triggered if the axis value crosses
|
||||
# - "direction"(only used for axis): "+" means the button is triggered when the axis value
|
||||
# is greater than the threshold; "-" means the button is triggered when the axis value
|
||||
# is smaller than the threshold
|
||||
button_a=
|
||||
button_b=
|
||||
button_x=
|
||||
button_y=
|
||||
button_up=
|
||||
button_down=
|
||||
button_left=
|
||||
button_right=
|
||||
button_l=
|
||||
button_r=
|
||||
button_start=
|
||||
button_select=
|
||||
button_debug=
|
||||
button_gpio14=
|
||||
button_zl=
|
||||
button_zr=
|
||||
button_home=
|
||||
|
||||
# for analog input, the following devices are available:
|
||||
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
|
||||
# - "up", "down", "left", "right": sub-devices for each direction.
|
||||
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
|
||||
# - "modifier": sub-devices as a modifier.
|
||||
# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
|
||||
# Must be in range of 0.0-1.0. Defaults to 0.5
|
||||
# - "sdl" for joystick input using SDL. Required parameters:
|
||||
# - "joystick": the index of the joystick to bind
|
||||
# - "axis_x": the index of the axis to bind as x-axis (default to 0)
|
||||
# - "axis_y": the index of the axis to bind as y-axis (default to 1)
|
||||
circle_pad=
|
||||
c_stick=
|
||||
|
||||
# for motion input, the following devices are available:
|
||||
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
|
||||
# - "update_period": update period in milliseconds (default to 100)
|
||||
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
|
||||
# - "tilt_clamp": the max value of the tilt angle in degrees (default to 90)
|
||||
# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
|
||||
motion_device=
|
||||
)") DECLARE_KEY(motion_device) BOOST_HANA_STRING(R"(
|
||||
|
||||
# for touch input, the following devices are available:
|
||||
# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
|
||||
# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
|
||||
# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
|
||||
touch_device= engine:emu_window
|
||||
)") DECLARE_KEY(touch_device) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Most desktop operating systems do not expose a way to poll the motion state of the controllers
|
||||
# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
|
||||
|
|
@ -78,136 +66,155 @@ touch_device= engine:emu_window
|
|||
# from any cemuhook compatible motion program.
|
||||
|
||||
# IPv4 address of the udp input server (Default "127.0.0.1")
|
||||
udp_input_address=
|
||||
)") DECLARE_KEY(udp_input_address) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Port of the udp input server. (Default 26760)
|
||||
udp_input_port=
|
||||
)") DECLARE_KEY(udp_input_port) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
|
||||
udp_pad_index=
|
||||
)") DECLARE_KEY(udp_pad_index) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Use Artic Controller when connected to Artic Base Server. (Default 0)
|
||||
use_artic_base_controller=
|
||||
)") DECLARE_KEY(use_artic_base_controller) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Core]
|
||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||
use_cpu_jit =
|
||||
)") DECLARE_KEY(use_cpu_jit) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Change the Clock Frequency of the emulated 3DS CPU.
|
||||
# Underclocking can increase the performance of the game at the risk of freezing.
|
||||
# Overclocking may fix lag that happens on console, but also comes with the risk of freezing.
|
||||
# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100
|
||||
cpu_clock_percentage =
|
||||
)") DECLARE_KEY(cpu_clock_percentage) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Renderer]
|
||||
# Whether to render using OpenGL
|
||||
# 1: OpenGL ES (default), 2: Vulkan
|
||||
graphics_api =
|
||||
)") DECLARE_KEY(graphics_api) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to compile shaders on multiple worker threads (Vulkan only)
|
||||
# 0: Off, 1: On (default)
|
||||
async_shader_compilation =
|
||||
)") DECLARE_KEY(async_shader_compilation) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to emit PICA fragment shader using SPIRV or GLSL (Vulkan only)
|
||||
# 0: GLSL, 1: SPIR-V (default)
|
||||
spirv_shader_gen =
|
||||
)") DECLARE_KEY(spirv_shader_gen) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to disable the SPIRV optimizer. Disabling it reduces stutter, but may slightly worsen performance
|
||||
# 0: Enabled, 1: Disabled (default)
|
||||
disable_spirv_optimizer =
|
||||
)") DECLARE_KEY(disable_spirv_optimizer) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to use hardware shaders to emulate 3DS shaders
|
||||
# 0: Software, 1 (default): Hardware
|
||||
use_hw_shader =
|
||||
)") DECLARE_KEY(use_hw_shader) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to use accurate multiplication in hardware shaders
|
||||
# 0: Off (Default. Faster, but causes issues in some games) 1: On (Slower, but correct)
|
||||
shaders_accurate_mul =
|
||||
)") DECLARE_KEY(shaders_accurate_mul) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to use the Just-In-Time (JIT) compiler for shader emulation
|
||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||
use_shader_jit =
|
||||
)") DECLARE_KEY(use_shader_jit) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Overrides the sampling filter used by games. This can be useful in certain
|
||||
# cases with poorly behaved games when upscaling.
|
||||
# 0 (default): Game Controlled, 1: Nearest Neighbor, 2: Linear
|
||||
texture_sampling =
|
||||
)") DECLARE_KEY(texture_sampling) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Forces VSync on the display thread. Can cause input delay, so only turn this on
|
||||
# if you have screen tearing, which is unusual on Android
|
||||
# 0 (default): Off, 1: On
|
||||
use_vsync =
|
||||
)") DECLARE_KEY(use_vsync) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Reduce stuttering by storing and loading generated shaders to disk
|
||||
# 0: Off, 1 (default. On)
|
||||
use_disk_shader_cache =
|
||||
)") DECLARE_KEY(use_disk_shader_cache) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Resolution scale factor
|
||||
# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale
|
||||
# factor for the 3DS resolution
|
||||
resolution_factor =
|
||||
)") DECLARE_KEY(resolution_factor) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Use Integer Scaling when the layout allows
|
||||
# 0: Off (default), 1: On
|
||||
)") DECLARE_KEY(use_integer_scaling) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Turns on the frame limiter, which will limit frames output to the target game speed
|
||||
# 0: Off, 1: On (default)
|
||||
use_frame_limit =
|
||||
)") DECLARE_KEY(use_frame_limit) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Limits the speed of the game to run no faster than this value as a percentage of target speed
|
||||
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
|
||||
frame_limit =
|
||||
)") DECLARE_KEY(frame_limit) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Alternative frame limit which can be triggered by the user
|
||||
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
|
||||
turbo_limit =
|
||||
)") DECLARE_KEY(turbo_limit) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The clear color for the renderer. What shows up on the sides of the bottom screen.
|
||||
# Must be in range of 0.0-1.0. Defaults to 0.0 for all.
|
||||
bg_red =
|
||||
bg_blue =
|
||||
bg_green =
|
||||
)") DECLARE_KEY(bg_red) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(bg_blue) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(bg_green) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Opacity of second layer when using custom layout option (bottom screen unless swapped). Useful if positioning on top of the first layer.
|
||||
custom_second_layer_opacity =
|
||||
)") DECLARE_KEY(custom_second_layer_opacity) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether and how Stereoscopic 3D should be rendered
|
||||
# 0: Off, 1: Half Width Side by Side, 2 (default): Full Width Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
|
||||
# 0 is no longer supported in the interface, as using render_3d_which_display = 0 has the same effect, but supported here for backwards compatibility
|
||||
render_3d =
|
||||
)") DECLARE_KEY(render_3d) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Change 3D Intensity
|
||||
# 0 - 255: Intensity. 0 (default)
|
||||
factor_3d =
|
||||
)") DECLARE_KEY(factor_3d) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Swap Eyes in 3d
|
||||
# true: Swap eyes, false (default): Do not swap eyes
|
||||
swap_eyes_3d =
|
||||
)") DECLARE_KEY(swap_eyes_3d) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which Display to render 3d mode to
|
||||
# 0 (default) - None. Equivalent to render_3d=0
|
||||
# 1: Both, 2: Primary Only, 3: Secondary Only
|
||||
render_3d_which_display =
|
||||
)") DECLARE_KEY(render_3d_which_display) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The name of the post processing shader to apply.
|
||||
# Loaded from shaders if render_3d is off or side by side.
|
||||
pp_shader_name =
|
||||
)") DECLARE_KEY(pp_shader_name) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The name of the shader to apply when render_3d is anaglyph.
|
||||
# Loaded from shaders/anaglyph
|
||||
anaglyph_shader_name =
|
||||
)") DECLARE_KEY(anaglyph_shader_name) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to enable linear filtering or not
|
||||
# This is required for some shaders to work correctly
|
||||
# 0: Nearest, 1 (default): Linear
|
||||
filter_mode =
|
||||
)") DECLARE_KEY(filter_mode) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Delays the game render thread by the specified amount of microseconds
|
||||
# Set to 0 for no delay, only useful in dynamic-fps games to simulate GPU delay.
|
||||
delay_game_render_thread_us =
|
||||
)") DECLARE_KEY(delay_game_render_thread_us) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Disables rendering the right eye image.
|
||||
# Disables rendering the right eye image
|
||||
# Greatly improves performance in some games, but can cause flickering in others.
|
||||
# 0 (default): Enable right eye rendering, 1: Disable right eye rendering
|
||||
disable_right_eye_render =
|
||||
# 0 : Enable right eye rendering, 1: Disable right eye rendering
|
||||
)") DECLARE_KEY(disable_right_eye_render) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Perform presentation on separate threads
|
||||
# Improves performance when using Vulkan in most applications.
|
||||
# Adds ~1 frame of input lag.
|
||||
# 0: Enable async presentation, 1 (default): Disable async presentation
|
||||
)") DECLARE_KEY(async_presentation) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which texture filter should be used
|
||||
# 0 (default): NoFilter
|
||||
# 1: Anime4K
|
||||
# 2: Bicubic
|
||||
# 3: ScaleForce
|
||||
# 4: xBRZ
|
||||
# 5: MMPX
|
||||
)") DECLARE_KEY(texture_filter) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Layout]
|
||||
# Layout for the screen inside the render window, landscape mode
|
||||
|
|
@ -217,12 +224,52 @@ disable_right_eye_render =
|
|||
# 3: Side by Side
|
||||
# 4: Hybrid
|
||||
# 5: Custom Layout
|
||||
layout_option =
|
||||
)") DECLARE_KEY(layout_option) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the screens should be rotated upright
|
||||
# 0 (default): Not rotated, 1: Rotated upright
|
||||
)") DECLARE_KEY(upright_screen) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which aspect ratio should be used by the emulated 3DS screens
|
||||
# 0: (default): Default
|
||||
# 1: 16:9
|
||||
# 2: 4:3
|
||||
# 3: 21:9
|
||||
# 4: 16:10
|
||||
# 5: Stretch
|
||||
)") DECLARE_KEY(aspect_ratio) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the performance overlay should be displayed
|
||||
# 0 (default): Hidden, 1: Shown
|
||||
)") DECLARE_KEY(performance_overlay_enable) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the emulation framerate should be displayed in the performance overlay
|
||||
# 0: Hidden, 1 (default): Shown
|
||||
)") DECLARE_KEY(performance_overlay_show_fps) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the emulation frame time should be displayed in the performance overlay
|
||||
# 0 (default): Hidden, 1: Shown
|
||||
)") DECLARE_KEY(performance_overlay_show_frame_time) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the emulation speed should be displayed as a percentage in the performance overlay
|
||||
)") DECLARE_KEY(performance_overlay_show_speed) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not Azahar's RAM usage should be displayed in the performance overlay
|
||||
)") DECLARE_KEY(performance_overlay_show_app_ram_usage) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the amount of available RAM should be displayed in the performance overlay
|
||||
)") DECLARE_KEY(performance_overlay_show_available_ram) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the battery's current temperature should be displayed in the performance overlay
|
||||
)") DECLARE_KEY(performance_overlay_show_battery_temp) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not the performance overlay should have a transparent background to aid visibility
|
||||
)") DECLARE_KEY(performance_overlay_background) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Storage]
|
||||
# Whether to compress the installed CIA contents
|
||||
# 0 (default): Do not compress, 1: Compress
|
||||
compress_cia_installs =
|
||||
)") DECLARE_KEY(compress_cia_installs) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Position of the performance overlay
|
||||
# 0: Top Left
|
||||
|
|
@ -231,38 +278,37 @@ compress_cia_installs =
|
|||
# 3: Bottom Left
|
||||
# 4: Center Bottom
|
||||
# 5: Bottom Right
|
||||
performance_overlay_position =
|
||||
)") DECLARE_KEY(performance_overlay_position) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Screen Gap - adds a gap between screens in all two-screen modes
|
||||
# Measured in pixels relative to the 240px default height of the screens
|
||||
# Scales with the larger screen (so 24 is 10% of the larger screen height)
|
||||
# Default value is 0.0
|
||||
screen_gap =
|
||||
)") DECLARE_KEY(screen_gap) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Large Screen Proportion - Relative size of large:small in large screen mode
|
||||
# Default value is 2.25
|
||||
large_screen_proportion =
|
||||
)") DECLARE_KEY(large_screen_proportion) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Small Screen Position - where is the small screen relative to the large
|
||||
# Default value is 0
|
||||
# 0: Top Right 1: Middle Right 2: Bottom Right
|
||||
# 3: Top Left 4: Middle left 5: Bottom Left
|
||||
# 6: Above the large screen 7: Below the large screen
|
||||
small_screen_position =
|
||||
|
||||
)") DECLARE_KEY(small_screen_position) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Screen placement when using Custom layout option
|
||||
# 0x, 0y is the top left corner of the render window.
|
||||
# suggested aspect ratio for top screen is 5:3
|
||||
# suggested aspect ratio for bottom screen is 4:3
|
||||
custom_top_x =
|
||||
custom_top_y =
|
||||
custom_top_width =
|
||||
custom_top_height =
|
||||
custom_bottom_x =
|
||||
custom_bottom_y =
|
||||
custom_bottom_width =
|
||||
custom_bottom_height =
|
||||
)") DECLARE_KEY(custom_top_x) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_top_y) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_top_width) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_top_height) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_bottom_x) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_bottom_y) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_bottom_width) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_bottom_height) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Orientation option for the emulator
|
||||
# 2 (default): Automatic
|
||||
|
|
@ -270,32 +316,32 @@ custom_bottom_height =
|
|||
# 8: Landscape (Flipped)
|
||||
# 1: Portrait
|
||||
# 9: Portrait (Flipped)
|
||||
screen_orientation =
|
||||
)") DECLARE_KEY(screen_orientation) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Layout for the portrait mode
|
||||
# 0 (default): Top and bottom screens at top, full width
|
||||
# 1: Custom Layout
|
||||
portrait_layout_option =
|
||||
)") DECLARE_KEY(portrait_layout_option) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Screen placement when using Portrait Custom layout option
|
||||
# 0x, 0y is the top left corner of the render window.
|
||||
custom_portrait_top_x =
|
||||
custom_portrait_top_y =
|
||||
custom_portrait_top_width =
|
||||
custom_portrait_top_height =
|
||||
custom_portrait_bottom_x =
|
||||
custom_portrait_bottom_y =
|
||||
custom_portrait_bottom_width =
|
||||
custom_portrait_bottom_height =
|
||||
)") DECLARE_KEY(custom_portrait_top_x) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_top_y) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_top_width) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_top_height) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_bottom_x) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_bottom_y) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_bottom_width) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(custom_portrait_bottom_height) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Swaps the prominent screen with the other screen.
|
||||
# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
|
||||
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
|
||||
swap_screen =
|
||||
)") DECLARE_KEY(swap_screen) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Expands the display area to include the cutout (or notch) area
|
||||
# 0 (default): Off, 1: On
|
||||
expand_to_cutout_area =
|
||||
)") DECLARE_KEY(expand_to_cutout_area) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Secondary Display Layout
|
||||
# What the game should do if a secondary display is connected physically or using
|
||||
|
|
@ -304,129 +350,137 @@ expand_to_cutout_area =
|
|||
# 1 - Show Top Screen Only
|
||||
# 2 - Show Bottom Screen Only
|
||||
# 3 - Show both screens side by side
|
||||
secondary_display_layout =
|
||||
)") DECLARE_KEY(secondary_display_layout) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Screen placement settings when using Cardboard VR (render3d = 4)
|
||||
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
|
||||
cardboard_screen_size =
|
||||
)") DECLARE_KEY(cardboard_screen_size) BOOST_HANA_STRING(R"(
|
||||
# -100 - 100: Screen X-Coordinate shift as a percentage of empty space. 0 (default)
|
||||
cardboard_x_shift =
|
||||
)") DECLARE_KEY(cardboard_x_shift) BOOST_HANA_STRING(R"(
|
||||
# -100 - 100: Screen Y-Coordinate shift as a percentage of empty space. 0 (default)
|
||||
cardboard_y_shift =
|
||||
)") DECLARE_KEY(cardboard_y_shift) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which of the available layouts should be cycled by Cycle Layouts
|
||||
# A list of any values from 0 to 5 inclusive (e.g. 0, 1, 2, 5)
|
||||
# Default: 0, 1, 2, 3, 4, 5
|
||||
# 0: Default,
|
||||
# 1: Single Screen,
|
||||
# 2: Large Screen,
|
||||
# 3: Side by Side,
|
||||
# 4: Hybrid Screens,
|
||||
# 5: Custom Layout,
|
||||
)") DECLARE_KEY(layouts_to_cycle) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Utility]
|
||||
# Dumps textures as PNG to dump/textures/[Title ID]/.
|
||||
# 0 (default): Off, 1: On
|
||||
dump_textures =
|
||||
)") DECLARE_KEY(dump_textures) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Reads PNG files from load/textures/[Title ID]/ and replaces textures.
|
||||
# 0 (default): Off, 1: On
|
||||
custom_textures =
|
||||
)") DECLARE_KEY(custom_textures) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Loads all custom textures into memory before booting.
|
||||
# 0 (default): Off, 1: On
|
||||
preload_textures =
|
||||
)") DECLARE_KEY(preload_textures) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Loads custom textures asynchronously with background threads.
|
||||
# 0: Off, 1 (default): On
|
||||
async_custom_loading =
|
||||
)") DECLARE_KEY(async_custom_loading) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Audio]
|
||||
# Whether or not to enable DSP LLE
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_dsp_lle =
|
||||
|
||||
# Whether or not to run DSP LLE on a different thread
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_dsp_lle_thread =
|
||||
# Whether or not audio emulation should be enabled
|
||||
# 0: Disabled, 1 (default): Enabled
|
||||
)") DECLARE_KEY(audio_emulation) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not to enable the audio-stretching post-processing effect.
|
||||
# Whether or not to enable the audio-stretching post-processing effect
|
||||
# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
|
||||
# at the cost of increasing audio latency.
|
||||
# 0: No, 1 (default): Yes
|
||||
enable_audio_stretching =
|
||||
)") DECLARE_KEY(enable_audio_stretching) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Scales audio playback speed to account for drops in emulation framerate
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_realtime_audio =
|
||||
)") DECLARE_KEY(enable_realtime_audio) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Output volume.
|
||||
# 1.0 (default): 100%, 0.0; mute
|
||||
volume =
|
||||
)") DECLARE_KEY(volume) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which audio output type to use.
|
||||
# 0 (default): Auto-select, 1: No audio output, 2: Cubeb (if available), 3: OpenAL (if available), 4: SDL2 (if available)
|
||||
output_type =
|
||||
)") DECLARE_KEY(output_type) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which audio output device to use.
|
||||
# auto (default): Auto-select
|
||||
output_device =
|
||||
)") DECLARE_KEY(output_device) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which audio input type to use.
|
||||
# 0 (default): Auto-select, 1: No audio input, 2: Static noise, 3: Cubeb (if available), 4: OpenAL (if available)
|
||||
input_type =
|
||||
)") DECLARE_KEY(input_type) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Which audio input device to use.
|
||||
# auto (default): Auto-select
|
||||
input_device =
|
||||
)") DECLARE_KEY(input_device) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Data Storage]
|
||||
# Whether to create a virtual SD card.
|
||||
# 1 (default): Yes, 0: No
|
||||
use_virtual_sd =
|
||||
)") DECLARE_KEY(use_virtual_sd) BOOST_HANA_STRING(R"(
|
||||
|
||||
[System]
|
||||
# The system model that Citra will try to emulate
|
||||
# 0: Old 3DS (default), 1: New 3DS
|
||||
is_new_3ds =
|
||||
)") DECLARE_KEY(is_new_3ds) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to use LLE system applets, if installed
|
||||
# 0: No, 1 (default): Yes
|
||||
lle_applets =
|
||||
)") DECLARE_KEY(lle_applets) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to enable LLE modules for online play
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_required_online_lle_modules =
|
||||
)") DECLARE_KEY(enable_required_online_lle_modules) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The system region that Citra will use during emulation
|
||||
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
||||
region_value =
|
||||
)") DECLARE_KEY(region_value) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The system language that Citra will use during emulation
|
||||
# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish,
|
||||
# 6: Simplified Chinese, 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Traditional Chinese
|
||||
language =
|
||||
)") DECLARE_KEY(language) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The clock to use when citra starts
|
||||
# 0: System clock (default), 1: fixed time
|
||||
init_clock =
|
||||
)") DECLARE_KEY(init_clock) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Time used when init_clock is set to fixed_time in the format %Y-%m-%d %H:%M:%S
|
||||
# set to fixed time. Default 2000-01-01 00:00:01
|
||||
# Note: 3DS can only handle times later then Jan 1 2000
|
||||
init_time =
|
||||
)") DECLARE_KEY(init_time) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The system ticks count to use when citra starts
|
||||
# 0: Random (default), 1: Fixed
|
||||
init_ticks_type =
|
||||
)") DECLARE_KEY(init_ticks_type) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Tick count to use when init_ticks_type is set to Fixed.
|
||||
# Defaults to 0.
|
||||
init_ticks_override =
|
||||
)") DECLARE_KEY(init_ticks_override) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Number of steps per hour reported by the pedometer. Range from 0 to 65,535.
|
||||
# Defaults to 0.
|
||||
steps_per_hour =
|
||||
)") DECLARE_KEY(steps_per_hour) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Plugin loader state, if enabled plugins will be loaded from the SD card.
|
||||
# You can also set if homebrew apps are allowed to enable the plugin loader
|
||||
plugin_loader =
|
||||
allow_plugin_loader =
|
||||
)") DECLARE_KEY(plugin_loader) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(allow_plugin_loader) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Apply region free patch to installed applications
|
||||
# Patches the region of installed applications to be region free, so that they always appear on the home menu.
|
||||
# 0: Disabled, 1 (default): Enabled
|
||||
apply_region_free_patch =
|
||||
)") DECLARE_KEY(apply_region_free_patch) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Camera]
|
||||
# Which camera engine to use for the right outer camera
|
||||
|
|
@ -437,66 +491,72 @@ apply_region_free_patch =
|
|||
# If you don't specify an ID, the default setting will be used. For outer cameras,
|
||||
# the back-facing camera will be used. For the inner camera, the front-facing
|
||||
# camera will be used. Please note that 'Legacy' cameras are not supported.
|
||||
camera_outer_right_name =
|
||||
)") DECLARE_KEY(camera_outer_right_name) BOOST_HANA_STRING(R"(
|
||||
|
||||
# A config string for the right outer camera. Its meaning is defined by the camera engine
|
||||
camera_outer_right_config =
|
||||
)") DECLARE_KEY(camera_outer_right_config) BOOST_HANA_STRING(R"(
|
||||
|
||||
# The image flip to apply
|
||||
# 0: None (default), 1: Horizontal, 2: Vertical, 3: Reverse
|
||||
camera_outer_right_flip =
|
||||
)") DECLARE_KEY(camera_outer_right_flip) BOOST_HANA_STRING(R"(
|
||||
|
||||
# ... for the left outer camera
|
||||
camera_outer_left_name =
|
||||
camera_outer_left_config =
|
||||
camera_outer_left_flip =
|
||||
)") DECLARE_KEY(camera_outer_left_name) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(camera_outer_left_config) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(camera_outer_left_flip) BOOST_HANA_STRING(R"(
|
||||
|
||||
# ... for the inner camera
|
||||
camera_inner_name =
|
||||
camera_inner_config =
|
||||
camera_inner_flip =
|
||||
)") DECLARE_KEY(camera_inner_name) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(camera_inner_config) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(camera_inner_flip) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Miscellaneous]
|
||||
# A filter which removes logs below a certain logging level.
|
||||
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
|
||||
log_filter = *:Info
|
||||
)") DECLARE_KEY(log_filter) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether or not Azahar-related images should be hidden from the Android gallery
|
||||
# 0 (default): No, 1: Yes
|
||||
)") DECLARE_KEY(android_hide_images) BOOST_HANA_STRING(R"(
|
||||
|
||||
[Debugging]
|
||||
# Record frame time data, can be found in the log directory. Boolean value
|
||||
record_frame_times =
|
||||
)") DECLARE_KEY(record_frame_times) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Whether to enable additional debugging information during emulation
|
||||
# 0 (default): Off, 1: On
|
||||
renderer_debug =
|
||||
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Port for listening to GDB connections.
|
||||
use_gdbstub=false
|
||||
gdbstub_port=24689
|
||||
)") DECLARE_KEY(use_gdbstub) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(gdbstub_port) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Flush log output on every message
|
||||
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
|
||||
instant_debug_log =
|
||||
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Enable RPC server for scripting purposes. Allows accessing guest memory remotely.
|
||||
# 0 (default): Off, 1: On
|
||||
enable_rpc_server =
|
||||
)") DECLARE_KEY(enable_rpc_server) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Delay the start of apps when LLE modules are enabled
|
||||
# 0: Off, 1 (default): On
|
||||
delay_start_for_lle_modules =
|
||||
)") DECLARE_KEY(delay_start_for_lle_modules) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Force deterministic async operations
|
||||
# Only needed for debugging, makes performance worse if enabled
|
||||
# 0: Off (default), 1: On
|
||||
deterministic_async_operations =
|
||||
)") DECLARE_KEY(deterministic_async_operations) BOOST_HANA_STRING(R"(
|
||||
|
||||
# To LLE a service module add "LLE\<module name>=true"
|
||||
|
||||
[WebService]
|
||||
# URL for Web API
|
||||
web_api_url =
|
||||
)") DECLARE_KEY(web_api_url) BOOST_HANA_STRING(R"(
|
||||
# Username and token for Citra Web Service
|
||||
citra_username =
|
||||
citra_token =
|
||||
)";
|
||||
}
|
||||
)") DECLARE_KEY(citra_username) BOOST_HANA_STRING(R"(
|
||||
)") DECLARE_KEY(citra_token) BOOST_HANA_STRING("\n")).c_str();
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace DefaultINI
|
||||
|
|
|
|||
19
src/android/app/src/main/jni/jni_setting_keys.cpp.in
Normal file
19
src/android/app/src/main/jni/jni_setting_keys.cpp.in
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define JNI_DEFINE_KEY(KEY, KEY_JNI_ESCAPED) \
|
||||
JNIEXPORT jstring JNICALL \
|
||||
Java_org_citra_citra_1emu_features_settings_SettingKeys_##KEY_JNI_ESCAPED( \
|
||||
JNIEnv* env, jobject obj \
|
||||
) { \
|
||||
return env->NewStringUTF(#KEY); \
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
@JNI_SETTING_KEY_DEFINITIONS@
|
||||
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
55
src/android/app/src/main/res/layout/dialog_auto_map.xml
Normal file
55
src/android/app/src/main/res/layout/dialog_auto_map.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:focusedByDefault="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_title"
|
||||
style="@style/TextAppearance.Material3.HeadlineSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
tools:text="Auto-Map Controller" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_face_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:contentDescription="@string/auto_map_image_description"
|
||||
tools:ignore="ImageContrastCheck" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_message"
|
||||
style="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
tools:text="Press this button!" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_cancel"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:focusable="false"
|
||||
android:text="@android:string/cancel" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -100,7 +100,6 @@
|
|||
<string name="search_recently_added">Acabats d\'afegir</string>
|
||||
<string name="search_installed">Instal·lats</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Pad circular</string>
|
||||
<string name="controller_c">Palanca C</string>
|
||||
<string name="controller_hotkeys">Tecles de drecera</string>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@
|
|||
<string name="search_recently_added">For nyligt tilføjet</string>
|
||||
<string name="search_installed">Installeret</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Genvejstaster</string>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@
|
|||
<string name="search_recently_added">Recién Añadidos</string>
|
||||
<string name="search_installed">Instalados</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Pad Circular</string>
|
||||
<string name="controller_c">Palanca C</string>
|
||||
<string name="controller_hotkeys">Teclas de atajo</string>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@
|
|||
<string name="search_recently_added">Ostatnio dodane</string>
|
||||
<string name="search_installed">Zainstalowane</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Analog</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Skróty klawiszowe</string>
|
||||
|
|
@ -229,6 +228,8 @@
|
|||
<string name="async_shaders_description">Kompiluje shadery w tle, aby zmniejszyć zacinanie się podczas rozgrywki. Po włączeniu należy spodziewać się tymczasowych błędów graficznych</string>
|
||||
<string name="linear_filtering">Filtrowanie Linear</string>
|
||||
<string name="linear_filtering_description">Włącza filtrowanie liniowe, które sprawia, że grafika w grach jest płynniejsza.</string>
|
||||
<string name="use_integer_scaling">Skalowanie całkowitoliczbowe</string>
|
||||
<string name="use_integer_scaling_description">Skaluje ekrany za pomocą całkowitego mnożnika oryginalnego ekranu 3DS. W przypadku układów z dwoma różnymi rozmiarami ekranu największy ekran jest skalowany całkowicie.</string>
|
||||
<string name="texture_filter_name">Filtr tekstur</string>
|
||||
<string name="texture_filter_description">Ulepsza oprawę wizualną aplikacji poprzez zastosowanie filtrów do tekstur. Obsługiwane filtry to Anime4K Ultrafast, Bicubic, ScaleForce, xBRZ freescale i MMPX.</string>
|
||||
<string name="delay_render_thread">Opóźnienie Renderowania Wątku Gry</string>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@
|
|||
<string name="search_recently_added">Recentemente adicionado</string>
|
||||
<string name="search_installed">Instalado</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Analógico</string>
|
||||
<string name="controller_c">Direcional C-Stick</string>
|
||||
<string name="controller_hotkeys">Teclas de atalho</string>
|
||||
|
|
@ -118,6 +117,8 @@
|
|||
<string name="controller_dpad_axis_description">Alguns controles podem não ser capazes de mapear os D-pads para um eixo. Se esse for o caso, use a seção de D-Pad (Botões).</string>
|
||||
<string name="controller_dpad_button">D-Pad (Botão)</string>
|
||||
<string name="controller_dpad_button_description">Só mapeie o D-pad para isso se você se você estiver encontrando problemas com o mapeamento de botão do D-Pad (Eixo).</string>
|
||||
<string name="controller_axis_vertical">Eixo Vertical</string>
|
||||
<string name="controller_axis_horizontal">Eixo Horizontal</string>
|
||||
<string name="direction_up">Cima</string>
|
||||
<string name="direction_down">Baixo</string>
|
||||
<string name="direction_left">Esquerda</string>
|
||||
|
|
@ -126,6 +127,8 @@
|
|||
<string name="input_dialog_description">Pressione ou mova uma entrada.</string>
|
||||
<string name="input_binding">Mapeamento de controles</string>
|
||||
<string name="input_binding_description">Pressione ou mova um botão/alavanca para mapear para %1$s.</string>
|
||||
<string name="input_binding_description_vertical_axis">Pressione para CIMA no seu joystick.</string>
|
||||
<string name="input_binding_description_horizontal_axis">Pressione para a DIREITA no seu joystick.</string>
|
||||
<string name="button_home">Menu Principal</string>
|
||||
<string name="button_swap">Trocar telas</string>
|
||||
<string name="button_turbo">Turbo</string>
|
||||
|
|
@ -546,6 +549,8 @@
|
|||
|
||||
<!-- Disk Shader Cache -->
|
||||
<string name="preparing_shaders">Preparando Shaders</string>
|
||||
<string name="building_shaders">Construindo %s</string>
|
||||
|
||||
<!-- About Game Dialog -->
|
||||
<string name="play">Jogar</string>
|
||||
<string name="uninstall_cia">Desinstalar Aplicativo</string>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@
|
|||
<string name="search_recently_added">Добавленные недавно</string>
|
||||
<string name="search_installed">Установленные</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Джойстик</string>
|
||||
<string name="controller_c">C-стик</string>
|
||||
<string name="controller_hotkeys">Горячие клавиши</string>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@
|
|||
<string name="search_recently_added">Son eklenen</string>
|
||||
<string name="search_installed">Yüklü</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Kısayol tuşları</string>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@
|
|||
<string name="search_recently_added">最近添加</string>
|
||||
<string name="search_installed">已安装</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">方向摇杆</string>
|
||||
<string name="controller_c">C 摇杆</string>
|
||||
<string name="controller_hotkeys">热键</string>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@
|
|||
<string name="search_recently_added">Kürzlich hinzugefügt</string>
|
||||
<string name="search_installed">Installiert</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Schiebepad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Tastenkürzel</string>
|
||||
|
|
|
|||
|
|
@ -108,9 +108,17 @@
|
|||
<string name="search_installed">Installé</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_auto_map">Assigner les boutons de la manette automatiquement</string>
|
||||
<string name="controller_auto_map_description">Applique un assignement standard de manette pour tous les boutons et axes</string>
|
||||
<string name="auto_map_prompt">Appuyez sur ce bouton de votre manette !</string>
|
||||
<string name="auto_map_image_description">Les quatre boutons situés à droite de la 3DS avec le bouton A surligné</string>
|
||||
<string name="controller_clear_all">Réinitialiser toutes les assignations de touche</string>
|
||||
<string name="controller_clear_all_confirm">Cela effacera toutes les assignations de touche de manette actuelles.</string>
|
||||
<string name="controller_circlepad">Pad circulaire</string>
|
||||
<string name="controller_c">Stick C</string>
|
||||
<string name="controller_hotkeys">Raccourcis</string>
|
||||
<string name="controller_hotkeys_description">Si la touche \"Activer les raccourcis\" est assignée, celle-ci doit être pressée en plus de tout autre touche de raccourci.</string>
|
||||
<string name="controller_hotkey_enable_button">Activer les raccourcis</string>
|
||||
<string name="controller_triggers">Gachettes</string>
|
||||
<string name="controller_trigger">Déclencheur</string>
|
||||
<string name="controller_dpad">Manette +</string>
|
||||
|
|
@ -229,6 +237,8 @@
|
|||
<string name="async_shaders_description">Compile les shaders en arrière-plan pour réduire les saccades pendant le jeu. Lorsqu\'il est activé, prévoyez des problèmes graphiques temporaires.</string>
|
||||
<string name="linear_filtering">Filtrage linéaire</string>
|
||||
<string name="linear_filtering_description">Active le filtrage linéaire, qui améliorera le lissage graphique du jeu.</string>
|
||||
<string name="use_integer_scaling">Redimensionnement par nombre entier</string>
|
||||
<string name="use_integer_scaling_description">Redimensionne les écrans avec un multiple entier de la taille de l\'écran original de la 3DS. Pour les dispositions avec deux tailles d\'écrans différentes, c\'est l\'écran le plus large qui est redimensionné par un multiple entier.</string>
|
||||
<string name="texture_filter_name">Filtrage des textures</string>
|
||||
<string name="texture_filter_description">Améliore l\'aspect visuel des applications en appliquant un filtre aux textures. Les filtres pris en charge sont Anime4K Ultrafast, Bicubique, ScaleForce, xBRZ freescale et MMPX.</string>
|
||||
<string name="delay_render_thread">Retarder le thread de rendu du jeu</string>
|
||||
|
|
@ -336,6 +346,8 @@
|
|||
<string name="layout_screen_orientation_landscape_reverse">Paysage inversé</string>
|
||||
<string name="layout_screen_orientation_portrait">Portait</string>
|
||||
<string name="layout_screen_orientation_portrait_reverse">Portait inversé</string>
|
||||
<string name="layouts_to_cycle">Dispositions à alterner</string>
|
||||
<string name="layouts_to_cycle_description">Quelles dispositions sont alternées par le raccourci Alterner les dispositions</string>
|
||||
<string name="aspect_ratio_default">Par défaut</string>
|
||||
<string name="aspect_ratio_16_9">16:9</string>
|
||||
<string name="aspect_ratio_4_3">4:3</string>
|
||||
|
|
@ -550,6 +562,8 @@
|
|||
|
||||
<!-- Disk Shader Cache -->
|
||||
<string name="preparing_shaders">Préparation des shaders</string>
|
||||
<string name="building_shaders">Construction %s</string>
|
||||
|
||||
<!-- About Game Dialog -->
|
||||
<string name="play">Jouer</string>
|
||||
<string name="uninstall_cia">Désinstaller l\'application</string>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_disclaimer">Questo software eseguirà applicazioni per la console portatile Nintendo 3DS. Nessun titolo è incluso.
|
||||
Prima di iniziare con l\'emulazione, seleziona una cartella che conterrà i dati utente.
|
||||
Cos\'è questo:
|
||||
<a href='https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage'> Wiki - Citra Android user data and storage</a></string>
|
||||
<string name="app_disclaimer">Questo software eseguirà applicazioni per la console portatile Nintendo 3DS. Nessun titolo è incluso.\n\nPrima di iniziare con l\'emulazione, seleziona una cartella che conterrà i dati utente.\n\nCos\'è questo:\n<a href='https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage'> Wiki - Citra Android user data and storage</a></string>
|
||||
<string name="app_notification_channel_description">Notifiche emulatore Azahar 3DS</string>
|
||||
<string name="app_notification_running">Azahar è in esecuzione</string>
|
||||
<string name="app_game_install_description">Ora è necessario selezionare una cartella Applicazioni. Azahar mostrerà tutte le ROM 3DS all\'interno della cartella selezionata nell\'app. Le ROM CIA, gli aggiornamenti e DLC dovranno essere installati separatamente cliccando sulla cartella e selezionando installa CIA.</string>
|
||||
|
|
@ -24,8 +21,7 @@ Cos\'è questo:
|
|||
<string name="install_gpu_driver_description">Installa driver alternativi per possibili miglioramenti delle prestazioni o dell\'accuratezza</string>
|
||||
<string name="driver_already_installed">Driver già installato</string>
|
||||
<string name="custom_driver_not_supported">Driver personalizzato non supportato</string>
|
||||
<string name="custom_driver_not_supported_description">Il caricamento di driver personalizzati non è disponibile per questo dispositivo.
|
||||
Controlla ancora questa opzione in futuro per controllare se il supporto è stato aggiunto!</string>
|
||||
<string name="custom_driver_not_supported_description">Il caricamento di driver personalizzati non è supportato su questo dispositivo.\nControlla di nuovo in futuro per verificare eventuali aggiornamenti!</string>
|
||||
<string name="share_log_not_found">Nessun file di log trovato</string>
|
||||
<string name="select_games_folder">Seleziona cartella applicazioni</string>
|
||||
<string name="select_games_folder_description">Permette ad Azahar di popolare la lista di applicazioni</string>
|
||||
|
|
@ -57,26 +53,25 @@ Controlla ancora questa opzione in futuro per controllare se il supporto è stat
|
|||
<string name="licenses">Licenze</string>
|
||||
<!-- Setup strings -->
|
||||
<string name="welcome">Benvenuto!</string>
|
||||
<string name="welcome_description">Scopri come impostare Azahar e immergiti nell\'emulazione.</string>
|
||||
<string name="welcome_description">Scopri come impostare <b>Azahar</b> e immergiti nell\'emulazione.</string>
|
||||
<string name="get_started">Inizia</string>
|
||||
<string name="step_complete">Completato!</string>
|
||||
<string name="games">Applicazioni</string>
|
||||
<string name="games_description">Seleziona la tua cartella <b>Applicazioni</b> usando il pulsante qui sotto.</string>
|
||||
<string name="done">Fatto</string>
|
||||
<string name="done_description">Adesso sei pronto.
|
||||
Divertiti usando l\'emulatore!</string>
|
||||
<string name="done_description">Adesso sei pronto.\nDivertiti usando l\'emulatore!</string>
|
||||
<string name="text_continue">Continua</string>
|
||||
<string name="notifications">Notifiche</string>
|
||||
<string name="notifications_description">Concedi il permesso di notifica usando il pulsante qui sotto.</string>
|
||||
<string name="give_permission">Concedi il permesso</string>
|
||||
<string name="notification_warning">Vuoi saltare la concessione del permesso di notifica?</string>
|
||||
<string name="notification_warning">Vuoi saltare l\'autorizzazione per le notifiche?</string>
|
||||
<string name="notification_warning_description">Azahar non avrà il permesso di notificarti con informazioni importanti.</string>
|
||||
<string name="filesystem_permission_warning">Permessi mancanti</string>
|
||||
<string name="filesystem_permission_warning_description">Azahar richiede l\'autorizzazione per gestire i file su questo dispositivo per archiviare e gestire i propri dati.\n\nDare il permesso \"Filesystem\" prima di continuare.</string>
|
||||
<string name="camera_permission">Fotocamera</string>
|
||||
<string name="camera_permission_description">Concedi il permesso alla fotocamera qui sotto per emulare la fotocamera del 3DS</string>
|
||||
<string name="camera_permission_description">Concedi il permesso alla fotocamera qui sotto per emulare la fotocamera del 3DS.</string>
|
||||
<string name="microphone_permission">Microfono</string>
|
||||
<string name="microphone_permission_description">Concedi il permesso all\'utilizzo del microfono per emulare il microfono del 3DS</string>
|
||||
<string name="microphone_permission_description">Concedi il permesso all\'utilizzo del microfono per emulare il microfono del 3DS.</string>
|
||||
<string name="filesystem_permission">Filesystem</string>
|
||||
<string name="filesystem_permission_description">Dare il permesso del file system qui sotto per consentire ad Azahar di archiviare file.</string>
|
||||
<string name="permission_denied">Permesso negato</string>
|
||||
|
|
@ -113,9 +108,17 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="search_installed">Installati</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_auto_map">Mappatura automatica controller</string>
|
||||
<string name="controller_auto_map_description">Applica la mappatura standard per tutti i tasti e gli assi</string>
|
||||
<string name="auto_map_prompt">Premi questo tasto sul controller!</string>
|
||||
<string name="auto_map_image_description">Schema tasti 3DS con il tasto A evidenziato</string>
|
||||
<string name="controller_clear_all">Rimuovi tutte le assegnazioni</string>
|
||||
<string name="controller_clear_all_confirm">Tutte le assegnazioni attuali dei controller verranno rimosse.</string>
|
||||
<string name="controller_circlepad">Pad scorrevole</string>
|
||||
<string name="controller_c">Levetta C</string>
|
||||
<string name="controller_hotkeys">Scorciatoie</string>
|
||||
<string name="controller_hotkeys">Tasti rapidi</string>
|
||||
<string name="controller_hotkeys_description">Se il tasto \"Abilita tasti rapidi\" è assegnato, va premuto insieme al tasto rapido configurato.</string>
|
||||
<string name="controller_hotkey_enable_button">Abilita tasti rapidi</string>
|
||||
<string name="controller_triggers">Grilletti</string>
|
||||
<string name="controller_trigger">Grilletto</string>
|
||||
<string name="controller_dpad">Tasti direzionali</string>
|
||||
|
|
@ -133,6 +136,8 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="input_dialog_description">Premi o sposta un comando</string>
|
||||
<string name="input_binding">Assegnazione Input</string>
|
||||
<string name="input_binding_description">Premi o muovi un comando per assegnarlo a %1$s.</string>
|
||||
<string name="input_binding_description_vertical_axis">Premi UP sul tuo controller.</string>
|
||||
<string name="input_binding_description_horizontal_axis">Premi RIGHT sul tuo controller.</string>
|
||||
<string name="button_home">Home</string>
|
||||
<string name="button_swap">Inverti schermi</string>
|
||||
<string name="button_turbo">Turbo</string>
|
||||
|
|
@ -232,6 +237,8 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="async_shaders_description">Compila gli shader in background per ridurre gli scatti durante il gioco. Se l\'opzione è abilitata, potrebbero verificarsi dei glitch grafici temporanei.</string>
|
||||
<string name="linear_filtering">Filtraggio lineare</string>
|
||||
<string name="linear_filtering_description">Abilita il filtraggio lineare, che fa sembrare più smussata la grafica dei giochi.</string>
|
||||
<string name="use_integer_scaling">Scalabilità intera</string>
|
||||
<string name="use_integer_scaling_description">Scala gli schermi usando un multiplo intero della risoluzione originale del 3DS. Nei layout con schermi di dimensioni diverse, la scalabilità intera viene applicata allo schermo più grande.</string>
|
||||
<string name="texture_filter_name">Filtro texture</string>
|
||||
<string name="texture_filter_description">Migliora la grafica delle applicazioni applicando un filtro alle texture. I filtri supportati sono Anime4k Ultrafast, Bicubic, ScaleForce, xBRZ freescale e MMPX.</string>
|
||||
<string name="delay_render_thread">Ritarda il thread di rendering del gioco</string>
|
||||
|
|
@ -244,13 +251,13 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="asynchronous_gpu">Abilita l\'emulazione GPU asincrona</string>
|
||||
<string name="asynchronous_gpu_description">Usa un thread separato per l\'emulazione asincrona della GPU. Quando è abilitato le prestazioni saranno migliori.</string>
|
||||
<string name="frame_limit_enable">Limita velocità</string>
|
||||
<string name="frame_limit_enable_description">Quando l\'opzione è abilitata, la velocità di emulazione sarà limitata a una percentuale specificata della velocità normale. Se è disabilitata, la velocità di emulazione non verrà limitata e la scorciatoia della velocità Turbo non funzionerà.</string>
|
||||
<string name="frame_limit_enable_description">Se attiva, la velocità di emulazione viene limitata a una percentuale specifica. Se disattivata, la velocità non avrà limiti e il tasto rapido per il turbo non funzionerà.</string>
|
||||
<string name="frame_limit_slider">Limite della velocità in percentuale</string>
|
||||
<string name="frame_limit_slider_description">Specifica a quale percentuale limitare la velocità d\'emulazione. Utilizzando l\'impostazione predefinita di 100% l\'emulazione sarà limitata alla velocità normale. Valori superiori o inferiori aumenteranno o diminuiranno il limite di velocità.</string>
|
||||
<string name="android_hide_images">Nascondi le immagini 3DS da Android</string>
|
||||
<string name="android_hide_images_description">Impedisci che le immagini della fotocamera 3DS, degli screenshot e delle texture personalizzate vengano indicizzate da Android e visualizzate nella galleria. Potrebbe essere necessario riavviare il dispositivo dopo aver modificato questa impostazione per avere effetto.</string>
|
||||
<string name="turbo_limit">Limite della velocità Turbo</string>
|
||||
<string name="turbo_limit_description">Limite di velocità dell\'emulazione usato quando la scorciatoia della velocità Turbo è attiva.</string>
|
||||
<string name="turbo_limit_description">Limite di velocità dell\'emulazione usato quando il tasto rapido della velocità Turbo è attiva.</string>
|
||||
<string name="expand_to_cutout_area">Espandi all\'area di ritaglio</string>
|
||||
<string name="expand_to_cutout_area_description">Espande l\'area di visualizzazione per includere l\'area di ritaglio (o notch).</string>
|
||||
<string name="internal_resolution">Risoluzione interna</string>
|
||||
|
|
@ -339,6 +346,8 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="layout_screen_orientation_landscape_reverse">Orizzontale rovesciato</string>
|
||||
<string name="layout_screen_orientation_portrait">Verticale</string>
|
||||
<string name="layout_screen_orientation_portrait_reverse">Verticale rovesciato</string>
|
||||
<string name="layouts_to_cycle">Layout da ciclare</string>
|
||||
<string name="layouts_to_cycle_description">Quali layout vengono alternati dal tasto rapido \"Cicla layout\"</string>
|
||||
<string name="aspect_ratio_default">Default</string>
|
||||
<string name="aspect_ratio_16_9">16:9</string>
|
||||
<string name="aspect_ratio_4_3">4:3</string>
|
||||
|
|
@ -367,14 +376,14 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="install">Installa</string>
|
||||
<string name="delete">Cancella</string>
|
||||
<string name="reset_all_settings">Reimpostare tutte le impostazioni?</string>
|
||||
<string name="reset_all_settings_description">Tutte le impostazioni avanzate verranno reimpostate alla loro configurazione predefinita. Questa operazione non può essere annullata.</string>
|
||||
<string name="reset_all_settings_description">Tutte le impostazioni avanzate verranno ripristinate ai valori predefiniti. L\'operazione non è annullabile.</string>
|
||||
<string name="settings_reset">Reimpostazione impostazioni</string>
|
||||
<string name="select_rtc_date">Seleziona data RTC</string>
|
||||
<string name="select_rtc_time">Seleziona orario RTC</string>
|
||||
<string name="reset_setting_confirmation">Vuoi reimpostare questa opzione al suo valore predefinito?</string>
|
||||
<string name="setting_not_editable">Non puoi modificare questo ora</string>
|
||||
<string name="setting_disabled">Impostazione disabilitata</string>
|
||||
<string name="setting_disabled_description">Questa impostazione è attualmente disattivata a causa di un\'altra impostazione che non è il valore appropriato.</string>
|
||||
<string name="setting_disabled_description">Questa impostazione è disattivata perché un\'altra opzione non ha il valore richiesto.</string>
|
||||
<string name="setting_not_editable_description">Questa opzione non può essere modificata mentre un gioco è in esecuzione.</string>
|
||||
<string name="auto_select">Seleziona automaticamente</string>
|
||||
<string name="start">Avvia</string>
|
||||
|
|
@ -383,7 +392,7 @@ Divertiti usando l\'emulatore!</string>
|
|||
<string name="information">Informazione</string>
|
||||
|
||||
<!-- Add Directory Screen-->
|
||||
<string name="select_game_folder">Seleziona cartella dei giochi</string>
|
||||
<string name="select_game_folder">Seleziona cartella del gioco</string>
|
||||
|
||||
<!-- Game Properties -->
|
||||
<string name="properties">Proprietà</string>
|
||||
|
|
@ -553,6 +562,8 @@ Divertiti usando l\'emulatore!</string>
|
|||
|
||||
<!-- Disk Shader Cache -->
|
||||
<string name="preparing_shaders">Preparazione degli shader</string>
|
||||
<string name="building_shaders">Compilazione %s</string>
|
||||
|
||||
<!-- About Game Dialog -->
|
||||
<string name="play">Riproduci</string>
|
||||
<string name="uninstall_cia">Disinstalla applicazione</string>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
<!-- Home Strings -->
|
||||
<string name="grid_menu_core_settings">Innstillinger</string>
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Gliplate</string>
|
||||
<string name="controller_c">C-Spak</string>
|
||||
<string name="controller_triggers">Utløser</string>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ Vink deze optie in de toekomst nogmaals aan om te zien of er ondersteuning is to
|
|||
<string name="search_recently_added">Recent Toegevoegd</string>
|
||||
<string name="search_installed">Geïnstalleerd</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Sneltoetsen</string>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@
|
|||
<string name="search_recently_added">Nyligen tillagd</string>
|
||||
<string name="search_installed">Installerad</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-spak</string>
|
||||
<string name="controller_hotkeys">Snabbtangenter</string>
|
||||
|
|
@ -550,6 +549,8 @@
|
|||
|
||||
<!-- Disk Shader Cache -->
|
||||
<string name="preparing_shaders">Förbereder shaders</string>
|
||||
<string name="building_shaders">Bygger %s</string>
|
||||
|
||||
<!-- About Game Dialog -->
|
||||
<string name="play">Spela</string>
|
||||
<string name="uninstall_cia">Avinstallera applikation</string>
|
||||
|
|
|
|||
|
|
@ -119,9 +119,17 @@
|
|||
<string name="search_installed">Installed</string>
|
||||
|
||||
<!-- Input related strings -->
|
||||
<string name="controller_auto_map">Auto-Map Controller</string>
|
||||
<string name="controller_auto_map_description">Apply standard gamepad mapping for all buttons and axes</string>
|
||||
<string name="auto_map_prompt">Press this button on your controller!</string>
|
||||
<string name="auto_map_image_description">3DS face button diamond with A button highlighted</string>
|
||||
<string name="controller_clear_all">Clear All Bindings</string>
|
||||
<string name="controller_clear_all_confirm">This will remove all current controller bindings.</string>
|
||||
<string name="controller_circlepad">Circle Pad</string>
|
||||
<string name="controller_c">C-Stick</string>
|
||||
<string name="controller_hotkeys">Hotkeys</string>
|
||||
<string name="controller_hotkeys_description">If the "Hotkey Enable" key is mapped, that key must be pressed in addition to the mapped hotkey</string>
|
||||
<string name="controller_hotkey_enable_button">Hotkey Enable</string>
|
||||
<string name="controller_triggers">Triggers</string>
|
||||
<string name="controller_trigger">Trigger</string>
|
||||
<string name="controller_dpad">D-Pad</string>
|
||||
|
|
@ -255,6 +263,8 @@
|
|||
<string name="async_shaders_description">Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches</string>
|
||||
<string name="linear_filtering">Linear Filtering</string>
|
||||
<string name="linear_filtering_description">Enables linear filtering, which causes game visuals to appear smoother.</string>
|
||||
<string name="use_integer_scaling">Integer Scaling</string>
|
||||
<string name="use_integer_scaling_description">Scales the screens with an integer multiplier of the original 3DS screen. For layouts with two different screen sizes, the largest screen is integer-scaled.</string>
|
||||
<string name="texture_filter_name">Texture Filter</string>
|
||||
<string name="texture_filter_description">Enhances the visuals of applications by applying a filter to textures. The supported filters are Anime4K Ultrafast, Bicubic, ScaleForce, xBRZ freescale, and MMPX.</string>
|
||||
<string name="delay_render_thread">Delay Game Render Thread</string>
|
||||
|
|
@ -362,6 +372,8 @@
|
|||
<string name="layout_screen_orientation_landscape_reverse">Reverse Landscape</string>
|
||||
<string name="layout_screen_orientation_portrait">Portrait</string>
|
||||
<string name="layout_screen_orientation_portrait_reverse">Reverse Portrait</string>
|
||||
<string name="layouts_to_cycle">Layouts to Cycle</string>
|
||||
<string name="layouts_to_cycle_description">Which layouts are cycled by the Cycle Layout hotkey</string>
|
||||
<string name="aspect_ratio_default">Default</string>
|
||||
<string name="aspect_ratio_16_9">16:9</string>
|
||||
<string name="aspect_ratio_4_3">4:3</string>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ add_library(audio_core STATIC
|
|||
|
||||
$<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h>
|
||||
$<$<BOOL:${ENABLE_LIBRETRO}>:libretro_sink.cpp libretro_sink.h libretro_input.cpp libretro_input.h>
|
||||
$<$<BOOL:${ENABLE_OPENAL}>:openal_input.cpp openal_input.h openal_sink.cpp openal_sink.h>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ void DspInterface::OutputFrame(StereoFrame16 frame) {
|
|||
return;
|
||||
}
|
||||
|
||||
fifo.Push(frame.data(), frame.size());
|
||||
if (sink->ImmediateSubmission()) {
|
||||
sink->PushSamples(frame.data(), frame.size());
|
||||
} else {
|
||||
fifo.Push(frame.data(), frame.size());
|
||||
}
|
||||
|
||||
auto video_dumper = system.GetVideoDumper();
|
||||
if (video_dumper && video_dumper->IsDumping()) {
|
||||
|
|
@ -54,7 +58,11 @@ void DspInterface::OutputSample(std::array<s16, 2> sample) {
|
|||
return;
|
||||
}
|
||||
|
||||
fifo.Push(&sample, 1);
|
||||
if (sink->ImmediateSubmission()) {
|
||||
sink->PushSamples(&sample, 1);
|
||||
} else {
|
||||
fifo.Push(&sample, 1);
|
||||
}
|
||||
|
||||
auto video_dumper = system.GetVideoDumper();
|
||||
if (video_dumper && video_dumper->IsDumping()) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -15,6 +15,9 @@
|
|||
#ifdef HAVE_OPENAL
|
||||
#include "audio_core/openal_input.h"
|
||||
#endif
|
||||
#ifdef HAVE_LIBRETRO
|
||||
#include "audio_core/libretro_input.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
|
||||
|
|
@ -22,6 +25,18 @@ namespace AudioCore {
|
|||
namespace {
|
||||
// input_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr std::array input_details = {
|
||||
#ifdef HAVE_LIBRETRO
|
||||
InputDetails{InputType::LibRetro, "Real Device (LibRetro)", true,
|
||||
[](Core::System& system, std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
if (!system.HasMicPermission()) {
|
||||
LOG_WARNING(Audio,
|
||||
"Microphone permission denied, falling back to null input.");
|
||||
return std::make_unique<NullInput>();
|
||||
}
|
||||
return std::make_unique<LibRetroInput>();
|
||||
},
|
||||
[] { return std::vector<std::string>{"LibRetro Microphone"}; }},
|
||||
#endif
|
||||
#ifdef HAVE_CUBEB
|
||||
InputDetails{InputType::Cubeb, "Real Device (Cubeb)", true,
|
||||
[](Core::System& system, std::string_view device_id) -> std::unique_ptr<Input> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -24,6 +24,9 @@ enum class InputType : u32 {
|
|||
Static = 2,
|
||||
Cubeb = 3,
|
||||
OpenAL = 4,
|
||||
#ifdef HAVE_LIBRETRO
|
||||
LibRetro = 5,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct InputDetails {
|
||||
|
|
|
|||
327
src/audio_core/libretro_input.cpp
Normal file
327
src/audio_core/libretro_input.cpp
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "audio_core/libretro_input.h"
|
||||
#include "citra_libretro/environment.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/ring_buffer.h"
|
||||
#include "libretro.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
namespace {
|
||||
// Global instance pointer for access from retro_run
|
||||
LibRetroInput* g_libretro_input = nullptr;
|
||||
} // namespace
|
||||
|
||||
struct LibRetroInput::Impl {
|
||||
std::optional<retro_microphone_interface> mic_interface;
|
||||
retro_microphone_t* mic_handle = nullptr;
|
||||
bool is_sampling = false;
|
||||
u8 sample_size_in_bytes = 2;
|
||||
int warmup_frames = 0;
|
||||
|
||||
// The rate at which the frontend actually provides samples (may differ from
|
||||
// what the 3DS mic service requested). We open the mic at this rate to avoid
|
||||
// RetroArch's internal resampler path, which has a convergence bug when
|
||||
// downsampling (ratio < 1). We resample ourselves in Read() instead.
|
||||
u32 native_sample_rate = 0;
|
||||
|
||||
// Ring buffer for thread-safe sample storage
|
||||
// Capacity: 4096 samples should be plenty for buffering between frames
|
||||
// The 3DS mic service reads 16 samples at a time at ~32728 Hz
|
||||
Common::RingBuffer<s16, 4096> sample_buffer;
|
||||
|
||||
// Temporary buffer for reading from frontend
|
||||
std::vector<s16> read_buffer;
|
||||
|
||||
Impl() {
|
||||
// Try to get the microphone interface from the frontend
|
||||
retro_microphone_interface interface{};
|
||||
interface.interface_version = RETRO_MICROPHONE_INTERFACE_VERSION;
|
||||
|
||||
if (LibRetro::GetMicrophoneInterface(&interface)) {
|
||||
if (interface.interface_version == RETRO_MICROPHONE_INTERFACE_VERSION) {
|
||||
mic_interface = interface;
|
||||
LOG_INFO(Audio, "LibRetro microphone interface available (version {})",
|
||||
interface.interface_version);
|
||||
} else {
|
||||
LOG_WARNING(Audio,
|
||||
"LibRetro microphone interface version mismatch: expected {}, got {}",
|
||||
RETRO_MICROPHONE_INTERFACE_VERSION, interface.interface_version);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Audio, "LibRetro microphone interface not available");
|
||||
}
|
||||
|
||||
// Keep this small enough that RetroArch's microphone_driver_read can
|
||||
// fill its outgoing FIFO in a single flush iteration. The CoreAudio
|
||||
// driver's internal FIFO is ~480 samples (10ms at 48kHz). If we
|
||||
// request more than that, the blocking while-loop in
|
||||
// microphone_driver_read must wait for the next hardware callback,
|
||||
// and on ARM64 without memory barriers in the FIFO, it may never
|
||||
// see the new data. 128 samples is conservative enough to succeed
|
||||
// in one pass.
|
||||
read_buffer.resize(128);
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
CloseMicrophone();
|
||||
}
|
||||
|
||||
bool EnsureMicrophoneOpen() {
|
||||
if (mic_handle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mic_interface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always open at 48000 Hz regardless of what the game requests.
|
||||
// RetroArch's microphone_driver_read has a resampler whose while-loop
|
||||
// deadlocks when the ratio is < 1 (core rate < device rate). The
|
||||
// libretro get_params API only returns the effective (requested) rate,
|
||||
// not the device's native rate, so we can't detect the mismatch.
|
||||
// Opening at 48000 Hz (the most common hardware rate) keeps the
|
||||
// frontend's internal resampling ratio at or near 1.0, avoiding the
|
||||
// bug. We resample to the game's requested rate ourselves in Read().
|
||||
static constexpr u32 kMicOpenRate = 48000;
|
||||
native_sample_rate = kMicOpenRate;
|
||||
|
||||
retro_microphone_params_t params{};
|
||||
params.rate = kMicOpenRate;
|
||||
|
||||
mic_handle = mic_interface->open_mic(¶ms);
|
||||
if (!mic_handle) {
|
||||
LOG_ERROR(Audio, "Failed to open LibRetro microphone");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The frontend may start recording immediately in open_mic (e.g.
|
||||
// CoreAudio calls AudioOutputUnitStart). Pause it right away so the
|
||||
// mic is available but idle until StartSampling enables it.
|
||||
mic_interface->set_mic_state(mic_handle, false);
|
||||
|
||||
LOG_INFO(Audio, "LibRetro microphone opened at {} Hz (idle)", native_sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CloseMicrophone() {
|
||||
if (mic_interface && mic_handle) {
|
||||
mic_interface->close_mic(mic_handle);
|
||||
mic_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool SetMicrophoneActive(bool active) {
|
||||
if (!mic_interface || !mic_handle) {
|
||||
return false;
|
||||
}
|
||||
return mic_interface->set_mic_state(mic_handle, active);
|
||||
}
|
||||
|
||||
bool IsMicrophoneActive() const {
|
||||
if (!mic_interface || !mic_handle) {
|
||||
return false;
|
||||
}
|
||||
return mic_interface->get_mic_state(mic_handle);
|
||||
}
|
||||
};
|
||||
|
||||
LibRetroInput::LibRetroInput() : impl(std::make_unique<Impl>()) {
|
||||
g_libretro_input = this;
|
||||
}
|
||||
|
||||
LibRetroInput::~LibRetroInput() {
|
||||
StopSampling();
|
||||
if (g_libretro_input == this) {
|
||||
g_libretro_input = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LibRetroInput::StartSampling(const InputParameters& params) {
|
||||
if (IsSampling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LibRetro only provides signed 16-bit PCM samples
|
||||
// We'll convert to the requested format in Read()
|
||||
if (params.sign == Signedness::Unsigned) {
|
||||
LOG_DEBUG(Audio, "Application requested unsigned PCM format; will convert from signed.");
|
||||
}
|
||||
|
||||
parameters = params;
|
||||
impl->sample_size_in_bytes = params.sample_size / 8;
|
||||
|
||||
if (!impl->EnsureMicrophoneOpen()) {
|
||||
LOG_WARNING(Audio, "Cannot start sampling: microphone not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable the microphone (transitions from idle to recording)
|
||||
if (!impl->SetMicrophoneActive(true)) {
|
||||
LOG_ERROR(Audio, "Failed to activate microphone");
|
||||
return;
|
||||
}
|
||||
|
||||
impl->is_sampling = true;
|
||||
// Give the audio hardware a few frames to start delivering data before
|
||||
// we attempt a (blocking) read_mic call. Without this, the very first
|
||||
// read can hang because the CoreAudio callback hasn't fired yet.
|
||||
impl->warmup_frames = 10;
|
||||
LOG_INFO(Audio, "LibRetro microphone sampling started at {} Hz, {} bit", params.sample_rate,
|
||||
params.sample_size);
|
||||
}
|
||||
|
||||
void LibRetroInput::StopSampling() {
|
||||
if (!impl->is_sampling) {
|
||||
return;
|
||||
}
|
||||
|
||||
impl->SetMicrophoneActive(false);
|
||||
impl->is_sampling = false;
|
||||
|
||||
LOG_INFO(Audio, "LibRetro microphone sampling stopped (mic remains idle)");
|
||||
}
|
||||
|
||||
bool LibRetroInput::IsSampling() {
|
||||
return impl->is_sampling;
|
||||
}
|
||||
|
||||
void LibRetroInput::AdjustSampleRate(u32 sample_rate) {
|
||||
if (!IsSampling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart with new sample rate
|
||||
auto new_parameters = parameters;
|
||||
new_parameters.sample_rate = sample_rate;
|
||||
StopSampling();
|
||||
StartSampling(new_parameters);
|
||||
}
|
||||
|
||||
void LibRetroInput::PollMicrophone() {
|
||||
// This is called from the main thread (retro_run)
|
||||
// Read samples from the frontend and push to the ring buffer
|
||||
|
||||
if (!impl->is_sampling || !impl->mic_interface || !impl->mic_handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the audio hardware to start delivering data before making
|
||||
// any blocking read_mic calls.
|
||||
if (impl->warmup_frames > 0) {
|
||||
impl->warmup_frames--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Issue a memory fence before reading. RetroArch's CoreAudio mic driver
|
||||
// fills its FIFO from a callback thread without memory barriers. On ARM64
|
||||
// (weak memory model), the main thread may not see the callback's writes
|
||||
// without an explicit barrier.
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
int samples_read = impl->mic_interface->read_mic(impl->mic_handle, impl->read_buffer.data(),
|
||||
static_cast<size_t>(impl->read_buffer.size()));
|
||||
|
||||
if (samples_read > 0) {
|
||||
impl->sample_buffer.Push(
|
||||
std::span<const s16>(impl->read_buffer.data(), static_cast<size_t>(samples_read)));
|
||||
}
|
||||
}
|
||||
|
||||
Samples LibRetroInput::Read() {
|
||||
// This is called from the CoreTiming scheduler thread
|
||||
// Pop samples from the ring buffer (thread-safe)
|
||||
|
||||
if (!impl->is_sampling) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Pop available samples from the buffer (at native device rate)
|
||||
std::vector<s16> raw_samples = impl->sample_buffer.Pop();
|
||||
|
||||
if (raw_samples.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Resample from native device rate to the rate the 3DS mic service expects
|
||||
if (impl->native_sample_rate != 0 && impl->native_sample_rate != parameters.sample_rate) {
|
||||
double ratio = static_cast<double>(parameters.sample_rate) / impl->native_sample_rate;
|
||||
auto output_count = static_cast<std::size_t>(raw_samples.size() * ratio);
|
||||
if (output_count == 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<s16> resampled(output_count);
|
||||
for (std::size_t i = 0; i < output_count; i++) {
|
||||
double src_pos = i / ratio;
|
||||
auto idx = static_cast<std::size_t>(src_pos);
|
||||
double frac = src_pos - idx;
|
||||
if (idx + 1 < raw_samples.size()) {
|
||||
resampled[i] =
|
||||
static_cast<s16>(raw_samples[idx] * (1.0 - frac) + raw_samples[idx + 1] * frac);
|
||||
} else {
|
||||
resampled[i] = raw_samples[std::min(idx, raw_samples.size() - 1)];
|
||||
}
|
||||
}
|
||||
raw_samples = std::move(resampled);
|
||||
}
|
||||
|
||||
// Convert sample format if needed
|
||||
constexpr auto convert_s16_to_u16 = [](s16 sample) -> u16 {
|
||||
return static_cast<u16>(sample) ^ 0x8000;
|
||||
};
|
||||
|
||||
constexpr auto convert_s16_to_s8 = [](s16 sample) -> s8 {
|
||||
return static_cast<s8>(sample >> 8);
|
||||
};
|
||||
|
||||
constexpr auto convert_s16_to_u8 = [](s16 sample) -> u8 {
|
||||
return static_cast<u8>((static_cast<u16>(sample) ^ 0x8000) >> 8);
|
||||
};
|
||||
|
||||
Samples output;
|
||||
output.reserve(raw_samples.size() * impl->sample_size_in_bytes);
|
||||
|
||||
if (impl->sample_size_in_bytes == 1) {
|
||||
// 8-bit output
|
||||
if (parameters.sign == Signedness::Unsigned) {
|
||||
for (s16 sample : raw_samples) {
|
||||
output.push_back(convert_s16_to_u8(sample));
|
||||
}
|
||||
} else {
|
||||
for (s16 sample : raw_samples) {
|
||||
output.push_back(static_cast<u8>(convert_s16_to_s8(sample)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 16-bit output
|
||||
if (parameters.sign == Signedness::Unsigned) {
|
||||
for (s16 sample : raw_samples) {
|
||||
u16 converted = convert_s16_to_u16(sample);
|
||||
output.push_back(static_cast<u8>(converted & 0xFF));
|
||||
output.push_back(static_cast<u8>(converted >> 8));
|
||||
}
|
||||
} else {
|
||||
// Signed 16-bit - just copy the raw bytes
|
||||
const u8* data = reinterpret_cast<const u8*>(raw_samples.data());
|
||||
output.insert(output.end(), data, data + raw_samples.size() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
LibRetroInput* GetLibRetroInput() {
|
||||
return g_libretro_input;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
36
src/audio_core/libretro_input.h
Normal file
36
src/audio_core/libretro_input.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "audio_core/input.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class LibRetroInput final : public Input {
|
||||
public:
|
||||
LibRetroInput();
|
||||
~LibRetroInput() override;
|
||||
|
||||
void StartSampling(const InputParameters& params) override;
|
||||
void StopSampling() override;
|
||||
bool IsSampling() override;
|
||||
void AdjustSampleRate(u32 sample_rate) override;
|
||||
Samples Read() override;
|
||||
|
||||
/// Called from main thread (retro_run) to read samples from the frontend
|
||||
/// and store them in the thread-safe buffer for Read() to consume.
|
||||
void PollMicrophone();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/// Returns the global LibRetroInput instance, or nullptr if not initialized.
|
||||
/// This is used by citra_libretro.cpp to poll the microphone from the main thread.
|
||||
LibRetroInput* GetLibRetroInput();
|
||||
|
||||
} // namespace AudioCore
|
||||
27
src/audio_core/libretro_sink.cpp
Normal file
27
src/audio_core/libretro_sink.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/libretro_sink.h"
|
||||
#include "citra_libretro/environment.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
LibRetroSink::LibRetroSink(std::string) {}
|
||||
|
||||
LibRetroSink::~LibRetroSink() = default;
|
||||
|
||||
unsigned int LibRetroSink::GetNativeSampleRate() const {
|
||||
return native_sample_rate;
|
||||
}
|
||||
|
||||
void LibRetroSink::PushSamples(const void* data, std::size_t num_samples) {
|
||||
// libretro calls stereo pairs "frames", Azahar calls them "samples"
|
||||
LibRetro::SubmitAudio(static_cast<const s16*>(data), num_samples);
|
||||
}
|
||||
|
||||
std::vector<std::string> ListLibretroSinkDevices() {
|
||||
return std::vector<std::string>{"LibRetro"};
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
33
src/audio_core/libretro_sink.h
Normal file
33
src/audio_core/libretro_sink.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "audio_core/sink.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class LibRetroSink final : public Sink {
|
||||
public:
|
||||
explicit LibRetroSink(std::string target_device_name);
|
||||
~LibRetroSink() override;
|
||||
|
||||
unsigned int GetNativeSampleRate() const override;
|
||||
|
||||
// Not used for immediate submission sinks
|
||||
void SetCallback(std::function<void(s16*, std::size_t)> cb) override {};
|
||||
|
||||
bool ImmediateSubmission() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PushSamples(const void* data, std::size_t num_samples) override;
|
||||
};
|
||||
|
||||
std::vector<std::string> ListLibretroSinkDevices();
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "common/common_types.h"
|
||||
#include "audio_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
|
|
@ -30,6 +30,23 @@ public:
|
|||
* @param sample_count Number of samples.
|
||||
*/
|
||||
virtual void SetCallback(std::function<void(s16*, std::size_t)> cb) = 0;
|
||||
|
||||
/**
|
||||
* Override and set this to true if the sink wants audio data submitted
|
||||
* immediately rather than requesting audio on demand
|
||||
* @return true if audio data should be pushed to the sink
|
||||
*/
|
||||
virtual bool ImmediateSubmission() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push audio samples directly to the sink, bypassing the FIFO.
|
||||
* Only called when ImmediateSubmission() returns true.
|
||||
* @param data Pointer to stereo PCM16 samples (each sample is L+R pair)
|
||||
* @param num_samples Number of stereo samples
|
||||
*/
|
||||
virtual void PushSamples(const void* data, std::size_t num_samples) {}
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -11,6 +11,9 @@
|
|||
#ifdef HAVE_SDL2
|
||||
#include "audio_core/sdl2_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_LIBRETRO
|
||||
#include "audio_core/libretro_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#endif
|
||||
|
|
@ -23,6 +26,13 @@ namespace AudioCore {
|
|||
namespace {
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr std::array sink_details = {
|
||||
#ifdef HAVE_LIBRETRO
|
||||
SinkDetails{SinkType::LibRetro, "libretro",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<LibRetroSink>(std::string(device_id));
|
||||
},
|
||||
&ListLibretroSinkDevices},
|
||||
#endif
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{SinkType::Cubeb, "Cubeb",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -20,6 +20,9 @@ enum class SinkType : u32 {
|
|||
Cubeb = 2,
|
||||
OpenAL = 3,
|
||||
SDL2 = 4,
|
||||
#ifdef HAVE_LIBRETRO
|
||||
LibRetro = 5,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct SinkDetails {
|
||||
|
|
|
|||
96
src/citra_libretro/CMakeLists.txt
Normal file
96
src/citra_libretro/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
||||
|
||||
# Object library for libretro code (can be linked into both shared lib and tests)
|
||||
add_library(azahar_libretro_common OBJECT
|
||||
emu_window/libretro_window.cpp
|
||||
emu_window/libretro_window.h
|
||||
input/input_factory.cpp
|
||||
input/input_factory.h
|
||||
input/mouse_tracker.cpp
|
||||
input/mouse_tracker.h
|
||||
$<$<BOOL:${ENABLE_VULKAN}>: libretro_vk.cpp libretro_vk.h>
|
||||
environment.cpp
|
||||
environment.h
|
||||
core_settings.cpp
|
||||
core_settings.h)
|
||||
|
||||
target_compile_definitions(azahar_libretro_common PRIVATE HAVE_LIBRETRO)
|
||||
target_link_libraries(azahar_libretro_common PRIVATE citra_common citra_core video_core libretro tsl::robin_map)
|
||||
if(ENABLE_OPENGL)
|
||||
target_link_libraries(azahar_libretro_common PRIVATE glad)
|
||||
endif()
|
||||
if(ENABLE_VULKAN)
|
||||
target_link_libraries(azahar_libretro_common PRIVATE sirit vulkan-headers vma)
|
||||
endif()
|
||||
|
||||
add_library(azahar_libretro SHARED
|
||||
citra_libretro.cpp
|
||||
citra_libretro.h
|
||||
$<TARGET_OBJECTS:azahar_libretro_common>)
|
||||
|
||||
create_target_directory_groups(azahar_libretro)
|
||||
|
||||
target_link_libraries(citra_common PRIVATE libretro)
|
||||
target_link_libraries(citra_core PRIVATE libretro)
|
||||
target_link_libraries(video_core PRIVATE libretro)
|
||||
target_link_libraries(audio_core PRIVATE libretro)
|
||||
target_link_libraries(input_common PRIVATE libretro)
|
||||
target_compile_definitions(citra_common PRIVATE HAVE_LIBRETRO)
|
||||
target_compile_definitions(citra_core PRIVATE HAVE_LIBRETRO)
|
||||
target_compile_definitions(video_core PRIVATE HAVE_LIBRETRO)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_LIBRETRO)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_LIBRETRO)
|
||||
|
||||
target_link_libraries(azahar_libretro PRIVATE citra_common citra_core)
|
||||
target_link_libraries(azahar_libretro PRIVATE Boost::boost dds-ktx libretro tsl::robin_map)
|
||||
if(ENABLE_VULKAN)
|
||||
target_link_libraries(azahar_libretro PRIVATE sirit vulkan-headers vma)
|
||||
endif()
|
||||
if(ENABLE_OPENGL)
|
||||
target_link_libraries(azahar_libretro PRIVATE glad)
|
||||
endif()
|
||||
target_link_libraries(azahar_libretro PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
if(DEFINED LIBRETRO_STATIC)
|
||||
target_link_libraries(azahar_libretro PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
set_target_properties(azahar_libretro PROPERTIES PREFIX "")
|
||||
target_compile_definitions(azahar_libretro PRIVATE HAVE_LIBRETRO)
|
||||
|
||||
if(ANDROID)
|
||||
target_compile_definitions(citra_common PRIVATE HAVE_LIBRETRO_VFS)
|
||||
target_compile_definitions(citra_core PRIVATE HAVE_LIBRETRO_VFS)
|
||||
target_compile_definitions(video_core PRIVATE HAVE_LIBRETRO_VFS)
|
||||
target_compile_definitions(azahar_libretro_common PRIVATE USING_GLES HAVE_LIBRETRO_VFS)
|
||||
target_compile_definitions(azahar_libretro PRIVATE USING_GLES HAVE_LIBRETRO_VFS)
|
||||
target_link_libraries(citra_common PRIVATE libretro_common)
|
||||
target_link_libraries(citra_core PRIVATE libretro_common)
|
||||
target_link_libraries(video_core PRIVATE libretro_common)
|
||||
target_link_libraries(azahar_libretro_common PRIVATE libretro_common)
|
||||
target_link_libraries(azahar_libretro PRIVATE libretro_common)
|
||||
# Link Android log library for __android_log_print
|
||||
target_link_libraries(azahar_libretro PRIVATE log)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(azahar_libretro PRIVATE crypt32)
|
||||
endif()
|
||||
|
||||
if(IOS)
|
||||
target_compile_definitions(azahar_libretro_common PRIVATE IOS)
|
||||
target_compile_definitions(azahar_libretro PRIVATE IOS)
|
||||
target_link_libraries(azahar_libretro PRIVATE "-framework CoreFoundation" "-framework Foundation")
|
||||
endif()
|
||||
|
||||
if (SSE42_COMPILE_OPTION)
|
||||
target_compile_definitions(azahar_libretro PRIVATE CITRA_HAS_SSE42)
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
|
||||
CMAKE_SYSTEM_NAME STREQUAL "iOS" OR
|
||||
CMAKE_SYSTEM_NAME STREQUAL "tvOS")
|
||||
target_link_libraries(azahar_libretro PRIVATE "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/libretro.osx.def")
|
||||
else()
|
||||
target_link_libraries(azahar_libretro PRIVATE "-Wl,-Bsymbolic")
|
||||
endif()
|
||||
759
src/citra_libretro/citra_libretro.cpp
Normal file
759
src/citra_libretro/citra_libretro.cpp
Normal file
|
|
@ -0,0 +1,759 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <list>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
#include "glad/glad.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#endif
|
||||
#include "libretro.h"
|
||||
|
||||
#include "audio_core/libretro_input.h"
|
||||
#include "audio_core/libretro_sink.h"
|
||||
#include "video_core/gpu.h"
|
||||
#ifdef ENABLE_OPENGL
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
#include "citra_libretro/libretro_vk.h"
|
||||
#endif
|
||||
#include "video_core/renderer_software/renderer_software.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#include "citra_libretro/citra_libretro.h"
|
||||
#include "citra_libretro/core_settings.h"
|
||||
#include "citra_libretro/environment.h"
|
||||
#include "citra_libretro/input/input_factory.h"
|
||||
|
||||
#include "common/arch.h"
|
||||
#if CITRA_ARCH(x86_64)
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/image_interface.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#ifdef HAVE_LIBRETRO_VFS
|
||||
#include <streams/file_stream_transforms.h>
|
||||
#endif
|
||||
|
||||
class CitraLibRetro {
|
||||
public:
|
||||
CitraLibRetro() : log_filter(Common::Log::Level::Debug) {}
|
||||
|
||||
Common::Log::Filter log_filter;
|
||||
std::unique_ptr<EmuWindow_LibRetro> emu_window;
|
||||
bool game_loaded = false;
|
||||
struct retro_hw_render_callback hw_render{};
|
||||
};
|
||||
|
||||
CitraLibRetro* emu_instance;
|
||||
|
||||
void retro_init() {
|
||||
emu_instance = new CitraLibRetro();
|
||||
Common::Log::LibRetroStart(LibRetro::GetLoggingBackend());
|
||||
Common::Log::SetGlobalFilter(emu_instance->log_filter);
|
||||
|
||||
LOG_DEBUG(Frontend, "Initializing core...");
|
||||
|
||||
// Set up LLE cores
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
Settings::values.lle_modules.emplace(service_module.name, false);
|
||||
}
|
||||
|
||||
// Setup default, stub handlers for HLE applets
|
||||
Frontend::RegisterDefaultApplets(Core::System::GetInstance());
|
||||
|
||||
// Register generic image interface
|
||||
Core::System::GetInstance().RegisterImageInterface(
|
||||
std::make_shared<Frontend::ImageInterface>());
|
||||
|
||||
LibRetro::Input::Init();
|
||||
}
|
||||
|
||||
void retro_deinit() {
|
||||
LOG_DEBUG(Frontend, "Shutting down core...");
|
||||
if (Core::System::GetInstance().IsPoweredOn()) {
|
||||
Core::System::GetInstance().Shutdown();
|
||||
}
|
||||
|
||||
LibRetro::Input::Shutdown();
|
||||
|
||||
delete emu_instance;
|
||||
|
||||
Common::Log::Stop();
|
||||
}
|
||||
|
||||
unsigned retro_api_version() {
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void LibRetro::OnConfigureEnvironment() {
|
||||
|
||||
#ifdef HAVE_LIBRETRO_VFS
|
||||
struct retro_vfs_interface_info vfs_iface_info{1, nullptr};
|
||||
LibRetro::SetVFSCallback(&vfs_iface_info);
|
||||
#endif
|
||||
|
||||
LibRetro::RegisterCoreOptions();
|
||||
|
||||
static const struct retro_controller_description controllers[] = {
|
||||
{"Nintendo 3DS", RETRO_DEVICE_JOYPAD},
|
||||
};
|
||||
|
||||
static const struct retro_controller_info ports[] = {
|
||||
{controllers, 1},
|
||||
{nullptr, 0},
|
||||
};
|
||||
|
||||
LibRetro::SetControllerInfo(ports);
|
||||
}
|
||||
|
||||
uintptr_t LibRetro::GetFramebuffer() {
|
||||
return emu_instance->hw_render.get_current_framebuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Citra's settings with Libretro's.
|
||||
*/
|
||||
static void UpdateSettings() {
|
||||
LibRetro::ParseCoreOptions();
|
||||
|
||||
struct retro_input_descriptor desc[] = {
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
|
||||
{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_RIGHT, "Right"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "X"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Y"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "ZL"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "ZR"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Home/Swap screens"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Touch Screen Touch"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X,
|
||||
"Circle Pad X"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y,
|
||||
"Circle Pad Y"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X,
|
||||
"C-Stick / Pointer X"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y,
|
||||
"C-Stick / Pointer Y"},
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
LibRetro::SetInputDescriptors(desc);
|
||||
|
||||
Settings::values.current_input_profile.touch_device = "engine:emu_window";
|
||||
|
||||
// Hardcode buttons to bind to libretro - it is entirely redundant to have
|
||||
// two methods of rebinding controls.
|
||||
// Citra: A = RETRO_DEVICE_ID_JOYPAD_A (8)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::A] =
|
||||
"button:8,joystick:0,engine:libretro";
|
||||
// Citra: B = RETRO_DEVICE_ID_JOYPAD_B (0)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::B] =
|
||||
"button:0,joystick:0,engine:libretro";
|
||||
// Citra: X = RETRO_DEVICE_ID_JOYPAD_X (9)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::X] =
|
||||
"button:9,joystick:0,engine:libretro";
|
||||
// Citra: Y = RETRO_DEVICE_ID_JOYPAD_Y (1)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Y] =
|
||||
"button:1,joystick:0,engine:libretro";
|
||||
// Citra: UP = RETRO_DEVICE_ID_JOYPAD_UP (4)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Up] =
|
||||
"button:4,joystick:0,engine:libretro";
|
||||
// Citra: DOWN = RETRO_DEVICE_ID_JOYPAD_DOWN (5)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Down] =
|
||||
"button:5,joystick:0,engine:libretro";
|
||||
// Citra: LEFT = RETRO_DEVICE_ID_JOYPAD_LEFT (6)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Left] =
|
||||
"button:6,joystick:0,engine:libretro";
|
||||
// Citra: RIGHT = RETRO_DEVICE_ID_JOYPAD_RIGHT (7)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Right] =
|
||||
"button:7,joystick:0,engine:libretro";
|
||||
// Citra: L = RETRO_DEVICE_ID_JOYPAD_L (10)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::L] =
|
||||
"button:10,joystick:0,engine:libretro";
|
||||
// Citra: R = RETRO_DEVICE_ID_JOYPAD_R (11)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::R] =
|
||||
"button:11,joystick:0,engine:libretro";
|
||||
// Citra: START = RETRO_DEVICE_ID_JOYPAD_START (3)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Start] =
|
||||
"button:3,joystick:0,engine:libretro";
|
||||
// Citra: SELECT = RETRO_DEVICE_ID_JOYPAD_SELECT (2)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Select] =
|
||||
"button:2,joystick:0,engine:libretro";
|
||||
// Citra: ZL = RETRO_DEVICE_ID_JOYPAD_L2 (12)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::ZL] =
|
||||
"button:12,joystick:0,engine:libretro";
|
||||
// Citra: ZR = RETRO_DEVICE_ID_JOYPAD_R2 (13)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::ZR] =
|
||||
"button:13,joystick:0,engine:libretro";
|
||||
// Citra: HOME = RETRO_DEVICE_ID_JOYPAD_L3 (as per above bindings) (14)
|
||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Values::Home] =
|
||||
"button:14,joystick:0,engine:libretro";
|
||||
|
||||
// Circle Pad
|
||||
Settings::values.current_input_profile.analogs[0] = "axis:0,joystick:0,engine:libretro";
|
||||
// C-Stick
|
||||
if (LibRetro::settings.analog_function != LibRetro::CStickFunction::Touchscreen) {
|
||||
Settings::values.current_input_profile.analogs[1] = "axis:1,joystick:0,engine:libretro";
|
||||
} else {
|
||||
Settings::values.current_input_profile.analogs[1] = "";
|
||||
}
|
||||
|
||||
if (!emu_instance->emu_window) {
|
||||
emu_instance->emu_window = std::make_unique<EmuWindow_LibRetro>();
|
||||
}
|
||||
|
||||
// Update the framebuffer sizing.
|
||||
emu_instance->emu_window->UpdateLayout();
|
||||
|
||||
Core::System::GetInstance().ApplySettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* libretro callback; Called every game tick.
|
||||
*/
|
||||
void retro_run() {
|
||||
if (!emu_instance->game_loaded) {
|
||||
// Game failed to load (e.g. encrypted ROM, bad path).
|
||||
// Present an empty frame so RetroArch doesn't hang.
|
||||
LibRetro::PollInput();
|
||||
LibRetro::UploadVideoFrame(nullptr, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if we actually have any config updates to process.
|
||||
if (LibRetro::HasUpdatedConfig()) {
|
||||
LibRetro::ParseCoreOptions();
|
||||
Core::System::GetInstance().ApplySettings();
|
||||
emu_instance->emu_window->UpdateLayout();
|
||||
}
|
||||
|
||||
// Poll microphone input from the frontend and buffer it for the emulator
|
||||
// This must be done from the main thread as LibRetro's mic interface is not thread-safe
|
||||
if (auto* mic_input = AudioCore::GetLibRetroInput()) {
|
||||
mic_input->PollMicrophone();
|
||||
}
|
||||
|
||||
// Check if the screen swap button is pressed
|
||||
static bool screen_swap_button_state = false;
|
||||
static bool screens_swapped = false;
|
||||
bool screen_swap_btn =
|
||||
!!LibRetro::CheckInput(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
|
||||
if (screen_swap_btn != screen_swap_button_state) {
|
||||
if (LibRetro::settings.swap_screen_mode == "Toggle") {
|
||||
if (!screen_swap_button_state)
|
||||
screens_swapped = !screens_swapped;
|
||||
|
||||
if (screens_swapped)
|
||||
Settings::values.swap_screen =
|
||||
LibRetro::FetchVariable("citra_swap_screen", "Top") != "Bottom";
|
||||
else
|
||||
Settings::values.swap_screen =
|
||||
LibRetro::FetchVariable("citra_swap_screen", "Top") == "Bottom";
|
||||
} else {
|
||||
if (screen_swap_btn)
|
||||
Settings::values.swap_screen =
|
||||
LibRetro::FetchVariable("citra_swap_screen", "Top") != "Bottom";
|
||||
else
|
||||
Settings::values.swap_screen =
|
||||
LibRetro::FetchVariable("citra_swap_screen", "Top") == "Bottom";
|
||||
}
|
||||
|
||||
Core::System::GetInstance().ApplySettings();
|
||||
|
||||
// Update the framebuffer sizing.
|
||||
emu_instance->emu_window->UpdateLayout();
|
||||
|
||||
screen_swap_button_state = screen_swap_btn;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGL) {
|
||||
// We can't assume that the frontend has been nice and preserved all OpenGL settings. Reset.
|
||||
auto last_state = OpenGL::OpenGLState::GetCurState();
|
||||
ResetGLState();
|
||||
last_state.Apply();
|
||||
}
|
||||
#endif
|
||||
|
||||
while (!emu_instance->emu_window->HasSubmittedFrame()) {
|
||||
auto result = Core::System::GetInstance().RunLoop();
|
||||
|
||||
if (result != Core::System::ResultStatus::Success) {
|
||||
std::string errorContent = Core::System::GetInstance().GetStatusDetails();
|
||||
std::string msg;
|
||||
|
||||
switch (result) {
|
||||
case Core::System::ResultStatus::ErrorSystemFiles:
|
||||
msg = "Azahar was unable to locate a 3DS system archive: " + errorContent;
|
||||
break;
|
||||
default:
|
||||
msg = "Fatal Error encountered (" + std::to_string(static_cast<int>(result)) +
|
||||
"): " + errorContent;
|
||||
break;
|
||||
}
|
||||
|
||||
LibRetro::DisplayMessage(msg.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_memory_maps() {
|
||||
auto process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
if (!process)
|
||||
return;
|
||||
|
||||
std::vector<retro_memory_descriptor> descs;
|
||||
|
||||
for (const auto& [addr, vma] : process->vm_manager.vma_map) {
|
||||
if (vma.type != Kernel::VMAType::BackingMemory)
|
||||
continue;
|
||||
if (vma.size == 0 || !vma.backing_memory)
|
||||
continue;
|
||||
|
||||
// Only expose the well-known user-accessible memory regions
|
||||
uint64_t flags = 0;
|
||||
if (vma.base >= Memory::HEAP_VADDR && vma.base < Memory::HEAP_VADDR_END) {
|
||||
flags = RETRO_MEMDESC_SYSTEM_RAM;
|
||||
} else if (vma.base >= Memory::LINEAR_HEAP_VADDR &&
|
||||
vma.base < Memory::LINEAR_HEAP_VADDR_END) {
|
||||
flags = RETRO_MEMDESC_SYSTEM_RAM;
|
||||
} else if (vma.base >= Memory::NEW_LINEAR_HEAP_VADDR &&
|
||||
vma.base < Memory::NEW_LINEAR_HEAP_VADDR_END) {
|
||||
flags = RETRO_MEMDESC_SYSTEM_RAM;
|
||||
} else if (vma.base >= Memory::VRAM_VADDR && vma.base < Memory::VRAM_VADDR_END) {
|
||||
flags = RETRO_MEMDESC_VIDEO_RAM;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
retro_memory_descriptor desc = {};
|
||||
desc.flags = flags;
|
||||
desc.ptr = const_cast<u8*>(vma.backing_memory.GetPtr());
|
||||
desc.start = vma.base;
|
||||
desc.len = vma.size;
|
||||
|
||||
// select=0 requires power-of-2 len AND start aligned to len.
|
||||
// When that doesn't hold, compute a select mask instead.
|
||||
bool need_select = (vma.size & (vma.size - 1)) != 0;
|
||||
if (!need_select && (vma.base & (vma.size - 1)) != 0)
|
||||
need_select = true;
|
||||
|
||||
if (need_select) {
|
||||
uint64_t np2 = 1;
|
||||
while (np2 < vma.size)
|
||||
np2 <<= 1;
|
||||
if (vma.base & (np2 - 1)) {
|
||||
LOG_WARNING(Frontend, "VMA at 0x{:08X} size 0x{:X} not aligned, skipping", vma.base,
|
||||
vma.size);
|
||||
continue;
|
||||
}
|
||||
desc.select = ~(np2 - 1);
|
||||
}
|
||||
|
||||
descs.push_back(desc);
|
||||
}
|
||||
|
||||
if (!descs.empty()) {
|
||||
retro_memory_map map = {descs.data(), static_cast<unsigned>(descs.size())};
|
||||
LibRetro::SetMemoryMaps(&map);
|
||||
}
|
||||
}
|
||||
|
||||
static bool do_load_game() {
|
||||
const Core::System::ResultStatus load_result{
|
||||
Core::System::GetInstance().Load(*emu_instance->emu_window, LibRetro::settings.file_path)};
|
||||
|
||||
switch (load_result) {
|
||||
case Core::System::ResultStatus::Success:
|
||||
break; // Expected case
|
||||
case Core::System::ResultStatus::ErrorGetLoader:
|
||||
LibRetro::DisplayMessage("Failed to obtain loader for specified ROM!");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorLoader:
|
||||
LibRetro::DisplayMessage("Failed to load ROM!");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
|
||||
LibRetro::DisplayMessage("The game that you are trying to load must be decrypted before "
|
||||
"being used with Azahar.");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
|
||||
LibRetro::DisplayMessage("Error while loading ROM: The ROM format is not supported.");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorGbaTitle:
|
||||
LibRetro::DisplayMessage(
|
||||
"Error loading the specified application as it is GBA Virtual Console");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorNotInitialized:
|
||||
LibRetro::DisplayMessage("CPUCore not initialized");
|
||||
return false;
|
||||
case Core::System::ResultStatus::ErrorSystemMode:
|
||||
LibRetro::DisplayMessage("Failed to determine system mode!");
|
||||
return false;
|
||||
default:
|
||||
LibRetro::DisplayMessage(
|
||||
("Unknown error: " + std::to_string(static_cast<int>(load_result))).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id{};
|
||||
Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id);
|
||||
Core::System::GetInstance().GPU().ApplyPerProgramSettings(program_id);
|
||||
|
||||
if (Settings::values.use_disk_shader_cache) {
|
||||
Core::System::GetInstance().GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(
|
||||
false, nullptr);
|
||||
}
|
||||
|
||||
setup_memory_maps();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
static void* load_opengl_func(const char* name) {
|
||||
return (void*)emu_instance->hw_render.get_proc_address(name);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void context_reset() {
|
||||
LOG_DEBUG(Frontend, "context_reset");
|
||||
|
||||
switch (Settings::values.graphics_api.GetValue()) {
|
||||
#ifdef ENABLE_OPENGL
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
#if defined(USING_GLES)
|
||||
Settings::values.use_gles = true;
|
||||
// Set the global GLES flag immediately to ensure any shader compilation
|
||||
// that happens before the Driver is created uses the correct version
|
||||
OpenGL::GLES = true;
|
||||
#else
|
||||
Settings::values.use_gles = false;
|
||||
OpenGL::GLES = false;
|
||||
#endif
|
||||
// Check to see if the frontend provides us with OpenGL symbols
|
||||
if (emu_instance->hw_render.get_proc_address != nullptr) {
|
||||
bool loaded = Settings::values.use_gles
|
||||
? gladLoadGLES2Loader((GLADloadproc)load_opengl_func)
|
||||
: gladLoadGLLoader((GLADloadproc)load_opengl_func);
|
||||
|
||||
if (!loaded) {
|
||||
LOG_CRITICAL(Frontend, "Glad failed to load (frontend-provided symbols)!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Else, try to load them on our own
|
||||
if (!gladLoadGL()) {
|
||||
LOG_CRITICAL(Frontend, "Glad failed to load (internal symbols)!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
LibRetro::VulkanResetContext();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
// software renderer never gets here
|
||||
break;
|
||||
}
|
||||
|
||||
emu_instance->emu_window->CreateContext();
|
||||
|
||||
if (!emu_instance->game_loaded) {
|
||||
emu_instance->game_loaded = do_load_game();
|
||||
} else {
|
||||
// Game is already loaded, just recreate the renderer for the new GL context
|
||||
if (Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGL) {
|
||||
Core::System::GetInstance().GPU().RecreateRenderer(*emu_instance->emu_window, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void context_destroy() {
|
||||
LOG_DEBUG(Frontend, "context_destroy");
|
||||
if (emu_instance->game_loaded &&
|
||||
Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGL) {
|
||||
// Release the renderer's OpenGL resources
|
||||
Core::System::GetInstance().GPU().ReleaseRenderer();
|
||||
}
|
||||
emu_instance->emu_window->DestroyContext();
|
||||
}
|
||||
|
||||
void retro_reset() {
|
||||
LOG_DEBUG(Frontend, "retro_reset");
|
||||
Core::System::GetInstance().Shutdown();
|
||||
emu_instance->game_loaded = do_load_game();
|
||||
}
|
||||
|
||||
/**
|
||||
* libretro callback; Called when a game is to be loaded.
|
||||
*/
|
||||
bool retro_load_game(const struct retro_game_info* info) {
|
||||
LOG_INFO(Frontend, "Starting Azahar RetroArch game...");
|
||||
|
||||
#if CITRA_ARCH(x86_64) && CITRA_HAS_SSE42
|
||||
if (!Common::GetCPUCaps().sse4_2) {
|
||||
LOG_CRITICAL(Frontend, "This CPU does not support SSE4.2, which is required by this build");
|
||||
LibRetro::DisplayMessage(
|
||||
"This CPU does not support SSE4.2, which is required by this build");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
UpdateSettings();
|
||||
|
||||
// If using HW rendering, don't actually load the game here. azahar wants
|
||||
// the graphics context ready and available before calling System::Load.
|
||||
LibRetro::settings.file_path = info->path;
|
||||
|
||||
// Early validation: check that the ROM can be loaded before committing to
|
||||
// the HW renderer setup. Without this, failures (encrypted ROMs, bad files)
|
||||
// are only detected in context_reset after retro_load_game already returned
|
||||
// true, leaving the frontend stuck on a black screen.
|
||||
// GetLoader + LoadKernelMemoryMode only read ROM headers — no renderer needed.
|
||||
{
|
||||
auto loader = Loader::GetLoader(LibRetro::settings.file_path);
|
||||
if (!loader) {
|
||||
LibRetro::DisplayMessage("Failed to obtain loader for the specified ROM.");
|
||||
return false;
|
||||
}
|
||||
auto [memory_mode, result] = loader->LoadKernelMemoryMode();
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
switch (result) {
|
||||
case Loader::ResultStatus::ErrorEncrypted:
|
||||
LibRetro::DisplayMessage(
|
||||
"This ROM is encrypted and must be decrypted before use with Azahar.");
|
||||
break;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
LibRetro::DisplayMessage("The ROM format is not supported.");
|
||||
break;
|
||||
case Loader::ResultStatus::ErrorGbaTitle:
|
||||
LibRetro::DisplayMessage("GBA Virtual Console titles are not supported.");
|
||||
break;
|
||||
default:
|
||||
LibRetro::DisplayMessage("Failed to load ROM metadata.");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Stash the loader so System::Load can reuse it instead of re-opening
|
||||
Core::System::GetInstance().RegisterAppLoaderEarly(loader);
|
||||
}
|
||||
|
||||
if (!LibRetro::SetPixelFormat(RETRO_PIXEL_FORMAT_XRGB8888)) {
|
||||
LibRetro::DisplayMessage("XRGB8888 is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
emu_instance->emu_window->UpdateLayout();
|
||||
|
||||
switch (Settings::values.graphics_api.GetValue()) {
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
#ifdef ENABLE_OPENGL
|
||||
LOG_INFO(Frontend, "Using OpenGL hw renderer");
|
||||
LibRetro::SetHWSharedContext();
|
||||
#if defined(USING_GLES)
|
||||
emu_instance->hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES3;
|
||||
emu_instance->hw_render.version_major = 3;
|
||||
emu_instance->hw_render.version_minor = 2;
|
||||
#else
|
||||
emu_instance->hw_render.context_type = RETRO_HW_CONTEXT_OPENGL_CORE;
|
||||
emu_instance->hw_render.version_major = 4;
|
||||
emu_instance->hw_render.version_minor = 3;
|
||||
#endif
|
||||
emu_instance->hw_render.context_reset = context_reset;
|
||||
emu_instance->hw_render.context_destroy = context_destroy;
|
||||
emu_instance->hw_render.cache_context = false;
|
||||
emu_instance->hw_render.bottom_left_origin = true;
|
||||
if (!LibRetro::SetHWRenderer(&emu_instance->hw_render)) {
|
||||
LibRetro::DisplayMessage("Failed to set HW renderer");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
#ifdef ENABLE_VULKAN
|
||||
LOG_INFO(Frontend, "Using Vulkan hw renderer");
|
||||
emu_instance->hw_render.context_type = RETRO_HW_CONTEXT_VULKAN;
|
||||
emu_instance->hw_render.version_major = VK_MAKE_VERSION(1, 1, 0);
|
||||
emu_instance->hw_render.version_minor = 0;
|
||||
emu_instance->hw_render.context_reset = context_reset;
|
||||
emu_instance->hw_render.context_destroy = context_destroy;
|
||||
emu_instance->hw_render.cache_context = true;
|
||||
if (!LibRetro::SetHWRenderer(&emu_instance->hw_render)) {
|
||||
LibRetro::DisplayMessage("Failed to set HW renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up Vulkan context negotiation interface
|
||||
static const struct retro_hw_render_context_negotiation_interface_vulkan vk_negotiation = {
|
||||
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN,
|
||||
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION,
|
||||
LibRetro::GetVulkanApplicationInfo,
|
||||
LibRetro::CreateVulkanDevice,
|
||||
nullptr, // destroy_device - not needed (frontend owns the device)
|
||||
};
|
||||
LibRetro::SetHWRenderContextNegotiationInterface((void**)&vk_negotiation);
|
||||
#endif
|
||||
break;
|
||||
case Settings::GraphicsAPI::Software:
|
||||
emu_instance->game_loaded = do_load_game();
|
||||
if (!emu_instance->game_loaded)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t quirks =
|
||||
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE | RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE;
|
||||
LibRetro::SetSerializationQuirks(quirks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void retro_unload_game() {
|
||||
LOG_DEBUG(Frontend, "Unloading game...");
|
||||
Core::System::GetInstance().Shutdown();
|
||||
}
|
||||
|
||||
unsigned retro_get_region() {
|
||||
return RETRO_REGION_NTSC;
|
||||
}
|
||||
|
||||
bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info,
|
||||
size_t num_info) {
|
||||
return retro_load_game(info);
|
||||
}
|
||||
|
||||
/// Drain any pending async kernel operations by running the emulation loop.
|
||||
///
|
||||
/// Savestates are unsafe to create while RunAsync operations (file I/O, network, etc.)
|
||||
/// are in flight. The Qt frontend handles this by deferring serialization inside
|
||||
/// System::RunLoop(): it sets a request flag via SendSignal(Signal::Save), and RunLoop
|
||||
/// only performs the save when !kernel->AreAsyncOperationsPending() (see core.cpp).
|
||||
///
|
||||
/// The Qt frontend needs that indirection because its UI and emulation run on separate
|
||||
/// threads. In libretro, the frontend calls API entry points (retro_run, retro_serialize,
|
||||
/// etc.) sequentially, so we can call RunLoop() directly from here to drain pending ops,
|
||||
/// then call SaveStateBuffer()/LoadStateBuffer() ourselves.
|
||||
///
|
||||
/// Note: RunLoop() can itself start new async operations (CPU executes HLE service calls),
|
||||
/// so the pending count may not decrease monotonically. In practice games reach quiescent
|
||||
/// points between frames; the 5-second timeout (matching RunLoop's existing handler)
|
||||
/// covers the pathological case.
|
||||
static bool DrainAsyncOperations(Core::System& system) {
|
||||
if (!system.KernelRunning() || !system.Kernel().AreAsyncOperationsPending()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
emu_instance->emu_window->suppressPresentation = true;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
while (system.Kernel().AreAsyncOperationsPending()) {
|
||||
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) {
|
||||
LOG_ERROR(Frontend, "Timed out waiting for async operations to complete");
|
||||
emu_instance->emu_window->suppressPresentation = false;
|
||||
return false;
|
||||
}
|
||||
auto result = system.RunLoop();
|
||||
if (result != Core::System::ResultStatus::Success) {
|
||||
emu_instance->emu_window->suppressPresentation = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
emu_instance->emu_window->suppressPresentation = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> savestate = {};
|
||||
|
||||
size_t retro_serialize_size() {
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (!system.IsPoweredOn())
|
||||
return 0;
|
||||
|
||||
if (!DrainAsyncOperations(system)) {
|
||||
savestate.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
savestate = system.SaveStateBuffer();
|
||||
return savestate->size();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Error saving state: {}", e.what());
|
||||
savestate.reset();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool retro_serialize(void* data, size_t size) {
|
||||
if (!savestate.has_value())
|
||||
return false;
|
||||
if (size < savestate->size())
|
||||
return false;
|
||||
memcpy(data, savestate->data(), savestate->size());
|
||||
savestate.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retro_unserialize(const void* data, size_t size) {
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (!system.IsPoweredOn())
|
||||
return false;
|
||||
|
||||
if (!DrainAsyncOperations(system)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> buffer(static_cast<const u8*>(data), static_cast<const u8*>(data) + size);
|
||||
try {
|
||||
return system.LoadStateBuffer(std::move(buffer));
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Error loading state: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void* retro_get_memory_data(unsigned id) {
|
||||
// Memory is exposed via RETRO_ENVIRONMENT_SET_MEMORY_MAPS instead,
|
||||
// using virtual addresses for stable cheat/achievement support.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void retro_cheat_reset() {}
|
||||
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char* code) {}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue