mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 10:43:39 -04:00
Compare commits
207 commits
2125.0-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379649dbce | ||
|
|
c03248f158 | ||
|
|
4867bb2e2b | ||
|
|
4e4c7e687b | ||
|
|
56f738eb06 | ||
|
|
b1e537a485 | ||
|
|
59da460177 | ||
|
|
8bdb60a6e1 | ||
|
|
383a28795e | ||
|
|
725544f3b4 | ||
|
|
135f10320a | ||
|
|
0b7114cbf8 | ||
|
|
95d42cb40a | ||
|
|
f0bc64d967 | ||
|
|
ad8526c4cf | ||
|
|
ae7d7dca1f | ||
|
|
4a4b75b0de | ||
|
|
b186b04995 | ||
|
|
ab6896a2ca | ||
|
|
e11f3da493 | ||
|
|
8ffb94b06c | ||
|
|
267887d7a9 | ||
|
|
778ca369cd | ||
|
|
dbe7fd979f | ||
|
|
1c7c7a5f1b | ||
|
|
bf59d26c48 | ||
|
|
652fc02175 | ||
|
|
854e198196 | ||
|
|
5ecd402811 | ||
|
|
0ce2a30d20 | ||
|
|
644a181aff | ||
|
|
422c7865a3 | ||
|
|
ca99574700 | ||
|
|
f902010f04 | ||
|
|
929a51afc6 | ||
|
|
260f08c497 | ||
|
|
921ea178b9 | ||
|
|
b540725090 | ||
|
|
5ddbaeae23 | ||
|
|
b081f800a4 | ||
|
|
76a71d76d4 | ||
|
|
83eef0012e | ||
|
|
ec6a0dd1c8 | ||
|
|
eb498e5ecd | ||
|
|
4fa793b945 | ||
|
|
5d84dfed91 | ||
|
|
eee7f076ee | ||
|
|
996abd1eaf | ||
|
|
91128d6625 | ||
|
|
37b6c91de6 | ||
|
|
b6c54ac8c7 | ||
|
|
b3ee2d8ac5 | ||
|
|
9701a3d874 | ||
|
|
a276623dbb | ||
|
|
2fff086e81 | ||
|
|
5bc58c78ed | ||
|
|
0fe6a8c7df | ||
|
|
d4b5633cf0 | ||
|
|
afbaf8e485 | ||
|
|
52b1e01a6f | ||
|
|
f1cd5f5ff4 | ||
|
|
1edc5de18e | ||
|
|
727377c012 | ||
|
|
4dbe0fd497 | ||
|
|
6d230d28da | ||
|
|
336d871a7f | ||
|
|
e8c75b4107 | ||
|
|
0fc3d692b9 | ||
|
|
5983a23d38 | ||
|
|
599069eb33 | ||
|
|
6eca4afac5 | ||
|
|
b2faa299d5 | ||
|
|
3d69741076 | ||
|
|
d29e15f219 | ||
|
|
c650473fdc | ||
|
|
000530c028 | ||
|
|
df05b5f3db | ||
|
|
06a535f50e | ||
|
|
4cbd75b413 | ||
|
|
60f331b43b | ||
|
|
ba6f8cb744 | ||
|
|
118579adb3 | ||
|
|
23393904e0 | ||
|
|
3066887ff4 | ||
|
|
901f010913 | ||
|
|
5fc9732f05 | ||
|
|
39363cd435 | ||
|
|
60661c3b8b | ||
|
|
be0f096f48 | ||
|
|
6201256e15 | ||
|
|
af188bb7b7 | ||
|
|
0862e5e98a | ||
|
|
49b0bef17d | ||
|
|
f14f095e72 | ||
|
|
7e58ac5bcf | ||
|
|
7220bd2edd | ||
|
|
d4e9daa739 | ||
|
|
9b045bf837 | ||
|
|
7a600e28d2 | ||
|
|
5a07260e1b | ||
|
|
2c8297c34c | ||
|
|
7f9f1e90ca | ||
|
|
04a543290a | ||
|
|
8bcb8a225a | ||
|
|
64cb0b57fb | ||
|
|
7a60160f68 | ||
|
|
dc91e8803e | ||
|
|
c55435b78d | ||
|
|
f721a474e4 | ||
|
|
ab39df3ff0 | ||
|
|
2ff04dccba | ||
|
|
3d5ba09eb1 | ||
|
|
ae9972b6be | ||
|
|
e677f72bda | ||
|
|
4109bb200b | ||
|
|
6ad642a984 | ||
|
|
ccd61d0134 | ||
|
|
d97da17263 | ||
|
|
0ff2aebdf1 | ||
|
|
100b00b3b5 | ||
|
|
9e162705f4 | ||
|
|
b3f82618d7 | ||
|
|
fc6a410dfa | ||
|
|
56a563c239 | ||
|
|
6715959382 | ||
|
|
75cd4ce649 | ||
|
|
463db8ffe4 | ||
|
|
ecaebc54ff | ||
|
|
1febb83942 | ||
|
|
a3db3be4a6 | ||
|
|
909e4b7e1f | ||
|
|
e92272ce31 | ||
|
|
51170ea85d | ||
|
|
9a7cc43d81 | ||
|
|
e351fa56ce | ||
|
|
845fadf49e | ||
|
|
98910fed1c | ||
|
|
d9f28c5b2a | ||
|
|
784fc8cca9 | ||
|
|
a35a619903 | ||
|
|
3a5fa35449 | ||
|
|
0407568006 | ||
|
|
30779d35cd | ||
|
|
32da5ea0ae | ||
|
|
e878174df8 | ||
|
|
7ad6621f91 | ||
|
|
8e1ffc1bdc | ||
|
|
96485a22f8 | ||
|
|
97c9a51015 | ||
|
|
a8ebd0f551 | ||
|
|
fac63ce6b1 | ||
|
|
c71b2dc822 | ||
|
|
e87635095a | ||
|
|
0c624f16a7 | ||
|
|
d1d14cef79 | ||
|
|
7d5da9eaeb | ||
|
|
abc1980418 | ||
|
|
70c9e18eea | ||
|
|
46ca83cc36 | ||
|
|
1e0df67cc4 | ||
|
|
ced1ec0112 | ||
|
|
1b41c78afc | ||
|
|
748b97ac5e | ||
|
|
2207be30a9 | ||
|
|
8d284aeccf | ||
|
|
d49aa070fd | ||
|
|
92cd488754 | ||
|
|
efccedbbd2 | ||
|
|
93e831decb | ||
|
|
d6fadff3ee | ||
|
|
af980b4117 | ||
|
|
f23e296802 | ||
|
|
a0ab331928 | ||
|
|
068d6598bc | ||
|
|
fe2f637467 | ||
|
|
0f9d5f29f3 | ||
|
|
cb09d1e064 | ||
|
|
526d9d4cea | ||
|
|
b477ba09c1 | ||
|
|
6b2ac400eb | ||
|
|
5ac0ef8fde | ||
|
|
3255620934 | ||
|
|
03d62efe13 | ||
|
|
7d19679cc5 | ||
|
|
b3fd0b6c89 | ||
|
|
15bdd27b9c | ||
|
|
d721cbe29b | ||
|
|
17f4c52e56 | ||
|
|
27c3e0e5c3 | ||
|
|
8fac24d2a4 | ||
|
|
fe59958b63 | ||
|
|
13e0fdeac1 | ||
|
|
76db4b08f6 | ||
|
|
5d583a8a41 | ||
|
|
8b72dcb235 | ||
|
|
fcb345e273 | ||
|
|
4c054ff2e7 | ||
|
|
43cecd1692 | ||
|
|
ac0ec5edea | ||
|
|
c55165e19b | ||
|
|
1092295f2a | ||
|
|
9628300ff5 | ||
|
|
4010f4bc1f | ||
|
|
f3fb0b729e | ||
|
|
7bcbf8aba4 | ||
|
|
d9b77cc21e | ||
|
|
d0eaf07a40 |
445 changed files with 49397 additions and 18491 deletions
|
|
@ -12,6 +12,9 @@ fi
|
|||
|
||||
echo "Tag name is: $TAG_NAME"
|
||||
|
||||
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
mkdir -p build
|
||||
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
|
||||
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
||||
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
||||
|
||||
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
||||
|
|
|
|||
133
.ci/libretro-ci.yml
Normal file
133
.ci/libretro-ci.yml
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
.core-defs:
|
||||
variables:
|
||||
JNI_PATH: .
|
||||
CORENAME: azahar
|
||||
API_LEVEL: 21
|
||||
BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_TESTS=OFF
|
||||
CORE_ARGS: ${BASE_CORE_ARGS}
|
||||
EXTRA_PATH: bin/Release
|
||||
|
||||
variables:
|
||||
STATIC_RETROARCH_BRANCH: master
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Inclusion templates, required for the build to work
|
||||
include:
|
||||
################################## DESKTOPS ############################## ##
|
||||
# Windows 64-bit
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/windows-cmake-mingw.yml'
|
||||
|
||||
# Linux 64-bit
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/linux-cmake.yml'
|
||||
|
||||
# MacOS x86_64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/osx-cmake-x86.yml'
|
||||
|
||||
# MacOS ARM64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/osx-cmake-arm64.yml'
|
||||
|
||||
################################## CELLULAR ############################## ##
|
||||
# Android
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/android-cmake.yml'
|
||||
|
||||
# iOS
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/ios-cmake.yml'
|
||||
|
||||
# tvOS
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/tvos-cmake.yml'
|
||||
|
||||
################################## CONSOLES ############################## ##
|
||||
|
||||
# Stages for building
|
||||
stages:
|
||||
- build-prepare
|
||||
- build-shared
|
||||
- build-static
|
||||
|
||||
##############################################################################
|
||||
#################################### STAGES ##################################
|
||||
##############################################################################
|
||||
#
|
||||
################################### DESKTOPS #################################
|
||||
# Windows 64-bit
|
||||
libretro-build-windows-x64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-windows-cmake-x86_64
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF -G Ninja
|
||||
|
||||
# Linux 64-bit
|
||||
libretro-build-linux-x64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-linux-cmake-x86_64
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF
|
||||
CC: /usr/bin/gcc-12
|
||||
CXX: /usr/bin/g++-12
|
||||
|
||||
# MacOS x86_64
|
||||
libretro-build-osx-x64:
|
||||
tags:
|
||||
- mac-apple-silicon
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-osx-cmake-x86_64
|
||||
|
||||
# MacOS ARM64
|
||||
libretro-build-osx-arm64:
|
||||
extends:
|
||||
- .core-defs
|
||||
- .libretro-osx-cmake-arm64
|
||||
variables:
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
|
||||
################################### CELLULAR #################################
|
||||
# Android ARMv8a
|
||||
android-arm64-v8a:
|
||||
extends:
|
||||
- .libretro-android-cmake-arm64-v8a
|
||||
- .core-defs
|
||||
variables:
|
||||
ANDROID_NDK_VERSION: 26.2.11394342
|
||||
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
||||
LIBNAME: ${CORENAME}_libretro.so
|
||||
artifacts:
|
||||
paths:
|
||||
- $LIBNAME
|
||||
|
||||
# iOS arm64
|
||||
libretro-build-ios-arm64:
|
||||
extends:
|
||||
- .libretro-ios-cmake-arm64
|
||||
- .core-defs
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
IOS_MINVER: "14.0"
|
||||
EXTRA_PATH: bin/RelWithDebInfo
|
||||
|
||||
# tvOS arm64
|
||||
libretro-build-tvos-arm64:
|
||||
extends:
|
||||
- .libretro-tvos-cmake-arm64
|
||||
- .core-defs
|
||||
variables:
|
||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
MINVER: "14.0"
|
||||
EXTRA_PATH: bin/RelWithDebInfo
|
||||
|
||||
################################### CONSOLES #################################
|
||||
|
||||
13
.ci/libretro-pack.sh
Executable file
13
.ci/libretro-pack.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
# Determine the full revision name.
|
||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||
GITREV="`git show -s --format='%h'`"
|
||||
|
||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
|
||||
fi
|
||||
|
||||
# Create .zip
|
||||
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
if [[ "$TARGET" == "appimage"* ]]; then
|
||||
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
|
||||
# Compile the AppImage we distribute with Clang.
|
||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
ARTIFACTS_LIST=($ARTIFACTS)
|
||||
|
||||
BUNDLE_DIR=build/bundle
|
||||
mkdir build
|
||||
BUILD_DIR=build
|
||||
UNIVERSAL_DIR=$BUILD_DIR/universal
|
||||
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
|
||||
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
|
||||
|
||||
# Set up the base artifact to combine into.
|
||||
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
|
||||
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
|
||||
mv $BASE_ARTIFACT $BUNDLE_DIR
|
||||
# Set up the base bundle to combine into.
|
||||
mkdir $UNIVERSAL_DIR
|
||||
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
|
||||
|
||||
# Executable binary paths that need to be combined.
|
||||
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
||||
|
|
@ -19,21 +20,18 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
|
|||
unset IFS
|
||||
|
||||
# Combine all of the executable binaries and dylibs.
|
||||
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
|
||||
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
|
||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
|
||||
done
|
||||
|
||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
|
||||
done
|
||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||
|
||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||
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
|
||||
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
|
||||
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
|
||||
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove leftover libs so that they aren't distributed
|
||||
|
|
|
|||
11
.ci/macos.sh
11
.ci/macos.sh
|
|
@ -4,12 +4,10 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
|||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||
fi
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -GNinja \
|
||||
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
||||
cmake ../.. -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DENABLE_ROOM_STANDALONE=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
|
|
@ -18,9 +16,8 @@ ninja
|
|||
ninja bundle
|
||||
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
||||
|
||||
ccache -s -v
|
||||
|
||||
CURRENT_ARCH=`arch`
|
||||
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
||||
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
|
||||
ctest -VV -C Release
|
||||
fi
|
||||
|
|
|
|||
26
.ci/mxe.sh
Executable file
26
.ci/mxe.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
|
||||
# It works fine when using the image locally.
|
||||
export PATH="/mxe/usr/bin:${PATH}"
|
||||
|
||||
mkdir build && cd build
|
||||
|
||||
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
||||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||
fi
|
||||
|
||||
x86_64-w64-mingw32.shared-cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DUSE_SYSTEM_BOOST=ON \
|
||||
-DUSE_SYSTEM_CRYPTOPP=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
|
||||
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
|
||||
make bundle
|
||||
|
||||
ccache -s -v
|
||||
35
.ci/pack.sh
35
.ci/pack.sh
|
|
@ -3,20 +3,21 @@
|
|||
# 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-$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.
|
||||
mkdir -p 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"
|
||||
|
||||
# Set up root directory for archive.
|
||||
|
|
@ -35,10 +36,10 @@ function pack_artifacts() {
|
|||
fi
|
||||
|
||||
# Create .zip/.tar.gz
|
||||
if [ "$OS" = "windows" ]; then
|
||||
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||
else
|
||||
|
|
@ -56,11 +57,23 @@ if [ -n "$UNPACKED" ]; then
|
|||
FILENAME=$(basename "$ARTIFACT")
|
||||
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"
|
||||
done
|
||||
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
||||
# Pack and upload the artifacts one-by-one.
|
||||
for ARTIFACT in build/bundle/*; do
|
||||
TARGET=$(basename "$ARTIFACT")
|
||||
pack_artifacts "$ARTIFACT"
|
||||
done
|
||||
else
|
||||
|
|
|
|||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,3 +1,6 @@
|
|||
- [ ] 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
|
||||
|
|
|
|||
301
.github/workflows/build.yml
vendored
301
.github/workflows/build.yml
vendored
|
|
@ -7,28 +7,48 @@ on:
|
|||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
source:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: ./
|
||||
format: spdx-json
|
||||
output-file: artifacts/source.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.tar.xz
|
||||
sbom-path: artifacts/source.spdx.json
|
||||
|
||||
linux:
|
||||
linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["appimage", "appimage-wayland", "fresh"]
|
||||
target: ["appimage", "appimage-wayland", "gcc-nopch"]
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
|
|
@ -39,19 +59,20 @@ jobs:
|
|||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
${{ github.job }}-${{ matrix.target }}-
|
||||
- name: Build
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: ./.ci/linux.sh
|
||||
|
|
@ -64,93 +85,132 @@ jobs:
|
|||
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||
- name: Generate SBOM
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
name: ${{ github.job }}-${{ matrix.target }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.AppImage
|
||||
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
|
||||
macos:
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
|
||||
linux-arm64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
target: ["clang", "gcc-nopch"]
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
OS: macos
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
${{ github.job }}-${{ matrix.target }}-
|
||||
- name: Build
|
||||
run: ./.ci/linux.sh
|
||||
|
||||
macos:
|
||||
runs-on: 'macos-26'
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: macos
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-
|
||||
- name: Install tools
|
||||
run: brew install ccache ninja spirv-tools
|
||||
- name: Build
|
||||
run: ./.ci/macos.sh
|
||||
- name: Prepare outputs for caching
|
||||
run: cp -R build/bundle $OS-$TARGET
|
||||
- name: Cache outputs for universal build
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
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: Build (x86_64)
|
||||
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
|
||||
- name: Build (arm64)
|
||||
run: BUILD_ARCH=arm64 ./.ci/macos.sh
|
||||
- name: Create universal app
|
||||
run: ./.ci/macos-universal.sh
|
||||
env:
|
||||
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
|
||||
- name: Prepare for packing
|
||||
run: |
|
||||
mkdir build/bundle
|
||||
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
|
||||
env:
|
||||
PACK_INDIVIDUALLY: 1
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/macos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
sbom-path: artifacts/macos.spdx.json
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["msvc", "msys2"]
|
||||
include:
|
||||
- target: msvc
|
||||
os: windows-latest
|
||||
- target: msys2
|
||||
os: windows-latest
|
||||
- target: mxe
|
||||
os: ubuntu-latest
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
||||
|
|
@ -158,14 +218,16 @@ jobs:
|
|||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
|
|
@ -173,7 +235,7 @@ jobs:
|
|||
${{ runner.os }}-${{ matrix.target }}-
|
||||
- name: Set up MSVC
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
uses: azahar-emu/msvc-dev-cmd@v1
|
||||
- name: Install extra tools (MSVC)
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
run: choco install ccache ninja ptime wget
|
||||
|
|
@ -194,34 +256,62 @@ jobs:
|
|||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Install extra tools (MSYS2)
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
uses: crazy-max/ghaction-chocolatey@v3
|
||||
uses: crazy-max/ghaction-chocolatey@v4
|
||||
with:
|
||||
args: install ptime wget
|
||||
- name: Install NSIS
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
|
||||
ptime D:/a/_temp/nsis-setup.exe /S
|
||||
shell: pwsh
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
- name: Build (Native)
|
||||
if: ${{ matrix.target != 'mxe' }}
|
||||
run: ./.ci/windows.sh
|
||||
- name: Generate installer
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
- name: Build (MXE)
|
||||
if: ${{ matrix.target == 'mxe' }}
|
||||
run: ./.ci/mxe.sh
|
||||
- name: Generate installer (Native)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
cd src\installer
|
||||
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir ..\..\artifacts 2> NUL
|
||||
move /y *.exe ..\..\artifacts\
|
||||
shell: cmd
|
||||
- name: Generate installer (MXE)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
|
||||
run: |
|
||||
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
|
||||
cd src/installer
|
||||
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir -p ../../artifacts
|
||||
mv ./*.exe ../../artifacts/
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
artifacts/*.exe
|
||||
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -233,17 +323,18 @@ jobs:
|
|||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: android
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
|
@ -281,23 +372,48 @@ jobs:
|
|||
working-directory: src/android/app
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: src/android
|
||||
format: spdx-json
|
||||
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
src/android/app/artifacts/*.apk
|
||||
src/android/app/artifacts/*.aab
|
||||
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64
|
||||
os: ubuntu-24.04
|
||||
- target: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: docker:dind
|
||||
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
||||
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: apk add bash
|
||||
- name: Fix git ownership
|
||||
run: git config --global --add safe.directory .
|
||||
- name: Build Docker image
|
||||
|
|
@ -306,8 +422,23 @@ jobs:
|
|||
run: |
|
||||
mkdir -p artifacts
|
||||
mv build/*.dockerimage artifacts/
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
name: docker
|
||||
path: artifacts/
|
||||
image: ${{ env.DOCKER_IMAGE_PATH }}
|
||||
format: spdx-json
|
||||
output-file: artifacts/docker-room.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.dockerimage
|
||||
sbom-path: artifacts/docker-room.spdx.json
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
(github.event.pull_request.author_association != 'OWNER')
|
||||
steps:
|
||||
- name: Detect PR if author is first-time contributor
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
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
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
|
|
|||
2
.github/workflows/format.yml
vendored
2
.github/workflows/format.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
|
|
|
|||
304
.github/workflows/libretro.yml
vendored
Normal file
304
.github/workflows/libretro.yml
vendored
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
name: citra-libretro
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
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@v6
|
||||
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: Install tools
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- 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)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-android.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-android.spdx.json
|
||||
|
||||
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@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-linux.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-linux.spdx.json
|
||||
|
||||
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: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
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) && \
|
||||
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-windows.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-windows.spdx.json
|
||||
macos:
|
||||
runs-on: macos-26
|
||||
strategy:
|
||||
fail-fast: false
|
||||
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@v6
|
||||
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
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
|
||||
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@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-ios.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-ios.spdx.json
|
||||
|
||||
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@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-tvos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-tvos.spdx.json
|
||||
2
.github/workflows/license-header.yml
vendored
2
.github/workflows/license-header.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch master branch
|
||||
|
|
|
|||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 90
|
||||
|
|
|
|||
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
|
|
@ -7,10 +7,10 @@ on:
|
|||
jobs:
|
||||
transifex:
|
||||
runs-on: ubuntu-latest
|
||||
container: opensauce04/azahar-build-environment:transifex
|
||||
container: opensauce04/azahar-build-environment:latest
|
||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
|
|
|||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -56,3 +56,10 @@ repo/
|
|||
.ccache/
|
||||
node_modules/
|
||||
VULKAN_SDK/
|
||||
|
||||
# Version info files
|
||||
GIT-COMMIT
|
||||
GIT-TAG
|
||||
|
||||
# verify-release.sh downloads
|
||||
verify/
|
||||
17
.gitmodules
vendored
17
.gitmodules
vendored
|
|
@ -55,18 +55,12 @@
|
|||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "cryptopp-cmake"]
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
[submodule "cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
[submodule "dds-ktx"]
|
||||
path = externals/dds-ktx
|
||||
url = https://github.com/septag/dds-ktx
|
||||
[submodule "openal-soft"]
|
||||
path = externals/openal-soft
|
||||
url = https://github.com/kcat/openal-soft
|
||||
url = https://github.com/azahar-emu/openal-soft
|
||||
[submodule "glslang"]
|
||||
path = externals/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
|
|
@ -103,3 +97,12 @@
|
|||
[submodule "externals/xxHash"]
|
||||
path = externals/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
[submodule "externals/libretro-common"]
|
||||
path = externals/libretro-common/libretro-common
|
||||
url = https://github.com/libretro/libretro-common.git
|
||||
[submodule "dllwalker"]
|
||||
path = externals/dllwalker
|
||||
url = https://github.com/azahar-emu/dllwalker
|
||||
[submodule "externals/cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/cryptopp-modern/cryptopp-modern.git
|
||||
|
|
|
|||
20
AI-POLICY.md
Normal file
20
AI-POLICY.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# 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.
|
||||
|
|
@ -13,24 +13,39 @@ cmake_policy(SET CMP0063 NEW)
|
|||
cmake_policy(SET CMP0127 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
|
||||
|
||||
# Prefer building bundled dependencies as static instead of shared
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
include(FindPkgConfig)
|
||||
|
||||
project(citra LANGUAGES C CXX ASM)
|
||||
# must be invoked after project() command when using CMAKE_TOOLCHAIN_FILE
|
||||
include(FindPkgConfig)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
|
||||
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
|
||||
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
|
||||
endif()
|
||||
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
add_link_options(-z wxneeded)
|
||||
endif()
|
||||
|
||||
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
|
||||
|
||||
# Some submodules like to pick their own default build type if not specified.
|
||||
# Make sure we default to Release build type always, unless the generator has custom types.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (APPLE AND NOT ENABLE_LIBRETRO)
|
||||
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
|
|
@ -90,8 +105,18 @@ else()
|
|||
set(DEFAULT_ENABLE_OPENGL ON)
|
||||
endif()
|
||||
|
||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||
set(_USER_SET_OPTIONS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
if(DEFINED ${_opt})
|
||||
list(APPEND _USER_SET_OPTIONS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
option(ENABLE_SDL2 "Enable using SDL2" ON)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF)
|
||||
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
||||
|
||||
# Set bundled qt as dependent options.
|
||||
|
|
@ -105,6 +130,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
|
|||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||
|
|
@ -113,7 +139,8 @@ 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_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
||||
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
|
|
@ -123,6 +150,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
|
|||
|
||||
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
|
||||
|
||||
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON)
|
||||
|
||||
# Compile options
|
||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
||||
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
||||
|
|
@ -130,6 +159,31 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
|
|||
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
|
||||
|
||||
# Handle incompatible options for libretro builds
|
||||
if(ENABLE_LIBRETRO)
|
||||
# Check for explicitly-set conflicting options
|
||||
set(_CONFLICTS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
list(FIND _USER_SET_OPTIONS ${_opt} _idx)
|
||||
if(NOT _idx EQUAL -1 AND ${_opt})
|
||||
list(APPEND _CONFLICTS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(_CONFLICTS)
|
||||
string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}")
|
||||
message(FATAL_ERROR
|
||||
"ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n"
|
||||
"These options were explicitly enabled but are not supported for libretro builds.\n"
|
||||
"Remove these options or set them to OFF.")
|
||||
endif()
|
||||
|
||||
# Force disable incompatible options (handles defaulted-on options)
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Pass the following values to C++ land
|
||||
if (ENABLE_QT)
|
||||
add_definitions(-DENABLE_QT)
|
||||
|
|
@ -143,9 +197,6 @@ endif()
|
|||
if (ENABLE_SDL2)
|
||||
add_definitions(-DENABLE_SDL2)
|
||||
endif()
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_definitions(-DENABLE_SDL2_FRONTEND)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
|
||||
message(STATUS "SSE4.2 enabled for x86_64")
|
||||
|
|
@ -223,7 +274,7 @@ function(check_submodules_present)
|
|||
foreach(module ${gitmodules})
|
||||
string(REGEX REPLACE "path *= *" "" module ${module})
|
||||
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
||||
message(SEND_ERROR "Git submodule ${module} not found."
|
||||
message(SEND_ERROR "Git submodule ${module} not found.\n"
|
||||
"Please run: git submodule update --init --recursive")
|
||||
endif()
|
||||
endforeach()
|
||||
|
|
@ -300,6 +351,9 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
|
|||
# set up output paths for executable binaries
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
|
@ -357,13 +411,21 @@ if (APPLE)
|
|||
endif()
|
||||
find_library(AVFOUNDATION_LIBRARY AVFoundation 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})
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
if (NOT USE_SYSTEM_MOLTENVK)
|
||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||
if (USE_SYSTEM_MOLTENVK)
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
else()
|
||||
download_moltenvk()
|
||||
if (IOS)
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
|
||||
else()
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
|
||||
endif()
|
||||
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
|
||||
endif()
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
|
||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
||||
endif()
|
||||
|
|
@ -513,8 +575,6 @@ if (NOT ANDROID AND NOT IOS)
|
|||
include(BundleTarget)
|
||||
if (ENABLE_QT)
|
||||
qt_bundle_target(citra_meta)
|
||||
elseif (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra_meta)
|
||||
endif()
|
||||
if (ENABLE_ROOM_STANDALONE)
|
||||
bundle_target(citra_room_standalone)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,10 @@ if (BUNDLE_TARGET_EXECUTE)
|
|||
|
||||
# On Linux, always bundle an AppImage.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (IS_MINGW)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (IN_PLACE)
|
||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||
endif()
|
||||
|
|
@ -273,15 +277,23 @@ else()
|
|||
|
||||
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
if (MINGW)
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
# The target here is arbitrary
|
||||
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
|
||||
POST_BUILD)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
|
@ -293,6 +305,11 @@ else()
|
|||
create_base_bundle_target()
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
|
||||
# We don't really need to "bundle" MXE builds, so don't do anything
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||
if (bundle_qt AND APPLE)
|
||||
# For Qt targets on Apple, expect an app bundle.
|
||||
|
|
@ -331,6 +348,7 @@ else()
|
|||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||
"-DBUNDLE_QT=${bundle_qt}"
|
||||
"-DIN_PLACE=${in_place}"
|
||||
"-DIS_MINGW=${MINGW}"
|
||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
|
|
|
|||
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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()
|
||||
|
|
@ -171,15 +171,8 @@ function(download_qt target)
|
|||
endfunction()
|
||||
|
||||
function(download_moltenvk)
|
||||
if (IOS)
|
||||
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
|
||||
else()
|
||||
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
|
||||
endif()
|
||||
|
||||
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
|
||||
if (NOT EXISTS ${MOLTENVK_DIR})
|
||||
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
if (NOT EXISTS ${MOLTENVK_TAR})
|
||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
||||
${MOLTENVK_TAR} SHOW_PROGRESS)
|
||||
|
|
@ -188,10 +181,6 @@ function(download_moltenvk)
|
|||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||
endif()
|
||||
|
||||
# Add the MoltenVK library path to the prefix so find_library can locate it.
|
||||
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(get_external_prefix lib_name prefix_var)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
macro(generate_build_info)
|
||||
find_package(Git QUIET)
|
||||
|
||||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
|
|
@ -6,9 +8,14 @@ macro(generate_build_info)
|
|||
endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
|
||||
|
||||
if (EXISTS "${SRC_DIR}/.git/objects")
|
||||
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
|
||||
# Find the package here with the known path so that the GetGit commands can find it as well
|
||||
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
||||
|
||||
|
|
@ -17,12 +24,6 @@ macro(generate_build_info)
|
|||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||
# unified source archive
|
||||
file(READ "${SRC_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
else()
|
||||
# self-packed archive?
|
||||
set(GIT_REV "UNKNOWN")
|
||||
|
|
@ -39,8 +40,8 @@ macro(generate_build_info)
|
|||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||
endif()
|
||||
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
|
||||
string(STRIP ${GIT_TAG} GIT_TAG)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
|
||||
|
||||
include(GenerateBuildInfo)
|
||||
generate_build_info()
|
||||
|
||||
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
||||
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
|
||||
set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||
|
|
@ -47,4 +48,4 @@ foreach (F IN LISTS HASH_FILES)
|
|||
set(COMBINED "${COMBINED}${TMP}")
|
||||
endforeach()
|
||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
|
|
|
|||
282
CMakeModules/GenerateSettingKeys.cmake
Normal file
282
CMakeModules/GenerateSettingKeys.cmake
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
## 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"
|
||||
"async_fs_operations"
|
||||
"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"
|
||||
"simulate_3ds_gpu_timings"
|
||||
"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"
|
||||
"simulate_headphones_plugged"
|
||||
"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"
|
||||
"break_on_unmapped_memory_access"
|
||||
"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 +0,0 @@
|
|||
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**
|
||||
|
|
@ -1,6 +1,8 @@
|
|||

|
||||
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
|
|
|
|||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
|
||||
Subproject commit d9f1126e42b606d02ecc89b10cb9a336a3b2f5a3
|
||||
2
dist/languages/.tx/config
vendored
2
dist/languages/.tx/config
vendored
|
|
@ -12,4 +12,4 @@ lang_map = ca@valencia:ca_ES_valencia
|
|||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW
|
||||
lang_map = ca@valencia:b+ca+ES+valencia, es_419:b+es+419, pt_BR:b+pt+BR, zh_CN:b+zh+CN, zh_TW:b+zh+TW
|
||||
|
|
|
|||
1314
dist/languages/ca_ES_valencia.ts
vendored
1314
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load diff
1163
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
1163
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load diff
1357
dist/languages/de.ts
vendored
1357
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
2212
dist/languages/el.ts
vendored
2212
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load diff
1198
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
1198
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load diff
7928
dist/languages/es_419.ts
vendored
Normal file
7928
dist/languages/es_419.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
1169
dist/languages/fi.ts
vendored
1169
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load diff
1198
dist/languages/fr.ts
vendored
1198
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
1167
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
1167
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load diff
1169
dist/languages/id.ts
vendored
1169
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load diff
1244
dist/languages/it.ts
vendored
1244
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
1165
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
1165
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
File diff suppressed because it is too large
Load diff
1173
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
1173
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
File diff suppressed because it is too large
Load diff
1171
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
1171
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
File diff suppressed because it is too large
Load diff
1601
dist/languages/nb.ts
vendored
1601
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
1169
dist/languages/nl.ts
vendored
1169
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
1180
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
1180
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load diff
1200
dist/languages/pt_BR.ts
vendored
1200
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
1171
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
1171
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
File diff suppressed because it is too large
Load diff
1165
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
1165
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
File diff suppressed because it is too large
Load diff
1182
dist/languages/sv.ts
vendored
1182
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load diff
1171
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
1171
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
File diff suppressed because it is too large
Load diff
1171
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
1171
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load diff
1173
dist/languages/zh_CN.ts
vendored
1173
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
1169
dist/languages/zh_TW.ts
vendored
1169
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -4,23 +4,19 @@
|
|||
# --- Builder ----------------
|
||||
FROM opensauce04/azahar-build-environment:latest AS builder
|
||||
|
||||
COPY . /var/azahar-src
|
||||
RUN mkdir /var/azahar-src/
|
||||
COPY ./ /var/azahar-src/
|
||||
|
||||
RUN mkdir builddir && cd builddir && \
|
||||
cmake /var/azahar-src -G Ninja \
|
||||
RUN mkdir ./builddir/
|
||||
WORKDIR ./builddir/
|
||||
RUN cmake /var/azahar-src -G Ninja \
|
||||
-DENABLE_QT=OFF \
|
||||
-DENABLE_GDBSTUB=OFF \
|
||||
-DENABLE_TESTS=OFF \
|
||||
-DENABLE_ROOM=ON \
|
||||
-DENABLE_ROOM_STANDALONE=ON \
|
||||
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
|
||||
-DENABLE_VULKAN=OFF \
|
||||
-DENABLE_SDL2=OFF \
|
||||
-DENABLE_LIBUSB=OFF \
|
||||
-DENABLE_CUBEB=OFF \
|
||||
-DENABLE_OPENAL=OFF && \
|
||||
ninja && \
|
||||
mv bin/Release/azahar-room /usr/local/bin/ && \
|
||||
cd .. && rm -rf builddir
|
||||
-DENABLE_ROOM_STANDALONE=ON
|
||||
RUN ninja
|
||||
RUN mv ./bin/Release/azahar-room /usr/local/bin/
|
||||
|
||||
# --- Final ------------------
|
||||
FROM debian:trixie AS final
|
||||
|
|
|
|||
96
externals/CMakeLists.txt
vendored
96
externals/CMakeLists.txt
vendored
|
|
@ -50,15 +50,18 @@ else()
|
|||
endif()
|
||||
|
||||
# Catch2
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
if (ENABLE_TESTS)
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
include(Catch)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
|
||||
# Crypto++
|
||||
if(USE_SYSTEM_CRYPTOPP)
|
||||
|
|
@ -66,17 +69,9 @@ if(USE_SYSTEM_CRYPTOPP)
|
|||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
|
||||
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
||||
add_subdirectory(cryptopp-cmake)
|
||||
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# dds-ktx
|
||||
|
|
@ -109,7 +104,13 @@ endif()
|
|||
|
||||
# Oaknut
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||
if(USE_SYSTEM_OAKNUT)
|
||||
find_package(oaknut REQUIRED)
|
||||
add_library(oaknut INTERFACE)
|
||||
target_link_libraries(oaknut INTERFACE merry::oaknut)
|
||||
else()
|
||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Dynarmic
|
||||
|
|
@ -133,7 +134,7 @@ endif()
|
|||
|
||||
# getopt
|
||||
if (MSVC)
|
||||
add_subdirectory(getopt)
|
||||
add_subdirectory(getopt EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# inih
|
||||
|
|
@ -142,7 +143,7 @@ if(USE_SYSTEM_INIH)
|
|||
add_library(inih INTERFACE)
|
||||
target_link_libraries(inih INTERFACE inih::inih inih::inir)
|
||||
else()
|
||||
add_subdirectory(inih)
|
||||
add_subdirectory(inih EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# MicroProfile
|
||||
|
|
@ -165,7 +166,7 @@ if (NOT MSVC)
|
|||
endif()
|
||||
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives)
|
||||
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
|
||||
|
||||
# faad2
|
||||
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||
|
|
@ -204,12 +205,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
|
|||
|
||||
# SDL2
|
||||
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
|
||||
add_subdirectory(sdl2)
|
||||
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# libusb
|
||||
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
||||
add_subdirectory(libusb)
|
||||
add_subdirectory(libusb EXCLUDE_FROM_ALL)
|
||||
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
|
||||
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
|
||||
endif()
|
||||
|
|
@ -253,7 +254,7 @@ if(USE_SYSTEM_ENET)
|
|||
add_library(enet INTERFACE)
|
||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||
else()
|
||||
add_subdirectory(enet)
|
||||
add_subdirectory(enet EXCLUDE_FROM_ALL)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
endif()
|
||||
|
||||
|
|
@ -292,6 +293,15 @@ if (USE_DISCORD_PRESENCE)
|
|||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
# LibRetro
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_library(libretro INTERFACE)
|
||||
target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include)
|
||||
if (ANDROID)
|
||||
add_subdirectory(libretro-common EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# JSON
|
||||
add_library(json-headers INTERFACE)
|
||||
if (USE_SYSTEM_JSON)
|
||||
|
|
@ -309,12 +319,8 @@ endif()
|
|||
# OpenSSL
|
||||
if (USE_SYSTEM_OPENSSL)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
else()
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
|
|
@ -356,7 +362,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
|||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_subdirectory(gamemode)
|
||||
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
|
|
@ -379,13 +385,13 @@ if(USE_SYSTEM_LODEPNG)
|
|||
find_package(lodepng REQUIRED)
|
||||
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
|
||||
else()
|
||||
add_subdirectory(lodepng)
|
||||
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
||||
if(ANDROID)
|
||||
# libyuv
|
||||
add_subdirectory(libyuv)
|
||||
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
|
||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||
endif()
|
||||
|
||||
|
|
@ -396,6 +402,9 @@ if (ENABLE_OPENAL)
|
|||
find_package(OpenAL REQUIRED)
|
||||
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
|
||||
else()
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
|
||||
endif()
|
||||
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
||||
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
||||
set(ALSOFT_INSTALL OFF CACHE BOOL "")
|
||||
|
|
@ -411,7 +420,7 @@ endif()
|
|||
# OpenGL dependencies
|
||||
if (ENABLE_OPENGL)
|
||||
# Glad
|
||||
add_subdirectory(glad)
|
||||
add_subdirectory(glad EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# Vulkan dependencies
|
||||
|
|
@ -451,7 +460,7 @@ if (ENABLE_VULKAN)
|
|||
set(ENABLE_CTEST OFF CACHE BOOL "")
|
||||
set(ENABLE_HLSL OFF CACHE BOOL "")
|
||||
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
||||
add_subdirectory(glslang)
|
||||
add_subdirectory(glslang EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# sirit
|
||||
|
|
@ -482,11 +491,24 @@ if (ENABLE_VULKAN)
|
|||
else()
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
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)
|
||||
elseif (BSD STREQUAL "OpenBSD")
|
||||
# This is fine to hardcode because it'll never change
|
||||
target_include_directories(vulkan-headers INTERFACE
|
||||
/usr/X11R6/include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# adrenotools
|
||||
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(libadrenotools)
|
||||
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
@ -502,4 +524,4 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|armv8")
|
|||
else()
|
||||
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SCALAR)
|
||||
message(STATUS "Disabling SIMD for xxHash")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
2
externals/boost
vendored
2
externals/boost
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61
|
||||
|
|
@ -14,6 +14,7 @@ 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_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_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_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)
|
||||
|
|
@ -40,6 +41,7 @@ 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_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_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_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
|
|
@ -66,6 +68,7 @@ set(LIB_VAR_LIST
|
|||
DYNARMIC
|
||||
FMT
|
||||
XBYAK
|
||||
OAKNUT
|
||||
INIH
|
||||
FFMPEG_HEADERS
|
||||
GLSLANG
|
||||
|
|
|
|||
2
externals/cryptopp
vendored
2
externals/cryptopp
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e
|
||||
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab
|
||||
1
externals/cryptopp-cmake
vendored
1
externals/cryptopp-cmake
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c
|
||||
1
externals/dllwalker
vendored
Submodule
1
externals/dllwalker
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e
|
||||
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
add_library(libretro_common STATIC
|
||||
libretro-common/compat/compat_posix_string.c
|
||||
libretro-common/compat/fopen_utf8.c
|
||||
libretro-common/encodings/encoding_utf.c
|
||||
libretro-common/compat/compat_strl.c
|
||||
libretro-common/file/file_path.c
|
||||
libretro-common/streams/file_stream.c
|
||||
libretro-common/streams/file_stream_transforms.c
|
||||
libretro-common/string/stdstring.c
|
||||
libretro-common/time/rtime.c
|
||||
libretro-common/vfs/vfs_implementation.c
|
||||
)
|
||||
target_include_directories(libretro_common PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include
|
||||
)
|
||||
1
externals/libretro-common/libretro-common
vendored
Submodule
1
externals/libretro-common/libretro-common
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d
|
||||
2
externals/openal-soft
vendored
2
externals/openal-soft
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727
|
||||
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# Enable modules to include each other's files
|
||||
include_directories(.)
|
||||
|
||||
include(GenerateSettingKeys)
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
|
@ -110,10 +112,14 @@ else()
|
|||
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
||||
-Wno-unused-command-line-argument
|
||||
# Build fortification options
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-protector-strong
|
||||
-fstack-clash-protection
|
||||
)
|
||||
if (NOT ENABLE_LIBRETRO)
|
||||
add_compile_options(
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-clash-protection
|
||||
)
|
||||
endif()
|
||||
|
||||
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
|
||||
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
|
||||
|
|
@ -177,6 +183,9 @@ endif()
|
|||
if(ENABLE_DEVELOPER_OPTIONS)
|
||||
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
|
||||
endif()
|
||||
if(ENABLE_BUILTIN_KEYBLOB)
|
||||
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
|
||||
endif()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
|
|
@ -189,18 +198,19 @@ if (ENABLE_TESTS)
|
|||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_subdirectory(citra_sdl)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(citra_qt)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
|
||||
if (ENABLE_QT) # Or any other hypothetical future frontends
|
||||
add_subdirectory(citra_cli)
|
||||
add_subdirectory(citra_meta)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_subdirectory(citra_libretro)
|
||||
endif()
|
||||
|
||||
if (ENABLE_ROOM)
|
||||
add_subdirectory(citra_room)
|
||||
endif()
|
||||
|
|
@ -209,7 +219,7 @@ if (ENABLE_ROOM_STANDALONE)
|
|||
add_subdirectory(citra_room_standalone)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
||||
add_subdirectory(android/app/src/main/jni)
|
||||
target_include_directories(citra-android PRIVATE android/app/src/main)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -24,12 +24,11 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
|
|||
|
||||
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.citra.citra_emu"
|
||||
|
||||
compileSdkVersion = "android-35"
|
||||
ndkVersion = "27.1.12297006"
|
||||
ndkVersion = "27.3.13750724"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
|
|
@ -63,7 +62,7 @@ android {
|
|||
defaultConfig {
|
||||
// The application ID refers to Lime3DS to allow for
|
||||
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
|
||||
applicationId = "io.github.lime3ds.android"
|
||||
applicationId = "org.azahar_emu.azahar"
|
||||
minSdk = 29
|
||||
targetSdk = 35
|
||||
versionCode = autoVersion
|
||||
|
|
@ -80,7 +79,8 @@ android {
|
|||
"-DENABLE_QT=0", // Don't use QT
|
||||
"-DENABLE_SDL2=0", // Don't use SDL
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
|
||||
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +173,7 @@ android {
|
|||
register("googlePlay") {
|
||||
dimension = "version"
|
||||
versionNameSuffix = "-googleplay"
|
||||
applicationId = "io.github.lime3ds.android"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ import android.view.Surface
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
|
@ -132,7 +134,27 @@ object NativeLibrary {
|
|||
* If not set, it auto-detects a location
|
||||
*/
|
||||
external fun setUserDirectory(directory: String)
|
||||
external fun getInstalledGamePaths(): Array<String?>
|
||||
|
||||
data class InstalledGame(
|
||||
val path: String,
|
||||
val mediaType: Game.MediaType
|
||||
)
|
||||
fun getInstalledGamePaths(): Array<InstalledGame> {
|
||||
val games = getInstalledGamePathsImpl()
|
||||
|
||||
return games.mapNotNull { entry ->
|
||||
entry?.let {
|
||||
val sep = it.lastIndexOf('|')
|
||||
if (sep == -1) return@mapNotNull null
|
||||
|
||||
val path = it.substring(0, sep)
|
||||
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
|
||||
|
||||
InstalledGame(path, mediaType!!)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
private external fun getInstalledGamePathsImpl(): Array<String?>
|
||||
|
||||
// Create the config.ini file.
|
||||
external fun createConfigFile()
|
||||
|
|
@ -230,6 +252,16 @@ object NativeLibrary {
|
|||
external fun playTimeManagerGetPlayTime(titleId: Long): Long
|
||||
external fun playTimeManagerGetCurrentTitleId(): Long
|
||||
|
||||
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
|
||||
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
|
||||
return uninstallTitle(titleId, mediaType.value)
|
||||
}
|
||||
|
||||
external fun nativeFileExists(path: String): Boolean
|
||||
|
||||
external fun deleteOpenGLShaderCache(titleId: Long)
|
||||
external fun deleteVulkanShaderCache(titleId: Long)
|
||||
|
||||
private var coreErrorAlertResult = false
|
||||
private val coreErrorAlertLock = Object()
|
||||
|
||||
|
|
@ -286,6 +318,12 @@ object NativeLibrary {
|
|||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorCoreExceptionRaised -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorUnknown -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
|
|
@ -408,7 +446,7 @@ object NativeLibrary {
|
|||
return
|
||||
}
|
||||
|
||||
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
||||
if (resultCode == CoreError.ShutdownRequested.value) {
|
||||
emulationActivity.finish()
|
||||
return
|
||||
}
|
||||
|
|
@ -427,23 +465,51 @@ object NativeLibrary {
|
|||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
|
||||
var captionId = R.string.loader_error_invalid_format
|
||||
val result = requireArguments().getInt(RESULT_CODE)
|
||||
if (result == ErrorLoader_ErrorEncrypted) {
|
||||
captionId = R.string.loader_error_encrypted
|
||||
}
|
||||
if (result == ErrorArticDisconnected) {
|
||||
captionId = R.string.artic_base
|
||||
var coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||
val title: String
|
||||
val message: String
|
||||
when (coreError) {
|
||||
CoreError.ErrorGetLoader, CoreError.ErrorLoader_ErrorInvalidFormat, CoreError.ErrorSystemMode -> {
|
||||
title = getString(R.string.loader_error_invalid_format)
|
||||
message = getString(R.string.loader_error_invalid_format_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorEncrypted -> {
|
||||
title = getString(R.string.loader_error_encrypted)
|
||||
message = getString(R.string.loader_error_encrypted_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorArticDisconnected -> {
|
||||
title = getString(R.string.artic_base)
|
||||
message = getString(R.string.artic_server_comm_error)
|
||||
}
|
||||
|
||||
CoreError.ErrorN3DSApplication -> {
|
||||
title = getString(R.string.loader_error_invalid_system_mode)
|
||||
message = getString(R.string.loader_error_invalid_system_mode_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorPatches -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_applying_patches_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorPatchesInvalidTitle -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_patch_wrong_application)
|
||||
}
|
||||
|
||||
else -> {
|
||||
title = getString(R.string.loader_error_generic_title)
|
||||
message = getString(R.string.loader_error_generic,
|
||||
getString(coreError.stringRes), coreError.value)
|
||||
}
|
||||
}
|
||||
|
||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(captionId)
|
||||
.setTitle(title)
|
||||
.setMessage(
|
||||
Html.fromHtml(
|
||||
if (result == ErrorArticDisconnected)
|
||||
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
|
||||
else
|
||||
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
||||
Html.fromHtml(message,
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
|
|
@ -465,20 +531,6 @@ object NativeLibrary {
|
|||
|
||||
const val RESULT_CODE = "resultcode"
|
||||
|
||||
const val Success = 0
|
||||
const val ErrorNotInitialized = 1
|
||||
const val ErrorGetLoader = 2
|
||||
const val ErrorSystemMode = 3
|
||||
const val ErrorLoader = 4
|
||||
const val ErrorLoader_ErrorEncrypted = 5
|
||||
const val ErrorLoader_ErrorInvalidFormat = 6
|
||||
const val ErrorLoader_ErrorGBATitle = 7
|
||||
const val ErrorSystemFiles = 8
|
||||
const val ErrorSavestate = 9
|
||||
const val ErrorArticDisconnected = 10
|
||||
const val ShutdownRequested = 11
|
||||
const val ErrorUnknown = 12
|
||||
|
||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putInt(RESULT_CODE, resultCode)
|
||||
|
|
@ -691,34 +743,47 @@ object NativeLibrary {
|
|||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getUserDirectory(uriOverride: Uri? = null): String {
|
||||
fun getNativePath(uri: Uri): String {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
|
||||
val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
|
||||
val dirSep = "/"
|
||||
val udUri = uriOverride ?:
|
||||
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||
val udPathSegment = udUri.lastPathSegment!!
|
||||
val udVirtualPath = udPathSegment.substringAfter(":")
|
||||
|
||||
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
|
||||
val uriString = uri.toString()
|
||||
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
|
||||
return uriString
|
||||
}
|
||||
|
||||
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
|
||||
return primaryStoragePath + dirSep + udVirtualPath + dirSep
|
||||
return primaryStoragePath + dirSep + virtualPath
|
||||
} else { // User directory probably located on a removable storage device
|
||||
val storageIdString = udPathSegment.substringBefore(":")
|
||||
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
|
||||
val storageIdString = pathSegment.substringBefore(":")
|
||||
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
|
||||
|
||||
if (udRemovablePath == null) {
|
||||
if (removablePath == null) {
|
||||
android.util.Log.e("NativeLibrary",
|
||||
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
|
||||
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
||||
)
|
||||
return ""
|
||||
}
|
||||
return udRemovablePath + dirSep + udVirtualPath + dirSep
|
||||
return removablePath + dirSep + virtualPath
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getUserDirectory(): String {
|
||||
val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||
return getNativePath(userDirectoryUri)
|
||||
}
|
||||
|
||||
@Keep
|
||||
|
|
@ -812,12 +877,31 @@ object NativeLibrary {
|
|||
FileUtil.deleteDocument(path)
|
||||
}
|
||||
|
||||
enum class CoreError {
|
||||
ErrorSystemFiles,
|
||||
ErrorSavestate,
|
||||
ErrorArticDisconnected,
|
||||
ErrorN3DSApplication,
|
||||
ErrorUnknown
|
||||
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
|
||||
Success(0, R.string.core_error_success),
|
||||
ErrorNotInitialized(1, R.string.core_error_not_initialized),
|
||||
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||
ErrorLoader(4, R.string.core_error_loader),
|
||||
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
|
||||
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
|
||||
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
|
||||
ErrorLoader_ErrorPatches(8, R.string.core_error_loader_error_patches),
|
||||
ErrorLoader_ErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
|
||||
ErrorSystemFiles(10, R.string.core_error_system_files),
|
||||
ErrorSavestate(11, R.string.core_error_savestate),
|
||||
ErrorArticDisconnected(12, R.string.core_error_artic_disconnected),
|
||||
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
|
||||
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
|
||||
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
|
||||
ShutdownRequested(16, R.string.core_error_shutdown_requested),
|
||||
ErrorUnknown(17, R.string.core_error_unknown);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): CoreError {
|
||||
return entries.find { it.value == value } ?: ErrorUnknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class InstallStatus {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import android.Manifest.permission
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
|
|
@ -21,6 +21,7 @@ import android.widget.Toast
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
|
@ -43,6 +44,7 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
|||
import org.citra.citra_emu.fragments.EmulationFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
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.FileBrowserHelper
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
|
|
@ -79,7 +81,9 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
|
||||
}
|
||||
|
||||
private var isRotationBlocked: Boolean = true
|
||||
private var isEmulationRunning: Boolean = false
|
||||
private var isEmulationReady: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
|
@ -88,12 +92,20 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
ThemeUtil.setTheme(this)
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
|
||||
// Block orientation until emulation is ready to prevent unneccesary
|
||||
// surface recreation until the renderer is ready.
|
||||
isRotationBlocked = true
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
|
@ -118,8 +130,6 @@ class EmulationActivity : AppCompatActivity() {
|
|||
isEmulationRunning = true
|
||||
instance = this
|
||||
|
||||
applyOrientationSettings() // Check for orientation settings at startup
|
||||
|
||||
val game = try {
|
||||
intent.extras?.let { extras ->
|
||||
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||
|
|
@ -135,13 +145,46 @@ class EmulationActivity : AppCompatActivity() {
|
|||
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
|
||||
NativeLibrary.stopEmulation()
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
|
||||
isEmulationReady = false
|
||||
isRotationBlocked = true
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
|
||||
val game = intent.extras?.let { extras ->
|
||||
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||
}
|
||||
if (game != null) {
|
||||
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||
}
|
||||
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
||||
}
|
||||
|
||||
// On some devices, the system bars will not disappear on first boot or after some
|
||||
// rotations. Here we set full screen immersive repeatedly in onResume and in
|
||||
// onWindowFocusChanged to prevent the unwanted status bar state.
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
enableFullscreenImmersive()
|
||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
||||
if (isEmulationReady) {
|
||||
// If emulation is ready then unblock rotation
|
||||
isRotationBlocked = false
|
||||
applyOrientationSettings()
|
||||
emulationViewModel.setEmulationStarted(true)
|
||||
} else {
|
||||
if (!isRotationBlocked) {
|
||||
applyOrientationSettings()
|
||||
}
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
@ -150,8 +193,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
enableFullscreenImmersive()
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
}
|
||||
|
||||
public override fun onRestart() {
|
||||
|
|
@ -163,11 +206,15 @@ class EmulationActivity : AppCompatActivity() {
|
|||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean("isEmulationRunning", isEmulationRunning)
|
||||
outState.putBoolean("isEmulationReady", isEmulationReady)
|
||||
outState.putBoolean("isRotationBlocked", isRotationBlocked)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
|
||||
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
|
||||
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -221,6 +268,11 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
fun onEmulationStarted() {
|
||||
emulationViewModel.setEmulationStarted(true)
|
||||
isEmulationReady = true
|
||||
if (isRotationBlocked) {
|
||||
isRotationBlocked = false
|
||||
applyOrientationSettings()
|
||||
}
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(R.string.emulation_menu_help),
|
||||
|
|
@ -267,41 +319,34 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
val button =
|
||||
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||
val action: Int = when (event.action) {
|
||||
when (event.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
hotkeyUtility.handleHotkey(button)
|
||||
|
||||
// On some devices, the back gesture / button press is not intercepted by androidx
|
||||
// and fails to open the emulation menu. So we're stuck running deprecated code to
|
||||
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// If the hotkey is pressed, we don't want to open the drawer
|
||||
if (!hotkeyUtility.HotkeyIsPressed) {
|
||||
if (!hotkeyUtility.hotkeyIsPressed) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
return hotkeyUtility.handleKeyPress(event)
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> {
|
||||
hotkeyUtility.HotkeyIsPressed = false
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
return hotkeyUtility.handleKeyRelease(event)
|
||||
}
|
||||
else -> {
|
||||
return false;
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
val input = event.device
|
||||
?: // Controller was disconnected
|
||||
return false
|
||||
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
|
||||
}
|
||||
|
||||
private fun onAmiiboSelected(selectedFile: String) {
|
||||
val success = NativeLibrary.loadAmiibo(selectedFile)
|
||||
if (!success) {
|
||||
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.amiibo_load_error,
|
||||
R.string.amiibo_load_error_message
|
||||
|
|
@ -524,13 +569,19 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return true
|
||||
}
|
||||
|
||||
val openFileLauncher =
|
||||
val openAmiiboFileLauncher =
|
||||
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
||||
if (result == null) return@registerForActivityResult
|
||||
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
||||
result, applicationContext, listOf<String>("bin")
|
||||
) ?: return@registerForActivityResult
|
||||
onAmiiboSelected(selectedFiles[0])
|
||||
if (BuildUtil.isGooglePlayBuild) {
|
||||
onAmiiboSelected(selectedFiles[0])
|
||||
} else {
|
||||
val fileUri = selectedFiles[0].toUri()
|
||||
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
|
||||
onAmiiboSelected(nativePath)
|
||||
}
|
||||
}
|
||||
|
||||
val openImageLauncher =
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.widget.PopupMenu
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -54,8 +55,10 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
|
|||
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
|
||||
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GameIconUtils
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
||||
class GameAdapter(
|
||||
|
|
@ -136,7 +139,7 @@ class GameAdapter(
|
|||
val holder = view.tag as GameViewHolder
|
||||
gameExists(holder)
|
||||
|
||||
if (holder.game.titleId == 0L) {
|
||||
if (!holder.game.valid) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.properties)
|
||||
.setMessage(R.string.properties_not_loaded)
|
||||
|
|
@ -153,12 +156,21 @@ class GameAdapter(
|
|||
if (holder.game.isInstalled) {
|
||||
return true
|
||||
}
|
||||
|
||||
val gameExists = DocumentFile.fromSingleUri(
|
||||
CitraApplication.appContext,
|
||||
Uri.parse(holder.game.path)
|
||||
)?.exists() == true
|
||||
val path = holder.game.path
|
||||
val pathUri = path.toUri()
|
||||
var gameExists: Boolean
|
||||
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
|
||||
gameExists =
|
||||
DocumentFile.fromSingleUri(
|
||||
CitraApplication.appContext,
|
||||
pathUri
|
||||
)?.exists() == true
|
||||
} else {
|
||||
val nativePath = NativeLibrary.getNativePath(pathUri)
|
||||
gameExists = NativeLibrary.nativeFileExists(nativePath)
|
||||
}
|
||||
return if (!gameExists) {
|
||||
Log.error("[GameAdapter] ROM file does not exist: $path")
|
||||
Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.loader_error_file_not_found,
|
||||
|
|
@ -323,14 +335,16 @@ class GameAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
val titleId = game.titleId
|
||||
val dlcTitleId = titleId or 0x8C00000000L
|
||||
val updateTitleId = titleId or 0xE00000000L
|
||||
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
val uninstallAction: () -> Unit = {
|
||||
when (menuItem.itemId) {
|
||||
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
|
||||
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
|
||||
.toString())
|
||||
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
|
||||
.toString())
|
||||
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
|
||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
|
||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
|
||||
}
|
||||
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
||||
bottomSheetDialog.dismiss()
|
||||
|
|
@ -501,6 +515,63 @@ class GameAdapter(
|
|||
showUninstallContextMenu(it, game, bottomSheetDialog)
|
||||
}
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
|
||||
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
|
||||
var selectedIndex = -1
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_cache_select_backend)
|
||||
.setSingleChoiceItems(options, -1) { dialog, which ->
|
||||
selectedIndex = which
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) {_, _ ->
|
||||
val progToast = Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.deleting_shader_cache,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
progToast.show()
|
||||
|
||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
||||
when (selectedIndex) {
|
||||
0 -> {
|
||||
NativeLibrary.deleteVulkanShaderCache(game.titleId)
|
||||
}
|
||||
1 -> {
|
||||
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
|
||||
}
|
||||
}
|
||||
|
||||
activity.runOnUiThread {
|
||||
progToast.cancel()
|
||||
Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.shader_cache_deleted,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
|
||||
|
||||
positiveButton.isEnabled = false
|
||||
|
||||
val listView = dialog.listView
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
selectedIndex = position
|
||||
positiveButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val bottomSheetBehavior = bottomSheetDialog.getBehavior()
|
||||
bottomSheetBehavior.skipCollapsed = true
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
|
@ -556,7 +627,9 @@ class GameAdapter(
|
|||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.titleId == newItem.titleId
|
||||
// The title is taken into account to support 3DSX, which all have the titleID 0.
|
||||
// This only works now because we always return the English title, adjust if that changes.
|
||||
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -57,6 +57,7 @@ object StillImageCameraHelper {
|
|||
val request = ImageRequest.Builder(context)
|
||||
.data(uri)
|
||||
.size(width, height)
|
||||
.allowHardware(false)
|
||||
.build()
|
||||
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
|
||||
width,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import org.citra.citra_emu.NativeLibrary
|
|||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
|
|
@ -31,8 +32,16 @@ class ScreenAdjustmentUtil(
|
|||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
fun cycleLayouts() {
|
||||
val landscapeValues = context.resources.getIntArray(R.array.landscapeValues)
|
||||
|
||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
|
||||
val landscapeValues =
|
||||
if (landscapeLayoutsToCycle.isNotEmpty())
|
||||
landscapeLayoutsToCycle.toIntArray()
|
||||
else context.resources.getIntArray(
|
||||
R.array.landscapeValues
|
||||
)
|
||||
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||
|
||||
if (NativeLibrary.isPortraitMode) {
|
||||
|
|
|
|||
|
|
@ -6,18 +6,15 @@ package org.citra.citra_emu.display
|
|||
|
||||
import android.app.Presentation
|
||||
import android.content.Context
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
|
||||
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
|
|
@ -66,6 +63,11 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||
}
|
||||
|
||||
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
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
|
|
@ -78,12 +80,25 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
||||
pres?.show()
|
||||
|
||||
try {
|
||||
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() {
|
||||
pres?.dismiss()
|
||||
try {
|
||||
pres?.dismiss()
|
||||
} catch (_: Exception) { }
|
||||
pres = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ enum class Hotkey(val button: Int) {
|
|||
PAUSE_OR_RESUME(10004),
|
||||
QUICKSAVE(10005),
|
||||
QUICKLOAD(10006),
|
||||
TURBO_LIMIT(10007);
|
||||
TURBO_LIMIT(10007),
|
||||
ENABLE(10008);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,50 +5,140 @@
|
|||
package org.citra.citra_emu.features.hotkeys
|
||||
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class HotkeyUtility(
|
||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||
private val context: Context) {
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
private val hotkeyButtons = Hotkey.entries.map { it.button }
|
||||
var HotkeyIsPressed = false
|
||||
private var hotkeyIsEnabled = false
|
||||
var hotkeyIsPressed = false
|
||||
private val currentlyPressedButtons = mutableSetOf<Int>()
|
||||
|
||||
fun handleKeyPress(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val enableButton =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
.getString(Settings.HOTKEY_ENABLE, "")
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
hotkeyIsEnabled = hotkeyIsEnabled || enableButton == "" || thisKeyIsEnableButton
|
||||
|
||||
// Now process all internal buttons associated with this keypress
|
||||
for (button in buttonSet) {
|
||||
currentlyPressedButtons.add(button)
|
||||
//option 1 - this is the enable command, which was already handled
|
||||
if (button == Hotkey.ENABLE.button) {
|
||||
handled = true
|
||||
}
|
||||
// option 2 - this is a different hotkey command
|
||||
else if (hotkeyButtons.contains(button)) {
|
||||
if (hotkeyIsEnabled) {
|
||||
handled = handleHotkey(button) || handled
|
||||
}
|
||||
}
|
||||
// option 3 - this is a normal key
|
||||
else {
|
||||
// if this key press is ALSO associated with a hotkey that will process, skip
|
||||
// the normal key event.
|
||||
if (!thisKeyIsHotkey || !hotkeyIsEnabled) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
) || handled
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleKeyRelease(keyEvent: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
if (thisKeyIsEnableButton) {
|
||||
handled = true; hotkeyIsEnabled = false
|
||||
}
|
||||
|
||||
for (button in buttonSet) {
|
||||
// this is a hotkey button
|
||||
if (hotkeyButtons.contains(button)) {
|
||||
currentlyPressedButtons.remove(button)
|
||||
if (!currentlyPressedButtons.any { hotkeyButtons.contains(it) }) {
|
||||
// all hotkeys are no longer pressed
|
||||
hotkeyIsPressed = false
|
||||
}
|
||||
} else {
|
||||
// if this key ALSO sends a hotkey command that we already/will handle,
|
||||
// or if we did not register the press of this button, e.g. if this key
|
||||
// was also a hotkey pressed after enable, but released after enable button release, then
|
||||
// skip the normal key event
|
||||
if ((!thisKeyIsHotkey || !hotkeyIsEnabled) && currentlyPressedButtons.contains(
|
||||
button
|
||||
)
|
||||
) {
|
||||
handled = NativeLibrary.onGamePadEvent(
|
||||
keyEvent.device.descriptor,
|
||||
button,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
) || handled
|
||||
currentlyPressedButtons.remove(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
fun handleHotkey(bindedButton: Int): Boolean {
|
||||
if(hotkeyButtons.contains(bindedButton)) {
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if(wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
else -> {}
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.saving),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
HotkeyIsPressed = true
|
||||
return true
|
||||
|
||||
Hotkey.QUICKLOAD.button -> {
|
||||
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
|
||||
val stringRes = if (wasLoaded) {
|
||||
R.string.loading
|
||||
} else {
|
||||
R.string.quickload_not_found
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(stringRes),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
hotkeyIsPressed = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
// 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 async_fs_operations(): 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 simulate_3ds_gpu_timings(): 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 simulate_headphones_plugged(): 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
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
interface AbstractListSetting<E> : AbstractSetting {
|
||||
var list: List<E>
|
||||
}
|
||||
|
|
@ -4,56 +4,62 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class BooleanSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
EXPAND_TO_CUTOUT_AREA("expand_to_cutout_area", Settings.SECTION_LAYOUT, false),
|
||||
SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
|
||||
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
|
||||
DISABLE_SPIRV_OPTIMIZER("disable_spirv_optimizer", Settings.SECTION_RENDERER, true),
|
||||
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
|
||||
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
|
||||
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
|
||||
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
|
||||
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
|
||||
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
|
||||
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED("performance_overlay_show_speed", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE("performance_overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM("performance_overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND("performance_overlay_background", Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
|
||||
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false),
|
||||
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true),
|
||||
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true),
|
||||
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false),
|
||||
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true),
|
||||
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false),
|
||||
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, false),
|
||||
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, true),
|
||||
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, false),
|
||||
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, true),
|
||||
ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, false),
|
||||
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, true),
|
||||
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, true),
|
||||
SHADER_JIT("use_shader_jit", Settings.SECTION_RENDERER, true),
|
||||
VSYNC("use_vsync", Settings.SECTION_RENDERER, false),
|
||||
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, true),
|
||||
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, false),
|
||||
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
|
||||
UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false),
|
||||
ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false),
|
||||
APPLY_REGION_FREE_PATCH("apply_region_free_patch", Settings.SECTION_SYSTEM, true);
|
||||
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
|
||||
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
|
||||
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
|
||||
DISABLE_SPIRV_OPTIMIZER(SettingKeys.disable_spirv_optimizer(), Settings.SECTION_RENDERER, true),
|
||||
PLUGIN_LOADER(SettingKeys.plugin_loader(), Settings.SECTION_SYSTEM, false),
|
||||
ALLOW_PLUGIN_LOADER(SettingKeys.allow_plugin_loader(), Settings.SECTION_SYSTEM, true),
|
||||
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
|
||||
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
|
||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
|
||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
|
||||
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
|
||||
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
|
||||
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
|
||||
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
|
||||
SHADERS_ACCURATE_MUL(SettingKeys.shaders_accurate_mul(), Settings.SECTION_RENDERER, false),
|
||||
DISK_SHADER_CACHE(SettingKeys.use_disk_shader_cache(), Settings.SECTION_RENDERER, true),
|
||||
DUMP_TEXTURES(SettingKeys.dump_textures(), Settings.SECTION_UTILITY, false),
|
||||
CUSTOM_TEXTURES(SettingKeys.custom_textures(), Settings.SECTION_UTILITY, false),
|
||||
ASYNC_CUSTOM_LOADING(SettingKeys.async_custom_loading(), Settings.SECTION_UTILITY, true),
|
||||
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
|
||||
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
|
||||
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
|
||||
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false),
|
||||
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
|
||||
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
||||
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
||||
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
|
||||
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
||||
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
|
||||
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
|
||||
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
|
||||
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
|
||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
|
||||
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
||||
|
|
@ -80,6 +86,7 @@ enum class BooleanSetting(
|
|||
REQUIRED_ONLINE_LLE_MODULES,
|
||||
NEW_3DS,
|
||||
LLE_APPLETS,
|
||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
|
||||
VSYNC,
|
||||
DEBUG_RENDERER,
|
||||
CPU_JIT,
|
||||
|
|
@ -87,6 +94,7 @@ enum class BooleanSetting(
|
|||
SHADERS_ACCURATE_MUL,
|
||||
USE_ARTIC_BASE_CONTROLLER,
|
||||
COMPRESS_INSTALLED_CIA_CONTENT,
|
||||
ASYNC_FS_OPERATIONS,
|
||||
ANDROID_HIDE_IMAGES,
|
||||
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
|
||||
APPLY_REGION_FREE_PATCH
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class FloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f),
|
||||
EMPTY_SETTING("", "", 0.0f);
|
||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
|
||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class IntListSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: List<Int>,
|
||||
val canBeEmpty: Boolean = true
|
||||
) : AbstractListSetting<Int> {
|
||||
|
||||
LAYOUTS_TO_CYCLE(SettingKeys.layouts_to_cycle(), Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
||||
|
||||
private var backingList: List<Int> = defaultValue
|
||||
private var lastValidList : List<Int> = defaultValue
|
||||
|
||||
override var list: List<Int>
|
||||
get() = backingList
|
||||
set(value) {
|
||||
if (!canBeEmpty && value.isEmpty()) {
|
||||
backingList = lastValidList
|
||||
} else {
|
||||
backingList = value
|
||||
lastValidList = value
|
||||
}
|
||||
}
|
||||
|
||||
override val valueAsString: String
|
||||
get() = list.joinToString()
|
||||
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||
if (setting == this) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
|
||||
|
||||
fun from(key: String): IntListSetting? =
|
||||
values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,57 +4,59 @@
|
|||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class IntSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Int
|
||||
) : AbstractIntSetting {
|
||||
FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
|
||||
EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
|
||||
INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
|
||||
CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
|
||||
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
|
||||
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
|
||||
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
|
||||
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
|
||||
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
|
||||
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
|
||||
SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
|
||||
LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||
LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT("secondary_display_layout",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
||||
PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
|
||||
PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||
PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||
AUDIO_INPUT_TYPE("input_type", Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
|
||||
TEXTURE_SAMPLING("texture_sampling", Settings.SECTION_RENDERER, 0),
|
||||
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
|
||||
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
|
||||
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
|
||||
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
|
||||
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
|
||||
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
|
||||
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
|
||||
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2),
|
||||
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
|
||||
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0),
|
||||
CARDBOARD_SCREEN_SIZE(SettingKeys.cardboard_screen_size(), Settings.SECTION_LAYOUT, 85),
|
||||
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
|
||||
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1),
|
||||
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
|
||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class ScaledFloatSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: Float,
|
||||
val scale: Int
|
||||
) : AbstractFloatSetting {
|
||||
AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
|
||||
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100);
|
||||
|
||||
override var float: Float = defaultValue
|
||||
get() = field * scale
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class Settings {
|
|||
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
|
||||
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
|
||||
const val SECTION_STORAGE = "Storage"
|
||||
const val SECTION_MISC = "Miscellaneous"
|
||||
|
||||
const val KEY_BUTTON_A = "button_a"
|
||||
const val KEY_BUTTON_B = "button_b"
|
||||
|
|
@ -135,6 +136,7 @@ class Settings {
|
|||
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
|
||||
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
|
||||
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
|
||||
const val HOTKEY_ENABLE = "hotkey_enable"
|
||||
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
|
||||
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
|
||||
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
||||
|
|
@ -202,6 +204,7 @@ class Settings {
|
|||
R.string.button_zr
|
||||
)
|
||||
val hotKeys = listOf(
|
||||
HOTKEY_ENABLE,
|
||||
HOTKEY_SCREEN_SWAP,
|
||||
HOTKEY_CYCLE_LAYOUT,
|
||||
HOTKEY_CLOSE_GAME,
|
||||
|
|
@ -211,6 +214,7 @@ class Settings {
|
|||
HOTKEY_TURBO_LIMIT
|
||||
)
|
||||
val hotkeyTitles = listOf(
|
||||
R.string.controller_hotkey_enable_button,
|
||||
R.string.emulation_swap_screens,
|
||||
R.string.emulation_cycle_landscape_layouts,
|
||||
R.string.emulation_close_game,
|
||||
|
|
@ -220,6 +224,7 @@ class Settings {
|
|||
R.string.turbo_limit_hotkey
|
||||
)
|
||||
|
||||
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_MATERIAL_YOU = "MaterialYouTheme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
|
||||
enum class StringSetting(
|
||||
override val key: String,
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
INIT_TIME("init_time", Settings.SECTION_SYSTEM, "946731601"),
|
||||
CAMERA_INNER_NAME("camera_inner_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG("camera_inner_config", Settings.SECTION_CAMERA, "_front"),
|
||||
CAMERA_OUTER_LEFT_NAME("camera_outer_left_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_LEFT_CONFIG("camera_outer_left_config", Settings.SECTION_CAMERA, "_back"),
|
||||
CAMERA_OUTER_RIGHT_NAME("camera_outer_right_name", Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back");
|
||||
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
|
||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
||||
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
|
||||
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.content.SharedPreferences
|
|||
import android.view.InputDevice
|
||||
import android.view.InputDevice.MotionRange
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
|
|
@ -128,6 +129,7 @@ class InputBindingSetting(
|
|||
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||
Settings.HOTKEY_ENABLE -> Hotkey.ENABLE.button
|
||||
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
|
||||
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
|
||||
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
||||
|
|
@ -162,36 +164,40 @@ class InputBindingSetting(
|
|||
fun removeOldMapping() {
|
||||
// Try remove all possible keys we wrote for this setting
|
||||
val oldKey = preferences.getString(reverseKey, "")
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
if (oldKey != "") {
|
||||
(setting as AbstractStringSetting).string = ""
|
||||
preferences.edit()
|
||||
.remove(abstractSetting.key) // Used for ui text
|
||||
.remove(oldKey) // Used for button mapping
|
||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||
.remove(oldKey + "_GuestButton") // Used for axis button
|
||||
.remove(oldKey + "_Inverted") // used for axis inversion
|
||||
.apply()
|
||||
.remove(reverseKey)
|
||||
val buttonCodes = try {
|
||||
preferences.getStringSet(oldKey, mutableSetOf<String>())!!.toMutableSet()
|
||||
} catch (e: ClassCastException) {
|
||||
// if this is an int pref, either old button or an axis, so just remove it
|
||||
preferences.edit().remove(oldKey).apply()
|
||||
return;
|
||||
}
|
||||
buttonCodes.remove(buttonCode.toString());
|
||||
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to write a gamepad button mapping for the setting.
|
||||
*/
|
||||
private fun writeButtonMapping(key: String) {
|
||||
private fun writeButtonMapping(keyEvent: KeyEvent) {
|
||||
val editor = preferences.edit()
|
||||
|
||||
// Remove mapping for another setting using this input
|
||||
val oldButtonCode = preferences.getInt(key, -1)
|
||||
if (oldButtonCode != -1) {
|
||||
val oldKey = getButtonKey(oldButtonCode)
|
||||
editor.remove(oldKey) // Only need to remove UI text setting, others will be overwritten
|
||||
}
|
||||
|
||||
val key = getInputButtonKey(keyEvent)
|
||||
// Pull in all codes associated with this key
|
||||
// Migrate from the old int preference if need be
|
||||
val buttonCodes = InputBindingSetting.getButtonSet(keyEvent)
|
||||
buttonCodes.add(buttonCode)
|
||||
// Cleanup old mapping for this setting
|
||||
removeOldMapping()
|
||||
|
||||
// Write new mapping
|
||||
editor.putInt(key, buttonCode)
|
||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
|
||||
|
||||
// Write next reverse mapping for future cleanup
|
||||
editor.putString(reverseKey, key)
|
||||
|
|
@ -229,9 +235,8 @@ class InputBindingSetting(
|
|||
}
|
||||
|
||||
val code = translateEventToKeyId(keyEvent)
|
||||
writeButtonMapping(getInputButtonKey(code))
|
||||
val uiString = "${keyEvent.device.name}: Button $code"
|
||||
value = uiString
|
||||
writeButtonMapping(keyEvent)
|
||||
value = "${keyEvent.device.name}: ${getButtonName(code)}"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -258,8 +263,7 @@ class InputBindingSetting(
|
|||
// use UP (-) to map vertical, but use RIGHT (+) to map horizontal
|
||||
val inverted = if (isHorizontalOrientation()) axisDir == '-' else axisDir == '+'
|
||||
writeAxisMapping(motionRange.axis, button, inverted)
|
||||
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
|
||||
value = uiString
|
||||
value = "Axis ${motionRange.axis}$axisDir"
|
||||
}
|
||||
|
||||
override val type = TYPE_INPUT_BINDING
|
||||
|
|
@ -267,6 +271,241 @@ class InputBindingSetting(
|
|||
companion object {
|
||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
||||
|
||||
private fun toTitleCase(raw: String): String =
|
||||
raw.replace("_", " ").lowercase()
|
||||
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
||||
|
||||
private const val BUTTON_NAME_L3 = "Button L3"
|
||||
private const val BUTTON_NAME_R3 = "Button R3"
|
||||
|
||||
private val buttonNameOverrides = mapOf(
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL to BUTTON_NAME_L3,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR to BUTTON_NAME_R3,
|
||||
LINUX_BTN_DPAD_UP to "Dpad Up",
|
||||
LINUX_BTN_DPAD_DOWN to "Dpad Down",
|
||||
LINUX_BTN_DPAD_LEFT to "Dpad Left",
|
||||
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
||||
)
|
||||
|
||||
fun getButtonName(keyCode: Int): String =
|
||||
buttonNameOverrides[keyCode]
|
||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||
|
||||
private data class DefaultButtonMapping(
|
||||
val settingKey: String,
|
||||
val hostKeyCode: Int,
|
||||
val guestButtonCode: Int
|
||||
)
|
||||
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
|
||||
private data class DefaultAxisMapping(
|
||||
val settingKey: String,
|
||||
val hostAxis: Int,
|
||||
val guestButton: Int,
|
||||
val orientation: Int,
|
||||
val inverted: Boolean
|
||||
)
|
||||
|
||||
private val xboxFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
private val nintendoFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
private val commonButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
|
||||
)
|
||||
|
||||
private val dpadButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
||||
)
|
||||
|
||||
private val stickAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
|
||||
)
|
||||
|
||||
private val dpadAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
|
||||
)
|
||||
|
||||
// Nintendo Switch Joy-Con specific mappings.
|
||||
// Joy-Cons connected via Bluetooth on Android have several quirks:
|
||||
// - They register as two separate InputDevices (left and right)
|
||||
// - Android's evdev translation swaps A<->B (BTN_EAST->BUTTON_B, BTN_SOUTH->BUTTON_A)
|
||||
// but does NOT swap X<->Y (BTN_NORTH->BUTTON_X, BTN_WEST->BUTTON_Y)
|
||||
// - D-pad buttons arrive as KEYCODE_UNKNOWN (0) with Linux BTN_DPAD_* scan codes
|
||||
// - Right stick uses AXIS_RX/AXIS_RY instead of AXIS_Z/AXIS_RZ
|
||||
private const val NINTENDO_VENDOR_ID = 0x057e
|
||||
|
||||
// Linux BTN_DPAD_* values (0x220-0x223). Joy-Con D-pad buttons arrive as
|
||||
// KEYCODE_UNKNOWN with these scan codes because Android's input layer doesn't
|
||||
// translate them to KEYCODE_DPAD_*. translateEventToKeyId() falls back to
|
||||
// the scan code in that case.
|
||||
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
|
||||
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
|
||||
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
|
||||
private const val LINUX_BTN_DPAD_RIGHT = 0x223 // 547
|
||||
|
||||
// Joy-Con face buttons: A/B are swapped by Android's evdev layer, but X/Y are not.
|
||||
// This is different from both the standard Xbox table (full swap) and the
|
||||
// Nintendo table (no swap).
|
||||
private val joyconFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
)
|
||||
|
||||
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
|
||||
private val joyconDpadButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
|
||||
)
|
||||
|
||||
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
|
||||
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to
|
||||
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
|
||||
private val joyconStickAxisMappings = listOf(
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
|
||||
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
|
||||
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
|
||||
)
|
||||
|
||||
/**
|
||||
* Detects whether a device is a Nintendo Switch Joy-Con (as opposed to a
|
||||
* Pro Controller or other Nintendo device) by checking vendor ID + device
|
||||
* capabilities. Joy-Cons lack AXIS_HAT_X/Y and use AXIS_RX/RY for the
|
||||
* right stick, while the Pro Controller has standard HAT axes and Z/RZ.
|
||||
*/
|
||||
fun isJoyCon(device: InputDevice?): Boolean {
|
||||
if (device == null) return false
|
||||
if (device.vendorId != NINTENDO_VENDOR_ID) return false
|
||||
|
||||
// Pro Controllers have HAT_X/HAT_Y (D-pad) and Z/RZ (right stick).
|
||||
// Joy-Cons lack both: no HAT axes, right stick on RX/RY instead of Z/RZ.
|
||||
var hasHatAxes = false
|
||||
var hasStandardRightStick = false
|
||||
for (range in device.motionRanges) {
|
||||
when (range.axis) {
|
||||
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> hasHatAxes = true
|
||||
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> hasStandardRightStick = true
|
||||
}
|
||||
}
|
||||
return !hasHatAxes && !hasStandardRightStick
|
||||
}
|
||||
|
||||
private val allBindingKeys: Set<String> by lazy {
|
||||
(Settings.buttonKeys + Settings.triggerKeys +
|
||||
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
|
||||
Settings.dPadButtonKeys).toSet()
|
||||
}
|
||||
|
||||
fun clearAllBindings() {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val editor = prefs.edit()
|
||||
val allKeys = prefs.all.keys.toList()
|
||||
for (key in allKeys) {
|
||||
if (key.startsWith(INPUT_MAPPING_PREFIX) || key in allBindingKeys) {
|
||||
editor.remove(key)
|
||||
}
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun applyBindings(
|
||||
buttonMappings: List<DefaultButtonMapping>,
|
||||
axisMappings: List<DefaultAxisMapping>
|
||||
) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val editor = prefs.edit()
|
||||
buttonMappings.forEach { applyDefaultButtonMapping(editor, it) }
|
||||
axisMappings.forEach { applyDefaultAxisMapping(editor, it) }
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Joy-Con specific bindings: scan code D-pad, partial face button
|
||||
* swap, and AXIS_RX/RY right stick.
|
||||
*/
|
||||
fun applyJoyConBindings() {
|
||||
applyBindings(
|
||||
joyconFaceButtonMappings + commonButtonMappings + joyconDpadButtonMappings,
|
||||
joyconStickAxisMappings
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies auto-mapped bindings based on detected controller layout and d-pad type.
|
||||
*
|
||||
* @param isNintendoLayout true if the controller uses Nintendo face button layout
|
||||
* (A=east, B=south), false for Xbox layout (A=south, B=east)
|
||||
* @param useAxisDpad true if the d-pad should be mapped as axis (HAT_X/HAT_Y),
|
||||
* false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT)
|
||||
*/
|
||||
fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) {
|
||||
val faceButtons = if (isNintendoLayout) nintendoFaceButtonMappings else xboxFaceButtonMappings
|
||||
val buttonMappings = if (useAxisDpad) {
|
||||
faceButtons + commonButtonMappings
|
||||
} else {
|
||||
faceButtons + commonButtonMappings + dpadButtonMappings
|
||||
}
|
||||
val axisMappings = if (useAxisDpad) {
|
||||
stickAxisMappings + dpadAxisMappings
|
||||
} else {
|
||||
stickAxisMappings
|
||||
}
|
||||
applyBindings(buttonMappings, axisMappings)
|
||||
}
|
||||
|
||||
private fun applyDefaultButtonMapping(
|
||||
editor: SharedPreferences.Editor,
|
||||
mapping: DefaultButtonMapping
|
||||
) {
|
||||
val prefKey = getInputButtonKey(mapping.hostKeyCode)
|
||||
editor.putInt(prefKey, mapping.guestButtonCode)
|
||||
editor.putString(mapping.settingKey, getButtonName(mapping.hostKeyCode))
|
||||
editor.putString(
|
||||
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}",
|
||||
prefKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyDefaultAxisMapping(
|
||||
editor: SharedPreferences.Editor,
|
||||
mapping: DefaultAxisMapping
|
||||
) {
|
||||
val axisKey = getInputAxisKey(mapping.hostAxis)
|
||||
editor.putInt(getInputAxisOrientationKey(mapping.hostAxis), mapping.orientation)
|
||||
editor.putInt(getInputAxisButtonKey(mapping.hostAxis), mapping.guestButton)
|
||||
editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted)
|
||||
val dir = if (mapping.orientation == 0) '+' else '-'
|
||||
editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir")
|
||||
val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
|
||||
editor.putString(reverseKey, axisKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings key for the specified Citra button code.
|
||||
*/
|
||||
|
|
@ -289,19 +528,31 @@ class InputBindingSetting(
|
|||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||
else -> ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad button.
|
||||
*
|
||||
* Get the mutable set of int button values this key should map to given an event
|
||||
*/
|
||||
@Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys")
|
||||
fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}"
|
||||
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
|
||||
val key = getInputButtonKey(keyCode)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
var buttonCodes = try {
|
||||
preferences.getStringSet(key, mutableSetOf<String>())
|
||||
} catch (e: ClassCastException) {
|
||||
val prefInt = preferences.getInt(key, -1);
|
||||
val migratedSet = if (prefInt != -1) {
|
||||
mutableSetOf(prefInt.toString())
|
||||
} else {
|
||||
mutableSetOf<String>()
|
||||
}
|
||||
migratedSet
|
||||
}
|
||||
if (buttonCodes == null) buttonCodes = mutableSetOf<String>()
|
||||
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad button.
|
||||
*
|
||||
*/
|
||||
fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}"
|
||||
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
|
||||
|
||||
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
|
||||
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad axis.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
class MultiChoiceSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Int,
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: List<Int>? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_MULTI_CHOICE
|
||||
|
||||
val selectedValues: List<Int>
|
||||
get() {
|
||||
if (setting == null) {
|
||||
return defaultValue!!
|
||||
}
|
||||
try {
|
||||
val setting = setting as IntListSetting
|
||||
return setting.list
|
||||
}catch (_: ClassCastException) {
|
||||
}
|
||||
return defaultValue!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing list. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
*
|
||||
* @param selection New value of the int.
|
||||
* @return the existing setting with the new value applied.
|
||||
*/
|
||||
fun setSelectedValue(selection: List<Int>): IntListSetting {
|
||||
val intSetting = setting as IntListSetting
|
||||
intSetting.list = selection
|
||||
return intSetting
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
|
||||
class RunnableSetting(
|
||||
titleId: Int,
|
||||
|
|
@ -12,7 +13,11 @@ class RunnableSetting(
|
|||
val isRuntimeRunnable: Boolean,
|
||||
@DrawableRes val iconId: Int = 0,
|
||||
val runnable: () -> Unit,
|
||||
val value: (() -> String)? = null
|
||||
val value: (() -> String)? = null,
|
||||
val onLongClick: (() -> Boolean)? = null
|
||||
) : SettingsItem(null, titleId, descriptionId) {
|
||||
override val type = TYPE_RUNNABLE
|
||||
|
||||
override val isEditable: Boolean
|
||||
get() = if (EmulationActivity.isRunning()) isRuntimeRunnable else true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ abstract class SettingsItem(
|
|||
) {
|
||||
abstract val type: Int
|
||||
|
||||
val isEditable: Boolean
|
||||
open val isEditable: Boolean
|
||||
get() {
|
||||
if (!EmulationActivity.isRunning()) return true
|
||||
return setting?.isRuntimeEditable ?: false
|
||||
|
|
@ -47,5 +47,6 @@ abstract class SettingsItem(
|
|||
const val TYPE_INPUT_BINDING = 8
|
||||
const val TYPE_STRING_INPUT = 9
|
||||
const val TYPE_FLOAT_INPUT = 10
|
||||
const val TYPE_MULTI_CHOICE = 11
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,14 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
|||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
|
|
@ -55,6 +57,7 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting
|
|||
import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.MultiChoiceViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
|
||||
|
|
@ -62,6 +65,7 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SliderViewHolder
|
|||
import org.citra.citra_emu.features.settings.ui.viewholder.StringInputViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SubmenuViewHolder
|
||||
import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHolder
|
||||
import org.citra.citra_emu.fragments.AutoMapDialogFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
|
|
@ -72,7 +76,8 @@ import kotlin.math.roundToInt
|
|||
class SettingsAdapter(
|
||||
private val fragmentView: SettingsFragmentView,
|
||||
public val context: Context
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
||||
DialogInterface.OnMultiChoiceClickListener {
|
||||
private var settings: ArrayList<SettingsItem>? = null
|
||||
private var clickedItem: SettingsItem? = null
|
||||
private var clickedPosition: Int
|
||||
|
|
@ -104,6 +109,10 @@ class SettingsAdapter(
|
|||
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
||||
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SLIDER -> {
|
||||
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
|
@ -181,21 +190,30 @@ class SettingsAdapter(
|
|||
SettingsItem.TYPE_SLIDER -> {
|
||||
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SWITCH -> {
|
||||
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SINGLE_CHOICE -> {
|
||||
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
|
||||
}
|
||||
SettingsItem.TYPE_MULTI_CHOICE -> {
|
||||
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_DATETIME_SETTING -> {
|
||||
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_INPUT -> {
|
||||
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
||||
}
|
||||
|
||||
else -> {
|
||||
oldItem == newItem
|
||||
}
|
||||
|
|
@ -214,7 +232,7 @@ class SettingsAdapter(
|
|||
|
||||
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
|
||||
if (fragmentView.activityView != null)
|
||||
// Reload the settings list to update the UI
|
||||
// Reload the settings list to update the UI
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +250,27 @@ class SettingsAdapter(
|
|||
onSingleChoiceClick(item)
|
||||
}
|
||||
|
||||
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
|
||||
clickedItem = item
|
||||
|
||||
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setMultiChoiceItems(item.choicesId, value, this)
|
||||
.setOnDismissListener {
|
||||
if (clickedPosition != -1) {
|
||||
notifyItemChanged(clickedPosition)
|
||||
clickedPosition = -1
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) {
|
||||
clickedPosition = position
|
||||
onMultiChoiceClick(item)
|
||||
}
|
||||
|
||||
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
|
||||
clickedItem = item
|
||||
dialog = context?.let {
|
||||
|
|
@ -360,14 +399,14 @@ class SettingsAdapter(
|
|||
sliderString = sliderProgress.roundToInt().toString()
|
||||
if (textSliderValue?.text.toString() != sliderString) {
|
||||
textSliderValue?.setText(sliderString)
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
||||
}
|
||||
} else {
|
||||
val currentText = textSliderValue?.text.toString()
|
||||
val currentTextValue = currentText.toFloat()
|
||||
if (currentTextValue != sliderProgress) {
|
||||
textSliderValue?.setText(sliderString)
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
|
||||
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -447,6 +486,7 @@ class SettingsAdapter(
|
|||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is AbstractShortSetting -> {
|
||||
val value = getValueForSingleChoiceSelection(it, which).toShort()
|
||||
if (it.selectedValue.toShort() != value) {
|
||||
|
|
@ -454,6 +494,7 @@ class SettingsAdapter(
|
|||
}
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
||||
}
|
||||
fragmentView?.putSetting(setting)
|
||||
|
|
@ -499,11 +540,12 @@ class SettingsAdapter(
|
|||
val setting = it.setSelectedValue(value)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val setting = it.setSelectedValue(sliderProgress)
|
||||
fragmentView?.putSetting(setting)
|
||||
}
|
||||
}
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
|
|
@ -519,7 +561,7 @@ class SettingsAdapter(
|
|||
fragmentView?.putSetting(setting)
|
||||
fragmentView.loadSettingsList()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedItem = null
|
||||
|
|
@ -527,6 +569,21 @@ class SettingsAdapter(
|
|||
textInputValue = ""
|
||||
}
|
||||
|
||||
//onclick for multichoice
|
||||
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
|
||||
val mcsetting = clickedItem as? MultiChoiceSetting
|
||||
mcsetting?.let {
|
||||
val value = getValueForMultiChoiceSelection(it, which)
|
||||
if (it.selectedValues.contains(value) != isChecked) {
|
||||
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
|
||||
fragmentView?.putSetting(setting)
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
|
|
@ -586,26 +643,42 @@ class SettingsAdapter(
|
|||
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onClickAutoMap() {
|
||||
val activity = fragmentView.activityView as FragmentActivity
|
||||
AutoMapDialogFragment.newInstance {
|
||||
fragmentView.loadSettingsList()
|
||||
fragmentView.onSettingChanged()
|
||||
}.show(activity.supportFragmentManager, AutoMapDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onLongClickAutoMap(): Boolean {
|
||||
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
|
||||
InputBindingSetting.clearAllBindings()
|
||||
fragmentView.loadSettingsList()
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun onClickRegenerateConsoleId() {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.regenerate_console_id)
|
||||
.setMessage(R.string.regenerate_console_id_description)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
SystemSaveGame.regenerateConsoleId()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
|
||||
SystemSaveGame.regenerateConsoleId()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRegenerateMAC() {
|
||||
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
|
||||
SystemSaveGame.regenerateMac()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmationDialog(titleId: Int, messageId: Int, onConfirm: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.regenerate_mac_address)
|
||||
.setMessage(R.string.regenerate_mac_address_description)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
SystemSaveGame.regenerateMac()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> onConfirm() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
|
@ -631,6 +704,16 @@ class SettingsAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getValueForMultiChoiceSelection(item: MultiChoiceSetting, which: Int): Int {
|
||||
val valuesId = item.valuesId
|
||||
return if (valuesId > 0) {
|
||||
val valuesArray = context.resources.getIntArray(valuesId)
|
||||
valuesArray[which]
|
||||
} else {
|
||||
which
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
|
||||
val value = item.selectedValue
|
||||
val valuesId = item.valuesId
|
||||
|
|
@ -647,4 +730,20 @@ class SettingsAdapter(
|
|||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
|
||||
val value = item.selectedValues;
|
||||
val valuesId = item.valuesId;
|
||||
if (valuesId > 0) {
|
||||
val valuesArray = context.resources.getIntArray(valuesId);
|
||||
val res = BooleanArray(valuesArray.size){false}
|
||||
for (index in valuesArray.indices) {
|
||||
if (value.contains(valuesArray[index])) {
|
||||
res[index] = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return BooleanArray(1){false};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import android.os.Build
|
|||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.serialization.builtins.IntArraySerializer
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.display.ScreenLayout
|
||||
|
|
@ -27,12 +28,14 @@ import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
|||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.HeaderSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.RunnableSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
||||
|
|
@ -596,6 +599,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.COMPRESS_INSTALLED_CIA_CONTENT.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.ASYNC_FS_OPERATIONS,
|
||||
R.string.async_fs_operations,
|
||||
R.string.async_fs_operations_description,
|
||||
BooleanSetting.ASYNC_FS_OPERATIONS.key,
|
||||
BooleanSetting.ASYNC_FS_OPERATIONS.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -776,6 +788,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
||||
sl.apply {
|
||||
add(
|
||||
RunnableSetting(
|
||||
R.string.controller_auto_map,
|
||||
R.string.controller_auto_map_description,
|
||||
true,
|
||||
R.drawable.ic_controller,
|
||||
{ settingsAdapter.onClickAutoMap() },
|
||||
onLongClick = { settingsAdapter.onLongClickAutoMap() }
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.generic_buttons))
|
||||
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
|
|
@ -811,7 +833,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
add(InputBindingSetting(button, Settings.triggerTitles[i]))
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.controller_hotkeys))
|
||||
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
|
||||
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
|
||||
val button = getInputObject(key)
|
||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||
|
|
@ -898,6 +920,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
IntSetting.RESOLUTION_FACTOR.key,
|
||||
IntSetting.RESOLUTION_FACTOR.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_INTEGER_SCALING,
|
||||
R.string.use_integer_scaling,
|
||||
R.string.use_integer_scaling_description,
|
||||
BooleanSetting.USE_INTEGER_SCALING.key,
|
||||
BooleanSetting.USE_INTEGER_SCALING.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
|
|
@ -1148,6 +1179,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.UPRIGHT_SCREEN.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
MultiChoiceSetting(
|
||||
IntListSetting.LAYOUTS_TO_CYCLE,
|
||||
R.string.layouts_to_cycle,
|
||||
R.string.layouts_to_cycle_description,
|
||||
R.array.landscapeLayouts,
|
||||
R.array.landscapeLayoutValues,
|
||||
IntListSetting.LAYOUTS_TO_CYCLE.key,
|
||||
IntListSetting.LAYOUTS_TO_CYCLE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.PORTRAIT_SCREEN_LAYOUT,
|
||||
|
|
@ -1242,7 +1284,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue
|
||||
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue.toInt()
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
|
|
@ -1265,7 +1307,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue
|
||||
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue.toInt()
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
|
|
@ -1288,7 +1330,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
override val section = null
|
||||
override val isRuntimeEditable = false
|
||||
override val valueAsString = int.toString()
|
||||
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue
|
||||
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue.toInt()
|
||||
}
|
||||
add(
|
||||
SliderSetting(
|
||||
|
|
@ -1671,6 +1713,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.ENABLE_REALTIME_AUDIO.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED,
|
||||
R.string.simulate_headphones_plugged,
|
||||
R.string.simulate_headphones_plugged_description,
|
||||
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED.key,
|
||||
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.AUDIO_INPUT_TYPE,
|
||||
|
|
@ -1757,6 +1808,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.VSYNC.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS,
|
||||
R.string.simulate_3ds_gpu_timings,
|
||||
R.string.simulate_3ds_gpu_timings_description,
|
||||
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS.key,
|
||||
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.DEBUG_RENDERER,
|
||||
|
|
@ -1784,6 +1844,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
BooleanSetting.ENABLE_RPC_SERVER.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
|
||||
R.string.toggle_unique_data_console_type,
|
||||
R.string.toggle_unique_data_console_type_desc,
|
||||
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.key,
|
||||
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.DELAY_START_LLE_MODULES,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: SettingsItem
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingValue.text = getTextSetting()
|
||||
|
||||
if (setting.isActive) {
|
||||
binding.textSettingName.alpha = 1f
|
||||
binding.textSettingDescription.alpha = 1f
|
||||
binding.textSettingValue.alpha = 1f
|
||||
} else {
|
||||
binding.textSettingName.alpha = 0.5f
|
||||
binding.textSettingDescription.alpha = 0.5f
|
||||
binding.textSettingValue.alpha = 0.5f
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTextSetting(): String {
|
||||
when (val item = setting) {
|
||||
is MultiChoiceSetting -> {
|
||||
val resMgr = binding.textSettingDescription.context.resources
|
||||
val values = resMgr.getIntArray(item.valuesId)
|
||||
var resList:List<String> = emptyList();
|
||||
values.forEachIndexed { i: Int, value: Int ->
|
||||
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
|
||||
resList = resList + resMgr.getStringArray(item.choicesId)[i];
|
||||
}
|
||||
}
|
||||
return resList.joinToString();
|
||||
}
|
||||
|
||||
else -> return ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
if (!setting.isEditable || !setting.isEnabled) {
|
||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||
return
|
||||
}
|
||||
|
||||
if (setting is MultiChoiceSetting) {
|
||||
adapter.onMultiChoiceClick(
|
||||
(setting as MultiChoiceSetting),
|
||||
bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
if (setting.isActive) {
|
||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
||||
} else {
|
||||
adapter.onClickDisabledSetting(!setting.isEditable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,10 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
// no-op
|
||||
return true
|
||||
if (!setting.isEditable) {
|
||||
adapter.onClickDisabledSetting(true)
|
||||
return true
|
||||
}
|
||||
return setting.onLongClick?.invoke() ?: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ import org.citra.citra_emu.R
|
|||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.SettingSection
|
||||
|
|
@ -255,6 +256,11 @@ object SettingsFile {
|
|||
return stringSetting
|
||||
}
|
||||
|
||||
val intListSetting = IntListSetting.from(key)
|
||||
if (intListSetting != null) {
|
||||
intListSetting.list = value.split(", ").map { it.toInt() }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogAutoMapBinding
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
||||
/**
|
||||
* Captures a single button press to detect controller layout (Xbox vs Nintendo)
|
||||
* and d-pad type (axis vs button), then applies the appropriate bindings.
|
||||
*/
|
||||
class AutoMapDialogFragment : BottomSheetDialogFragment() {
|
||||
private var _binding: DialogAutoMapBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var onComplete: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = DialogAutoMapBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
BottomSheetBehavior.from<View>(view.parent as View).state =
|
||||
BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
isCancelable = false
|
||||
view.requestFocus()
|
||||
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
|
||||
|
||||
binding.textTitle.setText(R.string.controller_auto_map)
|
||||
binding.textMessage.setText(R.string.auto_map_prompt)
|
||||
|
||||
binding.imageFaceButtons.setImageResource(R.drawable.automap_face_buttons)
|
||||
|
||||
dialog?.setOnKeyListener { _, _, event -> onKeyEvent(event) }
|
||||
|
||||
binding.buttonCancel.setOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.action != KeyEvent.ACTION_UP) return false
|
||||
|
||||
val keyCode = event.keyCode
|
||||
val device = event.device
|
||||
|
||||
// Check if this is a Nintendo Switch Joy-Con (not Pro Controller).
|
||||
// Joy-Cons have unique quirks: split devices, non-standard D-pad scan codes,
|
||||
// partial A/B swap but no X/Y swap from Android's evdev layer.
|
||||
val isJoyCon = InputBindingSetting.isJoyCon(device)
|
||||
|
||||
if (isJoyCon) {
|
||||
Log.info("[AutoMap] Detected Joy-Con - using Joy-Con mappings")
|
||||
InputBindingSetting.clearAllBindings()
|
||||
InputBindingSetting.applyJoyConBindings()
|
||||
onComplete?.invoke()
|
||||
dismiss()
|
||||
return true
|
||||
}
|
||||
|
||||
// For non-Joy-Con controllers, determine layout from which keycode arrives
|
||||
// for the east/right position.
|
||||
// The user is pressing the button in the "A" (east/right) position on the 3DS diamond.
|
||||
// Xbox layout: east position sends KEYCODE_BUTTON_B (97)
|
||||
// Nintendo layout: east position sends KEYCODE_BUTTON_A (96)
|
||||
val isNintendoLayout = when (keyCode) {
|
||||
KeyEvent.KEYCODE_BUTTON_A -> true
|
||||
KeyEvent.KEYCODE_BUTTON_B -> false
|
||||
else -> {
|
||||
// Unrecognized button - ignore and wait for a valid press
|
||||
Log.warning("[AutoMap] Ignoring unrecognized keycode $keyCode, waiting for A or B")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val layoutName = if (isNintendoLayout) "Nintendo" else "Xbox"
|
||||
Log.info("[AutoMap] Detected $layoutName layout (keyCode=$keyCode)")
|
||||
|
||||
val useAxisDpad = detectDpadType(device)
|
||||
|
||||
val dpadName = if (useAxisDpad) "axis" else "button"
|
||||
Log.info("[AutoMap] Detected $dpadName d-pad (device=${device?.name})")
|
||||
|
||||
InputBindingSetting.clearAllBindings()
|
||||
InputBindingSetting.applyAutoMapBindings(isNintendoLayout, useAxisDpad)
|
||||
|
||||
onComplete?.invoke()
|
||||
dismiss()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "AutoMapDialogFragment"
|
||||
|
||||
fun newInstance(
|
||||
onComplete: () -> Unit
|
||||
): AutoMapDialogFragment {
|
||||
val dialog = AutoMapDialogFragment()
|
||||
dialog.onComplete = onComplete
|
||||
return dialog
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for axis d-pad (HAT_X/HAT_Y), false for button d-pad (DPAD_UP/DOWN/LEFT/RIGHT).
|
||||
* Prefers axis when both are present. Defaults to axis if detection fails.
|
||||
*/
|
||||
private fun detectDpadType(device: InputDevice?): Boolean {
|
||||
if (device == null) return true
|
||||
|
||||
val hasAxisDpad = device.motionRanges.any {
|
||||
it.axis == MotionEvent.AXIS_HAT_X || it.axis == MotionEvent.AXIS_HAT_Y
|
||||
}
|
||||
|
||||
if (hasAxisDpad) return true
|
||||
|
||||
val dpadKeyCodes = intArrayOf(
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
)
|
||||
val hasButtonDpad = device.hasKeys(*dpadKeyCodes).any { it }
|
||||
|
||||
return !hasButtonDpad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,14 @@ import android.content.DialogInterface
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.SystemClock
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
|
|
@ -72,6 +74,7 @@ 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.utils.SettingsFile
|
||||
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.DirectoryInitializationState
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
|
|
@ -107,6 +110,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
private val onPause = Runnable{ togglePause() }
|
||||
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) {
|
||||
super.onAttach(context)
|
||||
if (context is EmulationActivity) {
|
||||
|
|
@ -124,25 +130,37 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
val intent = requireActivity().intent
|
||||
val intentUri: Uri? = intent.data
|
||||
var intentUri: Uri? = intent.data
|
||||
val oldIntentInfo = Pair(
|
||||
intent.getStringExtra("SelectedGame"),
|
||||
intent.getStringExtra("SelectedTitle")
|
||||
)
|
||||
var intentGame: Game? = null
|
||||
intentUri = if (intentUri == null && oldIntentInfo.first != null) {
|
||||
Uri.parse(oldIntentInfo.first)
|
||||
} else {
|
||||
intentUri
|
||||
}
|
||||
if (intentUri != null) {
|
||||
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
|
||||
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else if (oldIntentInfo.first != null) {
|
||||
val gameUri = Uri.parse(oldIntentInfo.first)
|
||||
intentGame = if (Game.extensions.contains(FileUtil.getExtension(gameUri))) {
|
||||
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false)
|
||||
} else {
|
||||
null
|
||||
if (!BuildUtil.isGooglePlayBuild) {
|
||||
val intentUriString = intentUri.toString()
|
||||
// We need to build a special path as the incoming URI may be SAF exclusive
|
||||
Log.warning("[EmulationFragment] Cannot determine native path of URI \"" +
|
||||
intentUriString + "\", using file descriptor instead.")
|
||||
if (!intentUriString.startsWith("!")) {
|
||||
gameFd = requireContext().contentResolver.openFileDescriptor(intentUri, "r")?.detachFd()
|
||||
intentUri = if (gameFd != null) {
|
||||
Uri.parse("fd://" + gameFd.toString())
|
||||
} else {
|
||||
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", "")
|
||||
|
|
@ -160,6 +178,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
return
|
||||
}
|
||||
|
||||
Log.info("[EmulationFragment] Starting application " + game.path)
|
||||
|
||||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||
retainInstance = true
|
||||
emulationState = EmulationState(game.path)
|
||||
|
|
@ -175,6 +195,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentEmulationBinding.inflate(inflater)
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_landscape_screen_layout).isVisible =
|
||||
CitraApplication.appContext.resources.configuration.orientation !=
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_portrait_screen_layout).isVisible =
|
||||
CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
|
@ -479,7 +505,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
super.onResume()
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
if (NativeLibrary.isRunning()) {
|
||||
emulationState.pause()
|
||||
emulationState.unpause()
|
||||
|
||||
// If the overlay is enabled, we need to update the position if changed
|
||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
||||
|
|
@ -517,8 +543,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (::emulationState.isInitialized && requireActivity().isFinishing) {
|
||||
emulationState.stop()
|
||||
}
|
||||
EmulationLifecycleUtil.removeHook(onPause)
|
||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||
if (gameFd != null) {
|
||||
ParcelFileDescriptor.adoptFd(gameFd!!).close()
|
||||
gameFd = null
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
@ -845,7 +878,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
popupMenu.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_emulation_amiibo_load -> {
|
||||
emulationActivity.openFileLauncher.launch(false)
|
||||
emulationActivity.openAmiiboFileLauncher.launch(false)
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.citra.citra_emu.adapters.GameAdapter
|
|||
import org.citra.citra_emu.databinding.FragmentGamesBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
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.GamesViewModel
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
|
|
@ -60,8 +61,14 @@ class GamesFragment : Fragment() {
|
|||
companion object {
|
||||
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
|
||||
if (outputUri != null) {
|
||||
val outputPath: String =
|
||||
if (!BuildUtil.isGooglePlayBuild) {
|
||||
"!" + NativeLibrary.getNativePath(outputUri)
|
||||
} else {
|
||||
outputUri.toString()
|
||||
}
|
||||
CompressProgressDialogViewModel.reset()
|
||||
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputUri.toString())
|
||||
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputPath)
|
||||
dialog.showNow(
|
||||
fragment.requireActivity().supportFragmentManager,
|
||||
CompressProgressDialogFragment.TAG
|
||||
|
|
@ -69,9 +76,9 @@ class GamesFragment : Fragment() {
|
|||
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val status = if (shouldCompress) {
|
||||
NativeLibrary.compressFile(inputPath, outputUri.toString())
|
||||
NativeLibrary.compressFile(inputPath, outputPath)
|
||||
} else {
|
||||
NativeLibrary.decompressFile(inputPath, outputUri.toString())
|
||||
NativeLibrary.decompressFile(inputPath, outputPath)
|
||||
}
|
||||
|
||||
fragment.requireActivity().runOnUiThread {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import org.citra.citra_emu.adapters.HomeSettingAdapter
|
|||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.model.Game
|
||||
|
|
@ -89,7 +89,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
{
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
||||
|
||||
inputBinding.editTextInput.setText(textInputValue)
|
||||
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||
|
|
@ -103,7 +103,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (textInputValue.isNotEmpty()) {
|
||||
preferences.edit()
|
||||
.putString("last_artic_base_addr", textInputValue)
|
||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
||||
.apply()
|
||||
val menu = Game(
|
||||
title = getString(R.string.artic_base),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
|
@ -32,7 +31,6 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
|
|
@ -92,23 +90,20 @@ class SetupFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
homeViewModel.selectedCitraDirectoryLiveData.observe(viewLifecycleOwner) { uri ->
|
||||
if (uri == null) {
|
||||
return@observe
|
||||
}
|
||||
)
|
||||
|
||||
requireActivity().window.navigationBarColor =
|
||||
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||
onOpenCitraDirectory(uri)
|
||||
homeViewModel.selectedCitraDirectory = null
|
||||
}
|
||||
homeViewModel.selectedGamesDirectoryLiveData.observe(viewLifecycleOwner) { uri ->
|
||||
if (uri == null) {
|
||||
return@observe
|
||||
}
|
||||
onGetGamesDirectory(uri)
|
||||
homeViewModel.selectedGamesDirectory = null
|
||||
}
|
||||
|
||||
pages = mutableListOf()
|
||||
pages.apply {
|
||||
|
|
@ -320,7 +315,7 @@ class SetupFragment : Fragment() {
|
|||
R.string.select_citra_user_folder_description,
|
||||
buttonAction = {
|
||||
pageButtonCallback = it
|
||||
PermissionsHandler.compatibleSelectDirectory(openCitraDirectory)
|
||||
PermissionsHandler.compatibleSelectDirectory(mainActivity.setupOpenCitraDirectory)
|
||||
},
|
||||
buttonState = {
|
||||
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||
|
|
@ -342,9 +337,9 @@ class SetupFragment : Fragment() {
|
|||
R.drawable.ic_controller,
|
||||
R.string.games,
|
||||
R.string.games_description,
|
||||
buttonAction = {
|
||||
buttonAction = {
|
||||
pageButtonCallback = it
|
||||
getGamesDirectory.launch(
|
||||
mainActivity.setupGetGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
},
|
||||
|
|
@ -409,27 +404,33 @@ class SetupFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding.viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||
var previousPosition: Int = 0
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(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
|
||||
updateNavigationButtons(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 {
|
||||
val index = binding.viewPager2.currentItem
|
||||
val currentPage = pages[index]
|
||||
|
|
@ -479,29 +480,23 @@ class SetupFragment : Fragment() {
|
|||
}
|
||||
binding.buttonBack.setOnClickListener { pageBackward() }
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
val nextIsVisible = savedInstanceState.getBoolean(KEY_NEXT_VISIBILITY)
|
||||
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
||||
|
||||
if (nextIsVisible) {
|
||||
binding.buttonNext.visibility = View.VISIBLE
|
||||
}
|
||||
if (backIsVisible) {
|
||||
binding.buttonBack.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
if (savedInstanceState == null) {
|
||||
hasBeenWarned = BooleanArray(pages.size)
|
||||
} else {
|
||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED) ?: BooleanArray(pages.size)
|
||||
}
|
||||
|
||||
updateNavigationButtons(binding.viewPager2.currentItem)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
|
||||
outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
|
||||
outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
|
||||
|
||||
if (::hasBeenWarned.isInitialized) {
|
||||
outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
|
@ -510,15 +505,39 @@ class SetupFragment : Fragment() {
|
|||
}
|
||||
|
||||
private lateinit var pageButtonCallback: SetupCallback
|
||||
private val checkForButtonState: () -> Unit = {
|
||||
val page = pages[binding.viewPager2.currentItem]
|
||||
page.pageButtons?.forEach {
|
||||
if (it.buttonState() == ButtonState.BUTTON_ACTION_COMPLETE) {
|
||||
pageButtonCallback.onStepCompleted(it.titleId, pageFullyCompleted = false)
|
||||
}
|
||||
|
||||
if (page.pageSteps() == PageState.PAGE_STEPS_COMPLETE) {
|
||||
pageButtonCallback.onStepCompleted(0, pageFullyCompleted = true)
|
||||
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 = {
|
||||
val currentIndex = binding.viewPager2.currentItem
|
||||
val page = pages[currentIndex]
|
||||
|
||||
val isPageComplete = page.pageSteps() == PageState.PAGE_STEPS_COMPLETE
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -559,48 +578,38 @@ class SetupFragment : Fragment() {
|
|||
showPermissionDeniedSnackbar()
|
||||
}
|
||||
|
||||
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
) { result: Uri? ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
private fun onOpenCitraDirectory(result: Uri) {
|
||||
if (!BuildUtil.isGooglePlayBuild) {
|
||||
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||
if (NativeLibrary.getNativePath(result) == "") {
|
||||
SelectUserDirectoryDialogFragment.newInstance(
|
||||
mainActivity,
|
||||
R.string.invalid_selection,
|
||||
R.string.invalid_user_directory
|
||||
).show(mainActivity.supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
|
||||
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result,
|
||||
null, checkForButtonState)
|
||||
}
|
||||
|
||||
private val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
private fun onGetGamesDirectory(result: Uri) {
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
requireActivity().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
homeViewModel.setGamesDir(requireActivity(), result.path!!)
|
||||
|
||||
homeViewModel.setGamesDir(requireActivity(), result.path!!)
|
||||
|
||||
checkForButtonState.invoke()
|
||||
}
|
||||
checkForButtonState.invoke()
|
||||
}
|
||||
|
||||
private fun finishSetup() {
|
||||
preferences.edit()
|
||||
|
|
@ -611,10 +620,12 @@ class SetupFragment : Fragment() {
|
|||
|
||||
fun pageForward() {
|
||||
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
|
||||
homeViewModel.setupCurrentPage = binding.viewPager2.currentItem
|
||||
}
|
||||
|
||||
fun pageBackward() {
|
||||
binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
|
||||
homeViewModel.setupCurrentPage = binding.viewPager2.currentItem
|
||||
}
|
||||
|
||||
fun setPageWarned(page: Int) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.citra.citra_emu.R
|
|||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.SettingKeys
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
|
@ -177,7 +178,7 @@ class SystemFilesFragment : Fragment() {
|
|||
binding.buttonSetUpSystemFiles.setOnClickListener {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
|
||||
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
|
||||
|
||||
val progressDialog = showProgressDialog(
|
||||
getText(R.string.setup_system_files),
|
||||
|
|
@ -274,7 +275,7 @@ class SystemFilesFragment : Fragment() {
|
|||
.setPositiveButton(android.R.string.ok) { diag, _ ->
|
||||
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
|
||||
preferences.edit()
|
||||
.putString("last_artic_base_addr", textInputValue)
|
||||
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
|
||||
.apply()
|
||||
val menu = Game(
|
||||
title = getString(R.string.artic_base),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue