mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-04-07 09:01:29 -06:00
Compare commits
No commits in common. "master" and "2124" have entirely different histories.
@ -1,133 +0,0 @@
|
|||||||
.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 #################################
|
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#!/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.*
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
|
if [[ "$TARGET" == "appimage"* ]]; then
|
||||||
# Compile the AppImage we distribute with Clang.
|
# Compile the AppImage we distribute with Clang.
|
||||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||||
-DCMAKE_C_COMPILER=clang
|
-DCMAKE_C_COMPILER=clang
|
||||||
|
|||||||
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
ARTIFACTS_LIST=($ARTIFACTS)
|
ARTIFACTS_LIST=($ARTIFACTS)
|
||||||
|
|
||||||
BUILD_DIR=build
|
BUNDLE_DIR=build/bundle
|
||||||
UNIVERSAL_DIR=$BUILD_DIR/universal
|
mkdir build
|
||||||
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
|
|
||||||
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
|
|
||||||
|
|
||||||
# Set up the base bundle to combine into.
|
# Set up the base artifact to combine into.
|
||||||
mkdir $UNIVERSAL_DIR
|
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
|
||||||
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
|
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
|
||||||
|
mv $BASE_ARTIFACT $BUNDLE_DIR
|
||||||
|
|
||||||
# Executable binary paths that need to be combined.
|
# Executable binary paths that need to be combined.
|
||||||
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
||||||
@ -20,18 +19,21 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
|
|||||||
unset IFS
|
unset IFS
|
||||||
|
|
||||||
# Combine all of the executable binaries and dylibs.
|
# Combine all of the executable binaries and dylibs.
|
||||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
|
||||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
|
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
|
||||||
done
|
|
||||||
|
|
||||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
|
||||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
done
|
||||||
|
|
||||||
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
|
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||||
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
|
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||||
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
|
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||||
fi
|
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
|
||||||
|
if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
|
||||||
|
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
|
||||||
|
fi
|
||||||
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# Remove leftover libs so that they aren't distributed
|
# Remove leftover libs so that they aren't distributed
|
||||||
|
|||||||
11
.ci/macos.sh
11
.ci/macos.sh
@ -4,10 +4,12 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
|||||||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
mkdir build && cd build
|
||||||
cmake ../.. -GNinja \
|
cmake .. -GNinja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
-DENABLE_QT_TRANSLATION=ON \
|
-DENABLE_QT_TRANSLATION=ON \
|
||||||
-DENABLE_ROOM_STANDALONE=OFF \
|
-DENABLE_ROOM_STANDALONE=OFF \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
-DUSE_DISCORD_PRESENCE=ON \
|
||||||
@ -16,8 +18,9 @@ ninja
|
|||||||
ninja bundle
|
ninja bundle
|
||||||
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
||||||
|
|
||||||
|
ccache -s -v
|
||||||
|
|
||||||
CURRENT_ARCH=`arch`
|
CURRENT_ARCH=`arch`
|
||||||
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
|
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
||||||
ctest -VV -C Release
|
ctest -VV -C Release
|
||||||
fi
|
fi
|
||||||
|
|||||||
31
.ci/pack.sh
31
.ci/pack.sh
@ -3,21 +3,20 @@
|
|||||||
# Determine the full revision name.
|
# Determine the full revision name.
|
||||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||||
GITREV="`git show -s --format='%h'`"
|
GITREV="`git show -s --format='%h'`"
|
||||||
|
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"
|
||||||
|
else
|
||||||
|
RELEASE_NAME=azahar-head
|
||||||
|
fi
|
||||||
|
|
||||||
# Archive and upload the artifacts.
|
# Archive and upload the artifacts.
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
|
|
||||||
function pack_artifacts() {
|
function pack_artifacts() {
|
||||||
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-$OS-$TARGET-$GITHUB_REF_NAME"
|
|
||||||
else
|
|
||||||
RELEASE_NAME=azahar-head
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARTIFACTS_PATH="$1"
|
ARTIFACTS_PATH="$1"
|
||||||
|
|
||||||
# Set up root directory for archive.
|
# Set up root directory for archive.
|
||||||
@ -57,23 +56,11 @@ if [ -n "$UNPACKED" ]; then
|
|||||||
FILENAME=$(basename "$ARTIFACT")
|
FILENAME=$(basename "$ARTIFACT")
|
||||||
EXTENSION="${FILENAME##*.}"
|
EXTENSION="${FILENAME##*.}"
|
||||||
|
|
||||||
# TODO: Deduplicate
|
|
||||||
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-$OS-$TARGET-$GITHUB_REF_NAME"
|
|
||||||
else
|
|
||||||
RELEASE_NAME=azahar-head
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
||||||
done
|
done
|
||||||
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
||||||
# Pack and upload the artifacts one-by-one.
|
# Pack and upload the artifacts one-by-one.
|
||||||
for ARTIFACT in build/bundle/*; do
|
for ARTIFACT in build/bundle/*; do
|
||||||
TARGET=$(basename "$ARTIFACT")
|
|
||||||
pack_artifacts "$ARTIFACT"
|
pack_artifacts "$ARTIFACT"
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
|
|||||||
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,15 +0,0 @@
|
|||||||
- [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms.
|
|
||||||
|
|
||||||
---
|
|
||||||
<!--
|
|
||||||
If you are contributing to Azahar for the first time please
|
|
||||||
keep the block of text between `---` and write your
|
|
||||||
PR description below it. Do not write anything inside
|
|
||||||
or change this block of text!
|
|
||||||
|
|
||||||
If you are a recurrent contributor, remove this entire
|
|
||||||
block of text and proceed as normal.
|
|
||||||
-->
|
|
||||||
|
|
||||||

|
|
||||||
---
|
|
||||||
BIN
.github/ignore_unless_human.png
vendored
BIN
.github/ignore_unless_human.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
113
.github/workflows/build.yml
vendored
113
.github/workflows/build.yml
vendored
@ -23,12 +23,12 @@ jobs:
|
|||||||
name: source
|
name: source
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
|
||||||
linux-x86_64:
|
linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target: ["appimage", "appimage-wayland", "gcc-nopch"]
|
target: ["appimage", "appimage-wayland", "fresh"]
|
||||||
container:
|
container:
|
||||||
image: opensauce04/azahar-build-environment:latest
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
@ -38,75 +38,47 @@ jobs:
|
|||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: linux
|
OS: linux
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ github.job }}-${{ matrix.target }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Build
|
- name: Build
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
|
||||||
run: ./.ci/linux.sh
|
run: ./.ci/linux.sh
|
||||||
- name: Move AppImage to artifacts directory
|
- name: Move AppImage to artifacts directory
|
||||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
if: ${{ contains(matrix.target, 'appimage') }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
mv build/bundle/*.AppImage artifacts/
|
mv build/bundle/*.AppImage artifacts/
|
||||||
- name: Rename AppImage
|
- name: Rename AppImage
|
||||||
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
if: ${{ matrix.target == 'appimage-wayland' }}
|
||||||
run: |
|
run: |
|
||||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||||
- name: Upload
|
- name: Upload
|
||||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
if: ${{ contains(matrix.target, 'appimage') }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ github.job }}-${{ matrix.target }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
|
||||||
linux-arm64:
|
macos:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target: ["clang", "gcc-nopch"]
|
target: ["x86_64", "arm64"]
|
||||||
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: 'macos-26'
|
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
CCACHE_COMPILERCHECK: content
|
CCACHE_COMPILERCHECK: content
|
||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: macos
|
OS: macos
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@ -115,31 +87,58 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: brew install ccache ninja spirv-tools
|
run: brew install ccache ninja spirv-tools
|
||||||
- name: Build (x86_64)
|
- name: Build
|
||||||
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
|
run: ./.ci/macos.sh
|
||||||
- name: Build (arm64)
|
- name: Prepare outputs for caching
|
||||||
run: BUILD_ARCH=arm64 ./.ci/macos.sh
|
run: cp -R build/bundle $OS-$TARGET
|
||||||
- name: Create universal app
|
- name: Cache outputs for universal build
|
||||||
run: ./.ci/macos-universal.sh
|
uses: actions/cache/save@v4
|
||||||
- name: Prepare for packing
|
with:
|
||||||
run: |
|
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
mkdir build/bundle
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
cp -r build/x86_64/bundle build/bundle/x86_64
|
|
||||||
cp -r build/arm64/bundle build/bundle/arm64
|
|
||||||
cp -r build/universal/bundle build/bundle/universal
|
|
||||||
- name: Pack
|
- name: Pack
|
||||||
env:
|
|
||||||
PACK_INDIVIDUALLY: 1
|
|
||||||
run: ./.ci/pack.sh
|
run: ./.ci/pack.sh
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
|
path: artifacts/
|
||||||
|
|
||||||
|
macos-universal:
|
||||||
|
runs-on: macos-26
|
||||||
|
needs: macos
|
||||||
|
env:
|
||||||
|
OS: macos
|
||||||
|
TARGET: universal
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Download x86_64 build from cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.OS }}-x86_64
|
||||||
|
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
- name: Download ARM64 build from cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.OS }}-arm64
|
||||||
|
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
- name: Create universal app
|
||||||
|
run: ./.ci/macos-universal.sh
|
||||||
|
env:
|
||||||
|
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
|
||||||
|
- name: Pack
|
||||||
|
run: ./.ci/pack.sh
|
||||||
|
- name: Upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
name: Detect first-time contributors
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
(github.repository == 'azahar-emu/azahar') &&
|
|
||||||
(github.event.pull_request.author_association != 'COLLABORATOR') &&
|
|
||||||
(github.event.pull_request.author_association != 'CONTRIBUTOR') &&
|
|
||||||
(github.event.pull_request.author_association != 'MANNEQUIN') &&
|
|
||||||
(github.event.pull_request.author_association != 'MEMBER') &&
|
|
||||||
(github.event.pull_request.author_association != 'OWNER')
|
|
||||||
steps:
|
|
||||||
- name: Detect PR if author is first-time contributor
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const pr = context.payload.pull_request;
|
|
||||||
|
|
||||||
// Add needs verification label so that the reopen action runs on comment.
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
labels: ['needs verification'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the pull request and wait for verification.
|
|
||||||
await github.rest.pulls.update({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr.number,
|
|
||||||
state: 'closed',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show the new contributor how to verify (they need to write a short poem about the Wii and 3DS being lovers)
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
body: 'Welcome to the Azahar Emulator repository! Due to the surge of AI bots we have decided to add an extra verification step to new contributors. Please follow the exact instructions in your own written Pull Request description to reopen it.',
|
|
||||||
});
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
name: Verify first-time contributors
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
|
|
||||||
steps:
|
|
||||||
- name: Verify and reopen PR
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const issue = context.payload.issue;
|
|
||||||
const comment = context.payload.comment;
|
|
||||||
const { data: pr } = await github.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: issue.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only allow verification of the comment user is the author
|
|
||||||
if (comment.user.login !== pr.user.login) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch user display and login names (lowercase)
|
|
||||||
const { data: user } = await github.rest.users.getByUsername({
|
|
||||||
username: pr.user.login,
|
|
||||||
});
|
|
||||||
const username = pr.user.login.toLowerCase();
|
|
||||||
const displayName = (user.name || '').toLowerCase();
|
|
||||||
|
|
||||||
// Make comment body lowercase and split words
|
|
||||||
const body = comment.body.toLowerCase().trim().replace(/[^a-z0-9_\-\s]/g, '').split(/\s+/);
|
|
||||||
|
|
||||||
// Check that the user verified themselves by writing a song about the NES and the SNES.
|
|
||||||
const verified =
|
|
||||||
(body.includes(username) ||
|
|
||||||
(displayName && body.includes(displayName))) &&
|
|
||||||
body.includes('azahar');
|
|
||||||
|
|
||||||
// Only reopen the PR and remove the label if verification succeeded
|
|
||||||
if (verified) {
|
|
||||||
await github.rest.pulls.update({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: issue.number,
|
|
||||||
state: 'open',
|
|
||||||
});
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
body: 'Verification successful! Pull request has been reopened. Please also edit your PR description to remove the block of text between `---` to make the description easier to read.',
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
name: 'needs verification',
|
|
||||||
});
|
|
||||||
} catch {}
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issue.number,
|
|
||||||
body: 'Verification failed! Pull request will remain closed.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
178
.github/workflows/libretro.yml
vendored
178
.github/workflows/libretro.yml
vendored
@ -1,178 +0,0 @@
|
|||||||
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
4
.gitignore
vendored
@ -56,7 +56,3 @@ repo/
|
|||||||
.ccache/
|
.ccache/
|
||||||
node_modules/
|
node_modules/
|
||||||
VULKAN_SDK/
|
VULKAN_SDK/
|
||||||
|
|
||||||
# Version info files
|
|
||||||
GIT-COMMIT
|
|
||||||
GIT-TAG
|
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -103,6 +103,3 @@
|
|||||||
[submodule "externals/xxHash"]
|
[submodule "externals/xxHash"]
|
||||||
path = externals/xxHash
|
path = externals/xxHash
|
||||||
url = https://github.com/Cyan4973/xxHash.git
|
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
|
|
||||||
|
|||||||
20
AI-POLICY.md
20
AI-POLICY.md
@ -1,20 +0,0 @@
|
|||||||
# Azahar Emulator AI Use Policy
|
|
||||||
|
|
||||||
The following document outlines the acceptable and unacceptable uses of AI within the Azahar codebase.
|
|
||||||
|
|
||||||
It describes whether or not submissions which were exposed to large language models (LLMs) such as ChatGPT, Claude, DeepSeek, and similar models would be capable of being merged in a pull request or otherwise utilized.
|
|
||||||
|
|
||||||
- ✅ Use of AI to help developers discover or understand problems in the codebase is acceptable **under the condition that any discovered issue is independently verified by a human**.
|
|
||||||
- ✅ Use of AI to write code snippets of a sufficiently small size that they aren't reasonably copyrightable **with disclosure in the PR description** is acceptable.
|
|
||||||
- This will be handled on a case-by-case basis and is up to the interpretation of the maintainer, but generic algorithm snippets up to a maximum of approximately 5 lines of code are acceptable.
|
|
||||||
- ❌ Use of AI to write code for submission without disclosure is prohibited.
|
|
||||||
- ❌ Use of AI to write the entirety/ a significant portion of a contribution is prohibited.
|
|
||||||
- ❌ Use of AI to write snippets of code which are of a size such that they could reasonably be copyrightable is prohibited.
|
|
||||||
- ❌ Use of AI to rewrite incompatibly-licensed code for submission to Azahar is prohibited.
|
|
||||||
- ❌ Use of AI to autonomously submit pull requests or issues is prohibited.
|
|
||||||
|
|
||||||
Pull requests which violate these rules will be closed. Previously accepted submissions which are found to violate these rules will be retroactively removed from the codebase.
|
|
||||||
|
|
||||||
This document may be updated in the future if further clarification is required.
|
|
||||||
|
|
||||||
This policy is effective for code submitted on or after the 20th of March 2026.
|
|
||||||
@ -17,23 +17,20 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
|||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||||
include(DownloadExternals)
|
include(DownloadExternals)
|
||||||
include(CMakeDependentOption)
|
include(CMakeDependentOption)
|
||||||
|
include(FindPkgConfig)
|
||||||
|
|
||||||
project(citra LANGUAGES C CXX ASM)
|
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")
|
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||||
enable_language(OBJC OBJCXX)
|
enable_language(OBJC OBJCXX)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
|
|
||||||
|
|
||||||
# Some submodules like to pick their own default build type if not specified.
|
# 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.
|
# 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)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE AND NOT ENABLE_LIBRETRO)
|
if (APPLE)
|
||||||
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
# 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_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||||
@ -93,18 +90,8 @@ else()
|
|||||||
set(DEFAULT_ENABLE_OPENGL ON)
|
set(DEFAULT_ENABLE_OPENGL ON)
|
||||||
endif()
|
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)
|
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)
|
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
||||||
|
|
||||||
# Set bundled qt as dependent options.
|
# Set bundled qt as dependent options.
|
||||||
@ -126,8 +113,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
|||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
||||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
|
||||||
|
|
||||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||||
|
|
||||||
@ -144,31 +130,6 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
|
|||||||
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||||
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" 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
|
# Pass the following values to C++ land
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
add_definitions(-DENABLE_QT)
|
add_definitions(-DENABLE_QT)
|
||||||
@ -182,6 +143,9 @@ endif()
|
|||||||
if (ENABLE_SDL2)
|
if (ENABLE_SDL2)
|
||||||
add_definitions(-DENABLE_SDL2)
|
add_definitions(-DENABLE_SDL2)
|
||||||
endif()
|
endif()
|
||||||
|
if (ENABLE_SDL2_FRONTEND)
|
||||||
|
add_definitions(-DENABLE_SDL2_FRONTEND)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
|
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
|
||||||
message(STATUS "SSE4.2 enabled for x86_64")
|
message(STATUS "SSE4.2 enabled for x86_64")
|
||||||
@ -259,7 +223,7 @@ function(check_submodules_present)
|
|||||||
foreach(module ${gitmodules})
|
foreach(module ${gitmodules})
|
||||||
string(REGEX REPLACE "path *= *" "" module ${module})
|
string(REGEX REPLACE "path *= *" "" module ${module})
|
||||||
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
||||||
message(SEND_ERROR "Git submodule ${module} not found.\n"
|
message(SEND_ERROR "Git submodule ${module} not found."
|
||||||
"Please run: git submodule update --init --recursive")
|
"Please run: git submodule update --init --recursive")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
@ -336,9 +300,6 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
|
|||||||
# set up output paths for executable binaries
|
# set up output paths for executable binaries
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
|
||||||
|
|
||||||
if (ENABLE_LIBRETRO)
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# System imported libraries
|
# System imported libraries
|
||||||
# ======================
|
# ======================
|
||||||
@ -349,7 +310,7 @@ find_package(Threads REQUIRED)
|
|||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (NOT USE_SYSTEM_QT)
|
if (NOT USE_SYSTEM_QT)
|
||||||
download_qt(6.9.3)
|
download_qt(6.9.2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||||
@ -398,7 +359,7 @@ if (APPLE)
|
|||||||
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
||||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
||||||
|
|
||||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
if (ENABLE_VULKAN)
|
||||||
if (NOT USE_SYSTEM_MOLTENVK)
|
if (NOT USE_SYSTEM_MOLTENVK)
|
||||||
download_moltenvk()
|
download_moltenvk()
|
||||||
endif()
|
endif()
|
||||||
@ -552,6 +513,8 @@ if (NOT ANDROID AND NOT IOS)
|
|||||||
include(BundleTarget)
|
include(BundleTarget)
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
qt_bundle_target(citra_meta)
|
qt_bundle_target(citra_meta)
|
||||||
|
elseif (ENABLE_SDL2_FRONTEND)
|
||||||
|
bundle_target(citra_meta)
|
||||||
endif()
|
endif()
|
||||||
if (ENABLE_ROOM_STANDALONE)
|
if (ENABLE_ROOM_STANDALONE)
|
||||||
bundle_target(citra_room_standalone)
|
bundle_target(citra_room_standalone)
|
||||||
|
|||||||
26
CMakeModules/BuildInstaller.cmake
Normal file
26
CMakeModules/BuildInstaller.cmake
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# To use this as a script, make sure you pass in the variables BASE_DIR, SRC_DIR, BUILD_DIR, and TARGET_FILE
|
||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(PLATFORM "windows")
|
||||||
|
elseif(APPLE)
|
||||||
|
set(PLATFORM "mac")
|
||||||
|
elseif(UNIX)
|
||||||
|
set(PLATFORM "linux")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Cannot build installer for this unsupported platform")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${BASE_DIR}/CMakeModules")
|
||||||
|
include(DownloadExternals)
|
||||||
|
download_qt(tools_ifw)
|
||||||
|
get_external_prefix(qt QT_PREFIX)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE INSTALLER_BASE "${QT_PREFIX}/**/installerbase*")
|
||||||
|
file(GLOB_RECURSE BINARY_CREATOR "${QT_PREFIX}/**/binarycreator*")
|
||||||
|
|
||||||
|
set(CONFIG_FILE "${SRC_DIR}/config/config_${PLATFORM}.xml")
|
||||||
|
set(PACKAGES_DIR "${BUILD_DIR}/packages")
|
||||||
|
file(MAKE_DIRECTORY ${PACKAGES_DIR})
|
||||||
|
|
||||||
|
execute_process(COMMAND ${BINARY_CREATOR} -t ${INSTALLER_BASE} -n -c ${CONFIG_FILE} -p ${PACKAGES_DIR} ${TARGET_FILE})
|
||||||
@ -1,11 +0,0 @@
|
|||||||
function(disable_pax_mprotect target)
|
|
||||||
if (BSD STREQUAL "NetBSD")
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD
|
|
||||||
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
|
|
||||||
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
@ -1,52 +1,49 @@
|
|||||||
macro(generate_build_info)
|
# Gets a UTC timstamp and sets the provided variable to it
|
||||||
find_package(Git QUIET)
|
function(get_timestamp _var)
|
||||||
|
string(TIMESTAMP timestamp UTC)
|
||||||
|
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
get_timestamp(BUILD_DATE)
|
||||||
|
|
||||||
# Gets a UTC timstamp and sets the provided variable to it
|
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||||
function(get_timestamp _var)
|
|
||||||
string(TIMESTAMP timestamp UTC)
|
|
||||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
|
||||||
endfunction()
|
|
||||||
get_timestamp(BUILD_DATE)
|
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
|
if (EXISTS "${SRC_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}")
|
||||||
|
|
||||||
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
# only use Git to check revision info when source is obtained via Git
|
||||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
include(GetGitRevisionDescription)
|
||||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
git_describe(GIT_DESC --always --long --dirty)
|
||||||
set(GIT_BRANCH "HEAD")
|
git_branch_name(GIT_BRANCH)
|
||||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
|
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||||
# Find the package here with the known path so that the GetGit commands can find it as well
|
# unified source archive
|
||||||
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
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")
|
||||||
|
set(GIT_DESC "UNKNOWN")
|
||||||
|
set(GIT_BRANCH "UNKNOWN")
|
||||||
|
endif()
|
||||||
|
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
|
||||||
|
|
||||||
# only use Git to check revision info when source is obtained via Git
|
# Set build version
|
||||||
include(GetGitRevisionDescription)
|
set(REPO_NAME "")
|
||||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
set(BUILD_VERSION "0")
|
||||||
git_describe(GIT_DESC --always --long --dirty)
|
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
|
||||||
git_branch_name(GIT_BRANCH)
|
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
|
||||||
else()
|
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||||
# self-packed archive?
|
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||||
set(GIT_REV "UNKNOWN")
|
|
||||||
set(GIT_DESC "UNKNOWN")
|
|
||||||
set(GIT_BRANCH "UNKNOWN")
|
|
||||||
endif()
|
endif()
|
||||||
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
|
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||||
|
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
|
||||||
|
string(STRIP ${GIT_TAG} GIT_TAG)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Set build version
|
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
|
||||||
set(REPO_NAME "")
|
set(BUILD_VERSION "${GIT_TAG}")
|
||||||
set(BUILD_VERSION "0")
|
set(BUILD_FULLNAME "${BUILD_VERSION}")
|
||||||
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
|
endif()
|
||||||
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
|
|
||||||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
|
||||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
|
||||||
endif()
|
|
||||||
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()
|
|
||||||
|
|
||||||
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
|
|
||||||
set(BUILD_VERSION "${GIT_TAG}")
|
|
||||||
set(BUILD_FULLNAME "${BUILD_VERSION}")
|
|
||||||
endif()
|
|
||||||
endmacro()
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
|
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
|
||||||
|
|
||||||
include(GenerateBuildInfo)
|
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)
|
# 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 "${CMAKE_SOURCE_DIR}/src/video_core")
|
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||||
set(HASH_FILES
|
set(HASH_FILES
|
||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||||
@ -12,10 +10,6 @@ set(HASH_FILES
|
|||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
"${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.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
||||||
@ -24,7 +18,6 @@ set(HASH_FILES
|
|||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
|
"${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.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
||||||
@ -48,4 +41,4 @@ foreach (F IN LISTS HASH_FILES)
|
|||||||
set(COMBINED "${COMBINED}${TMP}")
|
set(COMBINED "${COMBINED}${TMP}")
|
||||||
endforeach()
|
endforeach()
|
||||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||||
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||||
|
|||||||
@ -1,278 +0,0 @@
|
|||||||
## 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"
|
|
||||||
"enable_touch_pointer_timeout"
|
|
||||||
"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()
|
|
||||||
@ -1,8 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||

|

|
||||||

|
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
1
dist/apple/Info.plist.in
vendored
1
dist/apple/Info.plist.in
vendored
@ -35,7 +35,6 @@
|
|||||||
<string>cci</string>
|
<string>cci</string>
|
||||||
<string>cxi</string>
|
<string>cxi</string>
|
||||||
<string>cia</string>
|
<string>cia</string>
|
||||||
<string>3ds</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Nintendo 3DS File</string>
|
<string>Nintendo 3DS File</string>
|
||||||
|
|||||||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
@ -1 +1 @@
|
|||||||
Subproject commit d9f1126e42b606d02ecc89b10cb9a336a3b2f5a3
|
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
|
||||||
1147
dist/languages/ca_ES_valencia.ts
vendored
1147
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load Diff
1018
dist/languages/da_DK.ts
vendored
1018
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load Diff
1024
dist/languages/de.ts
vendored
1024
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
2125
dist/languages/el.ts
vendored
2125
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
1049
dist/languages/es_ES.ts
vendored
1049
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load Diff
1016
dist/languages/fi.ts
vendored
1016
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
1148
dist/languages/fr.ts
vendored
1148
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
1018
dist/languages/hu_HU.ts
vendored
1018
dist/languages/hu_HU.ts
vendored
File diff suppressed because it is too large
Load Diff
1018
dist/languages/id.ts
vendored
1018
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
1099
dist/languages/it.ts
vendored
1099
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
1022
dist/languages/ja_JP.ts
vendored
1022
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
1020
dist/languages/ko_KR.ts
vendored
1020
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
1016
dist/languages/lt_LT.ts
vendored
1016
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load Diff
1020
dist/languages/nb.ts
vendored
1020
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
1020
dist/languages/nl.ts
vendored
1020
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
1035
dist/languages/pl_PL.ts
vendored
1035
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load Diff
1041
dist/languages/pt_BR.ts
vendored
1041
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
1020
dist/languages/ro_RO.ts
vendored
1020
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load Diff
1024
dist/languages/ru_RU.ts
vendored
1024
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
1035
dist/languages/sv.ts
vendored
1035
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load Diff
1024
dist/languages/tr_TR.ts
vendored
1024
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
1018
dist/languages/vi_VN.ts
vendored
1018
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
1030
dist/languages/zh_CN.ts
vendored
1030
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
1018
dist/languages/zh_TW.ts
vendored
1018
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
1
dist/org.azahar_emu.Azahar.xml
vendored
1
dist/org.azahar_emu.Azahar.xml
vendored
@ -16,7 +16,6 @@
|
|||||||
<expanded-acronym>CTR Cart Image</expanded-acronym>
|
<expanded-acronym>CTR Cart Image</expanded-acronym>
|
||||||
<icon name="azahar"/>
|
<icon name="azahar"/>
|
||||||
<glob pattern="*.cci"/>
|
<glob pattern="*.cci"/>
|
||||||
<glob pattern="*.3ds"/>
|
|
||||||
<magic><match value="NCSD" type="string" offset="256"/></magic>
|
<magic><match value="NCSD" type="string" offset="256"/></magic>
|
||||||
</mime-type>
|
</mime-type>
|
||||||
|
|
||||||
|
|||||||
45
externals/CMakeLists.txt
vendored
45
externals/CMakeLists.txt
vendored
@ -50,18 +50,15 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
if (ENABLE_TESTS)
|
add_library(catch2 INTERFACE)
|
||||||
add_library(catch2 INTERFACE)
|
if(USE_SYSTEM_CATCH2)
|
||||||
if(USE_SYSTEM_CATCH2)
|
find_package(Catch2 3.0.0 REQUIRED)
|
||||||
find_package(Catch2 3.0.0 REQUIRED)
|
else()
|
||||||
else()
|
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
add_subdirectory(catch2)
|
||||||
add_subdirectory(catch2)
|
|
||||||
endif()
|
|
||||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
|
||||||
include(Catch)
|
|
||||||
endif()
|
endif()
|
||||||
|
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||||
|
|
||||||
# Crypto++
|
# Crypto++
|
||||||
if(USE_SYSTEM_CRYPTOPP)
|
if(USE_SYSTEM_CRYPTOPP)
|
||||||
@ -112,13 +109,7 @@ endif()
|
|||||||
|
|
||||||
# Oaknut
|
# Oaknut
|
||||||
if ("arm64" IN_LIST ARCHITECTURE)
|
if ("arm64" IN_LIST ARCHITECTURE)
|
||||||
if(USE_SYSTEM_OAKNUT)
|
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||||
find_package(oaknut REQUIRED)
|
|
||||||
add_library(oaknut INTERFACE)
|
|
||||||
target_link_libraries(oaknut INTERFACE merry::oaknut)
|
|
||||||
else()
|
|
||||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Dynarmic
|
# Dynarmic
|
||||||
@ -301,15 +292,6 @@ if (USE_DISCORD_PRESENCE)
|
|||||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||||
endif()
|
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
|
# JSON
|
||||||
add_library(json-headers INTERFACE)
|
add_library(json-headers INTERFACE)
|
||||||
if (USE_SYSTEM_JSON)
|
if (USE_SYSTEM_JSON)
|
||||||
@ -500,15 +482,6 @@ if (ENABLE_VULKAN)
|
|||||||
else()
|
else()
|
||||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||||
target_disable_warnings(vulkan-headers)
|
target_disable_warnings(vulkan-headers)
|
||||||
if (BSD STREQUAL "NetBSD")
|
|
||||||
# There may be a better way to do this with
|
|
||||||
# find_package(X11), but I couldn't get
|
|
||||||
# CMake to do it, so we're depending on
|
|
||||||
# the x11-links package and assuming the
|
|
||||||
# prefix location. -OS
|
|
||||||
target_include_directories(vulkan-headers INTERFACE
|
|
||||||
/usr/pkg/share/x11-links/include)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# adrenotools
|
# adrenotools
|
||||||
|
|||||||
2
externals/boost
vendored
2
externals/boost
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61
|
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||||
@ -14,7 +14,6 @@ option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of
|
|||||||
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
|
|
||||||
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
||||||
@ -41,7 +40,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
|
|||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
|
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
@ -68,7 +66,6 @@ set(LIB_VAR_LIST
|
|||||||
DYNARMIC
|
DYNARMIC
|
||||||
FMT
|
FMT
|
||||||
XBYAK
|
XBYAK
|
||||||
OAKNUT
|
|
||||||
INIH
|
INIH
|
||||||
FFMPEG_HEADERS
|
FFMPEG_HEADERS
|
||||||
GLSLANG
|
GLSLANG
|
||||||
|
|||||||
16
externals/libretro-common/CMakeLists.txt
vendored
16
externals/libretro-common/CMakeLists.txt
vendored
@ -1,16 +0,0 @@
|
|||||||
add_library(libretro_common STATIC
|
|
||||||
libretro-common/compat/compat_posix_string.c
|
|
||||||
libretro-common/compat/fopen_utf8.c
|
|
||||||
libretro-common/encodings/encoding_utf.c
|
|
||||||
libretro-common/compat/compat_strl.c
|
|
||||||
libretro-common/file/file_path.c
|
|
||||||
libretro-common/streams/file_stream.c
|
|
||||||
libretro-common/streams/file_stream_transforms.c
|
|
||||||
libretro-common/string/stdstring.c
|
|
||||||
libretro-common/time/rtime.c
|
|
||||||
libretro-common/vfs/vfs_implementation.c
|
|
||||||
)
|
|
||||||
target_include_directories(libretro_common PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include
|
|
||||||
)
|
|
||||||
1
externals/libretro-common/libretro-common
vendored
1
externals/libretro-common/libretro-common
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d
|
|
||||||
2
externals/sdl2/SDL
vendored
2
externals/sdl2/SDL
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71
|
Subproject commit 2359383fc187386204c3bb22de89655a494cd128
|
||||||
@ -1,8 +1,6 @@
|
|||||||
# Enable modules to include each other's files
|
# Enable modules to include each other's files
|
||||||
include_directories(.)
|
include_directories(.)
|
||||||
|
|
||||||
include(GenerateSettingKeys)
|
|
||||||
|
|
||||||
# CMake seems to only define _DEBUG on Windows
|
# CMake seems to only define _DEBUG on Windows
|
||||||
set_property(DIRECTORY APPEND PROPERTY
|
set_property(DIRECTORY APPEND PROPERTY
|
||||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||||
@ -112,14 +110,10 @@ else()
|
|||||||
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
||||||
-Wno-unused-command-line-argument
|
-Wno-unused-command-line-argument
|
||||||
# Build fortification options
|
# Build fortification options
|
||||||
|
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||||
-fstack-protector-strong
|
-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
|
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
|
||||||
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
|
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
|
||||||
@ -195,18 +189,18 @@ if (ENABLE_TESTS)
|
|||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_SDL2_FRONTEND)
|
||||||
|
add_subdirectory(citra_sdl)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
add_subdirectory(citra_qt)
|
add_subdirectory(citra_qt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_QT) # Or any other hypothetical future frontends
|
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
|
||||||
add_subdirectory(citra_meta)
|
add_subdirectory(citra_meta)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_LIBRETRO)
|
|
||||||
add_subdirectory(citra_libretro)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (ENABLE_ROOM)
|
if (ENABLE_ROOM)
|
||||||
add_subdirectory(citra_room)
|
add_subdirectory(citra_room)
|
||||||
endif()
|
endif()
|
||||||
@ -215,7 +209,7 @@ if (ENABLE_ROOM_STANDALONE)
|
|||||||
add_subdirectory(citra_room_standalone)
|
add_subdirectory(citra_room_standalone)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
if (ANDROID)
|
||||||
add_subdirectory(android/app/src/main/jni)
|
add_subdirectory(android/app/src/main/jni)
|
||||||
target_include_directories(citra-android PRIVATE android/app/src/main)
|
target_include_directories(citra-android PRIVATE android/app/src/main)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@ -63,7 +63,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
// The application ID refers to Lime3DS to allow for
|
// The application ID refers to Lime3DS to allow for
|
||||||
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
|
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
|
||||||
applicationId = "org.azahar_emu.azahar"
|
applicationId = "io.github.lime3ds.android"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = autoVersion
|
versionCode = autoVersion
|
||||||
@ -173,7 +173,6 @@ android {
|
|||||||
register("googlePlay") {
|
register("googlePlay") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
versionNameSuffix = "-googleplay"
|
versionNameSuffix = "-googleplay"
|
||||||
applicationId = "io.github.lime3ds.android"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
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.BuildUtil
|
||||||
import org.citra.citra_emu.utils.FileUtil
|
import org.citra.citra_emu.utils.FileUtil
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
@ -133,27 +132,7 @@ object NativeLibrary {
|
|||||||
* If not set, it auto-detects a location
|
* If not set, it auto-detects a location
|
||||||
*/
|
*/
|
||||||
external fun setUserDirectory(directory: String)
|
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.
|
// Create the config.ini file.
|
||||||
external fun createConfigFile()
|
external fun createConfigFile()
|
||||||
@ -251,13 +230,6 @@ object NativeLibrary {
|
|||||||
external fun playTimeManagerGetPlayTime(titleId: Long): Long
|
external fun playTimeManagerGetPlayTime(titleId: Long): Long
|
||||||
external fun playTimeManagerGetCurrentTitleId(): 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 var coreErrorAlertResult = false
|
||||||
private val coreErrorAlertLock = Object()
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
@ -504,9 +476,8 @@ object NativeLibrary {
|
|||||||
const val ErrorSystemFiles = 8
|
const val ErrorSystemFiles = 8
|
||||||
const val ErrorSavestate = 9
|
const val ErrorSavestate = 9
|
||||||
const val ErrorArticDisconnected = 10
|
const val ErrorArticDisconnected = 10
|
||||||
const val ErrorN3DSApplication = 11
|
const val ShutdownRequested = 11
|
||||||
const val ShutdownRequested = 12
|
const val ErrorUnknown = 12
|
||||||
const val ErrorUnknown = 13
|
|
||||||
|
|
||||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
@ -720,47 +691,34 @@ object NativeLibrary {
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getNativePath(uri: Uri): String {
|
fun getUserDirectory(uriOverride: Uri? = null): String {
|
||||||
BuildUtil.assertNotGooglePlay()
|
BuildUtil.assertNotGooglePlay()
|
||||||
|
|
||||||
|
val preferences: SharedPreferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
|
||||||
val dirSep = "/"
|
val dirSep = "/"
|
||||||
|
val udUri = uriOverride ?:
|
||||||
|
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||||
|
val udPathSegment = udUri.lastPathSegment!!
|
||||||
|
val udVirtualPath = udPathSegment.substringAfter(":")
|
||||||
|
|
||||||
val uriString = uri.toString()
|
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
|
||||||
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
|
|
||||||
return uriString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.scheme == "file") {
|
|
||||||
return uri.path!!
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
|
||||||
return primaryStoragePath + dirSep + virtualPath
|
return primaryStoragePath + dirSep + udVirtualPath + dirSep
|
||||||
} else { // User directory probably located on a removable storage device
|
} else { // User directory probably located on a removable storage device
|
||||||
val storageIdString = pathSegment.substringBefore(":")
|
val storageIdString = udPathSegment.substringBefore(":")
|
||||||
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
|
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
|
||||||
|
|
||||||
if (removablePath == null) {
|
if (udRemovablePath == null) {
|
||||||
android.util.Log.e("NativeLibrary",
|
android.util.Log.e("NativeLibrary",
|
||||||
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return removablePath + dirSep + virtualPath
|
return udRemovablePath + dirSep + udVirtualPath + dirSep
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
@JvmStatic
|
|
||||||
fun getUserDirectory(): String {
|
|
||||||
val preferences: SharedPreferences =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
|
||||||
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
|
||||||
return getNativePath(userDirectoryUri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import android.content.Intent
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
@ -20,7 +21,6 @@ import android.widget.Toast
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.os.BundleCompat
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
@ -43,7 +43,6 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
|||||||
import org.citra.citra_emu.fragments.EmulationFragment
|
import org.citra.citra_emu.fragments.EmulationFragment
|
||||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.utils.BuildUtil
|
|
||||||
import org.citra.citra_emu.utils.ControllerMappingHelper
|
import org.citra.citra_emu.utils.ControllerMappingHelper
|
||||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||||
@ -268,34 +267,41 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (event.action) {
|
val button =
|
||||||
|
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||||
|
val action: Int = when (event.action) {
|
||||||
KeyEvent.ACTION_DOWN -> {
|
KeyEvent.ACTION_DOWN -> {
|
||||||
|
hotkeyUtility.handleHotkey(button)
|
||||||
|
|
||||||
// On some devices, the back gesture / button press is not intercepted by androidx
|
// 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
|
// 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)
|
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||||
|
|
||||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
// If the hotkey is pressed, we don't want to open the drawer
|
// If the hotkey is pressed, we don't want to open the drawer
|
||||||
if (!hotkeyUtility.hotkeyIsPressed) {
|
if (!hotkeyUtility.HotkeyIsPressed) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hotkeyUtility.handleKeyPress(event)
|
|
||||||
|
// Normal key events.
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.ACTION_UP -> {
|
KeyEvent.ACTION_UP -> {
|
||||||
return hotkeyUtility.handleKeyRelease(event)
|
hotkeyUtility.HotkeyIsPressed = false
|
||||||
}
|
NativeLibrary.ButtonState.RELEASED
|
||||||
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) {
|
private fun onAmiiboSelected(selectedFile: String) {
|
||||||
val success = NativeLibrary.loadAmiibo(selectedFile)
|
val success = NativeLibrary.loadAmiibo(selectedFile)
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
|
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
R.string.amiibo_load_error,
|
R.string.amiibo_load_error,
|
||||||
R.string.amiibo_load_error_message
|
R.string.amiibo_load_error_message
|
||||||
@ -518,19 +524,13 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val openAmiiboFileLauncher =
|
val openFileLauncher =
|
||||||
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
||||||
if (result == null) return@registerForActivityResult
|
if (result == null) return@registerForActivityResult
|
||||||
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
||||||
result, applicationContext, listOf<String>("bin")
|
result, applicationContext, listOf<String>("bin")
|
||||||
) ?: return@registerForActivityResult
|
) ?: return@registerForActivityResult
|
||||||
if (BuildUtil.isGooglePlayBuild) {
|
onAmiiboSelected(selectedFiles[0])
|
||||||
onAmiiboSelected(selectedFiles[0])
|
|
||||||
} else {
|
|
||||||
val fileUri = selectedFiles[0].toUri()
|
|
||||||
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
|
|
||||||
onAmiiboSelected(nativePath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val openImageLauncher =
|
val openImageLauncher =
|
||||||
|
|||||||
@ -54,10 +54,8 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
|
|||||||
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
|
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
|
||||||
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
|
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
|
||||||
import org.citra.citra_emu.model.Game
|
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.FileUtil
|
||||||
import org.citra.citra_emu.utils.GameIconUtils
|
import org.citra.citra_emu.utils.GameIconUtils
|
||||||
import org.citra.citra_emu.utils.Log
|
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
|
|
||||||
class GameAdapter(
|
class GameAdapter(
|
||||||
@ -138,7 +136,7 @@ class GameAdapter(
|
|||||||
val holder = view.tag as GameViewHolder
|
val holder = view.tag as GameViewHolder
|
||||||
gameExists(holder)
|
gameExists(holder)
|
||||||
|
|
||||||
if (!holder.game.valid) {
|
if (holder.game.titleId == 0L) {
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(R.string.properties)
|
.setTitle(R.string.properties)
|
||||||
.setMessage(R.string.properties_not_loaded)
|
.setMessage(R.string.properties_not_loaded)
|
||||||
@ -155,21 +153,12 @@ class GameAdapter(
|
|||||||
if (holder.game.isInstalled) {
|
if (holder.game.isInstalled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
val path = holder.game.path
|
|
||||||
val pathUri = path.toUri()
|
val gameExists = DocumentFile.fromSingleUri(
|
||||||
var gameExists: Boolean
|
CitraApplication.appContext,
|
||||||
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
|
Uri.parse(holder.game.path)
|
||||||
gameExists =
|
)?.exists() == true
|
||||||
DocumentFile.fromSingleUri(
|
|
||||||
CitraApplication.appContext,
|
|
||||||
pathUri
|
|
||||||
)?.exists() == true
|
|
||||||
} else {
|
|
||||||
val nativePath = NativeLibrary.getNativePath(pathUri)
|
|
||||||
gameExists = NativeLibrary.nativeFileExists(nativePath)
|
|
||||||
}
|
|
||||||
return if (!gameExists) {
|
return if (!gameExists) {
|
||||||
Log.error("[GameAdapter] ROM file does not exist: $path")
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
CitraApplication.appContext,
|
CitraApplication.appContext,
|
||||||
R.string.loader_error_file_not_found,
|
R.string.loader_error_file_not_found,
|
||||||
@ -334,16 +323,14 @@ class GameAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val titleId = game.titleId
|
|
||||||
val dlcTitleId = titleId or 0x8C00000000L
|
|
||||||
val updateTitleId = titleId or 0xE00000000L
|
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
val uninstallAction: () -> Unit = {
|
val uninstallAction: () -> Unit = {
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
|
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
|
||||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
|
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
|
||||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
|
.toString())
|
||||||
|
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
|
||||||
|
.toString())
|
||||||
}
|
}
|
||||||
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
||||||
bottomSheetDialog.dismiss()
|
bottomSheetDialog.dismiss()
|
||||||
@ -569,9 +556,7 @@ class GameAdapter(
|
|||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
// The title is taken into account to support 3DSX, which all have the titleID 0.
|
return oldItem.titleId == newItem.titleId
|
||||||
// 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 {
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -57,7 +57,6 @@ object StillImageCameraHelper {
|
|||||||
val request = ImageRequest.Builder(context)
|
val request = ImageRequest.Builder(context)
|
||||||
.data(uri)
|
.data(uri)
|
||||||
.size(width, height)
|
.size(width, height)
|
||||||
.allowHardware(false)
|
|
||||||
.build()
|
.build()
|
||||||
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
|
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
|
||||||
width,
|
width,
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import org.citra.citra_emu.NativeLibrary
|
|||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
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.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.model.Settings
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||||
@ -32,16 +31,8 @@ class ScreenAdjustmentUtil(
|
|||||||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
||||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cycleLayouts() {
|
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)
|
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||||
|
|
||||||
if (NativeLibrary.isPortraitMode) {
|
if (NativeLibrary.isPortraitMode) {
|
||||||
|
|||||||
@ -6,15 +6,18 @@ package org.citra.citra_emu.display
|
|||||||
|
|
||||||
import android.app.Presentation
|
import android.app.Presentation
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.SurfaceTexture
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.Surface
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
|
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
|
|
||||||
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||||
@ -47,7 +50,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||||||
val currentDisplayId = context.display.displayId
|
val currentDisplayId = context.display.displayId
|
||||||
val displays = dm.displays
|
val displays = dm.displays
|
||||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
||||||
val extDisplays = displays.filter {
|
return displays.firstOrNull {
|
||||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
||||||
isNotDefaultOrPresentable &&
|
isNotDefaultOrPresentable &&
|
||||||
@ -56,18 +59,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||||||
it.state != Display.STATE_OFF &&
|
it.state != Display.STATE_OFF &&
|
||||||
it.isValid
|
it.isValid
|
||||||
}
|
}
|
||||||
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
|
|
||||||
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
|
|
||||||
?: extDisplays.firstOrNull()
|
|
||||||
return selected
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDisplay() {
|
fun updateDisplay() {
|
||||||
// return early if the parent context is dead or dying
|
|
||||||
if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// decide if we are going to the external display or the internal one
|
// decide if we are going to the external display or the internal one
|
||||||
var display = getExternalDisplay(context)
|
var display = getExternalDisplay(context)
|
||||||
if (display == null ||
|
if (display == null ||
|
||||||
@ -80,25 +74,12 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||||||
|
|
||||||
// otherwise, make a new presentation
|
// otherwise, make a new presentation
|
||||||
releasePresentation()
|
releasePresentation()
|
||||||
|
pres = SecondaryDisplayPresentation(context, display!!, this)
|
||||||
try {
|
pres?.show()
|
||||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
|
||||||
pres?.show()
|
|
||||||
}
|
|
||||||
// catch BadTokenException and InvalidDisplayException,
|
|
||||||
// the display became invalid asynchronously, so we can assign to null
|
|
||||||
// until onDisplayAdded/Removed/Changed is called and logic retriggered
|
|
||||||
catch (_: WindowManager.BadTokenException) {
|
|
||||||
pres = null
|
|
||||||
} catch (_: WindowManager.InvalidDisplayException) {
|
|
||||||
pres = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releasePresentation() {
|
fun releasePresentation() {
|
||||||
try {
|
pres?.dismiss()
|
||||||
pres?.dismiss()
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
pres = null
|
pres = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,5 @@ enum class Hotkey(val button: Int) {
|
|||||||
PAUSE_OR_RESUME(10004),
|
PAUSE_OR_RESUME(10004),
|
||||||
QUICKSAVE(10005),
|
QUICKSAVE(10005),
|
||||||
QUICKLOAD(10006),
|
QUICKLOAD(10006),
|
||||||
TURBO_LIMIT(10007),
|
TURBO_LIMIT(10007);
|
||||||
ENABLE(10008);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,140 +5,50 @@
|
|||||||
package org.citra.citra_emu.features.hotkeys
|
package org.citra.citra_emu.features.hotkeys
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.widget.Toast
|
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.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
import org.citra.citra_emu.utils.TurboHelper
|
||||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
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(
|
class HotkeyUtility(
|
||||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||||
private val context: Context
|
private val context: Context) {
|
||||||
) {
|
|
||||||
|
|
||||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||||
private var hotkeyIsEnabled = false
|
var HotkeyIsPressed = 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 {
|
fun handleHotkey(bindedButton: Int): Boolean {
|
||||||
when (bindedButton) {
|
if(hotkeyButtons.contains(bindedButton)) {
|
||||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
when (bindedButton) {
|
||||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||||
Hotkey.QUICKSAVE.button -> {
|
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
Hotkey.QUICKSAVE.button -> {
|
||||||
Toast.makeText(
|
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||||
context,
|
Toast.makeText(context,
|
||||||
context.getString(R.string.saving),
|
context.getString(R.string.saving),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT).show()
|
||||||
).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(
|
Hotkey.QUICKLOAD.button -> {
|
||||||
context,
|
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||||
context.getString(stringRes),
|
val stringRes = if(wasLoaded) {
|
||||||
Toast.LENGTH_SHORT
|
R.string.loading
|
||||||
).show()
|
} else {
|
||||||
|
R.string.quickload_not_found
|
||||||
|
}
|
||||||
|
Toast.makeText(context,
|
||||||
|
context.getString(stringRes),
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
HotkeyIsPressed = true
|
||||||
else -> {}
|
return true
|
||||||
}
|
}
|
||||||
hotkeyIsPressed = true
|
return false
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,141 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
// 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>
|
|
||||||
}
|
|
||||||
@ -4,59 +4,56 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
|
||||||
|
|
||||||
enum class BooleanSetting(
|
enum class BooleanSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Boolean
|
override val defaultValue: Boolean
|
||||||
) : AbstractBooleanSetting {
|
) : AbstractBooleanSetting {
|
||||||
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
EXPAND_TO_CUTOUT_AREA("expand_to_cutout_area", Settings.SECTION_LAYOUT, false),
|
||||||
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
|
||||||
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
|
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
|
||||||
DISABLE_SPIRV_OPTIMIZER(SettingKeys.disable_spirv_optimizer(), Settings.SECTION_RENDERER, true),
|
DISABLE_SPIRV_OPTIMIZER("disable_spirv_optimizer", Settings.SECTION_RENDERER, true),
|
||||||
PLUGIN_LOADER(SettingKeys.plugin_loader(), Settings.SECTION_SYSTEM, false),
|
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
|
||||||
ALLOW_PLUGIN_LOADER(SettingKeys.allow_plugin_loader(), Settings.SECTION_SYSTEM, true),
|
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
|
||||||
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
|
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
|
||||||
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
|
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
|
||||||
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
|
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
|
||||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
|
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
|
||||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
|
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
|
||||||
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
|
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
|
||||||
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
|
PERF_OVERLAY_SHOW_FPS("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_FRAMETIME("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_SPEED("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_APP_RAM_USAGE("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_AVAILABLE_RAM("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_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
|
||||||
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
|
PERF_OVERLAY_BACKGROUND("performance_overlay_background", Settings.SECTION_LAYOUT, false),
|
||||||
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
|
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
|
||||||
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
|
DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
|
||||||
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
|
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
|
||||||
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
|
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false),
|
||||||
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
|
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true),
|
||||||
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
|
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true),
|
||||||
SHADERS_ACCURATE_MUL(SettingKeys.shaders_accurate_mul(), Settings.SECTION_RENDERER, false),
|
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false),
|
||||||
DISK_SHADER_CACHE(SettingKeys.use_disk_shader_cache(), Settings.SECTION_RENDERER, true),
|
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true),
|
||||||
DUMP_TEXTURES(SettingKeys.dump_textures(), Settings.SECTION_UTILITY, false),
|
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false),
|
||||||
CUSTOM_TEXTURES(SettingKeys.custom_textures(), Settings.SECTION_UTILITY, false),
|
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, false),
|
||||||
ASYNC_CUSTOM_LOADING(SettingKeys.async_custom_loading(), Settings.SECTION_UTILITY, true),
|
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, true),
|
||||||
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
|
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, false),
|
||||||
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
|
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, true),
|
||||||
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
|
ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, false),
|
||||||
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
|
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, true),
|
||||||
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, true),
|
||||||
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
SHADER_JIT("use_shader_jit", Settings.SECTION_RENDERER, true),
|
||||||
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
|
VSYNC("use_vsync", Settings.SECTION_RENDERER, false),
|
||||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
|
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, true),
|
||||||
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, false),
|
||||||
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
|
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
|
||||||
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
|
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
|
||||||
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
|
UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false),
|
||||||
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
|
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false),
|
||||||
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
|
ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false),
|
||||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
APPLY_REGION_FREE_PATCH("apply_region_free_patch", Settings.SECTION_SYSTEM, true);
|
||||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false);
|
|
||||||
|
|
||||||
override var boolean: Boolean = defaultValue
|
override var boolean: Boolean = defaultValue
|
||||||
|
|
||||||
@ -83,7 +80,6 @@ enum class BooleanSetting(
|
|||||||
REQUIRED_ONLINE_LLE_MODULES,
|
REQUIRED_ONLINE_LLE_MODULES,
|
||||||
NEW_3DS,
|
NEW_3DS,
|
||||||
LLE_APPLETS,
|
LLE_APPLETS,
|
||||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
|
|
||||||
VSYNC,
|
VSYNC,
|
||||||
DEBUG_RENDERER,
|
DEBUG_RENDERER,
|
||||||
CPU_JIT,
|
CPU_JIT,
|
||||||
|
|||||||
@ -4,18 +4,17 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
|
||||||
|
|
||||||
enum class FloatSetting(
|
enum class FloatSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Float
|
override val defaultValue: Float
|
||||||
) : AbstractFloatSetting {
|
) : AbstractFloatSetting {
|
||||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
|
||||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f),
|
||||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f),
|
||||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f),
|
||||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f),
|
||||||
|
EMPTY_SETTING("", "", 0.0f);
|
||||||
|
|
||||||
override var float: Float = defaultValue
|
override var float: Float = defaultValue
|
||||||
|
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
|
||||||
|
|
||||||
enum class IntListSetting(
|
|
||||||
override val key: String,
|
|
||||||
override val section: String,
|
|
||||||
override val defaultValue: List<Int>,
|
|
||||||
val canBeEmpty: Boolean = true
|
|
||||||
) : AbstractListSetting<Int> {
|
|
||||||
|
|
||||||
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
|
||||||
|
|
||||||
private var backingList: List<Int> = defaultValue
|
|
||||||
private var lastValidList : List<Int> = defaultValue
|
|
||||||
|
|
||||||
override var list: List<Int>
|
|
||||||
get() = backingList
|
|
||||||
set(value) {
|
|
||||||
if (!canBeEmpty && value.isEmpty()) {
|
|
||||||
backingList = lastValidList
|
|
||||||
} else {
|
|
||||||
backingList = value
|
|
||||||
lastValidList = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val valueAsString: String
|
|
||||||
get() = list.joinToString()
|
|
||||||
|
|
||||||
|
|
||||||
override val isRuntimeEditable: Boolean
|
|
||||||
get() {
|
|
||||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
|
||||||
if (setting == this) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
|
|
||||||
|
|
||||||
fun from(key: String): IntListSetting? =
|
|
||||||
values().firstOrNull { it.key == key }
|
|
||||||
|
|
||||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,59 +4,57 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
|
||||||
|
|
||||||
enum class IntSetting(
|
enum class IntSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Int
|
override val defaultValue: Int
|
||||||
) : AbstractIntSetting {
|
) : AbstractIntSetting {
|
||||||
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
|
||||||
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
|
||||||
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
|
INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
|
||||||
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
|
CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
|
||||||
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
|
CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
|
||||||
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
|
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
|
||||||
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2),
|
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
|
||||||
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
|
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
|
||||||
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
|
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
|
||||||
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
|
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
|
||||||
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0),
|
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
|
||||||
CARDBOARD_SCREEN_SIZE(SettingKeys.cardboard_screen_size(), Settings.SECTION_LAYOUT, 85),
|
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
|
||||||
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
|
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
|
||||||
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
|
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
|
||||||
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
|
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
|
||||||
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
|
SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
|
||||||
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
|
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
|
||||||
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
|
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
|
||||||
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
|
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
|
||||||
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
|
LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
|
||||||
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
|
LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||||
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
|
LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||||
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
|
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
|
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||||
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
|
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
|
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
|
||||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
SECONDARY_DISPLAY_LAYOUT("secondary_display_layout",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
|
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
|
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
|
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
||||||
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
|
PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
|
||||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
|
PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
|
PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
|
PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||||
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||||
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
|
AUDIO_INPUT_TYPE("input_type", Settings.SECTION_AUDIO, 0),
|
||||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
|
||||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
|
||||||
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0),
|
TEXTURE_SAMPLING("texture_sampling", Settings.SECTION_RENDERER, 0),
|
||||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1),
|
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
|
||||||
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
|
||||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
|
||||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
|
||||||
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
|
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
|
||||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
|
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
|
||||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
|
||||||
|
|
||||||
override var int: Int = defaultValue
|
override var int: Int = defaultValue
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
|
||||||
|
|
||||||
enum class ScaledFloatSetting(
|
enum class ScaledFloatSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: Float,
|
override val defaultValue: Float,
|
||||||
val scale: Int
|
val scale: Int
|
||||||
) : AbstractFloatSetting {
|
) : AbstractFloatSetting {
|
||||||
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100);
|
AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
|
||||||
|
|
||||||
override var float: Float = defaultValue
|
override var float: Float = defaultValue
|
||||||
get() = field * scale
|
get() = field * scale
|
||||||
|
|||||||
@ -113,7 +113,6 @@ class Settings {
|
|||||||
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
|
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
|
||||||
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
|
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
|
||||||
const val SECTION_STORAGE = "Storage"
|
const val SECTION_STORAGE = "Storage"
|
||||||
const val SECTION_MISC = "Miscellaneous"
|
|
||||||
|
|
||||||
const val KEY_BUTTON_A = "button_a"
|
const val KEY_BUTTON_A = "button_a"
|
||||||
const val KEY_BUTTON_B = "button_b"
|
const val KEY_BUTTON_B = "button_b"
|
||||||
@ -136,7 +135,6 @@ class Settings {
|
|||||||
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
|
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
|
||||||
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
|
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
|
||||||
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
|
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_SCREEN_SWAP = "hotkey_screen_swap"
|
||||||
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
|
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
|
||||||
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
||||||
@ -204,7 +202,6 @@ class Settings {
|
|||||||
R.string.button_zr
|
R.string.button_zr
|
||||||
)
|
)
|
||||||
val hotKeys = listOf(
|
val hotKeys = listOf(
|
||||||
HOTKEY_ENABLE,
|
|
||||||
HOTKEY_SCREEN_SWAP,
|
HOTKEY_SCREEN_SWAP,
|
||||||
HOTKEY_CYCLE_LAYOUT,
|
HOTKEY_CYCLE_LAYOUT,
|
||||||
HOTKEY_CLOSE_GAME,
|
HOTKEY_CLOSE_GAME,
|
||||||
@ -214,7 +211,6 @@ class Settings {
|
|||||||
HOTKEY_TURBO_LIMIT
|
HOTKEY_TURBO_LIMIT
|
||||||
)
|
)
|
||||||
val hotkeyTitles = listOf(
|
val hotkeyTitles = listOf(
|
||||||
R.string.controller_hotkey_enable_button,
|
|
||||||
R.string.emulation_swap_screens,
|
R.string.emulation_swap_screens,
|
||||||
R.string.emulation_cycle_landscape_layouts,
|
R.string.emulation_cycle_landscape_layouts,
|
||||||
R.string.emulation_close_game,
|
R.string.emulation_close_game,
|
||||||
@ -224,7 +220,6 @@ class Settings {
|
|||||||
R.string.turbo_limit_hotkey
|
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_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||||
const val PREF_MATERIAL_YOU = "MaterialYouTheme"
|
const val PREF_MATERIAL_YOU = "MaterialYouTheme"
|
||||||
const val PREF_THEME_MODE = "ThemeMode"
|
const val PREF_THEME_MODE = "ThemeMode"
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
|
||||||
|
|
||||||
enum class StringSetting(
|
enum class StringSetting(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val section: String,
|
override val section: String,
|
||||||
override val defaultValue: String
|
override val defaultValue: String
|
||||||
) : AbstractStringSetting {
|
) : AbstractStringSetting {
|
||||||
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
INIT_TIME("init_time", Settings.SECTION_SYSTEM, "946731601"),
|
||||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
CAMERA_INNER_NAME("camera_inner_name", Settings.SECTION_CAMERA, "ndk"),
|
||||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
CAMERA_INNER_CONFIG("camera_inner_config", Settings.SECTION_CAMERA, "_front"),
|
||||||
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
|
CAMERA_OUTER_LEFT_NAME("camera_outer_left_name", Settings.SECTION_CAMERA, "ndk"),
|
||||||
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
|
CAMERA_OUTER_LEFT_CONFIG("camera_outer_left_config", Settings.SECTION_CAMERA, "_back"),
|
||||||
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
CAMERA_OUTER_RIGHT_NAME("camera_outer_right_name", Settings.SECTION_CAMERA, "ndk"),
|
||||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back");
|
||||||
|
|
||||||
override var string: String = defaultValue
|
override var string: String = defaultValue
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import android.content.SharedPreferences
|
|||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.InputDevice.MotionRange
|
import android.view.InputDevice.MotionRange
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
@ -129,7 +128,6 @@ class InputBindingSetting(
|
|||||||
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||||
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||||
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||||
Settings.HOTKEY_ENABLE -> Hotkey.ENABLE.button
|
|
||||||
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
|
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
|
||||||
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
|
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
|
||||||
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
||||||
@ -164,40 +162,36 @@ class InputBindingSetting(
|
|||||||
fun removeOldMapping() {
|
fun removeOldMapping() {
|
||||||
// Try remove all possible keys we wrote for this setting
|
// Try remove all possible keys we wrote for this setting
|
||||||
val oldKey = preferences.getString(reverseKey, "")
|
val oldKey = preferences.getString(reverseKey, "")
|
||||||
|
(setting as AbstractStringSetting).string = ""
|
||||||
if (oldKey != "") {
|
if (oldKey != "") {
|
||||||
(setting as AbstractStringSetting).string = ""
|
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.remove(abstractSetting.key) // Used for ui text
|
.remove(abstractSetting.key) // Used for ui text
|
||||||
|
.remove(oldKey) // Used for button mapping
|
||||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||||
.remove(oldKey + "_GuestButton") // Used for axis button
|
.remove(oldKey + "_GuestButton") // Used for axis button
|
||||||
.remove(oldKey + "_Inverted") // used for axis inversion
|
.remove(oldKey + "_Inverted") // used for axis inversion
|
||||||
.remove(reverseKey)
|
.apply()
|
||||||
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.
|
* Helper function to write a gamepad button mapping for the setting.
|
||||||
*/
|
*/
|
||||||
private fun writeButtonMapping(keyEvent: KeyEvent) {
|
private fun writeButtonMapping(key: String) {
|
||||||
val editor = preferences.edit()
|
val editor = preferences.edit()
|
||||||
val key = getInputButtonKey(keyEvent)
|
|
||||||
// Pull in all codes associated with this key
|
// Remove mapping for another setting using this input
|
||||||
// Migrate from the old int preference if need be
|
val oldButtonCode = preferences.getInt(key, -1)
|
||||||
val buttonCodes = InputBindingSetting.getButtonSet(keyEvent)
|
if (oldButtonCode != -1) {
|
||||||
buttonCodes.add(buttonCode)
|
val oldKey = getButtonKey(oldButtonCode)
|
||||||
|
editor.remove(oldKey) // Only need to remove UI text setting, others will be overwritten
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup old mapping for this setting
|
// Cleanup old mapping for this setting
|
||||||
removeOldMapping()
|
removeOldMapping()
|
||||||
|
|
||||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
|
// Write new mapping
|
||||||
|
editor.putInt(key, buttonCode)
|
||||||
|
|
||||||
// Write next reverse mapping for future cleanup
|
// Write next reverse mapping for future cleanup
|
||||||
editor.putString(reverseKey, key)
|
editor.putString(reverseKey, key)
|
||||||
@ -235,8 +229,9 @@ class InputBindingSetting(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val code = translateEventToKeyId(keyEvent)
|
val code = translateEventToKeyId(keyEvent)
|
||||||
writeButtonMapping(keyEvent)
|
writeButtonMapping(getInputButtonKey(code))
|
||||||
value = "${keyEvent.device.name}: ${getButtonName(code)}"
|
val uiString = "${keyEvent.device.name}: Button $code"
|
||||||
|
value = uiString
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,10 +255,9 @@ class InputBindingSetting(
|
|||||||
} else {
|
} else {
|
||||||
buttonCode
|
buttonCode
|
||||||
}
|
}
|
||||||
// use UP (-) to map vertical, but use RIGHT (+) to map horizontal
|
writeAxisMapping(motionRange.axis, button, axisDir == '-')
|
||||||
val inverted = if (isHorizontalOrientation()) axisDir == '-' else axisDir == '+'
|
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
|
||||||
writeAxisMapping(motionRange.axis, button, inverted)
|
value = uiString
|
||||||
value = "Axis ${motionRange.axis}$axisDir"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val type = TYPE_INPUT_BINDING
|
override val type = TYPE_INPUT_BINDING
|
||||||
@ -271,241 +265,6 @@ class InputBindingSetting(
|
|||||||
companion object {
|
companion object {
|
||||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
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.
|
* Returns the settings key for the specified Citra button code.
|
||||||
*/
|
*/
|
||||||
@ -528,31 +287,19 @@ class InputBindingSetting(
|
|||||||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the mutable set of int button values this key should map to given an event
|
* Helper function to get the settings key for an gamepad button.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
|
@Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys")
|
||||||
val key = getInputButtonKey(keyCode)
|
fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${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()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
|
/**
|
||||||
|
* Helper function to get the settings key for an gamepad button.
|
||||||
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
|
*
|
||||||
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
|
*/
|
||||||
|
fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to get the settings key for an gamepad axis.
|
* Helper function to get the settings key for an gamepad axis.
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
|
||||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
|
||||||
class MultiChoiceSetting(
|
|
||||||
setting: AbstractSetting?,
|
|
||||||
titleId: Int,
|
|
||||||
descriptionId: Int,
|
|
||||||
val choicesId: Int,
|
|
||||||
val valuesId: Int,
|
|
||||||
val key: String? = null,
|
|
||||||
val defaultValue: List<Int>? = null,
|
|
||||||
override var isEnabled: Boolean = true
|
|
||||||
) : SettingsItem(setting, titleId, descriptionId) {
|
|
||||||
override val type = TYPE_MULTI_CHOICE
|
|
||||||
|
|
||||||
val selectedValues: List<Int>
|
|
||||||
get() {
|
|
||||||
if (setting == null) {
|
|
||||||
return defaultValue!!
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val setting = setting as IntListSetting
|
|
||||||
return setting.list
|
|
||||||
}catch (_: ClassCastException) {
|
|
||||||
}
|
|
||||||
return defaultValue!!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a value to the backing list. If that int was previously null,
|
|
||||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
|
||||||
*
|
|
||||||
* @param selection New value of the int.
|
|
||||||
* @return the existing setting with the new value applied.
|
|
||||||
*/
|
|
||||||
fun setSelectedValue(selection: List<Int>): IntListSetting {
|
|
||||||
val intSetting = setting as IntListSetting
|
|
||||||
intSetting.list = selection
|
|
||||||
return intSetting
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,11 +1,10 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.model.view
|
package org.citra.citra_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
|
||||||
|
|
||||||
class RunnableSetting(
|
class RunnableSetting(
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
@ -13,11 +12,7 @@ class RunnableSetting(
|
|||||||
val isRuntimeRunnable: Boolean,
|
val isRuntimeRunnable: Boolean,
|
||||||
@DrawableRes val iconId: Int = 0,
|
@DrawableRes val iconId: Int = 0,
|
||||||
val runnable: () -> Unit,
|
val runnable: () -> Unit,
|
||||||
val value: (() -> String)? = null,
|
val value: (() -> String)? = null
|
||||||
val onLongClick: (() -> Boolean)? = null
|
|
||||||
) : SettingsItem(null, titleId, descriptionId) {
|
) : SettingsItem(null, titleId, descriptionId) {
|
||||||
override val type = TYPE_RUNNABLE
|
override val type = TYPE_RUNNABLE
|
||||||
|
|
||||||
override val isEditable: Boolean
|
|
||||||
get() = if (EmulationActivity.isRunning()) isRuntimeRunnable else true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ abstract class SettingsItem(
|
|||||||
) {
|
) {
|
||||||
abstract val type: Int
|
abstract val type: Int
|
||||||
|
|
||||||
open val isEditable: Boolean
|
val isEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (!EmulationActivity.isRunning()) return true
|
if (!EmulationActivity.isRunning()) return true
|
||||||
return setting?.isRuntimeEditable ?: false
|
return setting?.isRuntimeEditable ?: false
|
||||||
@ -47,6 +47,5 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_INPUT_BINDING = 8
|
const val TYPE_INPUT_BINDING = 8
|
||||||
const val TYPE_STRING_INPUT = 9
|
const val TYPE_STRING_INPUT = 9
|
||||||
const val TYPE_FLOAT_INPUT = 10
|
const val TYPE_FLOAT_INPUT = 10
|
||||||
const val TYPE_MULTI_CHOICE = 11
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,14 +41,12 @@ 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.AbstractSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
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.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.ScaledFloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
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.DateTimeSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
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.SettingsItem
|
||||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
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.SliderSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
@ -57,7 +55,6 @@ 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.DateTimeViewHolder
|
||||||
import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder
|
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.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.RunnableViewHolder
|
||||||
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
|
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
|
||||||
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
|
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
|
||||||
@ -65,7 +62,6 @@ 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.StringInputViewHolder
|
||||||
import org.citra.citra_emu.features.settings.ui.viewholder.SubmenuViewHolder
|
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.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.MessageDialogFragment
|
||||||
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
||||||
import org.citra.citra_emu.utils.SystemSaveGame
|
import org.citra.citra_emu.utils.SystemSaveGame
|
||||||
@ -76,8 +72,7 @@ import kotlin.math.roundToInt
|
|||||||
class SettingsAdapter(
|
class SettingsAdapter(
|
||||||
private val fragmentView: SettingsFragmentView,
|
private val fragmentView: SettingsFragmentView,
|
||||||
public val context: Context
|
public val context: Context
|
||||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
|
||||||
DialogInterface.OnMultiChoiceClickListener {
|
|
||||||
private var settings: ArrayList<SettingsItem>? = null
|
private var settings: ArrayList<SettingsItem>? = null
|
||||||
private var clickedItem: SettingsItem? = null
|
private var clickedItem: SettingsItem? = null
|
||||||
private var clickedPosition: Int
|
private var clickedPosition: Int
|
||||||
@ -109,10 +104,6 @@ class SettingsAdapter(
|
|||||||
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
|
||||||
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_SLIDER -> {
|
SettingsItem.TYPE_SLIDER -> {
|
||||||
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
@ -190,30 +181,21 @@ class SettingsAdapter(
|
|||||||
SettingsItem.TYPE_SLIDER -> {
|
SettingsItem.TYPE_SLIDER -> {
|
||||||
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
|
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_SWITCH -> {
|
SettingsItem.TYPE_SWITCH -> {
|
||||||
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
|
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
||||||
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
|
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
|
||||||
}
|
}
|
||||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
|
||||||
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_DATETIME_SETTING -> {
|
SettingsItem.TYPE_DATETIME_SETTING -> {
|
||||||
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
|
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||||
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_INPUT -> {
|
SettingsItem.TYPE_STRING_INPUT -> {
|
||||||
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
oldItem == newItem
|
oldItem == newItem
|
||||||
}
|
}
|
||||||
@ -232,7 +214,7 @@ class SettingsAdapter(
|
|||||||
|
|
||||||
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
||||||
if (fragmentView.activityView != null)
|
if (fragmentView.activityView != null)
|
||||||
// Reload the settings list to update the UI
|
// Reload the settings list to update the UI
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,27 +232,6 @@ class SettingsAdapter(
|
|||||||
onSingleChoiceClick(item)
|
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) {
|
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
|
||||||
clickedItem = item
|
clickedItem = item
|
||||||
dialog = context?.let {
|
dialog = context?.let {
|
||||||
@ -399,14 +360,14 @@ class SettingsAdapter(
|
|||||||
sliderString = sliderProgress.roundToInt().toString()
|
sliderString = sliderProgress.roundToInt().toString()
|
||||||
if (textSliderValue?.text.toString() != sliderString) {
|
if (textSliderValue?.text.toString() != sliderString) {
|
||||||
textSliderValue?.setText(sliderString)
|
textSliderValue?.setText(sliderString)
|
||||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val currentText = textSliderValue?.text.toString()
|
val currentText = textSliderValue?.text.toString()
|
||||||
val currentTextValue = currentText.toFloat()
|
val currentTextValue = currentText.toFloat()
|
||||||
if (currentTextValue != sliderProgress) {
|
if (currentTextValue != sliderProgress) {
|
||||||
textSliderValue?.setText(sliderString)
|
textSliderValue?.setText(sliderString)
|
||||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,7 +447,6 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
it.setSelectedValue(value)
|
it.setSelectedValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AbstractShortSetting -> {
|
is AbstractShortSetting -> {
|
||||||
val value = getValueForSingleChoiceSelection(it, which).toShort()
|
val value = getValueForSingleChoiceSelection(it, which).toShort()
|
||||||
if (it.selectedValue.toShort() != value) {
|
if (it.selectedValue.toShort() != value) {
|
||||||
@ -494,7 +454,6 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
it.setSelectedValue(value)
|
it.setSelectedValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
||||||
}
|
}
|
||||||
fragmentView?.putSetting(setting)
|
fragmentView?.putSetting(setting)
|
||||||
@ -540,12 +499,11 @@ class SettingsAdapter(
|
|||||||
val setting = it.setSelectedValue(value)
|
val setting = it.setSelectedValue(value)
|
||||||
fragmentView?.putSetting(setting)
|
fragmentView?.putSetting(setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val setting = it.setSelectedValue(sliderProgress)
|
val setting = it.setSelectedValue(sliderProgress)
|
||||||
fragmentView?.putSetting(setting)
|
fragmentView?.putSetting(setting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
@ -561,7 +519,7 @@ class SettingsAdapter(
|
|||||||
fragmentView?.putSetting(setting)
|
fragmentView?.putSetting(setting)
|
||||||
fragmentView.loadSettingsList()
|
fragmentView.loadSettingsList()
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clickedItem = null
|
clickedItem = null
|
||||||
@ -569,21 +527,6 @@ class SettingsAdapter(
|
|||||||
textInputValue = ""
|
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 {
|
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setMessage(R.string.reset_setting_confirmation)
|
.setMessage(R.string.reset_setting_confirmation)
|
||||||
@ -643,42 +586,26 @@ class SettingsAdapter(
|
|||||||
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
|
).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() {
|
fun onClickRegenerateConsoleId() {
|
||||||
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
|
MaterialAlertDialogBuilder(context)
|
||||||
SystemSaveGame.regenerateConsoleId()
|
.setTitle(R.string.regenerate_console_id)
|
||||||
notifyDataSetChanged()
|
.setMessage(R.string.regenerate_console_id_description)
|
||||||
}
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
|
SystemSaveGame.regenerateConsoleId()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onClickRegenerateMAC() {
|
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)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(titleId)
|
.setTitle(R.string.regenerate_mac_address)
|
||||||
.setMessage(messageId)
|
.setMessage(R.string.regenerate_mac_address_description)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> onConfirm() }
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
|
SystemSaveGame.regenerateMac()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@ -704,16 +631,6 @@ 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 {
|
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
||||||
val value = item.selectedValue
|
val value = item.selectedValue
|
||||||
val valuesId = item.valuesId
|
val valuesId = item.valuesId
|
||||||
@ -730,20 +647,4 @@ class SettingsAdapter(
|
|||||||
}
|
}
|
||||||
return -1
|
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};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import android.os.Build
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.serialization.builtins.IntArraySerializer
|
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.display.ScreenLayout
|
import org.citra.citra_emu.display.ScreenLayout
|
||||||
@ -28,14 +27,12 @@ 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.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
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.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.ScaledFloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
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.StringSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
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.HeaderSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
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.RunnableSetting
|
||||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
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.SingleChoiceSetting
|
||||||
@ -779,16 +776,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
||||||
sl.apply {
|
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))
|
add(HeaderSetting(R.string.generic_buttons))
|
||||||
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
||||||
val button = getInputObject(key)
|
val button = getInputObject(key)
|
||||||
@ -824,7 +811,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
|
add(HeaderSetting(R.string.controller_hotkeys))
|
||||||
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
||||||
val button = getInputObject(key)
|
val button = getInputObject(key)
|
||||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||||
@ -911,15 +898,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
IntSetting.RESOLUTION_FACTOR.key,
|
IntSetting.RESOLUTION_FACTOR.key,
|
||||||
IntSetting.RESOLUTION_FACTOR.defaultValue
|
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(
|
add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
@ -1170,17 +1148,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
BooleanSetting.UPRIGHT_SCREEN.defaultValue
|
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(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.PORTRAIT_SCREEN_LAYOUT,
|
IntSetting.PORTRAIT_SCREEN_LAYOUT,
|
||||||
@ -1275,7 +1242,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
override val section = null
|
override val section = null
|
||||||
override val isRuntimeEditable = false
|
override val isRuntimeEditable = false
|
||||||
override val valueAsString = int.toString()
|
override val valueAsString = int.toString()
|
||||||
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue.toInt()
|
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue
|
||||||
}
|
}
|
||||||
add(
|
add(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
@ -1298,7 +1265,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
override val section = null
|
override val section = null
|
||||||
override val isRuntimeEditable = false
|
override val isRuntimeEditable = false
|
||||||
override val valueAsString = int.toString()
|
override val valueAsString = int.toString()
|
||||||
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue.toInt()
|
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue
|
||||||
}
|
}
|
||||||
add(
|
add(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
@ -1321,7 +1288,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
override val section = null
|
override val section = null
|
||||||
override val isRuntimeEditable = false
|
override val isRuntimeEditable = false
|
||||||
override val valueAsString = int.toString()
|
override val valueAsString = int.toString()
|
||||||
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue.toInt()
|
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue
|
||||||
}
|
}
|
||||||
add(
|
add(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
@ -1817,15 +1784,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
BooleanSetting.ENABLE_RPC_SERVER.defaultValue
|
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(
|
add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.DELAY_START_LLE_MODULES,
|
BooleanSetting.DELAY_START_LLE_MODULES,
|
||||||
|
|||||||
@ -1,80 +0,0 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
|
||||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
|
||||||
|
|
||||||
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
|
||||||
SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: SettingsItem
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item
|
|
||||||
binding.textSettingName.setText(item.nameId)
|
|
||||||
if (item.descriptionId != 0) {
|
|
||||||
binding.textSettingDescription.visibility = View.VISIBLE
|
|
||||||
binding.textSettingDescription.setText(item.descriptionId)
|
|
||||||
} else {
|
|
||||||
binding.textSettingDescription.visibility = View.GONE
|
|
||||||
}
|
|
||||||
binding.textSettingValue.visibility = View.VISIBLE
|
|
||||||
binding.textSettingValue.text = getTextSetting()
|
|
||||||
|
|
||||||
if (setting.isActive) {
|
|
||||||
binding.textSettingName.alpha = 1f
|
|
||||||
binding.textSettingDescription.alpha = 1f
|
|
||||||
binding.textSettingValue.alpha = 1f
|
|
||||||
} else {
|
|
||||||
binding.textSettingName.alpha = 0.5f
|
|
||||||
binding.textSettingDescription.alpha = 0.5f
|
|
||||||
binding.textSettingValue.alpha = 0.5f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTextSetting(): String {
|
|
||||||
when (val item = setting) {
|
|
||||||
is MultiChoiceSetting -> {
|
|
||||||
val resMgr = binding.textSettingDescription.context.resources
|
|
||||||
val values = resMgr.getIntArray(item.valuesId)
|
|
||||||
var resList:List<String> = emptyList();
|
|
||||||
values.forEachIndexed { i: Int, value: Int ->
|
|
||||||
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
|
|
||||||
resList = resList + resMgr.getStringArray(item.choicesId)[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resList.joinToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
|
||||||
if (!setting.isEditable || !setting.isEnabled) {
|
|
||||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting is MultiChoiceSetting) {
|
|
||||||
adapter.onMultiChoiceClick(
|
|
||||||
(setting as MultiChoiceSetting),
|
|
||||||
bindingAdapterPosition
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
|
||||||
if (setting.isActive) {
|
|
||||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
|
||||||
} else {
|
|
||||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -67,10 +67,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (!setting.isEditable) {
|
// no-op
|
||||||
adapter.onClickDisabledSetting(true)
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
return setting.onLongClick?.invoke() ?: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -12,7 +12,6 @@ import org.citra.citra_emu.R
|
|||||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
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.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
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.IntSetting
|
||||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||||
import org.citra.citra_emu.features.settings.model.SettingSection
|
import org.citra.citra_emu.features.settings.model.SettingSection
|
||||||
@ -256,11 +255,6 @@ object SettingsFile {
|
|||||||
return stringSetting
|
return stringSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
val intListSetting = IntListSetting.from(key)
|
|
||||||
if (intListSetting != null) {
|
|
||||||
intListSetting.list = value.split(", ").map { it.toInt() }
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,152 +0,0 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.InputDevice
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
||||||
import org.citra.citra_emu.R
|
|
||||||
import org.citra.citra_emu.databinding.DialogAutoMapBinding
|
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
|
||||||
import org.citra.citra_emu.utils.Log
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures a single button press to detect controller layout (Xbox vs Nintendo)
|
|
||||||
* and d-pad type (axis vs button), then applies the appropriate bindings.
|
|
||||||
*/
|
|
||||||
class AutoMapDialogFragment : BottomSheetDialogFragment() {
|
|
||||||
private var _binding: DialogAutoMapBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
private var onComplete: (() -> Unit)? = null
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
_binding = DialogAutoMapBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
BottomSheetBehavior.from<View>(view.parent as View).state =
|
|
||||||
BottomSheetBehavior.STATE_EXPANDED
|
|
||||||
|
|
||||||
isCancelable = false
|
|
||||||
view.requestFocus()
|
|
||||||
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
|
|
||||||
|
|
||||||
binding.textTitle.setText(R.string.controller_auto_map)
|
|
||||||
binding.textMessage.setText(R.string.auto_map_prompt)
|
|
||||||
|
|
||||||
binding.imageFaceButtons.setImageResource(R.drawable.automap_face_buttons)
|
|
||||||
|
|
||||||
dialog?.setOnKeyListener { _, _, event -> onKeyEvent(event) }
|
|
||||||
|
|
||||||
binding.buttonCancel.setOnClickListener {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
|
||||||
if (event.action != KeyEvent.ACTION_UP) return false
|
|
||||||
|
|
||||||
val keyCode = event.keyCode
|
|
||||||
val device = event.device
|
|
||||||
|
|
||||||
// Check if this is a Nintendo Switch Joy-Con (not Pro Controller).
|
|
||||||
// Joy-Cons have unique quirks: split devices, non-standard D-pad scan codes,
|
|
||||||
// partial A/B swap but no X/Y swap from Android's evdev layer.
|
|
||||||
val isJoyCon = InputBindingSetting.isJoyCon(device)
|
|
||||||
|
|
||||||
if (isJoyCon) {
|
|
||||||
Log.info("[AutoMap] Detected Joy-Con - using Joy-Con mappings")
|
|
||||||
InputBindingSetting.clearAllBindings()
|
|
||||||
InputBindingSetting.applyJoyConBindings()
|
|
||||||
onComplete?.invoke()
|
|
||||||
dismiss()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-Joy-Con controllers, determine layout from which keycode arrives
|
|
||||||
// for the east/right position.
|
|
||||||
// The user is pressing the button in the "A" (east/right) position on the 3DS diamond.
|
|
||||||
// Xbox layout: east position sends KEYCODE_BUTTON_B (97)
|
|
||||||
// Nintendo layout: east position sends KEYCODE_BUTTON_A (96)
|
|
||||||
val isNintendoLayout = when (keyCode) {
|
|
||||||
KeyEvent.KEYCODE_BUTTON_A -> true
|
|
||||||
KeyEvent.KEYCODE_BUTTON_B -> false
|
|
||||||
else -> {
|
|
||||||
// Unrecognized button - ignore and wait for a valid press
|
|
||||||
Log.warning("[AutoMap] Ignoring unrecognized keycode $keyCode, waiting for A or B")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val layoutName = if (isNintendoLayout) "Nintendo" else "Xbox"
|
|
||||||
Log.info("[AutoMap] Detected $layoutName layout (keyCode=$keyCode)")
|
|
||||||
|
|
||||||
val useAxisDpad = detectDpadType(device)
|
|
||||||
|
|
||||||
val dpadName = if (useAxisDpad) "axis" else "button"
|
|
||||||
Log.info("[AutoMap] Detected $dpadName d-pad (device=${device?.name})")
|
|
||||||
|
|
||||||
InputBindingSetting.clearAllBindings()
|
|
||||||
InputBindingSetting.applyAutoMapBindings(isNintendoLayout, useAxisDpad)
|
|
||||||
|
|
||||||
onComplete?.invoke()
|
|
||||||
dismiss()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "AutoMapDialogFragment"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
onComplete: () -> Unit
|
|
||||||
): AutoMapDialogFragment {
|
|
||||||
val dialog = AutoMapDialogFragment()
|
|
||||||
dialog.onComplete = onComplete
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true for axis d-pad (HAT_X/HAT_Y), false for button d-pad (DPAD_UP/DOWN/LEFT/RIGHT).
|
|
||||||
* Prefers axis when both are present. Defaults to axis if detection fails.
|
|
||||||
*/
|
|
||||||
private fun detectDpadType(device: InputDevice?): Boolean {
|
|
||||||
if (device == null) return true
|
|
||||||
|
|
||||||
val hasAxisDpad = device.motionRanges.any {
|
|
||||||
it.axis == MotionEvent.AXIS_HAT_X || it.axis == MotionEvent.AXIS_HAT_Y
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAxisDpad) return true
|
|
||||||
|
|
||||||
val dpadKeyCodes = intArrayOf(
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT
|
|
||||||
)
|
|
||||||
val hasButtonDpad = device.hasKeys(*dpadKeyCodes).any { it }
|
|
||||||
|
|
||||||
return !hasButtonDpad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -11,14 +11,12 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
@ -74,7 +72,6 @@ import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
|||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.utils.BuildUtil
|
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||||
@ -110,9 +107,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
private val onPause = Runnable{ togglePause() }
|
private val onPause = Runnable{ togglePause() }
|
||||||
private val onShutdown = Runnable{ emulationState.stop() }
|
private val onShutdown = Runnable{ emulationState.stop() }
|
||||||
|
|
||||||
// Only used if a game is passed through intent on google play variant
|
|
||||||
private var gameFd: Int? = null
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (context is EmulationActivity) {
|
if (context is EmulationActivity) {
|
||||||
@ -130,37 +124,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val intent = requireActivity().intent
|
val intent = requireActivity().intent
|
||||||
var intentUri: Uri? = intent.data
|
val intentUri: Uri? = intent.data
|
||||||
val oldIntentInfo = Pair(
|
val oldIntentInfo = Pair(
|
||||||
intent.getStringExtra("SelectedGame"),
|
intent.getStringExtra("SelectedGame"),
|
||||||
intent.getStringExtra("SelectedTitle")
|
intent.getStringExtra("SelectedTitle")
|
||||||
)
|
)
|
||||||
var intentGame: Game? = null
|
var intentGame: Game? = null
|
||||||
intentUri = if (intentUri == null && oldIntentInfo.first != null) {
|
|
||||||
Uri.parse(oldIntentInfo.first)
|
|
||||||
} else {
|
|
||||||
intentUri
|
|
||||||
}
|
|
||||||
if (intentUri != null) {
|
if (intentUri != null) {
|
||||||
if (!BuildUtil.isGooglePlayBuild) {
|
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
|
||||||
val intentUriString = intentUri.toString()
|
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false)
|
||||||
// We need to build a special path as the incoming URI may be SAF exclusive
|
} else {
|
||||||
Log.warning("[EmulationFragment] Cannot determine native path of URI \"" +
|
null
|
||||||
intentUriString + "\", using file descriptor instead.")
|
}
|
||||||
if (!intentUriString.startsWith("!")) {
|
} else if (oldIntentInfo.first != null) {
|
||||||
gameFd = requireContext().contentResolver.openFileDescriptor(intentUri, "r")?.detachFd()
|
val gameUri = Uri.parse(oldIntentInfo.first)
|
||||||
intentUri = if (gameFd != null) {
|
intentGame = if (Game.extensions.contains(FileUtil.getExtension(gameUri))) {
|
||||||
Uri.parse("fd://" + gameFd.toString())
|
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
intentGame =
|
|
||||||
intentUri?.let {
|
|
||||||
// isInstalled, addedToLibrary and mediaType do not matter here
|
|
||||||
GameHelper.getGame(it, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val insertedCartridge = preferences.getString("insertedCartridge", "")
|
val insertedCartridge = preferences.getString("insertedCartridge", "")
|
||||||
@ -178,8 +160,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("[EmulationFragment] Starting application " + game.path)
|
|
||||||
|
|
||||||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||||
retainInstance = true
|
retainInstance = true
|
||||||
emulationState = EmulationState(game.path)
|
emulationState = EmulationState(game.path)
|
||||||
@ -195,12 +175,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
_binding = FragmentEmulationBinding.inflate(inflater)
|
_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
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +479,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
Choreographer.getInstance().postFrameCallback(this)
|
Choreographer.getInstance().postFrameCallback(this)
|
||||||
if (NativeLibrary.isRunning()) {
|
if (NativeLibrary.isRunning()) {
|
||||||
emulationState.unpause()
|
emulationState.pause()
|
||||||
|
|
||||||
// If the overlay is enabled, we need to update the position if changed
|
// If the overlay is enabled, we need to update the position if changed
|
||||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
||||||
@ -545,10 +519,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
EmulationLifecycleUtil.removeHook(onPause)
|
EmulationLifecycleUtil.removeHook(onPause)
|
||||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||||
if (gameFd != null) {
|
|
||||||
ParcelFileDescriptor.adoptFd(gameFd!!).close()
|
|
||||||
gameFd = null
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -875,7 +845,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
popupMenu.setOnMenuItemClickListener {
|
popupMenu.setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_emulation_amiibo_load -> {
|
R.id.menu_emulation_amiibo_load -> {
|
||||||
emulationActivity.openAmiiboFileLauncher.launch(false)
|
emulationActivity.openFileLauncher.launch(false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,16 @@ package org.citra.citra_emu.fragments
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
@ -36,7 +40,6 @@ import org.citra.citra_emu.adapters.GameAdapter
|
|||||||
import org.citra.citra_emu.databinding.FragmentGamesBinding
|
import org.citra.citra_emu.databinding.FragmentGamesBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.utils.BuildUtil
|
|
||||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||||
@ -47,6 +50,7 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
|
private var show3DSFileWarning: Boolean = true
|
||||||
private lateinit var gameAdapter: GameAdapter
|
private lateinit var gameAdapter: GameAdapter
|
||||||
|
|
||||||
private val openImageLauncher = registerForActivityResult(
|
private val openImageLauncher = registerForActivityResult(
|
||||||
@ -61,14 +65,8 @@ class GamesFragment : Fragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
|
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
|
||||||
if (outputUri != null) {
|
if (outputUri != null) {
|
||||||
val outputPath: String =
|
|
||||||
if (!BuildUtil.isGooglePlayBuild) {
|
|
||||||
"!" + NativeLibrary.getNativePath(outputUri)
|
|
||||||
} else {
|
|
||||||
outputUri.toString()
|
|
||||||
}
|
|
||||||
CompressProgressDialogViewModel.reset()
|
CompressProgressDialogViewModel.reset()
|
||||||
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputPath)
|
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputUri.toString())
|
||||||
dialog.showNow(
|
dialog.showNow(
|
||||||
fragment.requireActivity().supportFragmentManager,
|
fragment.requireActivity().supportFragmentManager,
|
||||||
CompressProgressDialogFragment.TAG
|
CompressProgressDialogFragment.TAG
|
||||||
@ -76,9 +74,9 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val status = if (shouldCompress) {
|
val status = if (shouldCompress) {
|
||||||
NativeLibrary.compressFile(inputPath, outputPath)
|
NativeLibrary.compressFile(inputPath, outputUri.toString())
|
||||||
} else {
|
} else {
|
||||||
NativeLibrary.decompressFile(inputPath, outputPath)
|
NativeLibrary.decompressFile(inputPath, outputUri.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.requireActivity().runOnUiThread {
|
fragment.requireActivity().runOnUiThread {
|
||||||
@ -226,6 +224,34 @@ class GamesFragment : Fragment() {
|
|||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if (show3DSFileWarning &&
|
||||||
|
!PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
.getBoolean("show_3ds_files_warning", false)) {
|
||||||
|
val message = HtmlCompat.fromHtml(getString(R.string.warning_3ds_files),
|
||||||
|
HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
|
|
||||||
|
context?.let {
|
||||||
|
val alert = MaterialAlertDialogBuilder(it)
|
||||||
|
.setTitle(getString(R.string.important))
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.dont_show_again) { _, _ ->
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
.edit() {
|
||||||
|
putBoolean("show_3ds_files_warning", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
|
||||||
|
val alertMessage = alert.findViewById<View>(android.R.id.message) as TextView
|
||||||
|
alertMessage.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show3DSFileWarning = false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import org.citra.citra_emu.adapters.HomeSettingAdapter
|
|||||||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||||
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.features.settings.SettingKeys
|
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
import org.citra.citra_emu.model.Game
|
import org.citra.citra_emu.model.Game
|
||||||
@ -89,7 +89,7 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
{
|
{
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||||
|
|
||||||
inputBinding.editTextInput.setText(textInputValue)
|
inputBinding.editTextInput.setText(textInputValue)
|
||||||
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||||
@ -103,7 +103,7 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (textInputValue.isNotEmpty()) {
|
if (textInputValue.isNotEmpty()) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
.putString("last_artic_base_addr", textInputValue)
|
||||||
.apply()
|
.apply()
|
||||||
val menu = Game(
|
val menu = Game(
|
||||||
title = getString(R.string.artic_base),
|
title = getString(R.string.artic_base),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
@ -31,6 +32,7 @@ import androidx.preference.PreferenceManager
|
|||||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.transition.MaterialFadeThrough
|
import com.google.android.material.transition.MaterialFadeThrough
|
||||||
|
import org.citra.citra_emu.BuildConfig
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
@ -90,20 +92,23 @@ class SetupFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
mainActivity = requireActivity() as MainActivity
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
homeViewModel.selectedCitraDirectoryLiveData.observe(viewLifecycleOwner) { uri ->
|
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
||||||
if (uri == null) {
|
|
||||||
return@observe
|
requireActivity().onBackPressedDispatcher.addCallback(
|
||||||
|
viewLifecycleOwner,
|
||||||
|
object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (binding.viewPager2.currentItem > 0) {
|
||||||
|
pageBackward()
|
||||||
|
} else {
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onOpenCitraDirectory(uri)
|
)
|
||||||
homeViewModel.selectedCitraDirectory = null
|
|
||||||
}
|
requireActivity().window.navigationBarColor =
|
||||||
homeViewModel.selectedGamesDirectoryLiveData.observe(viewLifecycleOwner) { uri ->
|
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||||
if (uri == null) {
|
|
||||||
return@observe
|
|
||||||
}
|
|
||||||
onGetGamesDirectory(uri)
|
|
||||||
homeViewModel.selectedGamesDirectory = null
|
|
||||||
}
|
|
||||||
|
|
||||||
pages = mutableListOf()
|
pages = mutableListOf()
|
||||||
pages.apply {
|
pages.apply {
|
||||||
@ -315,7 +320,7 @@ class SetupFragment : Fragment() {
|
|||||||
R.string.select_citra_user_folder_description,
|
R.string.select_citra_user_folder_description,
|
||||||
buttonAction = {
|
buttonAction = {
|
||||||
pageButtonCallback = it
|
pageButtonCallback = it
|
||||||
PermissionsHandler.compatibleSelectDirectory(mainActivity.setupOpenCitraDirectory)
|
PermissionsHandler.compatibleSelectDirectory(openCitraDirectory)
|
||||||
},
|
},
|
||||||
buttonState = {
|
buttonState = {
|
||||||
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||||
@ -337,9 +342,9 @@ class SetupFragment : Fragment() {
|
|||||||
R.drawable.ic_controller,
|
R.drawable.ic_controller,
|
||||||
R.string.games,
|
R.string.games,
|
||||||
R.string.games_description,
|
R.string.games_description,
|
||||||
buttonAction = {
|
buttonAction = {
|
||||||
pageButtonCallback = it
|
pageButtonCallback = it
|
||||||
mainActivity.setupGetGamesDirectory.launch(
|
getGamesDirectory.launch(
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -404,33 +409,27 @@ class SetupFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
binding.viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||||
|
var previousPosition: Int = 0
|
||||||
|
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
super.onPageSelected(position)
|
super.onPageSelected(position)
|
||||||
updateNavigationButtons(position)
|
|
||||||
|
if (position == 1 && previousPosition == 0) {
|
||||||
|
ViewUtils.showView(binding.buttonNext)
|
||||||
|
ViewUtils.showView(binding.buttonBack)
|
||||||
|
} else if (position == 0 && previousPosition == 1) {
|
||||||
|
ViewUtils.hideView(binding.buttonBack)
|
||||||
|
ViewUtils.hideView(binding.buttonNext)
|
||||||
|
} else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
|
||||||
|
ViewUtils.hideView(binding.buttonNext)
|
||||||
|
} else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
|
||||||
|
ViewUtils.showView(binding.buttonNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPosition = position
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
|
||||||
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
if (binding.viewPager2.currentItem > 0) {
|
|
||||||
pageBackward()
|
|
||||||
} else {
|
|
||||||
requireActivity().finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.viewPager2.currentItem = homeViewModel.setupCurrentPage
|
|
||||||
|
|
||||||
requireActivity().window.navigationBarColor =
|
|
||||||
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
|
||||||
|
|
||||||
binding.buttonNext.setOnClickListener {
|
binding.buttonNext.setOnClickListener {
|
||||||
val index = binding.viewPager2.currentItem
|
val index = binding.viewPager2.currentItem
|
||||||
val currentPage = pages[index]
|
val currentPage = pages[index]
|
||||||
@ -480,23 +479,29 @@ class SetupFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
binding.buttonBack.setOnClickListener { pageBackward() }
|
binding.buttonBack.setOnClickListener { pageBackward() }
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState != null) {
|
||||||
hasBeenWarned = BooleanArray(pages.size)
|
val nextIsVisible = savedInstanceState.getBoolean(KEY_NEXT_VISIBILITY)
|
||||||
} else {
|
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
||||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED) ?: BooleanArray(pages.size)
|
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
||||||
}
|
|
||||||
|
|
||||||
updateNavigationButtons(binding.viewPager2.currentItem)
|
if (nextIsVisible) {
|
||||||
|
binding.buttonNext.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
if (backIsVisible) {
|
||||||
|
binding.buttonBack.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasBeenWarned = BooleanArray(pages.size)
|
||||||
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
|
||||||
if (::hasBeenWarned.isInitialized) {
|
outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
|
||||||
outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
|
outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -505,39 +510,15 @@ class SetupFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var pageButtonCallback: SetupCallback
|
private lateinit var pageButtonCallback: SetupCallback
|
||||||
|
|
||||||
private fun updateNavigationButtons(position: Int) {
|
|
||||||
if (position == 0) {
|
|
||||||
ViewUtils.hideView(binding.buttonBack)
|
|
||||||
} else {
|
|
||||||
ViewUtils.showView(binding.buttonBack)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == 0 || position == pages.size - 1) {
|
|
||||||
ViewUtils.hideView(binding.buttonNext)
|
|
||||||
} else {
|
|
||||||
ViewUtils.showView(binding.buttonNext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val checkForButtonState: () -> Unit = {
|
private val checkForButtonState: () -> Unit = {
|
||||||
val currentIndex = binding.viewPager2.currentItem
|
val page = pages[binding.viewPager2.currentItem]
|
||||||
val page = pages[currentIndex]
|
page.pageButtons?.forEach {
|
||||||
|
if (it.buttonState() == ButtonState.BUTTON_ACTION_COMPLETE) {
|
||||||
|
pageButtonCallback.onStepCompleted(it.titleId, pageFullyCompleted = false)
|
||||||
|
}
|
||||||
|
|
||||||
val isPageComplete = page.pageSteps() == PageState.PAGE_STEPS_COMPLETE
|
if (page.pageSteps() == PageState.PAGE_STEPS_COMPLETE) {
|
||||||
|
pageButtonCallback.onStepCompleted(0, pageFullyCompleted = true)
|
||||||
if (isPageComplete) {
|
|
||||||
binding.viewPager2.adapter?.notifyItemChanged(currentIndex)
|
|
||||||
ViewUtils.showView(binding.buttonNext)
|
|
||||||
} else {
|
|
||||||
page.pageButtons?.forEach {
|
|
||||||
if (it.buttonState() == ButtonState.BUTTON_ACTION_COMPLETE) {
|
|
||||||
if (this::pageButtonCallback.isInitialized) {
|
|
||||||
pageButtonCallback.onStepCompleted(it.titleId, pageFullyCompleted = false)
|
|
||||||
} else {
|
|
||||||
binding.viewPager2.adapter?.notifyItemChanged(currentIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,38 +559,48 @@ class SetupFragment : Fragment() {
|
|||||||
showPermissionDeniedSnackbar()
|
showPermissionDeniedSnackbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onOpenCitraDirectory(result: Uri) {
|
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
|
||||||
|
ActivityResultContracts.OpenDocumentTree()
|
||||||
|
) { result: Uri? ->
|
||||||
|
if (result == null) {
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
if (!BuildUtil.isGooglePlayBuild) {
|
if (!BuildUtil.isGooglePlayBuild) {
|
||||||
if (NativeLibrary.getNativePath(result) == "") {
|
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||||
SelectUserDirectoryDialogFragment.newInstance(
|
SelectUserDirectoryDialogFragment.newInstance(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
R.string.invalid_selection,
|
R.string.invalid_selection,
|
||||||
R.string.invalid_user_directory
|
R.string.invalid_user_directory
|
||||||
).show(mainActivity.supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
).show(mainActivity.supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||||
return
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result,
|
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
|
||||||
null, checkForButtonState)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onGetGamesDirectory(result: Uri) {
|
private val getGamesDirectory =
|
||||||
requireActivity().contentResolver.takePersistableUriPermission(
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||||
result,
|
if (result == null) {
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
return@registerForActivityResult
|
||||||
)
|
}
|
||||||
|
|
||||||
// When a new directory is picked, we currently will reset the existing games
|
requireActivity().contentResolver.takePersistableUriPermission(
|
||||||
// database. This effectively means that only one game directory is supported.
|
result,
|
||||||
preferences.edit()
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
.putString(GameHelper.KEY_GAME_PATH, result.toString())
|
)
|
||||||
.apply()
|
|
||||||
|
|
||||||
homeViewModel.setGamesDir(requireActivity(), result.path!!)
|
// When a new directory is picked, we currently will reset the existing games
|
||||||
|
// database. This effectively means that only one game directory is supported.
|
||||||
|
preferences.edit()
|
||||||
|
.putString(GameHelper.KEY_GAME_PATH, result.toString())
|
||||||
|
.apply()
|
||||||
|
|
||||||
checkForButtonState.invoke()
|
homeViewModel.setGamesDir(requireActivity(), result.path!!)
|
||||||
}
|
|
||||||
|
checkForButtonState.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
private fun finishSetup() {
|
private fun finishSetup() {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
@ -620,12 +611,10 @@ class SetupFragment : Fragment() {
|
|||||||
|
|
||||||
fun pageForward() {
|
fun pageForward() {
|
||||||
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
|
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
|
||||||
homeViewModel.setupCurrentPage = binding.viewPager2.currentItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pageBackward() {
|
fun pageBackward() {
|
||||||
binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
|
binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
|
||||||
homeViewModel.setupCurrentPage = binding.viewPager2.currentItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPageWarned(page: Int) {
|
fun setPageWarned(page: Int) {
|
||||||
|
|||||||
@ -39,7 +39,6 @@ import org.citra.citra_emu.R
|
|||||||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||||
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
|
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
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.model.Game
|
||||||
import org.citra.citra_emu.utils.SystemSaveGame
|
import org.citra.citra_emu.utils.SystemSaveGame
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
@ -178,7 +177,7 @@ class SystemFilesFragment : Fragment() {
|
|||||||
binding.buttonSetUpSystemFiles.setOnClickListener {
|
binding.buttonSetUpSystemFiles.setOnClickListener {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||||
|
|
||||||
val progressDialog = showProgressDialog(
|
val progressDialog = showProgressDialog(
|
||||||
getText(R.string.setup_system_files),
|
getText(R.string.setup_system_files),
|
||||||
@ -275,7 +274,7 @@ class SystemFilesFragment : Fragment() {
|
|||||||
.setPositiveButton(android.R.string.ok) { diag, _ ->
|
.setPositiveButton(android.R.string.ok) { diag, _ ->
|
||||||
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
|
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
.putString("last_artic_base_addr", textInputValue)
|
||||||
.apply()
|
.apply()
|
||||||
val menu = Game(
|
val menu = Game(
|
||||||
title = getString(R.string.artic_base),
|
title = getString(R.string.artic_base),
|
||||||
|
|||||||
@ -7,26 +7,19 @@ package org.citra.citra_emu.model
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.HashSet
|
import java.util.HashSet
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.NativeLibrary
|
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
import org.citra.citra_emu.activities.EmulationActivity
|
||||||
import org.citra.citra_emu.utils.BuildUtil
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Serializable
|
@Serializable
|
||||||
class Game(
|
class Game(
|
||||||
val valid: Boolean = false,
|
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val path: String = "",
|
val path: String = "",
|
||||||
val titleId: Long = 0L,
|
val titleId: Long = 0L,
|
||||||
val mediaType: MediaType = MediaType.GAME_CARD,
|
|
||||||
val company: String = "",
|
val company: String = "",
|
||||||
val regions: String = "",
|
val regions: String = "",
|
||||||
val isInstalled: Boolean = false,
|
val isInstalled: Boolean = false,
|
||||||
@ -42,25 +35,12 @@ class Game(
|
|||||||
val keyLastPlayedTime get() = "${filename}_LastPlayed"
|
val keyLastPlayedTime get() = "${filename}_LastPlayed"
|
||||||
|
|
||||||
val launchIntent: Intent
|
val launchIntent: Intent
|
||||||
get() {
|
get() = Intent(CitraApplication.appContext, EmulationActivity::class.java).apply {
|
||||||
var appUri: Uri
|
action = Intent.ACTION_VIEW
|
||||||
if (isInstalled) {
|
data = if (isInstalled) {
|
||||||
if (BuildUtil.isGooglePlayBuild) {
|
CitraApplication.documentsTree.getUri(path)
|
||||||
appUri = CitraApplication.documentsTree.getUri(path)
|
|
||||||
} else {
|
|
||||||
val nativePath = NativeLibrary.getUserDirectory() + "/" + path
|
|
||||||
val nativeFile = File(nativePath)
|
|
||||||
if (!nativeFile.exists()) {
|
|
||||||
throw IOException("Attempting to create shortcut for an executable that doesn't exist: $nativePath")
|
|
||||||
}
|
|
||||||
appUri = Uri.fromFile(nativeFile)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
appUri = path.toUri()
|
Uri.parse(path)
|
||||||
}
|
|
||||||
return Intent(CitraApplication.appContext, EmulationActivity::class.java).apply {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
data = appUri
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,28 +58,15 @@ class Game(
|
|||||||
result = 31 * result + regions.hashCode()
|
result = 31 * result + regions.hashCode()
|
||||||
result = 31 * result + path.hashCode()
|
result = 31 * result + path.hashCode()
|
||||||
result = 31 * result + titleId.hashCode()
|
result = 31 * result + titleId.hashCode()
|
||||||
result = 31 * result + mediaType.hashCode()
|
|
||||||
result = 31 * result + company.hashCode()
|
result = 31 * result + company.hashCode()
|
||||||
return result
|
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 {
|
companion object {
|
||||||
val allExtensions: Set<String> get() = extensions + badExtensions
|
val allExtensions: Set<String> get() = extensions + badExtensions
|
||||||
|
|
||||||
val extensions: Set<String> = HashSet(
|
val extensions: Set<String> = HashSet(
|
||||||
listOf("3dsx", "app", "axf", "cci", "cxi", "elf", "z3dsx", "zcci", "zcxi", "3ds")
|
listOf("3dsx", "app", "axf", "cci", "cxi", "elf", "z3dsx", "zcci", "zcxi")
|
||||||
)
|
)
|
||||||
|
|
||||||
val badExtensions: Set<String> = HashSet(
|
val badExtensions: Set<String> = HashSet(
|
||||||
|
|||||||
@ -39,8 +39,6 @@ import androidx.work.OutOfQuotaPolicy
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.TimeSource
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.citra.citra_emu.BuildConfig
|
import org.citra.citra_emu.BuildConfig
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
@ -76,10 +74,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
override var themeId: Int = 0
|
override var themeId: Int = 0
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val KEY_SETUP_CURRENT_PAGE = "SetupCurrentPage"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
RefreshRateUtil.enforceRefreshRate(this)
|
RefreshRateUtil.enforceRefreshRate(this)
|
||||||
|
|
||||||
@ -136,22 +130,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var applicationsClickTimestamp = TimeSource.Monotonic.markNow()
|
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
setUpNavigation(savedInstanceState, navHostFragment.navController)
|
setUpNavigation(navHostFragment.navController)
|
||||||
(binding.navigationView as NavigationBarView).setOnItemReselectedListener {
|
(binding.navigationView as NavigationBarView).setOnItemReselectedListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.gamesFragment -> {
|
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
|
||||||
if (applicationsClickTimestamp.elapsedNow() < 300.milliseconds) {
|
|
||||||
Toast.makeText(this, BuildConfig.VERSION_NAME, Toast.LENGTH_LONG)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
applicationsClickTimestamp = TimeSource.Monotonic.markNow()
|
|
||||||
|
|
||||||
gamesViewModel.setShouldScrollToTop(true)
|
|
||||||
}
|
|
||||||
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
|
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
|
||||||
R.id.homeSettingsFragment -> SettingsActivity.launch(
|
R.id.homeSettingsFragment -> SettingsActivity.launch(
|
||||||
this,
|
this,
|
||||||
@ -192,14 +176,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
// Save the user's current game state.
|
|
||||||
outState.putInt(KEY_SETUP_CURRENT_PAGE, homeViewModel.setupCurrentPage)
|
|
||||||
|
|
||||||
// Always call the superclass so it can save the view hierarchy state.
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
checkUserPermissions()
|
checkUserPermissions()
|
||||||
|
|
||||||
@ -275,12 +251,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
|
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpNavigation(savedInstanceState: Bundle?, navController: NavController) {
|
private fun setUpNavigation(navController: NavController) {
|
||||||
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||||
|
|
||||||
if (firstTimeSetup && !homeViewModel.navigatedToSetup) {
|
if (firstTimeSetup && !homeViewModel.navigatedToSetup) {
|
||||||
homeViewModel.setupCurrentPage = savedInstanceState?.getInt(KEY_SETUP_CURRENT_PAGE) ?: 0
|
|
||||||
navController.navigate(R.id.firstTimeSetupFragment)
|
navController.navigate(R.id.firstTimeSetupFragment)
|
||||||
homeViewModel.navigatedToSetup = true
|
homeViewModel.navigatedToSetup = true
|
||||||
} else {
|
} else {
|
||||||
@ -392,7 +367,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!BuildUtil.isGooglePlayBuild) {
|
if (!BuildUtil.isGooglePlayBuild) {
|
||||||
if (NativeLibrary.getNativePath(result) == "") {
|
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||||
SelectUserDirectoryDialogFragment.newInstance(
|
SelectUserDirectoryDialogFragment.newInstance(
|
||||||
this,
|
this,
|
||||||
R.string.invalid_selection,
|
R.string.invalid_selection,
|
||||||
@ -437,16 +412,4 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val setupOpenCitraDirectory = registerForActivityResult(
|
|
||||||
ActivityResultContracts.OpenDocumentTree(),
|
|
||||||
) { result: Uri? ->
|
|
||||||
homeViewModel.selectedCitraDirectory = result
|
|
||||||
}
|
|
||||||
|
|
||||||
val setupGetGamesDirectory = registerForActivityResult(
|
|
||||||
ActivityResultContracts.OpenDocumentTree()
|
|
||||||
) { result: Uri? ->
|
|
||||||
homeViewModel.selectedGamesDirectory = result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -12,11 +12,9 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import org.citra.citra_emu.NativeLibrary
|
|
||||||
import org.citra.citra_emu.NativeLibrary.InstallStatus
|
import org.citra.citra_emu.NativeLibrary.InstallStatus
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.utils.FileUtil.getFilename
|
import org.citra.citra_emu.utils.FileUtil.getFilename
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
class CiaInstallWorker(
|
class CiaInstallWorker(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@ -133,7 +131,7 @@ class CiaInstallWorker(
|
|||||||
installProgressBuilder.setOngoing(true)
|
installProgressBuilder.setOngoing(true)
|
||||||
setProgressCallback(100, 0)
|
setProgressCallback(100, 0)
|
||||||
selectedFiles.forEachIndexed { i, file ->
|
selectedFiles.forEachIndexed { i, file ->
|
||||||
val filename = getFilename(file.toUri())
|
val filename = getFilename(Uri.parse(file))
|
||||||
installProgressBuilder.setContentText(
|
installProgressBuilder.setContentText(
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.cia_install_notification_installing,
|
R.string.cia_install_notification_installing,
|
||||||
@ -142,13 +140,7 @@ class CiaInstallWorker(
|
|||||||
selectedFiles.size
|
selectedFiles.size
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
var fileFinal: String
|
val res = installCIA(file)
|
||||||
if (BuildUtil.isGooglePlayBuild) {
|
|
||||||
fileFinal = file
|
|
||||||
} else {
|
|
||||||
fileFinal = "!" + NativeLibrary.getNativePath(file.toUri())
|
|
||||||
}
|
|
||||||
val res = installCIA(fileFinal)
|
|
||||||
notifyInstallStatus(filename, res)
|
notifyInstallStatus(filename, res)
|
||||||
}
|
}
|
||||||
notificationManager.cancel(PROGRESS_NOTIFICATION_ID)
|
notificationManager.cancel(PROGRESS_NOTIFICATION_ID)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -28,8 +28,8 @@ object DirectoryInitialization {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var directoryState: DirectoryInitializationState? = null
|
private var directoryState: DirectoryInitializationState? = null
|
||||||
var userPath: String? = null
|
var userPath: String? = null
|
||||||
val internalUserPath: String
|
val internalUserPath
|
||||||
get() = CitraApplication.appContext.filesDir.canonicalPath
|
get() = CitraApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
|
||||||
private val isCitraDirectoryInitializationRunning = AtomicBoolean(false)
|
private val isCitraDirectoryInitializationRunning = AtomicBoolean(false)
|
||||||
|
|
||||||
val context: Context get() = CitraApplication.appContext
|
val context: Context get() = CitraApplication.appContext
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ object DiskShaderCacheProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int, obj: String) {
|
fun loadProgress(stage: LoadCallbackStage, progress: Int, max: Int) {
|
||||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
|
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
|
||||||
@ -40,7 +40,7 @@ object DiskShaderCacheProgress {
|
|||||||
)
|
)
|
||||||
|
|
||||||
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
|
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
|
||||||
emulationActivity.getString(R.string.building_shaders, obj ),
|
emulationActivity.getString(R.string.building_shaders),
|
||||||
progress,
|
progress,
|
||||||
max
|
max
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import androidx.core.net.toUri
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.model.CheapDocument
|
import org.citra.citra_emu.model.CheapDocument
|
||||||
import org.citra.citra_emu.utils.BuildUtil
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.StringTokenizer
|
import java.util.StringTokenizer
|
||||||
@ -79,9 +77,8 @@ class DocumentsTree {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getFilename(filepath: String): String {
|
fun getFilename(filepath: String): String {
|
||||||
val components = filepath.split(DELIMITER).filter { it.isNotEmpty() }
|
val node = resolvePath(filepath) ?: return ""
|
||||||
val filename = components.last()
|
return node.name
|
||||||
return filename
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@ -263,17 +260,6 @@ class DocumentsTree {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun resolvePath(filepath: String): DocumentsNode? {
|
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
|
root ?: return null
|
||||||
val tokens = StringTokenizer(filepath, DELIMITER, false)
|
val tokens = StringTokenizer(filepath, DELIMITER, false)
|
||||||
var iterator = root
|
var iterator = root
|
||||||
@ -365,10 +351,5 @@ class DocumentsTree {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DELIMITER = "/"
|
const val DELIMITER = "/"
|
||||||
val kotlinDirectoryAccessWhitelist = arrayOf(
|
|
||||||
"/config/",
|
|
||||||
"/log/",
|
|
||||||
"/gpu_drivers/",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,20 +219,10 @@ object FileUtil {
|
|||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getFilename(uri: Uri): String {
|
fun getFilename(uri: Uri): String {
|
||||||
|
val columns = arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
||||||
var filename = ""
|
var filename = ""
|
||||||
var c: Cursor? = null
|
var c: Cursor? = null
|
||||||
try {
|
try {
|
||||||
if (uri.scheme == "fd") {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.scheme == "file") {
|
|
||||||
BuildUtil.assertNotGooglePlay()
|
|
||||||
val file = File(uri.path!!);
|
|
||||||
return file.name
|
|
||||||
}
|
|
||||||
|
|
||||||
val columns = arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
|
||||||
c = context.contentResolver.query(
|
c = context.contentResolver.query(
|
||||||
uri,
|
uri,
|
||||||
columns,
|
columns,
|
||||||
@ -547,7 +537,7 @@ object FileUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isNativePath(path: String): Boolean = // FIXME: This function name is bullshit -OS
|
fun isNativePath(path: String): Boolean =
|
||||||
try {
|
try {
|
||||||
path[0] == '/'
|
path[0] == '/'
|
||||||
} catch (e: StringIndexOutOfBoundsException) {
|
} catch (e: StringIndexOutOfBoundsException) {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ object GameHelper {
|
|||||||
|
|
||||||
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
|
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
|
||||||
NativeLibrary.getInstalledGamePaths().forEach {
|
NativeLibrary.getInstalledGamePaths().forEach {
|
||||||
games.add(getGame(Uri.parse(it.path), isInstalled = true, addedToLibrary = true, it.mediaType))
|
games.add(getGame(Uri.parse(it), isInstalled = true, addedToLibrary = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache list of games found on disk
|
// Cache list of games found on disk
|
||||||
@ -62,46 +62,27 @@ object GameHelper {
|
|||||||
addGamesRecursive(games, FileUtil.listFiles(it.uri), depth - 1)
|
addGamesRecursive(games, FileUtil.listFiles(it.uri), depth - 1)
|
||||||
} else {
|
} else {
|
||||||
if (Game.allExtensions.contains(FileUtil.getExtension(it.uri))) {
|
if (Game.allExtensions.contains(FileUtil.getExtension(it.uri))) {
|
||||||
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true, Game.MediaType.GAME_CARD))
|
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean, mediaType: Game.MediaType): Game {
|
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean): Game {
|
||||||
val filePath = uri.toString()
|
val filePath = uri.toString()
|
||||||
var nativePath: String? = null
|
var gameInfo: GameInfo? = GameInfo(filePath)
|
||||||
var gameInfo: GameInfo?
|
|
||||||
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) || filePath.startsWith("!")) {
|
|
||||||
gameInfo = GameInfo(filePath)
|
|
||||||
} else {
|
|
||||||
nativePath = if (uri.scheme == "fd") {
|
|
||||||
uri.toString()
|
|
||||||
} else {
|
|
||||||
"!" + NativeLibrary.getNativePath(uri)
|
|
||||||
};
|
|
||||||
gameInfo = GameInfo(nativePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
val valid = gameInfo.isValid()
|
if (gameInfo?.isValid() == false) {
|
||||||
if (!valid) {
|
|
||||||
gameInfo = null
|
gameInfo = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val isEncrypted = gameInfo?.isEncrypted() == true
|
val isEncrypted = gameInfo?.isEncrypted() == true
|
||||||
|
|
||||||
val newGame = Game(
|
val newGame = Game(
|
||||||
valid,
|
|
||||||
(gameInfo?.getTitle() ?: FileUtil.getFilename(uri)).replace("[\\t\\n\\r]+".toRegex(), " "),
|
(gameInfo?.getTitle() ?: FileUtil.getFilename(uri)).replace("[\\t\\n\\r]+".toRegex(), " "),
|
||||||
filePath.replace("\n", " "),
|
filePath.replace("\n", " "),
|
||||||
// TODO: This next line can be deduplicated but I don't want to right now -OS
|
filePath,
|
||||||
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) || filePath.startsWith("!")) {
|
|
||||||
filePath
|
|
||||||
} else {
|
|
||||||
nativePath!!
|
|
||||||
},
|
|
||||||
gameInfo?.getTitleID() ?: 0,
|
gameInfo?.getTitleID() ?: 0,
|
||||||
mediaType,
|
|
||||||
gameInfo?.getCompany() ?: "",
|
gameInfo?.getCompany() ?: "",
|
||||||
if (isEncrypted) { CitraApplication.appContext.getString(R.string.unsupported_encrypted) } else { gameInfo?.getRegions() ?: "" },
|
if (isEncrypted) { CitraApplication.appContext.getString(R.string.unsupported_encrypted) } else { gameInfo?.getRegions() ?: "" },
|
||||||
isInstalled,
|
isInstalled,
|
||||||
|
|||||||
@ -4,43 +4,28 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.utils
|
package org.citra.citra_emu.utils
|
||||||
|
|
||||||
import android.content.Context
|
import org.citra.citra_emu.utils.BuildUtil
|
||||||
import android.os.storage.StorageManager
|
import java.io.File
|
||||||
|
|
||||||
object RemovableStorageHelper {
|
object RemovableStorageHelper {
|
||||||
|
// This really shouldn't be necessary, but the Android API seemingly
|
||||||
private val pathCache = mutableMapOf<String, String?>()
|
// doesn't have a way of doing this?
|
||||||
private var scanned = false
|
fun getRemovableStoragePath(idString: String): String? {
|
||||||
|
|
||||||
private fun scanVolumes(context: Context) {
|
|
||||||
if (scanned) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
BuildUtil.assertNotGooglePlay()
|
||||||
val key = idString.uppercase()
|
|
||||||
|
|
||||||
if (!scanned) {
|
// On certain Android flavours the external storage mount location can
|
||||||
scanVolumes(context)
|
// vary, so add extra cases here if we discover them.
|
||||||
|
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathCache[key]
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -9,10 +9,6 @@ import android.view.ViewGroup
|
|||||||
|
|
||||||
object ViewUtils {
|
object ViewUtils {
|
||||||
fun showView(view: View, length: Long = 300) {
|
fun showView(view: View, length: Long = 300) {
|
||||||
if (view.visibility == View.VISIBLE) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
view.apply {
|
view.apply {
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright 2023 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -78,9 +78,8 @@ class GamesViewModel : ViewModel() {
|
|||||||
val filteredList = sortedList.filter {
|
val filteredList = sortedList.filter {
|
||||||
if (it.isSystemTitle) {
|
if (it.isSystemTitle) {
|
||||||
it.isVisibleSystemTitle
|
it.isVisibleSystemTitle
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
_games.value = filteredList
|
_games.value = filteredList
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user