Merge branch 'azahar-emu:master' into patch-1

This commit is contained in:
Kyuyrii 2026-03-13 02:41:51 -03:00 committed by GitHub
commit 2143eb6f2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
317 changed files with 33244 additions and 15585 deletions

133
.ci/libretro-ci.yml Normal file
View 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 #################################

13
.ci/libretro-pack.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash -ex
# Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
fi
# Create .zip
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*

View file

@ -1,6 +1,6 @@
#!/bin/bash -ex
if [[ "$TARGET" == "appimage"* ]]; then
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
# Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang

View file

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

View file

@ -23,12 +23,12 @@ jobs:
name: source
path: artifacts/
linux:
linux-x86_64:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: ["appimage", "appimage-wayland", "fresh"]
target: ["appimage", "appimage-wayland", "gcc-nopch"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
@ -49,9 +49,9 @@ jobs:
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
${{ github.job }}-${{ matrix.target }}-
- name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh
@ -68,11 +68,40 @@ jobs:
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
name: ${{ github.job }}-${{ matrix.target }}
path: artifacts/
linux-arm64:
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
target: ["clang", "gcc-nopch"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
OS: linux
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ github.job }}-${{ matrix.target }}-
- name: Build
run: ./.ci/linux.sh
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:

178
.github/workflows/libretro.yml vendored Normal file
View file

@ -0,0 +1,178 @@
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)
- name: Pack
run: ./.ci/libretro-pack.sh
- 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)
- name: Pack
run: ./.ci/libretro-pack.sh
- 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)"
- name: Pack
run: ./.ci/libretro-pack.sh
- 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
- name: Pack
run: ./.ci/libretro-pack.sh
- 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
- name: Pack
run: ./.ci/libretro-pack.sh
- 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
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip

4
.gitignore vendored
View file

@ -56,3 +56,7 @@ repo/
.ccache/
node_modules/
VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG

3
.gitmodules vendored
View file

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

View file

@ -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")
@ -223,7 +258,7 @@ function(check_submodules_present)
foreach(module ${gitmodules})
string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
message(SEND_ERROR "Git submodule ${module} not found."
message(SEND_ERROR "Git submodule ${module} not found.\n"
"Please run: git submodule update --init --recursive")
endif()
endforeach()
@ -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)

View file

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

View file

@ -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"
@ -11,6 +12,10 @@ set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
@ -19,6 +24,7 @@ set(HASH_FILES
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
"${VIDEO_CORE}/shader/generator/profile.h"
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@ -42,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)

View file

@ -0,0 +1,278 @@
## 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"
"toggle_unique_data_console_type"
"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_touchpad"
"controller_touch_device"
"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()

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

948
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

1602
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

942
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

963
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

944
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

1010
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

946
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

946
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

952
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

@ -1 +1 @@
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
Subproject commit f9b15f673a688982f78a5f63a49a27275b318e5f

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

@ -0,0 +1 @@
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d

View file

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

View file

@ -63,7 +63,7 @@ android {
defaultConfig {
// The application ID refers to Lime3DS to allow for
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
applicationId = "io.github.lime3ds.android"
applicationId = "org.azahar_emu.azahar"
minSdk = 29
targetSdk = 35
versionCode = autoVersion
@ -173,6 +173,7 @@ android {
register("googlePlay") {
dimension = "version"
versionNameSuffix = "-googleplay"
applicationId = "io.github.lime3ds.android"
}
}

View file

@ -25,6 +25,7 @@ import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
@ -132,7 +133,27 @@ object NativeLibrary {
* If not set, it auto-detects a location
*/
external fun setUserDirectory(directory: String)
external fun getInstalledGamePaths(): Array<String?>
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
return games.mapNotNull { entry ->
entry?.let {
val sep = it.lastIndexOf('|')
if (sep == -1) return@mapNotNull null
val path = it.substring(0, sep)
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
InstalledGame(path, mediaType!!)
}
}.toTypedArray()
}
private external fun getInstalledGamePathsImpl(): Array<String?>
// Create the config.ini file.
external fun createConfigFile()
@ -230,6 +251,13 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
external fun nativeFileExists(path: String): Boolean
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()
@ -691,34 +719,43 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getUserDirectory(uriOverride: Uri? = null): String {
fun getNativePath(uri: Uri): String {
BuildUtil.assertNotGooglePlay()
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val dirSep = "/"
val udUri = uriOverride ?:
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
val udPathSegment = udUri.lastPathSegment!!
val udVirtualPath = udPathSegment.substringAfter(":")
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
val uriString = uri.toString()
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
return uriString
}
val pathSegment = uri.lastPathSegment ?: return ""
val virtualPath = pathSegment.substringAfter(":")
if (pathSegment.startsWith("primary:")) { // User directory is located in primary storage
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
return primaryStoragePath + dirSep + udVirtualPath + dirSep
return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device
val storageIdString = udPathSegment.substringBefore(":")
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
if (udRemovablePath == null) {
if (removablePath == null) {
android.util.Log.e("NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
)
return ""
}
return udRemovablePath + dirSep + udVirtualPath + dirSep
return removablePath + dirSep + virtualPath
}
}
@Keep
@JvmStatic
fun getUserDirectory(): String {
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
return getNativePath(userDirectoryUri)
}
@Keep

View file

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

View file

@ -54,8 +54,10 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter(
@ -136,7 +138,7 @@ class GameAdapter(
val holder = view.tag as GameViewHolder
gameExists(holder)
if (holder.game.titleId == 0L) {
if (!holder.game.valid) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.properties)
.setMessage(R.string.properties_not_loaded)
@ -153,12 +155,21 @@ class GameAdapter(
if (holder.game.isInstalled) {
return true
}
val gameExists = DocumentFile.fromSingleUri(
CitraApplication.appContext,
Uri.parse(holder.game.path)
)?.exists() == true
val path = holder.game.path
val pathUri = path.toUri()
var gameExists: Boolean
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
gameExists =
DocumentFile.fromSingleUri(
CitraApplication.appContext,
pathUri
)?.exists() == true
} else {
val nativePath = NativeLibrary.getNativePath(pathUri)
gameExists = NativeLibrary.nativeFileExists(nativePath)
}
return if (!gameExists) {
Log.error("[GameAdapter] ROM file does not exist: $path")
Toast.makeText(
CitraApplication.appContext,
R.string.loader_error_file_not_found,
@ -323,14 +334,16 @@ class GameAdapter(
}
}
val titleId = game.titleId
val dlcTitleId = titleId or 0x8C00000000L
val updateTitleId = titleId or 0xE00000000L
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
.toString())
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
.toString())
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()
@ -556,7 +569,9 @@ class GameAdapter(
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.titleId == newItem.titleId
// The title is taken into account to support 3DSX, which all have the titleID 0.
// This only works now because we always return the English title, adjust if that changes.
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {

View file

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

View file

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

View file

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

View file

@ -0,0 +1,141 @@
// 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 toggle_unique_data_console_type(): 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
}

View file

@ -0,0 +1,9 @@
// 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
interface AbstractListSetting<E> : AbstractSetting {
var list: List<E>
}

View file

@ -4,56 +4,59 @@
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),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), 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);
override var boolean: Boolean = defaultValue
@ -80,6 +83,7 @@ enum class BooleanSetting(
REQUIRED_ONLINE_LLE_MODULES,
NEW_3DS,
LLE_APPLETS,
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
VSYNC,
DEBUG_RENDERER,
CPU_JIT,

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
const val KEY_BUTTON_B = "button_b"
@ -135,6 +136,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"
@ -202,6 +204,7 @@ class Settings {
R.string.button_zr
)
val hotKeys = listOf(
HOTKEY_ENABLE,
HOTKEY_SCREEN_SWAP,
HOTKEY_CYCLE_LAYOUT,
HOTKEY_CLOSE_GAME,
@ -211,6 +214,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,
@ -220,6 +224,7 @@ class Settings {
R.string.turbo_limit_hotkey
)
// 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"

View file

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

View file

@ -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
@ -128,6 +129,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
@ -162,36 +164,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)
@ -229,9 +235,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)}"
}
/**
@ -258,8 +263,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
@ -267,6 +271,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.
*/
@ -289,19 +528,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.

View file

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

View file

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

View file

@ -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
@ -47,5 +47,6 @@ abstract class SettingsItem(
const val TYPE_INPUT_BINDING = 8
const val TYPE_STRING_INPUT = 9
const val TYPE_FLOAT_INPUT = 10
const val TYPE_MULTI_CHOICE = 11
}
}

View file

@ -41,12 +41,14 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting
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.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.StringSingleChoiceSetting
@ -55,6 +57,7 @@ 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.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
@ -62,6 +65,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
@ -72,7 +76,8 @@ import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
private var clickedPosition: Int
@ -104,6 +109,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)
}
@ -181,21 +190,30 @@ 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_MULTI_CHOICE -> {
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).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
}
@ -214,7 +232,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()
}
@ -232,6 +250,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 {
@ -360,14 +399,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)
}
}
}
@ -447,6 +486,7 @@ class SettingsAdapter(
}
it.setSelectedValue(value)
}
is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(it, which).toShort()
if (it.selectedValue.toShort() != value) {
@ -454,6 +494,7 @@ class SettingsAdapter(
}
it.setSelectedValue(value)
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
}
fragmentView?.putSetting(setting)
@ -499,11 +540,12 @@ class SettingsAdapter(
val setting = it.setSelectedValue(value)
fragmentView?.putSetting(setting)
}
else -> {
val setting = it.setSelectedValue(sliderProgress)
fragmentView?.putSetting(setting)
}
}
}
fragmentView.loadSettingsList()
closeDialog()
}
@ -519,7 +561,7 @@ class SettingsAdapter(
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
}
}
clickedItem = null
@ -527,6 +569,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)
@ -586,26 +643,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()
}
@ -631,6 +704,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
@ -647,4 +730,20 @@ class SettingsAdapter(
}
return -1
}
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};
}
}

View file

@ -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
@ -27,12 +28,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
@ -776,6 +779,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
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)
@ -811,7 +824,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]))
@ -898,6 +911,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(
@ -1148,6 +1170,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,
@ -1784,6 +1817,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.ENABLE_RPC_SERVER.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
R.string.toggle_unique_data_console_type,
R.string.toggle_unique_data_console_type_desc,
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.key,
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.DELAY_START_LLE_MODULES,

View file

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

View file

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

View file

@ -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
@ -255,6 +256,11 @@ object SettingsFile {
return stringSetting
}
val intListSetting = IntListSetting.from(key)
if (intListSetting != null) {
intListSetting.list = value.split(", ").map { it.toInt() }
}
return null
}

View file

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

View file

@ -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
@ -132,14 +133,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
var intentGame: Game? = null
if (intentUri != null) {
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false)
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
} else {
null
}
} else if (oldIntentInfo.first != null) {
val gameUri = Uri.parse(oldIntentInfo.first)
intentGame = if (Game.extensions.contains(FileUtil.getExtension(gameUri))) {
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false)
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
} else {
null
}
@ -175,6 +178,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
}

View file

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

View file

@ -567,7 +567,7 @@ class SetupFragment : Fragment() {
}
if (!BuildUtil.isGooglePlayBuild) {
if (NativeLibrary.getUserDirectory(result) == "") {
if (NativeLibrary.getNativePath(result) == "") {
SelectUserDirectoryDialogFragment.newInstance(
mainActivity,
R.string.invalid_selection,

View file

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

View file

@ -16,10 +16,12 @@ import org.citra.citra_emu.activities.EmulationActivity
@Parcelize
@Serializable
class Game(
val valid: Boolean = false,
val title: String = "",
val description: String = "",
val path: String = "",
val titleId: Long = 0L,
val mediaType: MediaType = MediaType.GAME_CARD,
val company: String = "",
val regions: String = "",
val isInstalled: Boolean = false,
@ -58,10 +60,23 @@ class Game(
result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode()
result = 31 * result + titleId.hashCode()
result = 31 * result + mediaType.hashCode()
result = 31 * result + company.hashCode()
return result
}
enum class MediaType(val value: Int) {
NAND(0),
SDMC(1),
GAME_CARD(2);
companion object {
fun fromInt(value: Int): MediaType? {
return MediaType.entries.find { it.value == value }
}
}
}
companion object {
val allExtensions: Set<String> get() = extensions + badExtensions

View file

@ -367,7 +367,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
if (!BuildUtil.isGooglePlayBuild) {
if (NativeLibrary.getUserDirectory(result) == "") {
if (NativeLibrary.getNativePath(result) == "") {
SelectUserDirectoryDialogFragment.newInstance(
this,
R.string.invalid_selection,

View file

@ -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,9 +12,11 @@ import androidx.core.app.NotificationCompat
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.NativeLibrary.InstallStatus
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.FileUtil.getFilename
import androidx.core.net.toUri
class CiaInstallWorker(
val context: Context,
@ -131,7 +133,7 @@ class CiaInstallWorker(
installProgressBuilder.setOngoing(true)
setProgressCallback(100, 0)
selectedFiles.forEachIndexed { i, file ->
val filename = getFilename(Uri.parse(file))
val filename = getFilename(file.toUri())
installProgressBuilder.setContentText(
context.getString(
R.string.cia_install_notification_installing,
@ -140,7 +142,13 @@ class CiaInstallWorker(
selectedFiles.size
)
)
val res = installCIA(file)
var fileFinal: String
if (BuildUtil.isGooglePlayBuild) {
fileFinal = file
} else {
fileFinal = "!" + NativeLibrary.getNativePath(file.toUri())
}
val res = installCIA(fileFinal)
notifyInstallStatus(filename, res)
}
notificationManager.cancel(PROGRESS_NOTIFICATION_ID)

View file

@ -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.
@ -23,7 +23,7 @@ object DiskShaderCacheProgress {
}
@JvmStatic
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int) {
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int, obj: String) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
if (emulationActivity == null) {
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
@ -40,7 +40,7 @@ object DiskShaderCacheProgress {
)
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
emulationActivity.getString(R.string.building_shaders),
emulationActivity.getString(R.string.building_shaders, obj ),
progress,
max
)

View file

@ -10,6 +10,8 @@ import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.model.CheapDocument
import org.citra.citra_emu.utils.BuildUtil
import java.io.IOException
import java.net.URLDecoder
import java.nio.file.Paths
import java.util.StringTokenizer
@ -77,8 +79,9 @@ class DocumentsTree {
@Synchronized
fun getFilename(filepath: String): String {
val node = resolvePath(filepath) ?: return ""
return node.name
val components = filepath.split(DELIMITER).filter { it.isNotEmpty() }
val filename = components.last()
return filename
}
@Synchronized
@ -260,6 +263,17 @@ class DocumentsTree {
@Synchronized
private fun resolvePath(filepath: String): DocumentsNode? {
if (!BuildUtil.isGooglePlayBuild) {
var isLegalPath = false
kotlinDirectoryAccessWhitelist.forEach {
if (filepath.startsWith(it)) {
isLegalPath = true
}
}
if (!isLegalPath) {
throw IOException("Attempted to resolve forbidden path: " + filepath)
}
}
root ?: return null
val tokens = StringTokenizer(filepath, DELIMITER, false)
var iterator = root
@ -351,5 +365,10 @@ class DocumentsTree {
companion object {
const val DELIMITER = "/"
val kotlinDirectoryAccessWhitelist = arrayOf(
"/config/",
"/log/",
"/gpu_drivers/",
)
}
}

View file

@ -32,7 +32,7 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
NativeLibrary.getInstalledGamePaths().forEach {
games.add(getGame(Uri.parse(it), isInstalled = true, addedToLibrary = true))
games.add(getGame(Uri.parse(it.path), isInstalled = true, addedToLibrary = true, it.mediaType))
}
// Cache list of games found on disk
@ -62,27 +62,41 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(it.uri), depth - 1)
} else {
if (Game.allExtensions.contains(FileUtil.getExtension(it.uri))) {
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true))
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true, Game.MediaType.GAME_CARD))
}
}
}
}
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean): Game {
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean, mediaType: Game.MediaType): Game {
val filePath = uri.toString()
var gameInfo: GameInfo? = GameInfo(filePath)
var nativePath: String? = null
var gameInfo: GameInfo?
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath)) {
gameInfo = GameInfo(filePath)
} else {
nativePath = "!" + NativeLibrary.getNativePath(uri);
gameInfo = GameInfo(nativePath)
}
if (gameInfo?.isValid() == false) {
val valid = gameInfo.isValid()
if (!valid) {
gameInfo = null
}
val isEncrypted = gameInfo?.isEncrypted() == true
val newGame = Game(
valid,
(gameInfo?.getTitle() ?: FileUtil.getFilename(uri)).replace("[\\t\\n\\r]+".toRegex(), " "),
filePath.replace("\n", " "),
filePath,
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath)) {
filePath
} else {
nativePath!!
},
gameInfo?.getTitleID() ?: 0,
mediaType,
gameInfo?.getCompany() ?: "",
if (isEncrypted) { CitraApplication.appContext.getString(R.string.unsupported_encrypted) } else { gameInfo?.getRegions() ?: "" },
isInstalled,

View file

@ -4,28 +4,43 @@
package org.citra.citra_emu.utils
import org.citra.citra_emu.utils.BuildUtil
import java.io.File
import android.content.Context
import android.os.storage.StorageManager
object RemovableStorageHelper {
// This really shouldn't be necessary, but the Android API seemingly
// doesn't have a way of doing this?
fun getRemovableStoragePath(idString: String): String? {
BuildUtil.assertNotGooglePlay()
// On certain Android flavours the external storage mount location can
// vary, so add extra cases here if we discover them.
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
private val pathCache = mutableMapOf<String, String?>()
private var scanned = false
for (mountPath in possibleMountPaths) {
val pathFile = File(mountPath);
if (pathFile.exists()) {
// TODO: Cache which mount location is being used for the remainder of the
// session, as it should never change. -OS
return pathFile.absolutePath
}
private fun scanVolumes(context: Context) {
if (scanned) {
return
}
return null
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
for (volume in storageManager.storageVolumes) {
if (!volume.isRemovable) {
continue
}
val uuid = volume.uuid ?: continue
val dir = volume.directory ?: continue
pathCache[uuid.uppercase()] = dir.absolutePath
}
scanned = true
}
fun getRemovableStoragePath(context: Context, idString: String): String? {
BuildUtil.assertNotGooglePlay()
val key = idString.uppercase()
if (!scanned) {
scanVolumes(context)
}
return pathCache[key]
}
}

View file

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

View file

@ -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,26 +309,43 @@ 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);
ReadSetting("Debugging", Settings::values.instant_debug_log);
ReadSetting("Debugging", Settings::values.enable_rpc_server);
ReadSetting("Debugging", Settings::values.toggle_unique_data_console_type);
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();
}

View file

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

View file

@ -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,76 @@ 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"(
# Enables toggling the unique data console type (Old 3DS <-> New 3DS) to be able to download the opposite system firmware type from system settings.
# 0 (default): Off, 1: On
)") DECLARE_KEY(toggle_unique_data_console_type) 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

View file

@ -207,9 +207,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/utils/DiskShaderCacheProgress")));
jclass load_callback_stage_class =
env->FindClass("org/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage");
s_disk_cache_load_progress = env->GetStaticMethodID(
s_disk_cache_progress_class, "loadProgress",
"(Lorg/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage;II)V");
s_disk_cache_load_progress =
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress",
"(Lorg/citra/citra_emu/utils/"
"DiskShaderCacheProgress$LoadCallbackStage;IILjava/lang/String;)V");
s_compress_progress_method =
env->GetStaticMethodID(s_native_library_class, "onCompressProgress", "(JJ)V");
// Initialize LoadCallbackStage map

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

View file

@ -130,12 +130,13 @@ static bool HandleCoreError(Core::System::ResultStatus result, const std::string
env->NewStringUTF(details.c_str())) != JNI_FALSE;
}
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max,
const std::string& object) {
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
IDCache::GetDiskCacheLoadProgress(),
IDCache::GetJavaLoadCallbackStage(stage), static_cast<jint>(progress),
static_cast<jint>(max));
static_cast<jint>(max), env->NewStringUTF(object.c_str()));
}
static Camera::NDK::Factory* g_ndk_factory{};
@ -217,7 +218,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
true, shared_context);
#elif ENABLE_VULKAN
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library);
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library, false);
secondary_window =
std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface, vulkan_library, true);
#else
@ -267,7 +268,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
stop_run = false;
pause_emulation = false;
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0, "");
system.GPU().ApplyPerProgramSettings(program_id);
@ -275,7 +276,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run,
&LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0, "");
SCOPE_EXIT({ TryShutdown(); });
@ -651,34 +652,38 @@ void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env,
FileUtil::SetCurrentDir(GetJString(env, j_directory));
}
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths(
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePathsImpl(
JNIEnv* env, [[maybe_unused]] jclass clazz) {
std::vector<std::string> games;
const FileUtil::DirectoryEntryCallable ScanDir =
[&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) {
std::string path = directory + virtual_name;
if (FileUtil::IsDirectory(path)) {
path += '/';
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
} else {
if (!FileUtil::Exists(path))
return false;
auto loader = Loader::GetLoader(path);
if (loader) {
bool executable{};
const Loader::ResultStatus result = loader->IsExecutable(executable);
if (Loader::ResultStatus::Success == result && executable) {
games.emplace_back(path);
}
Service::FS::MediaType media_type;
const FileUtil::DirectoryEntryCallable ScanDir = [&games, &ScanDir, &media_type](
u64*, const std::string& directory,
const std::string& virtual_name) {
std::string path = directory + virtual_name;
if (FileUtil::IsDirectory(path)) {
path += '/';
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
} else {
if (!FileUtil::Exists(path))
return false;
auto loader = Loader::GetLoader(path);
if (loader) {
bool executable{};
const Loader::ResultStatus result = loader->IsExecutable(executable);
if (Loader::ResultStatus::Success == result && executable) {
games.emplace_back(path + "|" + std::to_string(static_cast<int>(media_type)));
}
}
return true;
};
}
return true;
};
media_type = Service::FS::MediaType::SDMC;
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
"Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
media_type = Service::FS::MediaType::NAND;
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"00000000000000000000000000000000/title/00040010");
@ -1106,4 +1111,23 @@ void Java_org_citra_citra_1emu_NativeLibrary_setInsertedCartridge(JNIEnv* env, j
inserted_cartridge = GetJString(env, path);
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_uninstallTitle(JNIEnv* env, jobject obj,
jlong j_titleid, jint j_mediatype) {
const auto titleid = static_cast<u64>(j_titleid);
const auto result =
Service::AM::UninstallProgram(static_cast<Service::FS::MediaType>(j_mediatype), titleid);
if (result.IsError()) {
LOG_ERROR(Frontend, "Failed to uninstall '{}': 0x{:08X}", std::to_string(titleid),
result.raw);
return false;
}
return true;
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_nativeFileExists(JNIEnv* env, jobject obj,
jstring j_path) {
const auto path = GetJString(env, j_path);
return FileUtil::Exists(path);
}
} // extern "C"

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

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

View file

@ -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>
@ -511,8 +510,6 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
<string name="unsupported_encrypted">Aplicació cifrada no suportada</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparant ombrejadors</string>
<string name="building_shaders">Construint ombrejadors</string>
<!-- About Game Dialog -->
<string name="play">Jugar</string>
<string name="uninstall_cia">Desinstal·lar Aplicació</string>

View file

@ -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>
@ -118,6 +117,8 @@
<string name="controller_dpad_axis_description">Nogle kontrollere er muligvis ikke i stand til at tilknytte deres D-Pad som en akse. Hvis det er tilfældet, skal du bruge afsnittet D-Pad (knapper).</string>
<string name="controller_dpad_button">D-Pad (knapper)</string>
<string name="controller_dpad_button_description">Udfyld kun disse D-Pad, hvis du har problemer med opsætningen af D-Pad (akser).</string>
<string name="controller_axis_vertical">Vertikal akse</string>
<string name="controller_axis_horizontal">Horisontal akse</string>
<string name="direction_up">Op</string>
<string name="direction_down">Ned</string>
<string name="direction_left">Venstre</string>
@ -126,6 +127,8 @@
<string name="input_dialog_description">Tryk på eller flyt et input.</string>
<string name="input_binding">Inputbinding</string>
<string name="input_binding_description">Tryk på eller flyt et input for at binde det til %1$s.</string>
<string name="input_binding_description_vertical_axis">Tryk OP på dit joystick.</string>
<string name="input_binding_description_horizontal_axis">Tryk HØJRE på dit joystick.</string>
<string name="button_home">HOME</string>
<string name="button_swap">Byt skærme</string>
<string name="button_turbo">Turbo</string>
@ -546,8 +549,6 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Forberedelse af shaders</string>
<string name="building_shaders">Bygning af shaders</string>
<!-- About Game Dialog -->
<string name="play">Spil</string>
<string name="uninstall_cia">Afinstaller applikation</string>

View file

@ -66,13 +66,14 @@
<string name="give_permission">Conceder permiso</string>
<string name="notification_warning">¿Saltarse la concesión del permiso de notificaciones?</string>
<string name="notification_warning_description">Azahar no podrá notificarte sobre información importante.</string>
<string name="filesystem_permission_warning">Falta Permisos</string>
<string name="filesystem_permission_warning">Faltan Permisos</string>
<string name="filesystem_permission_warning_description">Azahar necesita permiso para administrar archivos en este dispositivo para poder almacenar y administrar los datos de usuario de 3DS.\n\nPor favor, otorgue el permiso antes de continuar.</string>
<string name="camera_permission">Cámara</string>
<string name="camera_permission_description">Concede el permiso de cámara debajo para emular la cámara de la 3DS.</string>
<string name="microphone_permission">Micrófono</string>
<string name="microphone_permission_description">Concede el permiso de micrófono debajo para emular el micrófono de la 3DS.</string>
<string name="filesystem_permission">Sistema de archivos</string>
<string name="filesystem_permission_description">Concede el permiso de sistema de archivos debajo para permitir que Azahar almacene archivos.</string>
<string name="filesystem_permission_description">Concede el permiso de sistema de archivos abajo para permitir que Azahar almacene archivos.</string>
<string name="permission_denied">Permiso denegado</string>
<string name="add_games_warning">¿Saltarse la selección de la carpeta de aplicaciones?</string>
<string name="add_games_warning_description">Nada se mostrará en la lista de aplicaciones si no se selecciona una carpeta.</string>
@ -92,7 +93,9 @@
<string name="cannot_skip">No puedes saltarte el configuración de la carpeta de usuario</string>
<string name="cannot_skip_directory_description">Este paso es necesario para permitir que Azahar funcione. Por favor, seleccione un directorio y luego puede continuar.</string>
<string name="selecting_user_directory_without_write_permissions">Has perdido los permisos de escritura en tu <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">directorio de datos de usuario</a>, donde se guardan las partidas guardadas y otra información. Esto puede ocurrir después de algunas actualizaciones de aplicaciones o de Android. Vuelve a seleccionar el directorio para recuperar los permisos y poder continuar.</string>
<string name="invalid_selection">Selección no válido</string>
<string name="invalid_selection">Selección no válida</string>
<string name="invalid_user_directory">La selección del directorio de usuario no es válida.\nVuelve a seleccionar el directorio de usuario, asegurándote de navegar hasta él desde la raíz del almacenamiento del dispositivo.</string>
<string name="filesystem_permission_lost">Azahar ha perdido el permiso para administrar archivos en este dispositivo. Esto puede ocurrir después de alguna actualización de la aplicación o de Android. Vuelve a conceder este permiso en la siguiente pantalla para seguir usando la aplicación.</string>
<string name="set_up_theme_settings">Configuración de tema</string>
<string name="setup_theme_settings_description">Configura tus preferencias de tema de Azahar.</string>
<string name="setup_set_theme">Establecer tema</string>
@ -105,9 +108,17 @@
<string name="search_installed">Instalados</string>
<!-- Input related strings -->
<string name="controller_auto_map">Mapeo de Mando Automático</string>
<string name="controller_auto_map_description">Aplica la asignación de mando estándar para todos los botones y ejes</string>
<string name="auto_map_prompt">¡Presiona este botón en tu mando!</string>
<string name="auto_map_image_description">Botones de 3DS con el botón A resaltado</string>
<string name="controller_clear_all">Limpiar Todas las Asignaciones</string>
<string name="controller_clear_all_confirm">Esto eliminará todas los asignaciones del mando actual.</string>
<string name="controller_circlepad">Pad Circular</string>
<string name="controller_c">Palanca C</string>
<string name="controller_hotkeys">Teclas de atajo</string>
<string name="controller_hotkeys_description">Si la tecla \"Habilitar teclas de acceso rápido\" está asignada, se debe presionar esa tecla además de la tecla de acceso rápido asignada</string>
<string name="controller_hotkey_enable_button">Habilitar teclas de acceso rápido</string>
<string name="controller_triggers">Botones Traseros</string>
<string name="controller_trigger">Botón Trasero</string>
<string name="controller_dpad">Pad de Control</string>
@ -115,6 +126,8 @@
<string name="controller_dpad_axis_description">Es posible que algunos controladores no puedan asignar su D-pad como un eje. Si ese es el caso, utilice la sección D-Pad (botones).</string>
<string name="controller_dpad_button">D-Pad (Botón)</string>
<string name="controller_dpad_button_description">Asigne solo el D-pad a éstos si tiene problemas con las asignaciones de botones del D-Pad (Eje).</string>
<string name="controller_axis_vertical">Eje Vertical</string>
<string name="controller_axis_horizontal">Eje Horizontal</string>
<string name="direction_up">Arriba</string>
<string name="direction_down">Abajo</string>
<string name="direction_left">Izquierda</string>
@ -123,6 +136,8 @@
<string name="input_dialog_description">Pulsa o mueve un botón/palanca.</string>
<string name="input_binding">Asignación de botones</string>
<string name="input_binding_description">Pulsa o mueve un botón para asignarlo a %1$s.</string>
<string name="input_binding_description_vertical_axis">Presiona ARRIBA en tu joystick.</string>
<string name="input_binding_description_horizontal_axis">Presiona DERECHA en tu joystick.</string>
<string name="button_home">HOME</string>
<string name="button_swap">Intercambiar Pantallas</string>
<string name="button_turbo">Turbo</string>
@ -161,6 +176,8 @@
<string name="username">Nombre de usuario/a</string>
<string name="new_3ds">Modo New 3DS</string>
<string name="lle_applets">Usar Applets LLE (si están instaladas)</string>
<string name="apply_region_free_patch">Aplicar parche de región libre a las aplicaciones instaladas</string>
<string name="apply_region_free_patch_desc">Parchea la región de las aplicaciones instaladas para que estén libres de región, de modo que siempre aparezcan en el menú home.</string>
<string name="enable_required_online_lle_modules">Habilitar los módulos LLE necesarios para las funciones en línea (si están instalados)</string>
<string name="enable_required_online_lle_modules_desc">Habilita los módulos LLE necesarios para el modo multijugador en línea, acceso a la eShop, etc.</string>
<string name="clock">Reloj</string>
@ -221,6 +238,8 @@
Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="linear_filtering">Filtro Linear</string>
<string name="linear_filtering_description">Activa el filtro linear, que hace que los gráficos del juego se vean más suaves.</string>
<string name="use_integer_scaling">Escalado a múltiplos enteros</string>
<string name="use_integer_scaling_description">Escala las pantallas con un multiplicador entero de la pantalla original de 3DS. Para diseños con dos tamaños de pantalla diferentes, la pantalla más grande se escala con un multiplicador entero.</string>
<string name="texture_filter_name">Filtro de Texturas</string>
<string name="texture_filter_description">Mejora los gráficos visuales de las aplicaciones aplicando un filtro a las texturas. Los filtros soportados son Anime4K Ultrafast, Bicubic, ScaleForce, xBRZ freescale, y MMPX.</string>
<string name="delay_render_thread">Atrasar hilo de renderizado del juego</string>
@ -236,7 +255,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="frame_limit_enable_description">Cuando se active, la velocidad de emulación estará limitada a un porcentaje determinado de la velocidad normal. Cuando se desactive, la velocidad de emulación no tendrá límite y la tecla de acceso rápido de velocidad turbo no funcionará.</string>
<string name="frame_limit_slider">Limitar porcentaje de velocidad</string>
<string name="frame_limit_slider_description">Especifica el valor al que se limita la velocidad de emulación. Con el valor por defecto del 100%, la emulación se limitará a la velocidad normal. Los valores altos o altos incrementarán o reducirán el límite de velocidad.</string>
<string name="android_hide_images">Ocultar imágenes 3DS de Android</string>
<string name="android_hide_images">Ocultar las imágenes de 3DS en Android</string>
<string name="android_hide_images_description">Evita que Android indexe las imágenes de la cámara, capturas de pantalla y texturas personalizadas de la 3DS y las muestre en la galería. Es posible que tengas que reiniciar el dispositivo después de cambiar esta configuración para que surta efecto.</string>
<string name="turbo_limit">Límite de Velocidad Turbo</string>
<string name="turbo_limit_description">Límite de velocidad de emulación utilizado mientras la tecla de acceso rápido turbo está activa.</string>
<string name="expand_to_cutout_area">Expandir al área de recorte</string>
@ -258,12 +278,15 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="debug_warning">Aviso: Modificar estas configuraciones reducirán la velocidad de emulación.</string>
<string name="stereoscopy">Estereoscopía</string>
<string name="render3d">Modo 3D Estereoscópico</string>
<string name="render3d_description">Seleccione el modo 3D estereoscópico para renderizado 3D. Los modos lado a lado son los más comunes en la actualidad. Los modos Anaglifo y Entrelazado siempre se aplican a todas las pantallas conectadas.</string>
<string name="factor3d">Profundidad</string>
<string name="factor3d_description">Especifica el valor del regulador 3D. Debería estar puesto a más allá del 0% cuando el Modo 3D Estereoscópico está activado.\nNota: Los valores de profundidad superiores al 100 % no son posibles en hardware real y pueden causar problemas gráficos.</string>
<string name="disable_right_eye_render">Desactivar Renderizado de Ojo Derecho</string>
<string name="disable_right_eye_render_description">Mejora significativamente el rendimiento en algunas aplicaciones, pero puede causar parpadeo en otros.</string>
<string name="swap_eyes_3d">Intercambiar Ojos</string>
<string name="swap_eyes_3d_description">Intercambia qué ojo se muestra en cada lado. Combinado con el modo Lado a Lado, ¡permite ver en 3D cruzando los ojos!</string>
<string name="render_3d_which_display">Renderizado 3D Estereoscópico</string>
<string name="render_3d_which_display_description">Decide si se activa el 3D estereoscópico y en qué pantallas. Las opciones de pantalla única solo son relevantes cuando se conectan varias pantallas.</string>
<string name="render_3d_which_display_both">Activado (todas las pantallas)</string>
<string name="render_3d_which_display_primary">Activado (solo pantalla principal)</string>
<string name="render_3d_which_display_secondary">Activado (solo pantalla secundaria)</string>
@ -303,9 +326,10 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="hw_shaders_description">Usa el hardware para emular los sombreadores de 3DS. Cuando se active, el rendimiento mejorará notablemente.</string>
<string name="cpu_clock_speed">Velocidad de reloj de la CPU</string>
<string name="vsync">Activar Sincronización Vertical</string>
<string name="vsync_description">Sincroniza la frecuencia de fotogramas del juego con la frecuencia de actualización de tu dispositivo. Puede causar latencia de entrada adicional, pero puede reducir el tearing en algunos casos.</string>
<string name="renderer_debug">Renderizador de depuración</string>
<string name="renderer_debug_description">Archiva información adicional gráfica relacionada con la depuración. Cuando está activada, el rendimiento de los juegos será reducido considerablemente</string>
<string name="instant_debug_log">Limpiar la salida del registro en cada mensaje</string>
<string name="instant_debug_log">Escribir la salida del registro en cada mensaje</string>
<string name="instant_debug_log_description">Inmediatamente guarda el registro de depuración en el archivo. Utilice ésto si Azahar se bloquea y la salida del registro se está cortando.</string>
<string name="delay_start_lle_modules">Retrasar el comienzo con módulos LLE</string>
<string name="delay_start_lle_modules_description">Retrasa el inicio de la aplicación cuando los módulos LLE están habilitados.</string>
@ -313,6 +337,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="deterministic_async_operations_description">Hace que las operaciones asíncronas sean deterministas para la depuración. Habilitar esta opción puede causar bloqueos.</string>
<string name="enable_rpc_server">Activar servidor RPC</string>
<string name="enable_rpc_server_desc">Activa el servidor RPC en el puerto 45987. Esto permite leer/escribir de manera remota la memoria emulada.</string>
<string name="toggle_unique_data_console_type">Cambiar tipo de consola en los datos únicos de consola</string>
<string name="toggle_unique_data_console_type_desc">Permite alternar el tipo de consola (Old 3DS &#8596; New 3DS) para poder descargar el firmware del sistema opuesto desde la configuración del sistema.</string>
<string name="shader_jit">Activar Sombreador JIT</string>
<string name="shader_jit_description">Usar el motor JIT en lugar del intérprete para la emulación del sombreador de software.</string>
@ -323,6 +349,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="layout_screen_orientation_landscape_reverse">Horizontal invertida</string>
<string name="layout_screen_orientation_portrait">Retrato</string>
<string name="layout_screen_orientation_portrait_reverse">Retrato invertido</string>
<string name="layouts_to_cycle">Ciclo de Estilos</string>
<string name="layouts_to_cycle_description">Selecciona qué estilos se pueden ciclar con la tecla de acceso rápido</string>
<string name="aspect_ratio_default">Por defecto</string>
<string name="aspect_ratio_16_9">16:9</string>
<string name="aspect_ratio_4_3">4:3</string>
@ -431,11 +459,12 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="emulation_secondary_display_default">Por defecto del sistema (espejo)</string>
<string name="emulation_screen_layout_custom">Estilo personalizado</string>
<string name="bg_color">Color de fondo</string>
<string name="bg_color_description">El color que aparece detrás de las pantallas durante la emulación, representado como un valor RGB.</string>
<string name="bg_red">Rojo</string>
<string name="bg_green">Verde</string>
<string name="bg_blue">Azul</string>
<string name="second_screen_opacity">Opacidad personalizado de la segunda pantalla</string>
<string name="second_screen_opacity_description">La opacidad de la segunda pantalla 3DS al usar el pantalla personalizado. Útil si la segunda pantalla ésta posición en la parte superior de la primera pantalla.</string>
<string name="second_screen_opacity_description">La opacidad de la segunda pantalla de 3DS al usar el pantalla personalizado. Útil si la segunda pantalla ésta posicionada en la parte superior de la primera pantalla.</string>
<string name="emulation_small_screen_position">Posición Pantalla Pequeña</string>
<string name="small_screen_position_description">¿Dónde debería aparecer la pantalla pequeña en relación con la grande en Disposicion de Pantalla Grande?</string>
<string name="small_screen_position_top_right">Arriba a la Derecha</string>
@ -532,9 +561,11 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="fatal_error_message">Ha ocurrido un error fatal. Mira el registro para más detalles.\nSeguir con la emulación podría resultar en diversos cuelgues y bugs.</string>
<string name="unsupported_encrypted">Aplicación encriptada no soportada</string>
<string name="invalid_system_mode">Modo de sistema no válido</string>
<string name="invalid_system_mode_message">Las aplicaciones exclusivas de New 3DS no se pueden cargar sin activar el modo New 3DS.</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparando shaders</string>
<string name="building_shaders">Construyendo shaders</string>
<string name="building_shaders">Construyendo%s</string>
<!-- About Game Dialog -->
<string name="play">Jugar</string>
@ -579,7 +610,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="performance_overlay_show_battery_temp">Mostrar la temperatura de la batería</string>
<string name="performance_overlay_show_battery_temp_description">Muestra la temperatura actual de la batería en Celsius y Fahrenheit.</string>
<string name="performance_overlay_position">Posición de la información</string>
<string name="performance_overlay_position_description">Elegir donde el superposición de rendimiento esta mostrado en el pantalla</string>
<string name="performance_overlay_position_description">Elegir donde la superposición de rendimiento está mostrada en la pantalla</string>
<string name="performance_overlay_position_top_left">Arriba a la Izquierda</string>
<string name="performance_overlay_position_top_right">Arriba a la Derecha</string>
<string name="performance_overlay_position_bottom_left">Abajo a la Izquierda</string>
@ -883,15 +914,17 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<!-- File Compression -->
<string name="compress">Comprimir</string>
<string name="compressing">Compresando...</string>
<string name="compressing">Comprimiendo...</string>
<string name="decompress">Descomprimir</string>
<string name="decompressing">Descomprimiendo…</string>
<string name="compress_success">Compresión completada con éxito.</string>
<string name="compress_unsupported">Compresión no está soportado con este archivo.</string>
<string name="compress_unsupported">Compresión no soportada con este archivo.</string>
<string name="compress_already">Este archivo ya está comprimido.</string>
<string name="compress_failed">Falló la compresión.</string>
<string name="decompress_success">Descompresión completada con éxito.</string>
<string name="decompress_unsupported">Descompresión no está soportado con este archivo.</string>
<string name="decompress_unsupported">Descompresión no soportada con este archivo.</string>
<string name="decompress_not_compressed">El archivo no está comprimido.</string>
<string name="decompress_failed"> Falló la descompresión.</string>
</resources>
<string name="compress_decompress_installed_app">Las aplicaciones ya instaladas no se pueden comprimir ni descomprimir.</string>
</resources>

View file

@ -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>
@ -550,7 +551,7 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Przygotowanie shaderów</string>
<string name="building_shaders">Tworzenie shaderów</string>
<string name="building_shaders">Tworzenie%s</string>
<!-- About Game Dialog -->
<string name="play">Odtwórz</string>

View file

@ -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,7 +549,7 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparando Shaders</string>
<string name="building_shaders">Construindo Shaders</string>
<string name="building_shaders">Construindo %s</string>
<!-- About Game Dialog -->
<string name="play">Jogar</string>

View file

@ -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>
@ -304,8 +303,6 @@
<string name="fatal_error_message">Возникла критическая ошибка. Откройте лог для получения информации.\nВозобновление эмуляции может привести к сбоям и вылетам.</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Подготовка шейдеров</string>
<string name="building_shaders">Построение шейдеров</string>
<!-- Cheats -->
<string name="cheats">Чит-коды</string>
<string name="cheats_add">Добавить чит-код</string>

View file

@ -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>
@ -481,8 +480,6 @@
<string name="unsupported_encrypted">Desteklenmeyen şifreli uygulama</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Gölgelendiriciler Hazırlanıyor</string>
<string name="building_shaders">Gölgelendiriciler Oluşturuluyor</string>
<!-- About Game Dialog -->
<string name="play">Oyna</string>
<string name="uninstall_cia">Uygulamayı Sil</string>

View file

@ -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>
@ -528,8 +527,6 @@
<string name="unsupported_encrypted">不支持的加密应用</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">正在准备着色器</string>
<string name="building_shaders">正在构建着色器</string>
<!-- About Game Dialog -->
<string name="play">开始游戏</string>
<string name="uninstall_cia">卸载应用</string>

View file

@ -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>
@ -503,8 +502,6 @@
<string name="unsupported_encrypted">Nicht unterstützte verschlüsselte Anwendung</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Shader werden vorbereitet</string>
<string name="building_shaders">Shader werden erstellt</string>
<!-- About Game Dialog -->
<string name="play">Spielen</string>
<string name="uninstall_cia">Anwendung deinstallieren</string>

View file

@ -1,10 +1,400 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_notification_running">Το Azahar εκτελείται</string>
<!-- Home Strings -->
<string name="grid_menu_core_settings">Ρυθμίσεις</string>
<string name="home_options">Επιλογές</string>
<string name="home_search">Αναζήτηση</string>
<string name="home_games">Εφαρμογές</string>
<string name="install_game_content">Εγκατάσταση αρχείου CIA</string>
<string name="about">Σχετικά</string>
<string name="about">Πληροφορίες</string>
<string name="citra_description">Ένας εξομοιωτής 3DS ανοικτού κώδικα</string>
<string name="install_cia_title">Εγκατάσταση CIA</string>
<string name="select_gpu_driver_install">Εγκατάσταση</string>
<string name="select_gpu_driver_install_success">Εγκαταστάθηκε %s</string>
<string name="select_gpu_driver_default">Προεπιλογή</string>
<string name="select_gpu_driver_install_success">Εγκαταστάθηκε το %s</string>
<string name="licenses">Άδειες χρήσης</string>
<!-- Setup strings -->
<string name="welcome">Καλώς ορίσατε!</string>
<string name="step_complete">Ολοκληρώθηκε!</string>
<string name="games">Εφαρμογές</string>
<string name="done">Τέλος</string>
<string name="text_continue">Συνέχεια</string>
<string name="notifications">Ειδοποιήσεις</string>
<string name="camera_permission">Κάμερα</string>
<string name="microphone_permission">Μικρόφωνο</string>
<string name="filesystem_permission">Σύστημα αρχείων</string>
<string name="select_emulator_data_folders">Φάκελοι δεδομένων</string>
<string name="warning_help">Βοήθεια</string>
<string name="warning_skip">Παράλειψη</string>
<string name="warning_cancel">Ακύρωση</string>
<string name="select">Επιλογή</string>
<string name="invalid_selection">Μη έγκυρη επιλογή</string>
<string name="set_up_theme_settings">Ρυθμίσεις θέματος</string>
<string name="setup_set_theme">Ορισμός θέματος</string>
<string name="controller_axis_vertical">Κάθετος άξονας</string>
<string name="controller_axis_horizontal">Οριζόντιος άξονας</string>
<string name="button_swap">Εναλλαγή οθονών</string>
<!-- System files strings -->
<string name="setup_system_files">Αρχεία συστήματος</string>
<string name="setup_system_files_o3ds">Ρύθμιση για Old 3DS</string>
<string name="setup_system_files_n3ds">Ρύθμιση για New 3DS</string>
<!-- Generic buttons (Shared with lots of stuff) -->
<string name="generic_buttons">Κουμπιά</string>
<string name="button">Κουμπί</string>
<!-- System settings strings -->
<string name="emulation_settings">Ρυθμίσεις εξομοίωσης</string>
<string name="username">Όνομα χρήστη</string>
<string name="new_3ds">Λειτουργία New 3DS</string>
<string name="clock">Ρολόι</string>
<string name="init_clock">Ρολόι</string>
<string name="device_clock">Ρολόι συσκευής</string>
<string name="simulated_clock">Εξομοιωμένο ρολόι</string>
<string name="profile_settings">Ρυθμίσεις προφίλ</string>
<string name="emulated_region">Περιοχή</string>
<string name="emulated_language">Γλώσσα</string>
<string name="birthday">Γενέθλια</string>
<string name="birthday_month">Μήνας</string>
<string name="birthday_day">Ημέρα</string>
<string name="country">Χώρα</string>
<string name="mac_address">Διεύθυνση MAC</string>
<!-- Camera settings strings -->
<string name="inner_camera">Εσωτερική κάμερα</string>
<string name="camera_device">Κάμερα</string>
<string name="graphics_api">API γραφικών</string>
<string name="texture_filter_name">Φίλτρο υφής</string>
<string name="shaders_accurate_mul">Ακριβής πολλαπλασιασμός</string>
<string name="frame_limit_enable">Περιορισμός ταχύτητας</string>
<string name="internal_resolution">Εσωτερική ανάλυση</string>
<string name="internal_resolution_setting_1x">Εγγενής (400x240)</string>
<string name="factor3d">Βάθος</string>
<string name="cardboard_vr">Cardboard VR</string>
<string name="dump_textures">Αποτύπωση υφών</string>
<string name="custom_textures">Προσαρμοσμένες υφές</string>
<!-- Audio settings strings -->
<string name="audio_volume">Ένταση ήχου</string>
<string name="audio_input_type">Συσκευή εισόδου ήχου</string>
<string name="sound_output_mode">Λειτουργία εξόδου ήχου</string>
<string name="vsync">Ενεργοποίηση V-Sync</string>
<!-- Layout settings strings -->
<string name="layout_screen_orientation">Προσανατολισμός οθόνης</string>
<string name="aspect_ratio_default">Προεπιλογή</string>
<string name="aspect_ratio_16_9">16:9</string>
<string name="aspect_ratio_4_3">4:3</string>
<string name="aspect_ratio_21_9_fullscreen">21:9</string>
<string name="aspect_ratio_16_10_fullscreen_stretched">16:10</string>
<!-- Miscellaneous -->
<string name="clear">Απαλοιφή</string>
<string name="slider_default">Προεπιλογή</string>
<string name="saving">Αποθήκευση…</string>
<string name="loading">Φόρτωση…</string>
<string name="next">Επόμενο</string>
<string name="back">Πίσω</string>
<string name="learn_more">Μάθετε περισσότερα</string>
<string name="close">Κλείσιμο</string>
<string name="option_default">Προεπιλογή</string>
<string name="install">Εγκατάσταση</string>
<string name="delete">Διαγραφή</string>
<string name="reset_all_settings">Επαναφορά όλων των ρυθμίσεων;</string>
<string name="auto_select">Αυτόματη επιλογή</string>
<string name="cancelling">Ακύρωση…</string>
<string name="visibility">Ορατότητα</string>
<string name="information">Πληροφορίες</string>
<!-- Add Directory Screen-->
<string name="select_game_folder">Επιλογή φακέλου παιχνιδιού</string>
<!-- Game Properties -->
<string name="properties">Ιδιότητες</string>
<!-- Preferences Screen -->
<string name="preferences_settings">Ρυθμίσεις</string>
<string name="preferences_general">Γενικά</string>
<string name="preferences_system">Σύστημα</string>
<string name="preferences_camera">Κάμερα</string>
<string name="preferences_graphics">Γραφικά</string>
<string name="preferences_audio">Ήχος</string>
<string name="preferences_debug">Έλεγχος σφαλμάτων</string>
<string name="preferences_theme">Θέμα και χρώμα</string>
<string name="preferences_layout">Διάταξη</string>
<string name="loader_error_invalid_format">Μη έγκυρη μορφή ROM</string>
<string name="emulation_save_state">Αποθήκευση κατάστασης</string>
<string name="emulation_load_state">Φόρτωση κατάστασης</string>
<string name="emulation_edit_layout">Επεξεργασία διάταξης</string>
<string name="emulation_done">Τέλος</string>
<string name="emulation_control_scale">Προσαρμογή κλίμακας</string>
<string name="emulation_control_scale_global">Καθολική κλίμακα</string>
<string name="emulation_control_scale_reset_all">Επαναφορά όλων</string>
<string name="emulation_control_opacity">Προσαρμογή αδιαφάνειας</string>
<string name="emulation_open_cheats">Άνοιγμα cheat</string>
<string name="emulation_screen_layout_largescreen">Μεγάλη οθόνη</string>
<string name="emulation_screen_layout_single">Μονή οθόνη</string>
<string name="emulation_screen_layout_hybrid">Υβριδικές οθόνες</string>
<string name="emulation_portrait_layout_top_full">Προεπιλογή</string>
<string name="emulation_screen_layout_custom">Προσαρμοσμένη διάταξη</string>
<string name="bg_red">Κόκκινο</string>
<string name="bg_green">Πράσινο</string>
<string name="bg_blue">Μπλε</string>
<string name="emulation_top_screen">Πάνω οθόνη</string>
<string name="emulation_bottom_screen">Κάτω οθόνη</string>
<string name="emulation_custom_layout_x">Θέση X</string>
<string name="emulation_custom_layout_y">Θέση Y</string>
<string name="emulation_custom_layout_width">Πλάτος</string>
<string name="emulation_custom_layout_height">Ύψος</string>
<string name="emulation_close_game">Κλείσιμο παιχνιδιού</string>
<string name="menu_emulation_amiibo">Amiibo</string>
<string name="menu_emulation_amiibo_load">Φόρτωση</string>
<string name="menu_emulation_amiibo_remove">Αφαίρεση</string>
<string name="select_amiibo">Επιλογή αρχείου Amiibo</string>
<string name="amiibo_load_error">Σφάλμα φόρτωσης Amiibo</string>
<string name="pause_emulation">Παύση εξομοίωσης</string>
<string name="resume_emulation">Συνέχιση εξομοίωσης</string>
<string name="load_settings">Φόρτωση ρυθμίσεων…</string>
<string name="move_data">Μετακίνηση δεδομένων</string>
<string name="moving_data">Μετακίνηση δεδομένων…</string>
<string name="copy_file_name">Αντιγραφή αρχείου: %s</string>
<!-- Software Keyboard -->
<string name="software_keyboard">Εικονικό πληκτρολόγιο</string>
<!-- Camera -->
<string name="camera_select_image">Επιλογή εικόνας</string>
<string name="camera">Κάμερα</string>
<!-- Microphone -->
<string name="microphone">Μικρόφωνο</string>
<!-- Core Errors -->
<string name="abort_button">Ακύρωση</string>
<string name="continue_button">Συνέχεια</string>
<string name="system_archive_general">Ένα αρχείο συστήματος</string>
<string name="save_load_error">Σφάλμα αποθήκευσης/φόρτωσης</string>
<string name="fatal_error">Κρίσιμο σφάλμα</string>
<string name="unsupported_encrypted">Μη υποστηριζόμενη κρυπτογραφημένη εφαρμογή</string>
<string name="game_context_open_app">Άνοιγμα φακέλου εφαρμογής</string>
<string name="shortcut">Συντόμευση</string>
<string name="shortcut_name">Όνομα συντόμευσης</string>
<string name="edit_icon">Επεξεργασία εικονιδίου</string>
<string name="create_shortcut">Δημιουργία συντόμευσης</string>
<string name="game_context_file">Αρχείο:</string>
<string name="game_context_type">Τύπος:</string>
<string name="performance_overlay_show_speed">Εμφάνιση ταχύτητας</string>
<!-- Cheats -->
<string name="cheats">Cheat</string>
<string name="cheats_add">Προσθήκη cheat</string>
<string name="cheats_name">Όνομα</string>
<string name="cheats_notes">Σημειώσεις</string>
<string name="cheats_code">Κώδικας</string>
<string name="cheats_edit">Επεξεργασία</string>
<string name="cheats_delete">Διαγραφή</string>
<string name="cia_install_notification_title">Εγκατάσταση CIA</string>
<!-- Memory Sizes -->
<string name="memory_formatted">%1$s %2$s</string>
<string name="memory_byte">Byte</string>
<string name="memory_byte_shorthand">B</string>
<string name="memory_kilobyte">KB</string>
<string name="memory_megabyte">MB</string>
<string name="memory_gigabyte">GB</string>
<string name="memory_terabyte">TB</string>
<string name="memory_petabyte">PB</string>
<string name="memory_exabyte">EB</string>
<!-- Material You theme -->
<string name="material_you">Material You</string>
<!-- Static theme color -->
<string name="static_theme_color">Χρώμα θέματος</string>
<!-- Region names -->
<string name="system_region_jpn">JPN</string>
<string name="system_region_usa">USA</string>
<string name="system_region_eur">EUR</string>
<string name="system_region_aus">AUS</string>
<string name="system_region_chn">CHN</string>
<string name="system_region_kor">KOR</string>
<string name="system_region_twn">TWN</string>
<!-- Language names -->
<string name="language_japanese">Ιαπωνικά (日本語)</string>
<string name="language_french">Γαλλικά (Français)</string>
<string name="language_german">Γερμανικά (Deutsch)</string>
<string name="language_italian">Ιταλικά (Italiano)</string>
<string name="language_spanish">Ισπανικά (Español)</string>
<string name="language_simplified_chinese">Απλοποιημένα Κινεζικά (简体中文)</string>
<string name="language_korean">Κορεατικά (한국어)</string>
<string name="language_dutch">Ολλανδικά (Nederlands)</string>
<string name="language_portuguese">Πορτογαλικά (Português)</string>
<string name="language_russian">Ρωσικά (Русский)</string>
<string name="language_traditional_chinese">Παραδοσιακά Κινεζικά (正體中文)</string>
<string name="device_camera">Κάμερα συσκευής</string>
<!-- Graphics API names -->
<string name="opengles">OpenGLES</string>
<string name="vulkan">Vulkan</string>
<!-- Texture filter names -->
<string name="anime4k">Anime4K</string>
<string name="xbrz">xBRZ</string>
<string name="mmpx">MMPX</string>
<!-- Countries -->
<string name="japan">Ιαπωνία</string>
<string name="anguilla">Ανγκουίλα</string>
<string name="antigua_and_barbuda">Αντίγκουα και Μπαρμπούντα</string>
<string name="argentina">Αργεντινή</string>
<string name="aruba">Αρούμπα</string>
<string name="bahamas">Μπαχάμες</string>
<string name="barbados">Μπαρμπάντος</string>
<string name="belize">Μπελίζ</string>
<string name="bolivia">Βολιβία</string>
<string name="brazil">Βραζιλία</string>
<string name="british_virgin_islands">Βρετανικές Παρθένοι Νήσοι</string>
<string name="canada">Καναδάς</string>
<string name="cayman_islands">Νήσοι Κέιμαν</string>
<string name="chile">Χιλή</string>
<string name="colombia">Κολομβία</string>
<string name="costa_rica">Κόστα Ρίκα</string>
<string name="dominica">Δομινίκα</string>
<string name="dominican_republic">Δομινικανή Δημοκρατία</string>
<string name="ecuador">Εκουαδόρ</string>
<string name="el_salvador">Ελ Σαλβαδόρ</string>
<string name="french_guiana">Γαλλική Γουιάνα</string>
<string name="grenada">Γρενάδα</string>
<string name="guadeloupe">Γουαδελούπη</string>
<string name="guatemala">Γουατεμάλα</string>
<string name="guyana">Γουιάνα</string>
<string name="haiti">Αϊτή</string>
<string name="honduras">Ονδούρα</string>
<string name="jamaica">Τζαμάικα</string>
<string name="matinique">Μαρτινίκα</string>
<string name="mexico">Μεξικό</string>
<string name="monsterrat">Μοντσερράτ</string>
<string name="netherlands_antilles">Ολλανδικές Αντίλλες</string>
<string name="nicaragua">Νικαράγουα</string>
<string name="panama">Παναμάς</string>
<string name="paraguay">Παραγουάη</string>
<string name="peru">Περού</string>
<string name="saint_lucia">Αγία Λουκία</string>
<string name="suriname">Σουρινάμ</string>
<string name="trinidad_and_tobago">Τρινιντάντ και Τομπάγκο</string>
<string name="united_states">Ηνωμένες Πολιτείες</string>
<string name="uruguay">Ουρουγουάη</string>
<string name="us_virgin_islands">Αμερικανικές Παρθένοι Νήσοι</string>
<string name="venezuela">Βενεζουέλα</string>
<string name="albania">Αλβανία</string>
<string name="australia">Αυστραλία</string>
<string name="austria">Αυστρία</string>
<string name="belgium">Βέλγιο</string>
<string name="bosnia_and_herzegovnia">Βοσνία και Ερζεγοβίνη</string>
<string name="botswana">Μποτσουάνα</string>
<string name="bulgaria">Βουλγαρία</string>
<string name="croatia">Κροατία</string>
<string name="cyprus">Κύπρος</string>
<string name="czech_republic">Τσεχία</string>
<string name="denmark">Δανία</string>
<string name="estonia">Εσθονία</string>
<string name="finland">Φινλανδία</string>
<string name="france">Γαλλία</string>
<string name="germany">Γερμανία</string>
<string name="greece">Ελλάδα</string>
<string name="hungary">Ουγγαρία</string>
<string name="iceland">Ισλανδία</string>
<string name="ireland">Ιρλανδία</string>
<string name="italy">Ιταλία</string>
<string name="latvia">Λετονία</string>
<string name="lesotho">Λεσότο</string>
<string name="liechtenstein">Λιχτενστάιν</string>
<string name="lithuania">Λιθουανία</string>
<string name="luxembourg">Λουξεμβούργο</string>
<string name="macedonia">Βόρεια Μακεδονία</string>
<string name="malta">Μάλτα</string>
<string name="montenegro">Μαυροβούνιο</string>
<string name="mozambique">Μοζαμβίκη</string>
<string name="namibia">Ναμίμπια</string>
<string name="netherlands">Ολλανδία</string>
<string name="new_zealand">Νέα Ζηλανδία</string>
<string name="norway">Νορβηγία</string>
<string name="poland">Πολωνία</string>
<string name="portugal">Πορτογαλία</string>
<string name="romania">Ρουμανία</string>
<string name="russia">Ρωσία</string>
<string name="serbia">Σερβία</string>
<string name="slovakia">Σλοβακία</string>
<string name="slovenia">Σλοβενία</string>
<string name="south_africa">Νότια Αφρική</string>
<string name="spain">Ισπανία</string>
<string name="swaziland">Εσουατίνι</string>
<string name="sweden">Σουηδία</string>
<string name="switzerland">Ελβετία</string>
<string name="turkey">Τουρκία</string>
<string name="united_kingdom">Ηνωμένο Βασίλειο</string>
<string name="zambia">Ζάμπια</string>
<string name="zimbabwe">Ζιμπάμπουε</string>
<string name="azerbaijan">Αζερμπαϊτζάν</string>
<string name="mauritania">Μαυριτανία</string>
<string name="mali">Μάλι</string>
<string name="niger">Νίγηρας</string>
<string name="chad">Τσαντ</string>
<string name="sudan">Σουδάν</string>
<string name="eritrea">Ερυθραία</string>
<string name="djibouti">Τζιμπουτί</string>
<string name="somalia">Σομαλία</string>
<string name="andorra">Ανδόρρα</string>
<string name="gibraltar">Γιβραλτάρ</string>
<string name="guernsey">Γκέρνζι</string>
<string name="isle_of_man">Νήσος του Μαν</string>
<string name="jersey">Τζέρσεϊ</string>
<string name="monaco">Μονακό</string>
<string name="taiwan">Ταϊβάν</string>
<string name="south_korea">Νότια Κορέα</string>
<string name="hong_kong">Χονγκ Κονγκ</string>
<string name="macau">Μακάο</string>
<string name="indonesia">Ινδονησία</string>
<string name="singapore">Σιγκαπούρη</string>
<string name="thailand">Ταϊλάνδη</string>
<string name="philippines">Φιλιππίνες</string>
<string name="malaysia">Μαλαισία</string>
<string name="china">Κίνα</string>
<string name="united_arab_emirates">Ηνωμένα Αραβικά Εμιράτα</string>
<string name="india">Ινδία</string>
<string name="egypt">Αίγυπτος</string>
<string name="oman">Ομάν</string>
<string name="qatar">Κατάρ</string>
<string name="kuwait">Κουβέιτ</string>
<string name="saudi_arabia">Σαουδική Αραβία</string>
<string name="syria">Συρία</string>
<string name="bahrain">Μπαχρέιν</string>
<string name="jordan">Ιορδανία</string>
<string name="san_marino">Άγιος Μαρίνος</string>
<string name="vatican_city">Βατικανό</string>
<string name="bermuda">Βερμούδες</string>
<!-- Months -->
<string name="january">Ιανουάριος</string>
<string name="february">Φεβρουάριος</string>
<string name="march">Μάρτιος</string>
<string name="april">Απρίλιος</string>
<string name="may">Μάιος</string>
<string name="june">Ιούνιος</string>
<string name="july">Ιούλιος</string>
<string name="august">Αύγουστος</string>
<string name="september">Σεπτέμβριος</string>
<string name="october">Οκτώβριος</string>
<string name="november">Νοέμβριος</string>
<string name="december">Δεκέμβριος</string>
<string name="artic_base">Artic Base</string>
<!-- Quickload&Save-->
<string name="emulation_quicksave_slot">Γρήγορη αποθήκευση</string>
<string name="emulation_quicksave">Γρήγορη αποθήκευση</string>
<string name="emulation_quickload">Γρήγορη φόρτωση</string>
<string name="emulation_occupied_quicksave_slot">Γρήγορη αποθήκευση - %1$tF %1$tR</string>
<!-- File Compression -->
<string name="compress">Συμπίεση</string>
<string name="compressing">Συμπίεση…</string>
<string name="decompress">Αποσυμπίεση</string>
<string name="decompressing">Αποσυμπίεση…</string>
<string name="compress_failed">Η συμπίεση απέτυχε.</string>
<string name="decompress_failed">Η αποσυμπίεση απέτυχε.</string>
</resources>

View file

@ -67,7 +67,7 @@
<string name="notification_warning">Ne pas autoriser les notifications ?</string>
<string name="notification_warning_description">Azahar ne pourra pas vous avertir des informations importantes.</string>
<string name="filesystem_permission_warning">Autorisations manquantes</string>
<string name="filesystem_permission_warning_description">Azahar a besoin de l\'autorisation de gérer les fichiers de cet appareil afin de stocker et gérer ses données.\n\nVeuillez accorder l\'autorisation \"Accès à tous les fichiers\" avant de continuer.</string>
<string name="filesystem_permission_warning_description">Azahar a besoin de l\'autorisation de gérer les fichiers de cet appareil afin de stocker et gérer ses données.\n\nVeuillez accorder l\'autorisation \"Accès à tous les fichiers\" pour continuer.</string>
<string name="camera_permission">Caméra</string>
<string name="camera_permission_description">Accorder l\'autorisation de la caméra ci-dessous pour émuler la caméra 3DS.</string>
<string name="microphone_permission">Microphone</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,9 +237,11 @@
<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>
<string name="delay_render_thread">Retarder le fil de rendu du jeu</string>
<string name="delay_render_thread_description">Délai le thread de rendu du jeu lorsqu\'il soumet des données au GPU. Cela permet de résoudre les problèmes de performance dans les (très rares) applications avec des fréquences d\'images dynamiques.</string>
<string name="advanced">Avancé</string>
<string name="texture_sampling_name">Échantillonnage de texture</string>
@ -315,10 +325,10 @@
<string name="hw_shaders_description">Utilise le hardware pour émuler les shaders de la 3DS. Lorsqu\'il est activé, la performance des jeux sera améliorée de manière significative.</string>
<string name="cpu_clock_speed">Fréquence d\'horloge du CPU</string>
<string name="vsync">Activer la synchronisation verticale (VSync)</string>
<string name="vsync_description">Synchronise la fréquence d\'images du jeu avec la fréquence de rafraîchissement de votre appareil. Peut rajouter de la latence d\'entrée mais peut réduire le tearing dans certains cas.</string>
<string name="vsync_description">Synchronise la fréquence d\'images du jeu avec la fréquence de rafraîchissement de votre appareil. Peut rajouter de la latence d\'entrée mais peut réduire le déchirement d\'écran dans certains cas.</string>
<string name="renderer_debug">Rendu de débogage</string>
<string name="renderer_debug_description">Enregistre des informations de débogage supplémentaires liées aux graphiques. Lorsqu\'il est activé, les performances du jeu seront significativement réduites.</string>
<string name="instant_debug_log">Vider la sortie des logs sur chaque message</string>
<string name="instant_debug_log">Vider la sortie des logs à chaque message</string>
<string name="instant_debug_log_description">Enregistre immédiatement le log de débogage dans un fichier. A utiliser si Azahar se plante et que la sortie du journal est coupée.</string>
<string name="delay_start_lle_modules">Démarrage différé avec les modules LLE</string>
<string name="delay_start_lle_modules_description">Retarde le démarrage de l\'application lorsque les modules LLE sont activés.</string>
@ -326,6 +336,8 @@
<string name="deterministic_async_operations_description">Rend les opérations asynchrones déterministes pour le débogage. L\'activation de cette fonction peut entraîner des blocages.</string>
<string name="enable_rpc_server">Activer le serveur RPC</string>
<string name="enable_rpc_server_desc">Active le serveur RPC sur le port 45987. Cela permet de lire/écrire à distance la mémoire invitée.</string>
<string name="toggle_unique_data_console_type">Basculer le type de console des données uniques</string>
<string name="toggle_unique_data_console_type_desc">Change le type de console des données uniques (Old 3DS &#8596; New 3DS) afin de pouvoir télécharger le firmware système opposé depuis les paramètres de la console.</string>
<string name="shader_jit">Activer Shader JIT</string>
<string name="shader_jit_description">Utilisez le moteur JIT à la place de l\'interpréteur pour l\'émulation des shaders logiciels.</string>
@ -336,6 +348,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>
@ -473,7 +487,7 @@
<string name="emulation_custom_layout_y">Position Y</string>
<string name="emulation_custom_layout_width">Largeur</string>
<string name="emulation_custom_layout_height">Hauteur</string>
<string name="emulation_cycle_landscape_layouts">Dispositions de cycle</string>
<string name="emulation_cycle_landscape_layouts">Dispositions à alterner</string>
<string name="emulation_swap_screens">Permuter les écrans</string>
<string name="emulation_rotate_upright">Tourner l\'écran à la verticale</string>
<string name="emulation_touch_overlay_reset">Réinitialiser l\'overlay</string>
@ -550,7 +564,7 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Préparation des shaders</string>
<string name="building_shaders">Construction des shaders</string>
<string name="building_shaders">Construction %s</string>
<!-- About Game Dialog -->
<string name="play">Jouer</string>

View file

@ -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,28 +53,27 @@ 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 &lt;b>Azahar&lt;/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 &lt;b>Applicazioni&lt;/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="filesystem_permission_description">Autorizza l\'accesso al filesystem per consentire il salvataggio dei dati.</string>
<string name="permission_denied">Permesso negato</string>
<string name="add_games_warning">Saltare la selezione della cartella applicazioni?</string>
<string name="add_games_warning_description">Non verrà mostrato alcun software nella lista applicazioni se non viene selezionata alcuna cartella.</string>
@ -91,15 +86,15 @@ Divertiti usando l\'emulatore!</string>
<string name="warning_cancel">Annulla</string>
<string name="select_citra_user_folder">Seleziona cartella utente</string>
<string name="select_citra_user_folder_description"><![CDATA[Seleziona la tua cartella <a href=\"https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage\">utente</a> usando il pulsante qui sotto.]]></string>
<string name="select_which_user_directory_to_use">Sembra che tu abbia impostato la cartella utente sia per Lime3DS che per Azahar. Questo probabilmente è dovuto al fatto che hai eseguito l\'aggiornamento ad Azahar e, al momento della richiesta, hai scelto una directory utente diversa da quella utilizzata per Lime3DS.\n\nQuesto potrebbe averti fatto pensare di aver perso salvataggi o altre impostazioni - ci scusiamo se è successo.\n\nDesideri tornare a utilizzare la tua cartella utente originale di Lime3DS, ripristinando impostazioni e salvataggi da Lime3DS, oppure preferisci mantenere la cartella utente attuale di Azahar?\n\nNessuna delle cartelle verrà eliminata, indipendentemente dalla tua scelta, e potrai passare liberamente da una all\'altra utilizzando l\'opzione \"Seleziona Cartella Utente\".</string>
<string name="select_which_user_directory_to_use">Sembra che tu abbia impostato cartelle utente sia per Lime3DS che per Azahar. Questo è probabilmente dovuto al fatto che hai eseguito l\'aggiornamento ad Azahar e, al momento della richiesta, hai scelto una cartella utente diversa da quella utilizzata per Lime3DS.\n\nQuesto potrebbe averti fatto pensare di aver perso salvataggi o altre impostazioni - ci scusiamo se è successo.\n\nDesideri tornare a utilizzare la tua cartella utente originale di Lime3DS, ripristinando impostazioni e salvataggi da Lime3DS, oppure preferisci mantenere la cartella utente attuale di Azahar?\n\nNessuna delle cartelle verrà eliminata, indipendentemente dalla tua scelta, e potrai passare liberamente da una all\'altra utilizzando l\'opzione \"Seleziona Cartella Utente\".</string>
<string name="keep_current_azahar_directory">Mantieni la cartella di Azahar attuale</string>
<string name="use_prior_lime3ds_directory">Usa la cartella precedente di Lime3DS</string>
<string name="select">Seleziona</string>
<string name="cannot_skip">Non puoi saltare la configurazione della cartella utente</string>
<string name="cannot_skip_directory_description">Questo passaggio è richiesto per permettere che Azahar funzioni. Quando la cartella verrà selezionata potrai continuare.</string>
<string name="selecting_user_directory_without_write_permissions">Hai perso i permessi di scrittura sulla tua <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">cartella dei dati utente</a>, dove sono memorizzati i salvataggi e altre informazioni. Questo può accadere dopo alcuni aggiornamenti app o Android. Ri-seleziona la cartella per recuperare le autorizzazioni in modo da poter continuare.</string>
<string name="cannot_skip_directory_description">Questo passaggio è necessario per consentire ad Azahar di funzionare. Seleziona una cartella per poter continuare.</string>
<string name="selecting_user_directory_without_write_permissions">Hai perso i permessi di scrittura sulla tua <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">cartella dei dati utente</a>, dove sono memorizzati i salvataggi e altre informazioni. Questo può accadere dopo alcuni aggiornamenti app o Android. Seleziona nuovamente la cartella per ripristinare i permessi e continuare.</string>
<string name="invalid_selection">Selezione non valida</string>
<string name="invalid_user_directory">La selezione della directory utente non era valida.\nSeleziona nuovamente la directory utente, assicurandoti di accedervi dalla radice dello spazio di archiviazione del tuo dispositivo.</string>
<string name="invalid_user_directory">La cartella utente selezionata non è valida.\nSelezionala di nuovo assicurandoti di partire dalla memoria principale del dispositivo.</string>
<string name="filesystem_permission_lost">Azahar ha perso l\'autorizzazione a gestire i file su questo dispositivo. Ciò può accadere dopo alcuni aggiornamenti di app o Android. Si prega di concedere nuovamente questa autorizzazione nella schermata successiva per continuare a utilizzare l\'app.</string>
<string name="set_up_theme_settings">Impostazioni tema</string>
<string name="setup_theme_settings_description">Configura le impostazioni del tema di Azahar.</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>
@ -143,11 +148,11 @@ Divertiti usando l\'emulatore!</string>
<string name="turbo_disabled_toast">Velocità Turbo disabilitata</string>
<!-- System files strings -->
<string name="setup_system_files">File di Sistema</string>
<string name="setup_system_files">File di sistema</string>
<string name="setup_system_files_description">Esegui operazioni sui file di sistema come installare file di sistema o avviare il Menu Home</string>
<string name="setup_tool_connect">Connettiti a Artic Setup Tool </string>
<string name="setup_system_files_preamble"><![CDATA[Azahar necessita di dati unici della console e file firmware provenienti da una console reale per poter utilizzare alcune delle sue funzionalità. Questi file e dati possono essere configurati con <a href=https://github.com/azahar-emu/ArticSetupTool>Azahar Artic Setup Tool</a>.<br>Note: <ul><li><b>Questa operazione installerà dati unici della console su Azahar, non condividere le tue cartelle utente o NAND dopo aver completato il processo di configurazione!</b></li><li>Durante il processo di configurazione, Azahar si collegherà alla console che sta eseguendo lo strumento di configurazione. Puoi scollegare la console in seguito dalla scheda \"File di sistema\" nel menu delle opzioni dell\'emulatore.</li><li>Non andare online contemporaneamente con Azahar e la tua console 3DS dopo aver configurato i file di sistema, poiché ciò potrebbe causare problemi.</li><li>La configurazione del vecchio 3DS è necessaria affinché la configurazione del nuovo 3DS funzioni (si consiglia di configurare entrambi).</li><li>Entrambe le modalità di configurazione funzioneranno indipendentemente dal modello della console che esegue lo strumento di configurazione.</li></ul>]]></string>
<string name="setup_system_files_detect">Recupero dello stato attuale dei file di sistema, per favore attendi...</string>
<string name="setup_system_files_detect">Recupero dello stato attuale dei file di sistema, attendere...</string>
<string name="delete_system_files">Scollega i dati univoci della console</string>
<string name="delete_system_files_description"><![CDATA[Questa azione scollegherà la tua console reale da Azahar, con le seguenti conseguenze:<br><ul><li>il tuo OTP, SecureInfo e LocalFriendCodeSeed verranno rimossi da Azahar.</li><li>La tua lista amici verrà reimpostata e verrai disconnesso dal tuo account NNID/PNID.</li><li>I file di sistema e i titoli eshop ottenuti tramite Azahar diventeranno inaccessibili finché la stessa console non verrà nuovamente collegata tramite lo strumento di configurazione (i dati di salvataggio non andranno persi).</li></ul><br>Continuare?]]></string>
<string name="setup_system_files_o3ds">Setup Old 3DS</string>
@ -213,7 +218,7 @@ Divertiti usando l\'emulatore!</string>
<string name="outer_left_camera">Fotocamera Esterna di Sinistra</string>
<string name="outer_right_camera">Fotocamera Esterna di Destra</string>
<string name="image_source">Sorgente Fotocamera</string>
<string name="image_source_description">Imposta l\'immagine sorgente della fotocamera virtuale. Puoi utilizzare un file immagine, oppure la fotocamera del dispositivo quando supportato.</string>
<string name="image_source_description">Imposta la sorgente dell\'immagine per la fotocamera virtuale. Puoi usare un file immagine o la fotocamera del dispositivo, se supportata.</string>
<string name="camera_device">Fotocamera</string>
<string name="camera_device_description">Se l\'impostazione \"Origine immagine\" è impostata su \"Fotocamera dispositivo\", questa imposta la fotocamera fisica da utilizzare.</string>
<string name="camera_facing_front">Fronte</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>
@ -273,7 +280,7 @@ Divertiti usando l\'emulatore!</string>
<string name="render3d_description">Scegli la modalità 3D stereoscopica per il rendering 3D. Le modalità \"Affiancato\" sono le più comuni nell\'uso moderno. Le modalità \"Anaglifo\" e \"Interlacciato\" si applicheranno sempre a tutti i display collegati.</string>
<string name="factor3d">Profondità</string>
<string name="factor3d_description">Specifica il valore del regolatore di profondità 3D. Questo valore deve essere impostato su un valore superiore allo 0% quando è abilitato il 3D stereoscopico.\nNota: Valori di profondità superiori al 100% non sono possibili su un dispositivo reale e possono causare problemi grafici</string>
<string name="disable_right_eye_render">Disabilita il rendering dell\'occhio destro</string>
<string name="disable_right_eye_render">Disattiva rendering occhio destro</string>
<string name="disable_right_eye_render_description">Migliora notevolmente le prestazioni in alcune applicazioni, ma può causare flickering in altre.</string>
<string name="swap_eyes_3d">Inverti occhi</string>
<string name="swap_eyes_3d_description">Cambia quale occhio viene visualizzato in quale lato. In combinazione con la modalità \"Affiancato\" è possibile vedere il 3D incrociando gli occhi!</string>
@ -329,6 +336,8 @@ Divertiti usando l\'emulatore!</string>
<string name="deterministic_async_operations_description">Rende le operazioni asincrone deterministiche per il debug. Abilitare questa opzione potrebbe causare blocchi.</string>
<string name="enable_rpc_server">Abilita server RPC</string>
<string name="enable_rpc_server_desc">Abilita il server RPC sulla porta 45987. Questo permette di leggere e scrivere remotamente la memoria guest.</string>
<string name="toggle_unique_data_console_type">Alterna tipo dati univoci console</string>
<string name="toggle_unique_data_console_type_desc">Alterna il tipo di console (Old 3DS &#8596; New 3DS) per scaricare il firmware del sistema opposto dalle impostazioni.</string>
<string name="shader_jit">Abilita shader JIT</string>
<string name="shader_jit_description">Usa il motore JIT al posto dell\'interprete per l\'emulazione software degli shader.</string>
@ -339,6 +348,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 +378,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 +394,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>
@ -498,7 +509,7 @@ Divertiti usando l\'emulatore!</string>
<string name="lock_drawer">Blocca menù laterale</string>
<string name="unlock_drawer">Sblocca menù laterale</string>
<string name="write_permission_needed">L\'emulatore ha bisogno dei permessi per l\'archiviazione dei dati su memoria esterna per funzionare correttamente</string>
<string name="write_permission_needed">Accesso alla memoria esterna richiesto per il funzionamento dell\'emulatore</string>
<string name="load_settings">Caricando le Impostazioni...</string>
<string name="external_storage_not_mounted">La memoria esterna deve essere disponibile per poter utilizzare Azahar</string>
@ -553,7 +564,7 @@ Divertiti usando l\'emulatore!</string>
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparazione degli shader</string>
<string name="building_shaders">Compilazione degli shader</string>
<string name="building_shaders">Compilazione %s</string>
<!-- About Game Dialog -->
<string name="play">Riproduci</string>
@ -636,7 +647,7 @@ Divertiti usando l\'emulatore!</string>
<string name="cia_install_error_aborted">L\'installazione di \"%s\" è stata annullata.\n Consulta i log per maggiori dettagli</string>
<string name="cia_install_error_invalid">\"%s\" non è un CIA valido</string>
<string name="cia_install_error_encrypted">\"%s\" deve essere decriptato prima di essere usato in Azahar.\n Un 3DS reale è richiesto</string>
<string name="cia_install_error_unknown">Errore Sconosciuto durante l\'installazione di \"%s\".\n Consulta i log per maggiori dettagli</string>
<string name="cia_install_error_unknown">Errore sconosciuto durante l\'installazione di \"%s\".\n Consulta i log per maggiori dettagli</string>
<!-- Memory Sizes -->
<string name="memory_formatted">%1$s%2$s</string>

View file

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

Some files were not shown because too many files have changed in this diff Show more