mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-08 11:43:40 -04:00
Compare commits
No commits in common. "master" and "2125.0-alpha2" have entirely different histories.
master
...
2125.0-alp
395 changed files with 15833 additions and 42739 deletions
|
|
@ -12,9 +12,6 @@ fi
|
||||||
|
|
||||||
echo "Tag name is: $TAG_NAME"
|
echo "Tag name is: $TAG_NAME"
|
||||||
|
|
||||||
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
|
||||||
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
|
||||||
|
|
||||||
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/bash -ex
|
|
||||||
|
|
||||||
# Determine the full revision name.
|
|
||||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
|
||||||
GITREV="`git show -s --format='%h'`"
|
|
||||||
|
|
||||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
|
|
||||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
|
||||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create .zip
|
|
||||||
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
|
if [[ "$TARGET" == "appimage"* ]]; then
|
||||||
# Compile the AppImage we distribute with Clang.
|
# Compile the AppImage we distribute with Clang.
|
||||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||||
-DCMAKE_C_COMPILER=clang
|
-DCMAKE_C_COMPILER=clang
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@
|
||||||
|
|
||||||
ARTIFACTS_LIST=($ARTIFACTS)
|
ARTIFACTS_LIST=($ARTIFACTS)
|
||||||
|
|
||||||
BUILD_DIR=build
|
BUNDLE_DIR=build/bundle
|
||||||
UNIVERSAL_DIR=$BUILD_DIR/universal
|
mkdir build
|
||||||
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
|
|
||||||
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
|
|
||||||
|
|
||||||
# Set up the base bundle to combine into.
|
# Set up the base artifact to combine into.
|
||||||
mkdir $UNIVERSAL_DIR
|
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
|
||||||
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
|
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
|
||||||
|
mv $BASE_ARTIFACT $BUNDLE_DIR
|
||||||
|
|
||||||
# Executable binary paths that need to be combined.
|
# Executable binary paths that need to be combined.
|
||||||
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
||||||
|
|
@ -20,18 +19,21 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
|
||||||
unset IFS
|
unset IFS
|
||||||
|
|
||||||
# Combine all of the executable binaries and dylibs.
|
# Combine all of the executable binaries and dylibs.
|
||||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
|
||||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
|
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
|
||||||
done
|
|
||||||
|
|
||||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
|
||||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
done
|
||||||
|
|
||||||
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
|
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||||
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
|
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||||
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
|
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||||
fi
|
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
|
||||||
|
if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
|
||||||
|
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
|
||||||
|
fi
|
||||||
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# Remove leftover libs so that they aren't distributed
|
# Remove leftover libs so that they aren't distributed
|
||||||
|
|
|
||||||
11
.ci/macos.sh
11
.ci/macos.sh
|
|
@ -4,10 +4,12 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
||||||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
mkdir build && cd build
|
||||||
cmake ../.. -GNinja \
|
cmake .. -GNinja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
-DENABLE_QT_TRANSLATION=ON \
|
-DENABLE_QT_TRANSLATION=ON \
|
||||||
-DENABLE_ROOM_STANDALONE=OFF \
|
-DENABLE_ROOM_STANDALONE=OFF \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
-DUSE_DISCORD_PRESENCE=ON \
|
||||||
|
|
@ -16,8 +18,9 @@ ninja
|
||||||
ninja bundle
|
ninja bundle
|
||||||
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
||||||
|
|
||||||
|
ccache -s -v
|
||||||
|
|
||||||
CURRENT_ARCH=`arch`
|
CURRENT_ARCH=`arch`
|
||||||
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
|
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
||||||
ctest -VV -C Release
|
ctest -VV -C Release
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
26
.ci/mxe.sh
26
.ci/mxe.sh
|
|
@ -1,26 +0,0 @@
|
||||||
#!/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,21 +3,20 @@
|
||||||
# Determine the full revision name.
|
# Determine the full revision name.
|
||||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||||
GITREV="`git show -s --format='%h'`"
|
GITREV="`git show -s --format='%h'`"
|
||||||
|
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
||||||
|
|
||||||
|
# Determine the name of the release being built.
|
||||||
|
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||||
|
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
||||||
|
REV_NAME="azahar-$GITHUB_REF_NAME-$OS-$TARGET"
|
||||||
|
else
|
||||||
|
RELEASE_NAME=azahar-head
|
||||||
|
fi
|
||||||
|
|
||||||
# Archive and upload the artifacts.
|
# Archive and upload the artifacts.
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
|
|
||||||
function pack_artifacts() {
|
function pack_artifacts() {
|
||||||
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
|
||||||
|
|
||||||
# Determine the name of the release being built.
|
|
||||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
|
||||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
|
||||||
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
|
|
||||||
else
|
|
||||||
RELEASE_NAME=azahar-head
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARTIFACTS_PATH="$1"
|
ARTIFACTS_PATH="$1"
|
||||||
|
|
||||||
# Set up root directory for archive.
|
# Set up root directory for archive.
|
||||||
|
|
@ -36,10 +35,10 @@ function pack_artifacts() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create .zip/.tar.gz
|
# Create .zip/.tar.gz
|
||||||
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
|
if [ "$OS" = "windows" ]; then
|
||||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||||
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
||||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
|
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
|
||||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||||
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||||
else
|
else
|
||||||
|
|
@ -57,23 +56,11 @@ if [ -n "$UNPACKED" ]; then
|
||||||
FILENAME=$(basename "$ARTIFACT")
|
FILENAME=$(basename "$ARTIFACT")
|
||||||
EXTENSION="${FILENAME##*.}"
|
EXTENSION="${FILENAME##*.}"
|
||||||
|
|
||||||
# TODO: Deduplicate
|
|
||||||
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
|
||||||
|
|
||||||
# Determine the name of the release being built.
|
|
||||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
|
||||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
|
||||||
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
|
|
||||||
else
|
|
||||||
RELEASE_NAME=azahar-head
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
||||||
done
|
done
|
||||||
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
||||||
# Pack and upload the artifacts one-by-one.
|
# Pack and upload the artifacts one-by-one.
|
||||||
for ARTIFACT in build/bundle/*; do
|
for ARTIFACT in build/bundle/*; do
|
||||||
TARGET=$(basename "$ARTIFACT")
|
|
||||||
pack_artifacts "$ARTIFACT"
|
pack_artifacts "$ARTIFACT"
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
|
|
|
||||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,6 +1,3 @@
|
||||||
- [ ] 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
|
If you are contributing to Azahar for the first time please
|
||||||
|
|
|
||||||
299
.github/workflows/build.yml
vendored
299
.github/workflows/build.yml
vendored
|
|
@ -7,48 +7,28 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
attestations: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
source:
|
source:
|
||||||
if: ${{ !github.head_ref }}
|
if: ${{ !github.head_ref }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Pack
|
- name: Pack
|
||||||
run: ./.ci/source.sh
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: source
|
name: source
|
||||||
path: artifacts/
|
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-x86_64:
|
linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target: ["appimage", "appimage-wayland", "gcc-nopch"]
|
target: ["appimage", "appimage-wayland", "fresh"]
|
||||||
container:
|
container:
|
||||||
image: opensauce04/azahar-build-environment:latest
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
|
|
@ -59,20 +39,19 @@ jobs:
|
||||||
OS: linux
|
OS: linux
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
||||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ github.job }}-${{ matrix.target }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Build
|
- name: Build
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
run: ./.ci/linux.sh
|
run: ./.ci/linux.sh
|
||||||
|
|
@ -85,132 +64,93 @@ jobs:
|
||||||
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
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
|
- name: Upload
|
||||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ github.job }}-${{ matrix.target }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
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
|
|
||||||
|
|
||||||
linux-arm64:
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target: ["clang", "gcc-nopch"]
|
|
||||||
container:
|
|
||||||
image: opensauce04/azahar-build-environment:latest
|
|
||||||
options: -u 1001
|
|
||||||
env:
|
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
||||||
CCACHE_COMPILERCHECK: content
|
|
||||||
CCACHE_SLOPPINESS: time_macros
|
|
||||||
OS: linux
|
|
||||||
TARGET: ${{ matrix.target }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- name: Set up cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: ${{ env.CCACHE_DIR }}
|
|
||||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ github.job }}-${{ matrix.target }}-
|
|
||||||
- name: Build
|
|
||||||
run: ./.ci/linux.sh
|
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
runs-on: 'macos-26'
|
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target: ["x86_64", "arm64"]
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
CCACHE_COMPILERCHECK: content
|
CCACHE_COMPILERCHECK: content
|
||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
|
||||||
OS: macos
|
OS: macos
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
uses: actions/cache@v4
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: brew install ccache ninja spirv-tools
|
run: brew install ccache ninja spirv-tools
|
||||||
- name: Build (x86_64)
|
- name: Build
|
||||||
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
|
run: ./.ci/macos.sh
|
||||||
- name: Build (arm64)
|
- name: Prepare outputs for caching
|
||||||
run: BUILD_ARCH=arm64 ./.ci/macos.sh
|
run: cp -R build/bundle $OS-$TARGET
|
||||||
|
- name: 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: Create universal app
|
- name: Create universal app
|
||||||
run: ./.ci/macos-universal.sh
|
run: ./.ci/macos-universal.sh
|
||||||
- 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:
|
env:
|
||||||
PACK_INDIVIDUALLY: 1
|
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
|
||||||
|
- name: Pack
|
||||||
run: ./.ci/pack.sh
|
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/macos.spdx.json
|
|
||||||
upload-artifact: false
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
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:
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
target: ["msvc", "msys2"]
|
||||||
- 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:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
||||||
|
|
@ -218,16 +158,14 @@ jobs:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
CCACHE_COMPILERCHECK: content
|
CCACHE_COMPILERCHECK: content
|
||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
|
||||||
OS: windows
|
OS: windows
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
uses: actions/cache@v4
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
|
|
@ -235,7 +173,7 @@ jobs:
|
||||||
${{ runner.os }}-${{ matrix.target }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Set up MSVC
|
- name: Set up MSVC
|
||||||
if: ${{ matrix.target == 'msvc' }}
|
if: ${{ matrix.target == 'msvc' }}
|
||||||
uses: azahar-emu/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
- name: Install extra tools (MSVC)
|
- name: Install extra tools (MSVC)
|
||||||
if: ${{ matrix.target == 'msvc' }}
|
if: ${{ matrix.target == 'msvc' }}
|
||||||
run: choco install ccache ninja ptime wget
|
run: choco install ccache ninja ptime wget
|
||||||
|
|
@ -256,62 +194,34 @@ jobs:
|
||||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||||
- name: Install extra tools (MSYS2)
|
- name: Install extra tools (MSYS2)
|
||||||
if: ${{ matrix.target == 'msys2' }}
|
if: ${{ matrix.target == 'msys2' }}
|
||||||
uses: crazy-max/ghaction-chocolatey@v4
|
uses: crazy-max/ghaction-chocolatey@v3
|
||||||
with:
|
with:
|
||||||
args: install ptime wget
|
args: install ptime wget
|
||||||
- name: Install NSIS
|
- name: Install NSIS
|
||||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
if: ${{ github.ref_type == 'tag' }}
|
||||||
run: |
|
run: |
|
||||||
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
|
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
|
ptime D:/a/_temp/nsis-setup.exe /S
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Disable line ending translation
|
- name: Disable line ending translation
|
||||||
run: git config --global core.autocrlf input
|
run: git config --global core.autocrlf input
|
||||||
- name: Build (Native)
|
- name: Build
|
||||||
if: ${{ matrix.target != 'mxe' }}
|
|
||||||
run: ./.ci/windows.sh
|
run: ./.ci/windows.sh
|
||||||
- name: Build (MXE)
|
- name: Generate installer
|
||||||
if: ${{ matrix.target == 'mxe' }}
|
if: ${{ github.ref_type == 'tag' }}
|
||||||
run: ./.ci/mxe.sh
|
|
||||||
- name: Generate installer (Native)
|
|
||||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
|
||||||
run: |
|
run: |
|
||||||
cd src\installer
|
cd src\installer
|
||||||
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||||
mkdir ..\..\artifacts 2> NUL
|
mkdir ..\..\artifacts 2> NUL
|
||||||
move /y *.exe ..\..\artifacts\
|
move /y *.exe ..\..\artifacts\
|
||||||
shell: cmd
|
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
|
- name: Pack
|
||||||
run: ./.ci/pack.sh
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
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:
|
android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -323,18 +233,17 @@ jobs:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
CCACHE_COMPILERCHECK: content
|
CCACHE_COMPILERCHECK: content
|
||||||
CCACHE_SLOPPINESS: time_macros
|
CCACHE_SLOPPINESS: time_macros
|
||||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
|
||||||
OS: android
|
OS: android
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
|
|
@ -372,48 +281,23 @@ jobs:
|
||||||
working-directory: src/android/app
|
working-directory: src/android/app
|
||||||
env:
|
env:
|
||||||
UNPACKED: 1
|
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
|
- name: Upload
|
||||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: src/android/app/artifacts/
|
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:
|
docker:
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: x86_64
|
|
||||||
os: ubuntu-24.04
|
|
||||||
- target: arm64
|
|
||||||
os: ubuntu-24.04-arm
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
container:
|
container:
|
||||||
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
image: docker:dind
|
||||||
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
|
||||||
env:
|
|
||||||
TARGET: ${{ matrix.target }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
- name: Install tools
|
||||||
|
run: apk add bash
|
||||||
- name: Fix git ownership
|
- name: Fix git ownership
|
||||||
run: git config --global --add safe.directory .
|
run: git config --global --add safe.directory .
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
|
|
@ -422,23 +306,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
mv build/*.dockerimage artifacts/
|
mv build/*.dockerimage artifacts/
|
||||||
- name: Generate SBOM
|
|
||||||
if: ${{ github.ref_type == 'tag' }}
|
|
||||||
uses: anchore/sbom-action@v0
|
|
||||||
with:
|
|
||||||
image: ${{ env.DOCKER_IMAGE_PATH }}
|
|
||||||
format: spdx-json
|
|
||||||
output-file: artifacts/docker-room.spdx.json
|
|
||||||
upload-artifact: false
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docker-${{ env.TARGET }}
|
name: docker
|
||||||
path: artifacts/
|
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')
|
(github.event.pull_request.author_association != 'OWNER')
|
||||||
steps:
|
steps:
|
||||||
- name: Detect PR if author is first-time contributor
|
- name: Detect PR if author is first-time contributor
|
||||||
uses: actions/github-script@v9
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
|
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
|
||||||
steps:
|
steps:
|
||||||
- name: Verify and reopen PR
|
- name: Verify and reopen PR
|
||||||
uses: actions/github-script@v9
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { owner, repo } = context.repo;
|
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
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|
|
||||||
178
.github/workflows/libretro.yml
vendored
178
.github/workflows/libretro.yml
vendored
|
|
@ -11,11 +11,6 @@ on:
|
||||||
env:
|
env:
|
||||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
attestations: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
android:
|
android:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
@ -28,7 +23,7 @@ jobs:
|
||||||
BUILD_DIR: build/android-arm64-v8a
|
BUILD_DIR: build/android-arm64-v8a
|
||||||
EXTRA_PATH: bin/Release
|
EXTRA_PATH: bin/Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set tag name
|
- name: Set tag name
|
||||||
|
|
@ -37,10 +32,6 @@ jobs:
|
||||||
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
echo $GIT_TAG_NAME
|
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
|
- name: Update Android SDK CMake version
|
||||||
run: |
|
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 "ndk;$ANDROID_NDK_VERSION"
|
||||||
|
|
@ -50,32 +41,11 @@ jobs:
|
||||||
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
|
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 $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)
|
${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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro_android.so
|
||||||
./*.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:
|
linux:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
|
|
@ -85,43 +55,18 @@ jobs:
|
||||||
EXTRA_PATH: bin/Release
|
EXTRA_PATH: bin/Release
|
||||||
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
|
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install tools
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y llvm
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.so
|
||||||
./*.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:
|
windows:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
|
|
@ -130,10 +75,10 @@ jobs:
|
||||||
BUILD_DIR: build/windows-x86_64
|
BUILD_DIR: build/windows-x86_64
|
||||||
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
|
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
|
||||||
CMAKE: x86_64-w64-mingw32.static-cmake
|
CMAKE: x86_64-w64-mingw32.static-cmake
|
||||||
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
|
IMAGE: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
||||||
EXTRA_PATH: bin/Release
|
EXTRA_PATH: bin/Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build in cross-container
|
- name: Build in cross-container
|
||||||
|
|
@ -145,36 +90,15 @@ jobs:
|
||||||
$IMAGE \
|
$IMAGE \
|
||||||
bash -lc "\
|
bash -lc "\
|
||||||
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
|
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
|
||||||
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
|
${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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dll
|
||||||
./*.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:
|
macos:
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
target: ["x86_64", "arm64"]
|
target: ["x86_64", "arm64"]
|
||||||
env:
|
env:
|
||||||
|
|
@ -184,7 +108,7 @@ jobs:
|
||||||
BUILD_DIR: build/osx-${{ matrix.target }}
|
BUILD_DIR: build/osx-${{ matrix.target }}
|
||||||
EXTRA_PATH: bin/Release
|
EXTRA_PATH: bin/Release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
|
|
@ -193,32 +117,11 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
|
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
|
||||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dylib
|
||||||
./*.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:
|
ios:
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
env:
|
env:
|
||||||
|
|
@ -228,39 +131,18 @@ jobs:
|
||||||
EXTRA_PATH: bin/Release
|
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
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dylib
|
||||||
./*.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:
|
tvos:
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
env:
|
env:
|
||||||
|
|
@ -270,35 +152,15 @@ jobs:
|
||||||
EXTRA_PATH: bin/Release
|
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
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
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
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: |
|
path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dylib
|
||||||
./*.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
|
image: opensauce04/azahar-build-environment:latest
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Fetch master branch
|
- name: Fetch master branch
|
||||||
|
|
|
||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10.2.0
|
- uses: actions/stale@v9.1.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-issue-stale: 90
|
days-before-issue-stale: 90
|
||||||
|
|
|
||||||
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
|
|
@ -7,10 +7,10 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
transifex:
|
transifex:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: opensauce04/azahar-build-environment:latest
|
container: opensauce04/azahar-build-environment:transifex
|
||||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -56,10 +56,3 @@ repo/
|
||||||
.ccache/
|
.ccache/
|
||||||
node_modules/
|
node_modules/
|
||||||
VULKAN_SDK/
|
VULKAN_SDK/
|
||||||
|
|
||||||
# Version info files
|
|
||||||
GIT-COMMIT
|
|
||||||
GIT-TAG
|
|
||||||
|
|
||||||
# verify-release.sh downloads
|
|
||||||
verify/
|
|
||||||
|
|
@ -82,7 +82,7 @@ libretro-build-osx-x64:
|
||||||
- mac-apple-silicon
|
- mac-apple-silicon
|
||||||
variables:
|
variables:
|
||||||
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
|
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
||||||
extends:
|
extends:
|
||||||
- .core-defs
|
- .core-defs
|
||||||
- .libretro-osx-cmake-x86_64
|
- .libretro-osx-cmake-x86_64
|
||||||
|
|
@ -92,8 +92,6 @@ libretro-build-osx-arm64:
|
||||||
extends:
|
extends:
|
||||||
- .core-defs
|
- .core-defs
|
||||||
- .libretro-osx-cmake-arm64
|
- .libretro-osx-cmake-arm64
|
||||||
variables:
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
|
||||||
|
|
||||||
################################### CELLULAR #################################
|
################################### CELLULAR #################################
|
||||||
# Android ARMv8a
|
# Android ARMv8a
|
||||||
|
|
@ -104,10 +102,6 @@ android-arm64-v8a:
|
||||||
variables:
|
variables:
|
||||||
ANDROID_NDK_VERSION: 26.2.11394342
|
ANDROID_NDK_VERSION: 26.2.11394342
|
||||||
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
||||||
LIBNAME: ${CORENAME}_libretro.so
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- $LIBNAME
|
|
||||||
|
|
||||||
# iOS arm64
|
# iOS arm64
|
||||||
libretro-build-ios-arm64:
|
libretro-build-ios-arm64:
|
||||||
14
.gitmodules
vendored
14
.gitmodules
vendored
|
|
@ -55,12 +55,18 @@
|
||||||
[submodule "sdl2"]
|
[submodule "sdl2"]
|
||||||
path = externals/sdl2/SDL
|
path = externals/sdl2/SDL
|
||||||
url = https://github.com/libsdl-org/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"]
|
[submodule "dds-ktx"]
|
||||||
path = externals/dds-ktx
|
path = externals/dds-ktx
|
||||||
url = https://github.com/septag/dds-ktx
|
url = https://github.com/septag/dds-ktx
|
||||||
[submodule "openal-soft"]
|
[submodule "openal-soft"]
|
||||||
path = externals/openal-soft
|
path = externals/openal-soft
|
||||||
url = https://github.com/azahar-emu/openal-soft
|
url = https://github.com/kcat/openal-soft
|
||||||
[submodule "glslang"]
|
[submodule "glslang"]
|
||||||
path = externals/glslang
|
path = externals/glslang
|
||||||
url = https://github.com/KhronosGroup/glslang
|
url = https://github.com/KhronosGroup/glslang
|
||||||
|
|
@ -100,9 +106,3 @@
|
||||||
[submodule "externals/libretro-common"]
|
[submodule "externals/libretro-common"]
|
||||||
path = externals/libretro-common/libretro-common
|
path = externals/libretro-common/libretro-common
|
||||||
url = https://github.com/libretro/libretro-common.git
|
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
20
AI-POLICY.md
|
|
@ -1,20 +0,0 @@
|
||||||
# Azahar Emulator AI Use Policy
|
|
||||||
|
|
||||||
The following document outlines the acceptable and unacceptable uses of AI within the Azahar codebase.
|
|
||||||
|
|
||||||
It describes whether or not submissions which were exposed to large language models (LLMs) such as ChatGPT, Claude, DeepSeek, and similar models would be capable of being merged in a pull request or otherwise utilized.
|
|
||||||
|
|
||||||
- ✅ Use of AI to help developers discover or understand problems in the codebase is acceptable **under the condition that any discovered issue is independently verified by a human**.
|
|
||||||
- ✅ Use of AI to write code snippets of a sufficiently small size that they aren't reasonably copyrightable **with disclosure in the PR description** is acceptable.
|
|
||||||
- This will be handled on a case-by-case basis and is up to the interpretation of the maintainer, but generic algorithm snippets up to a maximum of approximately 5 lines of code are acceptable.
|
|
||||||
- ❌ Use of AI to write code for submission without disclosure is prohibited.
|
|
||||||
- ❌ Use of AI to write the entirety/ a significant portion of a contribution is prohibited.
|
|
||||||
- ❌ Use of AI to write snippets of code which are of a size such that they could reasonably be copyrightable is prohibited.
|
|
||||||
- ❌ Use of AI to rewrite incompatibly-licensed code for submission to Azahar is prohibited.
|
|
||||||
- ❌ Use of AI to autonomously submit pull requests or issues is prohibited.
|
|
||||||
|
|
||||||
Pull requests which violate these rules will be closed. Previously accepted submissions which are found to violate these rules will be retroactively removed from the codebase.
|
|
||||||
|
|
||||||
This document may be updated in the future if further clarification is required.
|
|
||||||
|
|
||||||
This policy is effective for code submitted on or after the 20th of March 2026.
|
|
||||||
|
|
@ -13,9 +13,6 @@ cmake_policy(SET CMP0063 NEW)
|
||||||
cmake_policy(SET CMP0127 NEW)
|
cmake_policy(SET CMP0127 NEW)
|
||||||
set(CMAKE_POLICY_DEFAULT_CMP0063 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}/CMakeModules")
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||||
include(DownloadExternals)
|
include(DownloadExternals)
|
||||||
|
|
@ -28,15 +25,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||||
enable_language(OBJC OBJCXX)
|
enable_language(OBJC OBJCXX)
|
||||||
endif()
|
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)
|
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
|
||||||
|
|
||||||
# Some submodules like to pick their own default build type if not specified.
|
# Some submodules like to pick their own default build type if not specified.
|
||||||
|
|
@ -107,7 +95,7 @@ endif()
|
||||||
|
|
||||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
|
||||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||||
set(_USER_SET_OPTIONS "")
|
set(_USER_SET_OPTIONS "")
|
||||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||||
|
|
@ -130,7 +118,6 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
|
||||||
|
|
||||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" 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)
|
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||||
|
|
@ -139,8 +126,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
||||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
|
||||||
|
|
||||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||||
|
|
||||||
|
|
@ -150,8 +136,6 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
|
||||||
|
|
||||||
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
|
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
|
# Compile options
|
||||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
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})
|
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
||||||
|
|
@ -411,21 +395,13 @@ if (APPLE)
|
||||||
endif()
|
endif()
|
||||||
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
|
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
|
||||||
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
||||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
|
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
||||||
|
|
||||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||||
if (USE_SYSTEM_MOLTENVK)
|
if (NOT USE_SYSTEM_MOLTENVK)
|
||||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
|
||||||
else()
|
|
||||||
download_moltenvk()
|
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()
|
endif()
|
||||||
|
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -198,10 +198,6 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
|
|
||||||
# On Linux, always bundle an AppImage.
|
# On Linux, always bundle an AppImage.
|
||||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||||
if (IS_MINGW)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (IN_PLACE)
|
if (IN_PLACE)
|
||||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -277,23 +273,15 @@ else()
|
||||||
|
|
||||||
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||||
if (MINGW)
|
add_custom_command(
|
||||||
add_custom_command(
|
TARGET bundle
|
||||||
TARGET bundle
|
COMMAND ${CMAKE_COMMAND}
|
||||||
# The target here is arbitrary
|
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||||
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
|
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||||
POST_BUILD)
|
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||||
else()
|
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||||
add_custom_command(
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||||
TARGET bundle
|
POST_BUILD)
|
||||||
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()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
@ -305,11 +293,6 @@ else()
|
||||||
create_base_bundle_target()
|
create_base_bundle_target()
|
||||||
endif()
|
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}>")
|
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||||
if (bundle_qt AND APPLE)
|
if (bundle_qt AND APPLE)
|
||||||
# For Qt targets on Apple, expect an app bundle.
|
# For Qt targets on Apple, expect an app bundle.
|
||||||
|
|
@ -348,7 +331,6 @@ else()
|
||||||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||||
"-DBUNDLE_QT=${bundle_qt}"
|
"-DBUNDLE_QT=${bundle_qt}"
|
||||||
"-DIN_PLACE=${in_place}"
|
"-DIN_PLACE=${in_place}"
|
||||||
"-DIS_MINGW=${MINGW}"
|
|
||||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
function(disable_pax_mprotect target)
|
|
||||||
if (BSD STREQUAL "NetBSD")
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD
|
|
||||||
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
|
|
||||||
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
@ -171,8 +171,15 @@ function(download_qt target)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(download_moltenvk)
|
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")
|
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
|
||||||
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
if (NOT EXISTS ${MOLTENVK_DIR})
|
||||||
if (NOT EXISTS ${MOLTENVK_TAR})
|
if (NOT EXISTS ${MOLTENVK_TAR})
|
||||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
||||||
${MOLTENVK_TAR} SHOW_PROGRESS)
|
${MOLTENVK_TAR} SHOW_PROGRESS)
|
||||||
|
|
@ -181,6 +188,10 @@ function(download_moltenvk)
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||||
endif()
|
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()
|
endfunction()
|
||||||
|
|
||||||
function(get_external_prefix lib_name prefix_var)
|
function(get_external_prefix lib_name prefix_var)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
macro(generate_build_info)
|
macro(generate_build_info)
|
||||||
find_package(Git QUIET)
|
|
||||||
|
|
||||||
# Gets a UTC timstamp and sets the provided variable to it
|
# Gets a UTC timstamp and sets the provided variable to it
|
||||||
function(get_timestamp _var)
|
function(get_timestamp _var)
|
||||||
string(TIMESTAMP timestamp UTC)
|
string(TIMESTAMP timestamp UTC)
|
||||||
|
|
@ -8,14 +6,9 @@ macro(generate_build_info)
|
||||||
endfunction()
|
endfunction()
|
||||||
get_timestamp(BUILD_DATE)
|
get_timestamp(BUILD_DATE)
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
|
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||||
|
|
||||||
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
if (EXISTS "${SRC_DIR}/.git/objects")
|
||||||
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 the package here with the known path so that the GetGit commands can find it as well
|
||||||
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
||||||
|
|
||||||
|
|
@ -24,6 +17,12 @@ macro(generate_build_info)
|
||||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||||
git_describe(GIT_DESC --always --long --dirty)
|
git_describe(GIT_DESC --always --long --dirty)
|
||||||
git_branch_name(GIT_BRANCH)
|
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()
|
else()
|
||||||
# self-packed archive?
|
# self-packed archive?
|
||||||
set(GIT_REV "UNKNOWN")
|
set(GIT_REV "UNKNOWN")
|
||||||
|
|
@ -40,8 +39,8 @@ macro(generate_build_info)
|
||||||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||||
endif()
|
endif()
|
||||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
|
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
|
||||||
string(STRIP ${GIT_TAG} GIT_TAG)
|
string(STRIP ${GIT_TAG} GIT_TAG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
|
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
|
||||||
|
|
||||||
include(GenerateBuildInfo)
|
include(GenerateBuildInfo)
|
||||||
generate_build_info()
|
generate_build_info()
|
||||||
|
|
||||||
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
||||||
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
|
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||||
set(HASH_FILES
|
set(HASH_FILES
|
||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||||
|
|
@ -48,4 +47,4 @@ foreach (F IN LISTS HASH_FILES)
|
||||||
set(COMBINED "${COMBINED}${TMP}")
|
set(COMBINED "${COMBINED}${TMP}")
|
||||||
endforeach()
|
endforeach()
|
||||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||||
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||||
|
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
## This file should be the *only place* where setting keys exist as strings.
|
|
||||||
# All references to setting strings should be derived from the
|
|
||||||
# `setting_keys.h` and `jni_setting_keys.cpp` files generated here.
|
|
||||||
|
|
||||||
# !!! Changes made here should be mirrored to SettingKeys.kt if used on Android
|
|
||||||
|
|
||||||
# Shared setting keys (multi-platform)
|
|
||||||
foreach(KEY IN ITEMS
|
|
||||||
"use_artic_base_controller"
|
|
||||||
"enable_gamemode"
|
|
||||||
"use_cpu_jit"
|
|
||||||
"cpu_clock_percentage"
|
|
||||||
"is_new_3ds"
|
|
||||||
"lle_applets"
|
|
||||||
"deterministic_async_operations"
|
|
||||||
"enable_required_online_lle_modules"
|
|
||||||
"use_virtual_sd"
|
|
||||||
"use_custom_storage"
|
|
||||||
"compress_cia_installs"
|
|
||||||
"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
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||

|

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

|

|
||||||

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

|

|
||||||

|

|
||||||

|

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