mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-07-09 17:24:49 -06:00
Compare commits
77 Commits
2126.0-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c19c33bd3 | ||
|
|
bda4671be6 | ||
|
|
c2dd5f3bea | ||
|
|
31e4ecea58 | ||
|
|
49ba2e8d5f | ||
|
|
b64040b5fa | ||
|
|
64a967b4a8 | ||
|
|
7e976e852c | ||
|
|
ad0dde0725 | ||
|
|
d61801ad76 | ||
|
|
9a0f0e3caf | ||
|
|
4f63caf5fc | ||
|
|
2a39345e4b | ||
|
|
209173fa0c | ||
|
|
8c4e8b77b2 | ||
|
|
fcb10fb899 | ||
|
|
77cb5cecd8 | ||
|
|
6b197e2c04 | ||
|
|
384913be70 | ||
|
|
307f558d28 | ||
|
|
d93adebb16 | ||
|
|
7c225c6abb | ||
|
|
93143bcb15 | ||
|
|
32fd435928 | ||
|
|
1d59c9a0fc | ||
|
|
9293767e1d | ||
|
|
c9d2593c2c | ||
|
|
9e43e451d1 | ||
|
|
0a269688a3 | ||
|
|
3a77813821 | ||
|
|
fbeb53c25f | ||
|
|
1137d530b0 | ||
|
|
04f3a93854 | ||
|
|
28960bf166 | ||
|
|
176cd5cf14 | ||
|
|
735c63174c | ||
|
|
f015a208e5 | ||
|
|
deb130cdb7 | ||
|
|
4687226fa5 | ||
|
|
bd733284a3 | ||
|
|
cd1ef53569 | ||
|
|
7786dae94d | ||
|
|
5ea4bb2511 | ||
|
|
22d7be4e3c | ||
|
|
60e44ff049 | ||
|
|
d70099278a | ||
|
|
1afa24387f | ||
|
|
bfaaaee10d | ||
|
|
2940069fe2 | ||
|
|
a2a20fcc65 | ||
|
|
b4bda9d7f5 | ||
|
|
609ed7caf3 | ||
|
|
9ffb39eab2 | ||
|
|
792cde35ca | ||
|
|
398b13abc2 | ||
|
|
823901a364 | ||
|
|
8a6d597dec | ||
|
|
3dc357a8c9 | ||
|
|
379649dbce | ||
|
|
c03248f158 | ||
|
|
4867bb2e2b | ||
|
|
4e4c7e687b | ||
|
|
56f738eb06 | ||
|
|
b1e537a485 | ||
|
|
59da460177 | ||
|
|
8bdb60a6e1 | ||
|
|
383a28795e | ||
|
|
725544f3b4 | ||
|
|
135f10320a | ||
|
|
0b7114cbf8 | ||
|
|
95d42cb40a | ||
|
|
f0bc64d967 | ||
|
|
ad8526c4cf | ||
|
|
ae7d7dca1f | ||
|
|
4a4b75b0de | ||
|
|
b186b04995 | ||
|
|
ab6896a2ca |
@ -12,9 +12,9 @@ fi
|
||||
|
||||
echo "Tag name is: $TAG_NAME"
|
||||
|
||||
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
mkdir -p build
|
||||
FILENAME="azahar-room-$TAG_NAME.dockerimage"
|
||||
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
||||
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
||||
|
||||
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
||||
@ -6,8 +6,7 @@ cmake .. -GNinja \
|
||||
-DCMAKE_SYSTEM_NAME=iOS \
|
||||
-DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
ninja
|
||||
|
||||
ccache -s -v
|
||||
|
||||
@ -25,9 +25,8 @@ cmake .. -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DENABLE_ROOM_STANDALONE=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
strip -s bin/Release/*
|
||||
|
||||
@ -8,9 +8,8 @@ mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
||||
cmake ../.. -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DENABLE_ROOM_STANDALONE=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
ninja bundle
|
||||
|
||||
@ -14,8 +14,7 @@ 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 \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
-DUSE_SYSTEM_BOOST=ON \
|
||||
-DUSE_SYSTEM_CRYPTOPP=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
|
||||
@ -6,7 +6,7 @@ gcc -v
|
||||
tx --version
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
||||
cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
||||
make translation
|
||||
cd ..
|
||||
|
||||
|
||||
@ -10,8 +10,7 @@ cmake .. -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
ninja bundle
|
||||
|
||||
59
.github/workflows/build.yml
vendored
59
.github/workflows/build.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
output-file: artifacts/source.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
@ -61,13 +61,13 @@ jobs:
|
||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ github.job }}-${{ matrix.target }}
|
||||
path: artifacts/
|
||||
@ -123,11 +123,11 @@ jobs:
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -145,12 +145,12 @@ jobs:
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: macos
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
@ -183,7 +183,7 @@ jobs:
|
||||
output-file: artifacts/macos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}
|
||||
path: artifacts/
|
||||
@ -222,12 +222,12 @@ jobs:
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -235,7 +235,7 @@ jobs:
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
- name: Set up MSVC
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
uses: azahar-emu/msvc-dev-cmd@v1
|
||||
- name: Install extra tools (MSVC)
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
run: choco install ccache ninja ptime wget
|
||||
@ -256,7 +256,7 @@ jobs:
|
||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Install extra tools (MSYS2)
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
uses: crazy-max/ghaction-chocolatey@v3
|
||||
uses: crazy-max/ghaction-chocolatey@v4
|
||||
with:
|
||||
args: install ptime wget
|
||||
- name: Install NSIS
|
||||
@ -300,7 +300,7 @@ jobs:
|
||||
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@ -328,13 +328,13 @@ jobs:
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@ -382,7 +382,7 @@ jobs:
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
@ -396,15 +396,24 @@ jobs:
|
||||
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64
|
||||
os: ubuntu-24.04
|
||||
- target: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: docker:dind
|
||||
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
||||
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: apk add bash
|
||||
- name: Fix git ownership
|
||||
run: git config --global --add safe.directory .
|
||||
- name: Build Docker image
|
||||
@ -422,9 +431,9 @@ jobs:
|
||||
output-file: artifacts/docker-room.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker
|
||||
name: docker-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
|
||||
@ -20,7 +20,7 @@ jobs:
|
||||
(github.event.pull_request.author_association != 'OWNER')
|
||||
steps:
|
||||
- name: Detect PR if author is first-time contributor
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
@ -14,7 +14,7 @@ jobs:
|
||||
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
|
||||
steps:
|
||||
- name: Verify and reopen PR
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
14
.github/workflows/format.yml
vendored
14
.github/workflows/format.yml
vendored
@ -13,10 +13,22 @@ jobs:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
env:
|
||||
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
||||
run: ./.ci/clang-format.sh
|
||||
|
||||
ktlint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
run: ./tools/check-kotlin-formatting.sh
|
||||
37
.github/workflows/libretro.yml
vendored
37
.github/workflows/libretro.yml
vendored
@ -17,7 +17,7 @@ permissions:
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
android:
|
||||
libretro-android:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: android
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
BUILD_DIR: build/android-arm64-v8a
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set tag name
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
output-file: libretro-android.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
@ -76,7 +76,7 @@ jobs:
|
||||
./*.zip
|
||||
sbom-path: libretro-android.spdx.json
|
||||
|
||||
linux:
|
||||
libretro-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: linux
|
||||
@ -85,7 +85,7 @@ jobs:
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
output-file: libretro-linux.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
@ -122,7 +122,7 @@ jobs:
|
||||
./*.zip
|
||||
sbom-path: libretro-linux.spdx.json
|
||||
|
||||
windows:
|
||||
libretro-windows:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OS: windows
|
||||
@ -133,7 +133,7 @@ jobs:
|
||||
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build in cross-container
|
||||
@ -158,7 +158,7 @@ jobs:
|
||||
output-file: libretro-windows.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
@ -171,9 +171,10 @@ jobs:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-windows.spdx.json
|
||||
macos:
|
||||
libretro-macos:
|
||||
runs-on: macos-26
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
env:
|
||||
@ -183,7 +184,7 @@ jobs:
|
||||
BUILD_DIR: build/osx-${{ matrix.target }}
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
@ -204,7 +205,7 @@ jobs:
|
||||
output-file: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
@ -218,7 +219,7 @@ jobs:
|
||||
./*.zip
|
||||
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
|
||||
ios:
|
||||
libretro-ios:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: ios
|
||||
@ -227,7 +228,7 @@ jobs:
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@ -246,7 +247,7 @@ jobs:
|
||||
output-file: libretro-ios.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
@ -260,7 +261,7 @@ jobs:
|
||||
./*.zip
|
||||
sbom-path: libretro-ios.spdx.json
|
||||
|
||||
tvos:
|
||||
libretro-tvos:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: tvos
|
||||
@ -269,7 +270,7 @@ jobs:
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@ -288,7 +289,7 @@ jobs:
|
||||
output-file: libretro-tvos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
|
||||
2
.github/workflows/license-header.yml
vendored
2
.github/workflows/license-header.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch master branch
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 90
|
||||
|
||||
2
.github/workflows/transifex.yml
vendored
2
.github/workflows/transifex.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
container: opensauce04/azahar-build-environment:latest
|
||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,7 +17,8 @@ src/common/scm_rev.cpp
|
||||
*.swp
|
||||
*.kdev4
|
||||
.markdown-preview.html
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -55,12 +55,6 @@
|
||||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "cryptopp-cmake"]
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
[submodule "cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
[submodule "dds-ktx"]
|
||||
path = externals/dds-ktx
|
||||
url = https://github.com/septag/dds-ktx
|
||||
@ -109,3 +103,6 @@
|
||||
[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
|
||||
|
||||
@ -121,7 +121,7 @@ option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OF
|
||||
|
||||
# Set bundled qt as dependent options.
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON)
|
||||
option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
|
||||
@ -142,7 +142,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENA
|
||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF)
|
||||
|
||||
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
|
||||
|
||||
@ -411,13 +411,21 @@ if (APPLE)
|
||||
endif()
|
||||
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
|
||||
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
|
||||
|
||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||
if (NOT USE_SYSTEM_MOLTENVK)
|
||||
download_moltenvk()
|
||||
endif()
|
||||
if (USE_SYSTEM_MOLTENVK)
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
else()
|
||||
download_moltenvk()
|
||||
if (IOS)
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
|
||||
else()
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
|
||||
endif()
|
||||
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
||||
endif()
|
||||
|
||||
@ -171,27 +171,16 @@ function(download_qt target)
|
||||
endfunction()
|
||||
|
||||
function(download_moltenvk)
|
||||
if (IOS)
|
||||
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
|
||||
else()
|
||||
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
|
||||
endif()
|
||||
|
||||
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
|
||||
if (NOT EXISTS ${MOLTENVK_DIR})
|
||||
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
if (NOT EXISTS ${MOLTENVK_TAR})
|
||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar
|
||||
${MOLTENVK_TAR} SHOW_PROGRESS)
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||
endif()
|
||||
|
||||
# Add the MoltenVK library path to the prefix so find_library can locate it.
|
||||
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(get_external_prefix lib_name prefix_var)
|
||||
|
||||
@ -32,6 +32,7 @@ foreach(KEY IN ITEMS
|
||||
"physical_device"
|
||||
"use_gles"
|
||||
"renderer_debug"
|
||||
"pica_debugging"
|
||||
"dump_command_buffers"
|
||||
"spirv_shader_gen"
|
||||
"disable_spirv_optimizer"
|
||||
@ -41,6 +42,7 @@ foreach(KEY IN ITEMS
|
||||
"use_disk_shader_cache"
|
||||
"shaders_accurate_mul"
|
||||
"use_vsync"
|
||||
"use_skip_duplicate_frames"
|
||||
"use_display_refresh_rate_detection"
|
||||
"use_shader_jit"
|
||||
"resolution_factor"
|
||||
@ -238,6 +240,8 @@ if (ANDROID)
|
||||
"android_hide_images"
|
||||
"screen_orientation"
|
||||
"performance_overlay_position"
|
||||
"enable_secondary_display"
|
||||
"combo_button_buttons"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
|
||||
299
dist/dumpkeys/DumpKeys.gm9
vendored
299
dist/dumpkeys/DumpKeys.gm9
vendored
@ -1,299 +0,0 @@
|
||||
set PREVIEW_MODE "Key Dumper\n \nWorking..."
|
||||
|
||||
# Boot9
|
||||
|
||||
set BOOT9_BIN "M:/boot9.bin"
|
||||
|
||||
fget $[BOOT9_BIN]@D9D0:10 KEYX_2C
|
||||
set KEYX_2D $[KEYX_2C]
|
||||
set KEYX_2E $[KEYX_2C]
|
||||
set KEYX_2F $[KEYX_2C]
|
||||
fget $[BOOT9_BIN]@D9E0:10 KEYX_30
|
||||
set KEYX_31 $[KEYX_30]
|
||||
set KEYX_32 $[KEYX_30]
|
||||
set KEYX_33 $[KEYX_30]
|
||||
fget $[BOOT9_BIN]@D9F0:10 KEYX_34
|
||||
set KEYX_35 $[KEYX_34]
|
||||
set KEYX_36 $[KEYX_34]
|
||||
set KEYX_37 $[KEYX_34]
|
||||
fget $[BOOT9_BIN]@DA00:10 KEYX_38
|
||||
set KEYX_39 $[KEYX_38]
|
||||
set KEYX_3A $[KEYX_38]
|
||||
set KEYX_3B $[KEYX_38]
|
||||
fget $[BOOT9_BIN]@DA10:10 KEYX_3C
|
||||
fget $[BOOT9_BIN]@DA20:10 KEYX_3D
|
||||
fget $[BOOT9_BIN]@DA30:10 KEYX_3E
|
||||
fget $[BOOT9_BIN]@DA40:10 KEYX_3F
|
||||
|
||||
fget $[BOOT9_BIN]@DA50:10 KEYY_04
|
||||
fget $[BOOT9_BIN]@DA60:10 KEYY_05
|
||||
fget $[BOOT9_BIN]@DA70:10 KEYY_06
|
||||
fget $[BOOT9_BIN]@DA80:10 KEYY_07
|
||||
fget $[BOOT9_BIN]@DA90:10 KEYY_08
|
||||
fget $[BOOT9_BIN]@DAA0:10 KEYY_09
|
||||
fget $[BOOT9_BIN]@DAB0:10 KEYY_0A
|
||||
fget $[BOOT9_BIN]@DAC0:10 KEYY_0B
|
||||
|
||||
fget $[BOOT9_BIN]@DAD0:10 KEYN_0D
|
||||
fget $[BOOT9_BIN]@DBA0:10 KEYN_2D
|
||||
fget $[BOOT9_BIN]@DBB0:10 KEYN_32
|
||||
fget $[BOOT9_BIN]@DBC0:10 KEYN_36
|
||||
fget $[BOOT9_BIN]@DBD0:10 KEYN_38
|
||||
|
||||
# NATIVE_FIRM
|
||||
|
||||
if chk $[ONTYPE] "N3DS"
|
||||
if not find 1:/title/00040138/20000002/content/????????.app NATIVE_FIRM_APP
|
||||
echo "New 3DS NATIVE_FIRM not found."
|
||||
goto Exit
|
||||
end
|
||||
set EXPECTED_NATIVE_FIRM_VER "1F00"
|
||||
set KEYY_2E_OFFSET "66504"
|
||||
set KEYY_39_NFC_OFFSET "66524"
|
||||
set COMMON_0_OFFSET "6CE51"
|
||||
set COMMON_1_OFFSET "6CE65"
|
||||
set COMMON_2_OFFSET "6CE79"
|
||||
set COMMON_3_OFFSET "6CE8D"
|
||||
set COMMON_4_OFFSET "6CEA1"
|
||||
set COMMON_5_OFFSET "6CEB5"
|
||||
else
|
||||
if not find 1:/title/00040138/00000002/content/????????.app NATIVE_FIRM_APP
|
||||
echo "Old 3DS NATIVE_FIRM not found."
|
||||
goto Exit
|
||||
end
|
||||
set EXPECTED_NATIVE_FIRM_VER "1F00"
|
||||
set KEYY_2E_OFFSET "66488"
|
||||
set KEYY_39_NFC_OFFSET "664A8"
|
||||
set COMMON_0_OFFSET "6CDC1"
|
||||
set COMMON_1_OFFSET "6CDD5"
|
||||
set COMMON_2_OFFSET "6CDE9"
|
||||
set COMMON_3_OFFSET "6CDFD"
|
||||
set COMMON_4_OFFSET "6CE11"
|
||||
set COMMON_5_OFFSET "6CE25"
|
||||
end
|
||||
set NATIVE_FIRM_EXTHEADER "G:/extheader.bin"
|
||||
set NATIVE_FIRM_ENCRYPTED "G:/exefs/.firm"
|
||||
set NATIVE_FIRM_DECRYPTED "$[GM9OUT]/native.firm"
|
||||
set PROCESS9_CODE "G:/0004013000003000.Process9/exefs/.code"
|
||||
|
||||
imgmount $[NATIVE_FIRM_APP]
|
||||
|
||||
fget $[NATIVE_FIRM_EXTHEADER]@E:2 NATIVE_FIRM_VER
|
||||
if not chk $[NATIVE_FIRM_VER] $[EXPECTED_NATIVE_FIRM_VER]
|
||||
echo "Unsupported NATIVE_FIRM version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NATIVE_FIRM_VER], got $[NATIVE_FIRM_VER]"
|
||||
goto Exit
|
||||
end
|
||||
|
||||
cp -w $[NATIVE_FIRM_ENCRYPTED] $[NATIVE_FIRM_DECRYPTED]
|
||||
decrypt $[NATIVE_FIRM_DECRYPTED]
|
||||
|
||||
imgumount
|
||||
|
||||
imgmount $[NATIVE_FIRM_DECRYPTED]
|
||||
|
||||
fget $[PROCESS9_CODE]@$[KEYY_2E_OFFSET]:10 KEYY_2E
|
||||
set KEYY_31 $[KEYY_2E]
|
||||
set KEYY_39_DLP $[KEYY_2E]
|
||||
fget $[PROCESS9_CODE]@$[KEYY_39_NFC_OFFSET]:10 KEYY_39_NFC
|
||||
fget $[PROCESS9_CODE]@$[COMMON_0_OFFSET]:10 COMMON_0
|
||||
fget $[PROCESS9_CODE]@$[COMMON_1_OFFSET]:10 COMMON_1
|
||||
fget $[PROCESS9_CODE]@$[COMMON_2_OFFSET]:10 COMMON_2
|
||||
fget $[PROCESS9_CODE]@$[COMMON_3_OFFSET]:10 COMMON_3
|
||||
fget $[PROCESS9_CODE]@$[COMMON_4_OFFSET]:10 COMMON_4
|
||||
fget $[PROCESS9_CODE]@$[COMMON_5_OFFSET]:10 COMMON_5
|
||||
|
||||
imgumount
|
||||
rm -o -s $[NATIVE_FIRM_DECRYPTED]
|
||||
|
||||
# NFC
|
||||
|
||||
if chk $[ONTYPE] "N3DS"
|
||||
if not find 1:/title/00040130/20004002/content/????????.app NFC_APP
|
||||
echo "New 3DS NFC not found."
|
||||
goto Exit
|
||||
end
|
||||
set EXPECTED_NFC_VER "0700"
|
||||
set NFC_PHRASE_0_OFFSET "355EE"
|
||||
set NFC_SEED_0_OFFSET "355FC"
|
||||
set NFC_HMAC_KEY_0_OFFSET "3560A"
|
||||
set NFC_PHRASE_1_OFFSET "3561A"
|
||||
set NFC_SEED_1_OFFSET "35628"
|
||||
set NFC_HMAC_KEY_1_OFFSET "35638"
|
||||
set NFC_IV_OFFSET "35648"
|
||||
else
|
||||
if not find 1:/title/00040130/00004002/content/????????.app NFC_APP
|
||||
echo "Old 3DS NFC not found."
|
||||
goto Exit
|
||||
end
|
||||
set EXPECTED_NFC_VER "0800"
|
||||
set NFC_PHRASE_0_OFFSET "17382"
|
||||
set NFC_SEED_0_OFFSET "17390"
|
||||
set NFC_HMAC_KEY_0_OFFSET "1739E"
|
||||
set NFC_PHRASE_1_OFFSET "173AE"
|
||||
set NFC_SEED_1_OFFSET "173BC"
|
||||
set NFC_HMAC_KEY_1_OFFSET "173CC"
|
||||
set NFC_IV_OFFSET "173DC"
|
||||
end
|
||||
set NFC_EXTHEADER "G:/extheader.bin"
|
||||
set NFC_CODE "$[GM9OUT]/nfc_code.bin"
|
||||
|
||||
imgmount $[NFC_APP]
|
||||
|
||||
fget $[NFC_EXTHEADER]@E:2 NFC_VER
|
||||
if not chk $[NFC_VER] $[EXPECTED_NFC_VER]
|
||||
echo "Unsupported NFC module version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NFC_VER], got $[NFC_VER]"
|
||||
goto Exit
|
||||
end
|
||||
|
||||
imgumount
|
||||
|
||||
extrcode $[NFC_APP] $[NFC_CODE]
|
||||
|
||||
fget $[NFC_CODE]@$[NFC_PHRASE_0_OFFSET]:E NFC_PHRASE_0
|
||||
fget $[NFC_CODE]@$[NFC_SEED_0_OFFSET]:E NFC_SEED_0
|
||||
fget $[NFC_CODE]@$[NFC_HMAC_KEY_0_OFFSET]:10 NFC_HMAC_KEY_0
|
||||
|
||||
fget $[NFC_CODE]@$[NFC_PHRASE_1_OFFSET]:E NFC_PHRASE_1
|
||||
fget $[NFC_CODE]@$[NFC_SEED_1_OFFSET]:10 NFC_SEED_1
|
||||
fget $[NFC_CODE]@$[NFC_HMAC_KEY_1_OFFSET]:10 NFC_HMAC_KEY_1
|
||||
|
||||
fget $[NFC_CODE]@$[NFC_IV_OFFSET]:10 NFC_IV
|
||||
|
||||
rm -o -s $[NFC_CODE]
|
||||
|
||||
# GodMode9 Key Database
|
||||
|
||||
set KEY_DB "V:/aeskeydb.bin"
|
||||
set KEY_DB_18X "K:/slot0x18KeyX.ret.bin"
|
||||
set KEY_DB_19X "K:/slot0x19KeyX.ret.bin"
|
||||
set KEY_DB_1AX "K:/slot0x1AKeyX.ret.bin"
|
||||
set KEY_DB_1BX "K:/slot0x1BKeyX.ret.bin"
|
||||
set KEY_DB_1CX "K:/slot0x1CKeyX.ret.bin"
|
||||
set KEY_DB_1DX "K:/slot0x1DKeyX.ret.bin"
|
||||
set KEY_DB_1EX "K:/slot0x1EKeyX.ret.bin"
|
||||
set KEY_DB_1FX "K:/slot0x1FKeyX.ret.bin"
|
||||
set KEY_DB_25X "K:/slot0x25KeyX.ret.bin"
|
||||
set KEY_DB_24Y "K:/slot0x24KeyY.bin"
|
||||
set KEY_DB_2FY "K:/slot0x2FKeyY.ret.bin"
|
||||
|
||||
imgmount $[KEY_DB]
|
||||
|
||||
fget $[KEY_DB_18X]@0:10 KEYX_18
|
||||
fget $[KEY_DB_19X]@0:10 KEYX_19
|
||||
fget $[KEY_DB_1AX]@0:10 KEYX_1A
|
||||
fget $[KEY_DB_1BX]@0:10 KEYX_1B
|
||||
fget $[KEY_DB_1CX]@0:10 KEYX_1C
|
||||
fget $[KEY_DB_1DX]@0:10 KEYX_1D
|
||||
fget $[KEY_DB_1EX]@0:10 KEYX_1E
|
||||
fget $[KEY_DB_1FX]@0:10 KEYX_1F
|
||||
fget $[KEY_DB_25X]@0:10 KEYX_25
|
||||
|
||||
fget $[KEY_DB_24Y]@0:10 KEYY_24
|
||||
fget $[KEY_DB_2FY]@0:10 KEYY_2F
|
||||
|
||||
imgumount
|
||||
|
||||
# Write Keys To File
|
||||
|
||||
set OUT "0:/gm9/aes_keys.txt"
|
||||
|
||||
dumptxt $[OUT] "# KeyX"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "slot0x18KeyX=$[KEYX_18]"
|
||||
dumptxt -p $[OUT] "slot0x19KeyX=$[KEYX_19]"
|
||||
dumptxt -p $[OUT] "slot0x1AKeyX=$[KEYX_1A]"
|
||||
dumptxt -p $[OUT] "slot0x1BKeyX=$[KEYX_1B]"
|
||||
dumptxt -p $[OUT] "slot0x1CKeyX=$[KEYX_1C]"
|
||||
dumptxt -p $[OUT] "slot0x1DKeyX=$[KEYX_1D]"
|
||||
dumptxt -p $[OUT] "slot0x1EKeyX=$[KEYX_1E]"
|
||||
dumptxt -p $[OUT] "slot0x1FKeyX=$[KEYX_1F]"
|
||||
dumptxt -p $[OUT] "slot0x25KeyX=$[KEYX_25]"
|
||||
dumptxt -p $[OUT] "slot0x2CKeyX=$[KEYX_2C]"
|
||||
dumptxt -p $[OUT] "slot0x2DKeyX=$[KEYX_2D]"
|
||||
dumptxt -p $[OUT] "slot0x2EKeyX=$[KEYX_2E]"
|
||||
dumptxt -p $[OUT] "slot0x2FKeyX=$[KEYX_2F]"
|
||||
dumptxt -p $[OUT] "slot0x30KeyX=$[KEYX_30]"
|
||||
dumptxt -p $[OUT] "slot0x31KeyX=$[KEYX_31]"
|
||||
dumptxt -p $[OUT] "slot0x32KeyX=$[KEYX_32]"
|
||||
dumptxt -p $[OUT] "slot0x33KeyX=$[KEYX_33]"
|
||||
dumptxt -p $[OUT] "slot0x34KeyX=$[KEYX_34]"
|
||||
dumptxt -p $[OUT] "slot0x35KeyX=$[KEYX_35]"
|
||||
dumptxt -p $[OUT] "slot0x36KeyX=$[KEYX_36]"
|
||||
dumptxt -p $[OUT] "slot0x37KeyX=$[KEYX_37]"
|
||||
dumptxt -p $[OUT] "slot0x38KeyX=$[KEYX_38]"
|
||||
dumptxt -p $[OUT] "slot0x39KeyX=$[KEYX_39]"
|
||||
dumptxt -p $[OUT] "slot0x3AKeyX=$[KEYX_3A]"
|
||||
dumptxt -p $[OUT] "slot0x3BKeyX=$[KEYX_3B]"
|
||||
dumptxt -p $[OUT] "slot0x3CKeyX=$[KEYX_3C]"
|
||||
dumptxt -p $[OUT] "slot0x3DKeyX=$[KEYX_3D]"
|
||||
dumptxt -p $[OUT] "slot0x3EKeyX=$[KEYX_3E]"
|
||||
dumptxt -p $[OUT] "slot0x3FKeyX=$[KEYX_3F]"
|
||||
|
||||
dumptxt -p $[OUT] ""
|
||||
dumptxt -p $[OUT] "# KeyY"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "slot0x04KeyY=$[KEYY_04]"
|
||||
dumptxt -p $[OUT] "slot0x05KeyY=$[KEYY_05]"
|
||||
dumptxt -p $[OUT] "slot0x06KeyY=$[KEYY_06]"
|
||||
dumptxt -p $[OUT] "slot0x07KeyY=$[KEYY_07]"
|
||||
dumptxt -p $[OUT] "slot0x08KeyY=$[KEYY_08]"
|
||||
dumptxt -p $[OUT] "slot0x09KeyY=$[KEYY_09]"
|
||||
dumptxt -p $[OUT] "slot0x0AKeyY=$[KEYY_0A]"
|
||||
dumptxt -p $[OUT] "slot0x0BKeyY=$[KEYY_0B]"
|
||||
dumptxt -p $[OUT] "slot0x24KeyY=$[KEYY_24]"
|
||||
dumptxt -p $[OUT] "slot0x2EKeyY=$[KEYY_2E]"
|
||||
dumptxt -p $[OUT] "slot0x2FKeyY=$[KEYY_2F]"
|
||||
dumptxt -p $[OUT] "slot0x31KeyY=$[KEYY_31]"
|
||||
|
||||
dumptxt -p $[OUT] ""
|
||||
dumptxt -p $[OUT] "# DLP/NFC KeyY (slot 0x39)"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "dlpKeyY=$[KEYY_39_DLP]"
|
||||
dumptxt -p $[OUT] "nfcKeyY=$[KEYY_39_NFC]"
|
||||
|
||||
dumptxt -p $[OUT] ""
|
||||
dumptxt -p $[OUT] "# Ticket Common KeyY (slot 0x3D)"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "common0=$[COMMON_0]"
|
||||
dumptxt -p $[OUT] "common1=$[COMMON_1]"
|
||||
dumptxt -p $[OUT] "common2=$[COMMON_2]"
|
||||
dumptxt -p $[OUT] "common3=$[COMMON_3]"
|
||||
dumptxt -p $[OUT] "common4=$[COMMON_4]"
|
||||
dumptxt -p $[OUT] "common5=$[COMMON_5]"
|
||||
|
||||
dumptxt -p $[OUT] ""
|
||||
dumptxt -p $[OUT] "# KeyN"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "slot0x0DKeyN=$[KEYN_0D]"
|
||||
dumptxt -p $[OUT] "slot0x2DKeyN=$[KEYN_2D]"
|
||||
dumptxt -p $[OUT] "slot0x32KeyN=$[KEYN_32]"
|
||||
dumptxt -p $[OUT] "slot0x36KeyN=$[KEYN_36]"
|
||||
dumptxt -p $[OUT] "slot0x38KeyN=$[KEYN_38]"
|
||||
|
||||
dumptxt -p $[OUT] ""
|
||||
dumptxt -p $[OUT] "# NFC Secrets"
|
||||
dumptxt -p $[OUT] ""
|
||||
|
||||
dumptxt -p $[OUT] "nfcSecret0Phrase=$[NFC_PHRASE_0]"
|
||||
dumptxt -p $[OUT] "nfcSecret0Seed=$[NFC_SEED_0]"
|
||||
dumptxt -p $[OUT] "nfcSecret0HmacKey=$[NFC_HMAC_KEY_0]"
|
||||
dumptxt -p $[OUT] "nfcSecret1Phrase=$[NFC_PHRASE_1]"
|
||||
dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
|
||||
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
|
||||
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
|
||||
|
||||
# Dump seeddb.bin as well
|
||||
|
||||
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
|
||||
set SEEDDB_OUT "0:/gm9/seeddb.bin"
|
||||
|
||||
sdump -w seeddb.bin
|
||||
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
|
||||
|
||||
@Exit
|
||||
|
||||
10
dist/dumpkeys/README.md
vendored
10
dist/dumpkeys/README.md
vendored
@ -1,10 +0,0 @@
|
||||
# DumpKeys
|
||||
|
||||
This is a GodMode9 script that dumps all the keys and other related secrets that Azahar needs from a real 3DS.
|
||||
|
||||
Usage:
|
||||
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
|
||||
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
|
||||
3. Wait for the script to complete and return you to the GodMode9 main menu.
|
||||
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Azahar directory)/sysdata/".
|
||||
|
||||
2
dist/languages/.tx/config
vendored
2
dist/languages/.tx/config
vendored
@ -12,4 +12,4 @@ lang_map = ca@valencia:ca_ES_valencia
|
||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW, es_419:b+es+419
|
||||
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
|
||||
|
||||
582
dist/languages/ca_ES_valencia.ts
vendored
582
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
584
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load Diff
612
dist/languages/de.ts
vendored
612
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/el.ts
vendored
582
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
586
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
586
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load Diff
682
dist/languages/es_419.ts
vendored
682
dist/languages/es_419.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/fi.ts
vendored
582
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/fr.ts
vendored
582
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
584
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/id.ts
vendored
582
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/it.ts
vendored
582
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
608
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
608
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
584
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
584
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
File diff suppressed because it is too large
Load Diff
606
dist/languages/nb.ts
vendored
606
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/nl.ts
vendored
582
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
584
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/pt_BR.ts
vendored
582
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
584
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
File diff suppressed because it is too large
Load Diff
590
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
590
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
File diff suppressed because it is too large
Load Diff
594
dist/languages/sv.ts
vendored
594
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
584
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
File diff suppressed because it is too large
Load Diff
584
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
584
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/zh_CN.ts
vendored
582
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
582
dist/languages/zh_TW.ts
vendored
582
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -4,24 +4,19 @@
|
||||
# --- Builder ----------------
|
||||
FROM opensauce04/azahar-build-environment:latest AS builder
|
||||
|
||||
COPY . /var/azahar-src
|
||||
RUN mkdir /var/azahar-src/
|
||||
COPY ./ /var/azahar-src/
|
||||
|
||||
RUN mkdir builddir && cd builddir && \
|
||||
cmake /var/azahar-src -G Ninja \
|
||||
RUN mkdir ./builddir/
|
||||
WORKDIR ./builddir/
|
||||
RUN cmake /var/azahar-src -G Ninja \
|
||||
-DENABLE_QT=OFF \
|
||||
-DENABLE_GDBSTUB=OFF \
|
||||
-DENABLE_TESTS=OFF \
|
||||
-DENABLE_ROOM=ON \
|
||||
-DENABLE_ROOM_STANDALONE=ON \
|
||||
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
|
||||
-DENABLE_VULKAN=OFF \
|
||||
-DENABLE_SDL2=OFF \
|
||||
-DENABLE_LIBUSB=OFF \
|
||||
-DENABLE_CUBEB=OFF \
|
||||
-DENABLE_OPENAL=OFF && \
|
||||
ninja && \
|
||||
mv bin/Release/azahar-room /usr/local/bin/ && \
|
||||
cd .. && rm -rf builddir
|
||||
-DENABLE_ROOM_STANDALONE=ON
|
||||
RUN ninja
|
||||
RUN mv ./bin/Release/azahar-room /usr/local/bin/
|
||||
|
||||
# --- Final ------------------
|
||||
FROM debian:trixie AS final
|
||||
|
||||
12
externals/CMakeLists.txt
vendored
12
externals/CMakeLists.txt
vendored
@ -69,17 +69,9 @@ if(USE_SYSTEM_CRYPTOPP)
|
||||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
|
||||
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
||||
add_subdirectory(cryptopp-cmake EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# dds-ktx
|
||||
@ -284,7 +276,7 @@ if (ENABLE_CUBEB)
|
||||
endif()
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
|
||||
include(TestBigEndian)
|
||||
test_big_endian(RAPIDJSON_BIG_ENDIAN)
|
||||
|
||||
2
externals/cryptopp
vendored
2
externals/cryptopp
vendored
@ -1 +1 @@
|
||||
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e
|
||||
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab
|
||||
1
externals/cryptopp-cmake
vendored
1
externals/cryptopp-cmake
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c
|
||||
2
externals/discord-rpc
vendored
2
externals/discord-rpc
vendored
@ -1 +1 @@
|
||||
Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a
|
||||
Subproject commit 049826c3f42875c2f9599552f425e76435789cc7
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
@ -1 +1 @@
|
||||
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f
|
||||
Subproject commit e77b1ba0b7da7cbe93021b01a663acfe7c4dd516
|
||||
20
src/android/.editorconfig
Normal file
20
src/android/.editorconfig
Normal file
@ -0,0 +1,20 @@
|
||||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
ktlint_code_style = android_studio
|
||||
|
||||
# Disable wildcard imports
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||
|
||||
# Style configuration
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
ktlint_standard_comment-wrapping = disabled
|
||||
ktlint_standard_no-unused-imports = enabled
|
||||
# ^- Reportedly finicky, use for now but maybe delete later if there are issues.
|
||||
# See https://github.com/ktlint/ktlint/issues/3038
|
||||
ktlint_standard_package-name = disabled
|
||||
max_line_length = 100
|
||||
3
src/android/.gitignore
vendored
3
src/android/.gitignore
vendored
@ -34,7 +34,8 @@ captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following line if you do not want to check your keystore files in.
|
||||
|
||||
15
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
15
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AndroidLintUseKtx" enabled="true" level="STYLE_SUGGESTION" enabled_by_default="true" editorAttributes="TEXT_STYLE_SUGGESTION" />
|
||||
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@ -12,6 +12,7 @@ plugins {
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "2.0.20"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,8 +80,9 @@ android {
|
||||
"-DENABLE_QT=0", // Don't use QT
|
||||
"-DENABLE_SDL2=0", // Don't use SDL
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
|
||||
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page
|
||||
// sizes
|
||||
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -125,7 +127,8 @@ android {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
|
||||
isShrinkResources = true
|
||||
// TODO: ^- Does this actually do anything when isDebuggable is enabled? -OS
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
proguardFiles(
|
||||
@ -136,8 +139,10 @@ android {
|
||||
}
|
||||
|
||||
// Same as above, but with isDebuggable disabled.
|
||||
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS) without constantly tripping over years-old and seemingly harmless memory bugs.
|
||||
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
|
||||
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS)
|
||||
// without constantly tripping over years-old and seemingly harmless memory bugs.
|
||||
// We should fix those bugs eventually, but for now this exists as a workaround to
|
||||
// allow other work to be done on these devices.
|
||||
register("relWithDebInfoLite") {
|
||||
initWith(getByName("relWithDebInfo"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
@ -147,7 +152,7 @@ android {
|
||||
}
|
||||
lint {
|
||||
checkReleaseBuilds = false // Ditto
|
||||
// The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||
// ^- The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +220,9 @@ dependencies {
|
||||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
||||
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip")
|
||||
src(
|
||||
"https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip"
|
||||
)
|
||||
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
|
||||
onlyIfModified(true)
|
||||
}
|
||||
@ -235,6 +242,11 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
|
||||
|
||||
tasks.named("preBuild") {
|
||||
dependsOn(unzipVulkanValidationLayers)
|
||||
dependsOn("ktlintCheck")
|
||||
}
|
||||
|
||||
ktlint {
|
||||
version = "1.8.0"
|
||||
}
|
||||
|
||||
fun getGitVersion(): String {
|
||||
@ -292,7 +304,7 @@ android.applicationVariants.configureEach {
|
||||
val variant = this
|
||||
val capitalizedName = variant.name.capitalizeUS()
|
||||
|
||||
val copyTask = tasks.register("copyBundle${capitalizedName}") {
|
||||
val copyTask = tasks.register("copyBundle$capitalizedName") {
|
||||
doLast {
|
||||
project.copy {
|
||||
from(variant.outputs.first().outputFile.parentFile)
|
||||
@ -306,5 +318,5 @@ android.applicationVariants.configureEach {
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
|
||||
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
|
||||
}
|
||||
|
||||
@ -12,10 +12,10 @@ import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DocumentsTree
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.GraphicsUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.MemoryUtil
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
|
||||
class CitraApplication : Application() {
|
||||
private fun createNotificationChannel() {
|
||||
@ -69,6 +69,7 @@ class CitraApplication : Application() {
|
||||
Log.info("SoC Model - ${Build.SOC_MODEL}")
|
||||
}
|
||||
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
|
||||
Log.info("OpenGL ES Renderer - ${GraphicsUtil.openGLRendererString}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@ -25,15 +25,16 @@ import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GraphicsUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RemovableStorageHelper
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Class which contains methods that interact
|
||||
@ -43,7 +44,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Default touchscreen device
|
||||
*/
|
||||
const val TouchScreenDevice = "Touchscreen"
|
||||
const val TOUCHSCREEN_DEVICE = "Touchscreen"
|
||||
|
||||
@JvmField
|
||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||
@ -135,10 +136,7 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun setUserDirectory(directory: String)
|
||||
|
||||
data class InstalledGame(
|
||||
val path: String,
|
||||
val mediaType: Game.MediaType
|
||||
)
|
||||
data class InstalledGame(val path: String, val mediaType: Game.MediaType)
|
||||
fun getInstalledGamePaths(): Array<InstalledGame> {
|
||||
val games = getInstalledGamePathsImpl()
|
||||
|
||||
@ -253,9 +251,8 @@ object NativeLibrary {
|
||||
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)
|
||||
}
|
||||
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
|
||||
uninstallTitle(titleId, mediaType.value)
|
||||
|
||||
external fun nativeFileExists(path: String): Boolean
|
||||
|
||||
@ -348,11 +345,12 @@ object NativeLibrary {
|
||||
return coreErrorAlertResult
|
||||
}
|
||||
|
||||
@get:Keep
|
||||
@get:JvmStatic
|
||||
val isPortraitMode: Boolean
|
||||
get() = CitraApplication.appContext.resources.configuration.orientation ==
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isPortraitMode(): Boolean = (
|
||||
CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -465,16 +463,18 @@ object NativeLibrary {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
|
||||
var coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||
val title: String
|
||||
val message: String
|
||||
when (coreError) {
|
||||
CoreError.ErrorGetLoader, CoreError.ErrorLoader_ErrorInvalidFormat, CoreError.ErrorSystemMode -> {
|
||||
CoreError.ErrorGetLoader,
|
||||
CoreError.ErrorLoaderErrorInvalidFormat,
|
||||
CoreError.ErrorSystemMode -> {
|
||||
title = getString(R.string.loader_error_invalid_format)
|
||||
message = getString(R.string.loader_error_invalid_format_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorEncrypted -> {
|
||||
CoreError.ErrorLoaderErrorEncrypted -> {
|
||||
title = getString(R.string.loader_error_encrypted)
|
||||
message = getString(R.string.loader_error_encrypted_description)
|
||||
}
|
||||
@ -489,27 +489,31 @@ object NativeLibrary {
|
||||
message = getString(R.string.loader_error_invalid_system_mode_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorPatches -> {
|
||||
CoreError.ErrorLoaderErrorPatches -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_applying_patches_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoader_ErrorPatchesInvalidTitle -> {
|
||||
CoreError.ErrorLoaderErrorPatchesInvalidTitle -> {
|
||||
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)
|
||||
message = getString(
|
||||
R.string.loader_error_generic,
|
||||
getString(coreError.stringRes),
|
||||
coreError.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(title)
|
||||
.setMessage(
|
||||
Html.fromHtml(message,
|
||||
Html.fromHtml(
|
||||
message,
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
@ -679,19 +683,17 @@ object NativeLibrary {
|
||||
// Compression / Decompression
|
||||
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
|
||||
CompressStatus.fromValue(
|
||||
compressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
|
||||
CompressStatus.fromValue(
|
||||
decompressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
|
||||
|
||||
@ -725,8 +727,7 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun openContentUri(path: String, openMode: String): Int =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openMode)
|
||||
@ -734,8 +735,7 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getFilesName(path: String): Array<String?> =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFilesName(path)
|
||||
} else {
|
||||
FileUtil.getFilesName(path)
|
||||
@ -765,10 +765,14 @@ object NativeLibrary {
|
||||
return primaryStoragePath + dirSep + virtualPath
|
||||
} else { // User directory probably located on a removable storage device
|
||||
val storageIdString = pathSegment.substringBefore(":")
|
||||
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
|
||||
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
|
||||
CitraApplication.appContext,
|
||||
storageIdString
|
||||
)
|
||||
|
||||
if (removablePath == null) {
|
||||
android.util.Log.e("NativeLibrary",
|
||||
android.util.Log.e(
|
||||
"NativeLibrary",
|
||||
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
||||
)
|
||||
return ""
|
||||
@ -788,8 +792,7 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String): Long =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
@ -801,8 +804,11 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun fileExists(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
@ -810,8 +816,7 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
@ -823,8 +828,7 @@ object NativeLibrary {
|
||||
sourcePath: String,
|
||||
destinationParentPath: String,
|
||||
destinationFilename: String
|
||||
): Boolean =
|
||||
if (FileUtil.isNativePath(sourcePath) &&
|
||||
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
|
||||
FileUtil.isNativePath(destinationParentPath)
|
||||
) {
|
||||
CitraApplication.documentsTree
|
||||
@ -870,8 +874,7 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun deleteDocument(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.deleteDocument(path)
|
||||
} else {
|
||||
FileUtil.deleteDocument(path)
|
||||
@ -883,11 +886,11 @@ object NativeLibrary {
|
||||
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||
ErrorLoader(4, R.string.core_error_loader),
|
||||
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
|
||||
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
|
||||
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
|
||||
ErrorLoader_ErrorPatches(8, R.string.core_error_loader_error_patches),
|
||||
ErrorLoader_ErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
|
||||
ErrorLoaderErrorEncrypted(5, R.string.core_error_loader_encrypted),
|
||||
ErrorLoaderErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
|
||||
ErrorLoaderErrorGBATitle(7, R.string.core_error_loader_gba_title),
|
||||
ErrorLoaderErrorPatches(8, R.string.core_error_loader_error_patches),
|
||||
ErrorLoaderErrorPatchesInvalidTitle(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),
|
||||
@ -898,9 +901,7 @@ object NativeLibrary {
|
||||
ErrorUnknown(17, R.string.core_error_unknown);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): CoreError {
|
||||
return entries.find { it.value == value } ?: ErrorUnknown
|
||||
}
|
||||
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
|
||||
}
|
||||
}
|
||||
|
||||
@ -952,7 +953,11 @@ object NativeLibrary {
|
||||
const val MESSAGE = "message"
|
||||
const val CAN_CONTINUE = "canContinue"
|
||||
|
||||
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
|
||||
fun newInstance(
|
||||
title: String,
|
||||
message: String,
|
||||
canContinue: Boolean
|
||||
): CoreErrorDialogFragment {
|
||||
val frag = CoreErrorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString(TITLE, title)
|
||||
|
||||
@ -31,7 +31,7 @@ import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
|
||||
import org.citra.citra_emu.camera.StillImageCameraHelper.onFilePickerResult
|
||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
@ -46,9 +46,9 @@ import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.ControllerMappingHelper
|
||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RefreshRateUtil
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
@ -64,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
private lateinit var hotkeyUtility: HotkeyUtility
|
||||
private lateinit var secondaryDisplay: SecondaryDisplay
|
||||
lateinit var secondaryDisplayManager: SecondaryDisplay
|
||||
|
||||
private val onShutdown = Runnable {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
@ -102,8 +102,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
secondaryDisplayManager = SecondaryDisplay(this)
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||
@ -188,7 +188,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
|
||||
public override fun onRestart() {
|
||||
super.onRestart()
|
||||
secondaryDisplay.updateDisplay()
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
NativeLibrary.reloadCameraDevices()
|
||||
}
|
||||
|
||||
@ -222,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
isEmulationRunning = false
|
||||
instance = null
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
secondaryDisplayManager.releaseVD()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
@ -334,11 +334,13 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
return hotkeyUtility.handleKeyPress(event)
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> {
|
||||
return hotkeyUtility.handleKeyRelease(event)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -358,7 +360,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
|
||||
if (!NativeLibrary.isRunning() ||
|
||||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
|
||||
emulationFragment.isDrawerOpen()) {
|
||||
emulationFragment.isDrawerOpen()
|
||||
) {
|
||||
return super.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
@ -387,16 +390,19 @@ class EmulationActivity : AppCompatActivity() {
|
||||
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
|
||||
val guestOrientation =
|
||||
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
|
||||
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
|
||||
val inverted = preferences.getBoolean(
|
||||
InputBindingSetting.getInputAxisInvertedKey(axis),
|
||||
false
|
||||
)
|
||||
if (nextMapping == -1 || guestOrientation == -1) {
|
||||
// Axis is unmapped
|
||||
continue
|
||||
}
|
||||
if (value > 0f && value < 0.1f || value < 0f && value > -0.1f) {
|
||||
if ((value > 0f && value < 0.1f) || (value < 0f && value > -0.1f)) {
|
||||
// Skip joystick wobble
|
||||
value = 0f
|
||||
}
|
||||
if (inverted) value = -value;
|
||||
if (inverted) value = -value
|
||||
|
||||
when (nextMapping) {
|
||||
NativeLibrary.ButtonType.STICK_LEFT -> {
|
||||
@ -450,7 +456,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
// Triggers L/R and ZL/ZR
|
||||
if (isTriggerPressedLMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.TRIGGER_L,
|
||||
if (isTriggerPressedL) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
@ -461,7 +467,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
if (isTriggerPressedRMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.TRIGGER_R,
|
||||
if (isTriggerPressedR) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
@ -472,7 +478,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
if (isTriggerPressedZLMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.BUTTON_ZL,
|
||||
if (isTriggerPressedZL) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
@ -483,7 +489,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
}
|
||||
if (isTriggerPressedZRMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.BUTTON_ZR,
|
||||
if (isTriggerPressedZR) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
@ -496,72 +502,72 @@ class EmulationActivity : AppCompatActivity() {
|
||||
// Work-around to allow D-pad axis to be bound to emulated buttons
|
||||
if (axisValuesDPad[0] == 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[0] < 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[0] > 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] == 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] < 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] > 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
@ -573,7 +579,9 @@ class EmulationActivity : AppCompatActivity() {
|
||||
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
||||
if (result == null) return@registerForActivityResult
|
||||
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
||||
result, applicationContext, listOf<String>("bin")
|
||||
result,
|
||||
applicationContext,
|
||||
listOf<String>("bin")
|
||||
) ?: return@registerForActivityResult
|
||||
if (BuildUtil.isGooglePlayBuild) {
|
||||
onAmiiboSelected(selectedFiles[0])
|
||||
@ -590,14 +598,12 @@ class EmulationActivity : AppCompatActivity() {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
OnFilePickerResult(result.toString())
|
||||
onFilePickerResult(result.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: EmulationActivity? = null
|
||||
|
||||
fun isRunning(): Boolean {
|
||||
return instance?.isEmulationRunning ?: false
|
||||
}
|
||||
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.CardDriverOptionBinding
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.GpuDriverMetadata
|
||||
import org.citra.citra_emu.viewmodel.DriverViewModel
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
|
||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
|
||||
@ -105,15 +105,11 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
override fun areItemsTheSame(
|
||||
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||
newItem: Pair<Uri, GpuDriverMetadata>
|
||||
): Boolean {
|
||||
return oldItem.first == newItem.first
|
||||
}
|
||||
): Boolean = oldItem.first == newItem.first
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||
newItem: Pair<Uri, GpuDriverMetadata>
|
||||
): Boolean {
|
||||
return oldItem.second == newItem.second
|
||||
}
|
||||
): Boolean = oldItem.second == newItem.second
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,24 +4,25 @@
|
||||
|
||||
package org.citra.citra_emu.adapters
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.TextView
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.Bitmap
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
@ -29,24 +30,23 @@ import androidx.core.graphics.scale
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.widget.PopupMenu
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.citra.citra_emu.HomeNavigationDirections
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.HomeNavigationDirections
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
|
||||
@ -65,10 +65,12 @@ class GameAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
private val inflater: LayoutInflater,
|
||||
private val openImageLauncher: ActivityResultLauncher<String>?,
|
||||
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
|
||||
) :
|
||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
private val onRequestCompressOrDecompress: (
|
||||
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
|
||||
)? = null
|
||||
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||
View.OnClickListener,
|
||||
View.OnLongClickListener {
|
||||
private var lastClickTime = 0L
|
||||
private var imagePath: String? = null
|
||||
private var dialogShortcutBinding: DialogShortcutBinding? = null
|
||||
@ -212,7 +214,8 @@ class GameAdapter(
|
||||
binding.textGameTitle.text = game.title
|
||||
binding.textCompany.text = game.company
|
||||
binding.textGameRegion.text = game.regions
|
||||
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||
binding.imageCartridge.visibility =
|
||||
if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
@ -220,7 +223,9 @@ class GameAdapter(
|
||||
|
||||
val backgroundColorId =
|
||||
if (
|
||||
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
|
||||
isValidGame(
|
||||
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
|
||||
)
|
||||
) {
|
||||
R.attr.colorSurface
|
||||
} else {
|
||||
@ -260,16 +265,45 @@ class GameAdapter(
|
||||
val extraDir: String
|
||||
)
|
||||
private fun getGameDirectories(game: Game): GameDirectories {
|
||||
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
||||
val basePath =
|
||||
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
||||
return GameDirectories(
|
||||
gameDir = game.path.substringBeforeLast("/"),
|
||||
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
|
||||
saveDir =
|
||||
basePath +
|
||||
"/title/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(
|
||||
0,
|
||||
8
|
||||
)}/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/data/00000001",
|
||||
modsDir = "load/mods/${String.format("%016X", game.titleId)}",
|
||||
texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
|
||||
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
|
||||
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
||||
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
||||
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
|
||||
appDir = game.path.substringBeforeLast("/").split("/").filter {
|
||||
it.isNotEmpty()
|
||||
}.joinToString("/"),
|
||||
dlcDir =
|
||||
basePath +
|
||||
"/title/0004008c/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/content",
|
||||
updatesDir =
|
||||
basePath +
|
||||
"/title/0004000e/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/content",
|
||||
extraDir =
|
||||
basePath +
|
||||
"/extdata/00000000/${String.format(
|
||||
"%016X",
|
||||
game.titleId
|
||||
).substring(8, 14).padStart(8, '0')}"
|
||||
)
|
||||
}
|
||||
|
||||
@ -299,13 +333,36 @@ class GameAdapter(
|
||||
.setType("*/*")
|
||||
|
||||
val uri = when (menuItem.itemId) {
|
||||
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
|
||||
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
|
||||
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
|
||||
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
|
||||
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
|
||||
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
|
||||
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
|
||||
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.appDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.saveDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.updatesDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.dlcDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.extraDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.texturesDir,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.modsDir,
|
||||
true
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
@ -319,7 +376,11 @@ class GameAdapter(
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
|
||||
private fun showUninstallContextMenu(
|
||||
view: View,
|
||||
game: Game,
|
||||
bottomSheetDialog: BottomSheetDialog
|
||||
) {
|
||||
val dirs = getGameDirectories(game)
|
||||
val popup = PopupMenu(view.context, view).apply {
|
||||
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
|
||||
@ -342,16 +403,38 @@ class GameAdapter(
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
val uninstallAction: () -> Unit = {
|
||||
when (menuItem.itemId) {
|
||||
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
|
||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
|
||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
|
||||
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
|
||||
titleId,
|
||||
game.mediaType
|
||||
)
|
||||
|
||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(
|
||||
dlcTitleId,
|
||||
Game.MediaType.SDMC
|
||||
)
|
||||
|
||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(
|
||||
updateTitleId,
|
||||
Game.MediaType.SDMC
|
||||
)
|
||||
}
|
||||
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
|
||||
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
|
||||
if (menuItem.itemId in
|
||||
listOf(
|
||||
R.id.game_context_uninstall,
|
||||
R.id.game_context_uninstall_dlc,
|
||||
R.id.game_context_uninstall_updates
|
||||
)
|
||||
) {
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.uninstalling,
|
||||
false,
|
||||
uninstallAction
|
||||
)
|
||||
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||
true
|
||||
} else {
|
||||
@ -362,7 +445,12 @@ class GameAdapter(
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
|
||||
private fun showAboutGameDialog(
|
||||
context: Context,
|
||||
game: Game,
|
||||
holder: GameViewHolder,
|
||||
view: View
|
||||
) {
|
||||
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
|
||||
|
||||
val bottomSheetDialog = BottomSheetDialog(context)
|
||||
@ -374,12 +462,22 @@ class GameAdapter(
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text =
|
||||
context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text =
|
||||
context.getString(R.string.game_context_file) + " " + game.filename
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text =
|
||||
context.getString(R.string.game_context_type) + " " + game.fileType
|
||||
|
||||
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
|
||||
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
|
||||
val insertButton = bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.insert_cartridge_button
|
||||
)
|
||||
insertButton.text =
|
||||
if (inserted) {
|
||||
context.getString(R.string.game_context_eject)
|
||||
} else {
|
||||
context.getString(R.string.game_context_insert)
|
||||
}
|
||||
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
|
||||
insertButton.setOnClickListener {
|
||||
if (inserted) {
|
||||
@ -420,7 +518,7 @@ class GameAdapter(
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
// Default to false for zoomed in shortcut icons
|
||||
preferences.edit() {
|
||||
preferences.edit {
|
||||
putBoolean(
|
||||
"shouldStretchIcon",
|
||||
false
|
||||
@ -452,10 +550,15 @@ class GameAdapter(
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
|
||||
if (shortcutName.isEmpty()) {
|
||||
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.shortcut_name_empty,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
return@setPositiveButton
|
||||
}
|
||||
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
|
||||
val iconBitmap =
|
||||
(dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
|
||||
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
@ -463,9 +566,11 @@ class GameAdapter(
|
||||
val shortcut = ShortcutInfo.Builder(context, shortcutName)
|
||||
.setShortLabel(shortcutName)
|
||||
.setIcon(icon)
|
||||
.setIntent(game.launchIntent.apply {
|
||||
.setIntent(
|
||||
game.launchIntent.apply {
|
||||
putExtra("launchedFromShortcut", true)
|
||||
})
|
||||
}
|
||||
)
|
||||
.build()
|
||||
|
||||
shortcutManager?.requestPinShortcut(shortcut, null)
|
||||
@ -486,7 +591,9 @@ class GameAdapter(
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.compress_decompress
|
||||
)
|
||||
if (game.isInstalled) {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
Toast.makeText(
|
||||
@ -499,24 +606,35 @@ class GameAdapter(
|
||||
} else {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
val shouldCompress = !game.isCompressed
|
||||
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
|
||||
val recommendedExt = NativeLibrary.getRecommendedExtension(
|
||||
holder.game.path,
|
||||
shouldCompress
|
||||
)
|
||||
val baseName = holder.game.filename.substringBeforeLast('.')
|
||||
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
|
||||
onRequestCompressOrDecompress?.invoke(
|
||||
holder.game.path,
|
||||
"$baseName.$recommendedExt",
|
||||
shouldCompress
|
||||
)
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
}
|
||||
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
||||
compressDecompressButton.text =
|
||||
context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
|
||||
showOpenContextMenu(it, game)
|
||||
}
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener {
|
||||
bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.menu_button_uninstall
|
||||
).setOnClickListener {
|
||||
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))
|
||||
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)
|
||||
@ -532,11 +650,11 @@ class GameAdapter(
|
||||
progToast.show()
|
||||
|
||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
||||
when (selectedIndex) {
|
||||
0 -> {
|
||||
NativeLibrary.deleteVulkanShaderCache(game.titleId)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
|
||||
}
|
||||
@ -620,10 +738,8 @@ class GameAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidGame(extension: String): Boolean {
|
||||
return Game.badExtensions.stream()
|
||||
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
|
||||
.noneMatch { extension == it.lowercase() }
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
@ -632,8 +748,6 @@ class GameAdapter(
|
||||
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -9,27 +9,24 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.CardHomeOptionBinding
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.model.HomeSetting
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
||||
class HomeSettingAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
private val viewLifecycle: LifecycleOwner,
|
||||
var options: List<HomeSetting>
|
||||
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
|
||||
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
|
||||
View.OnClickListener {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
||||
val binding =
|
||||
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
@ -37,9 +34,7 @@ class HomeSettingAdapter(
|
||||
return HomeOptionViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return options.size
|
||||
}
|
||||
override fun getItemCount(): Int = options.size
|
||||
|
||||
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
|
||||
holder.bind(options[position])
|
||||
|
||||
@ -8,18 +8,17 @@ import android.content.res.ColorStateList
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.PageSetupBinding
|
||||
import org.citra.citra_emu.model.ButtonState
|
||||
import org.citra.citra_emu.model.PageState
|
||||
import org.citra.citra_emu.model.SetupCallback
|
||||
import org.citra.citra_emu.model.SetupPage
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.ViewUtils
|
||||
|
||||
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
|
||||
@ -35,7 +34,8 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
||||
holder.bind(pages[position])
|
||||
|
||||
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
||||
RecyclerView.ViewHolder(binding.root), SetupCallback {
|
||||
RecyclerView.ViewHolder(binding.root),
|
||||
SetupCallback {
|
||||
lateinit var page: SetupPage
|
||||
|
||||
init {
|
||||
@ -49,7 +49,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
||||
onStepCompleted(0, pageFullyCompleted = true)
|
||||
}
|
||||
|
||||
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
|
||||
if (page.pageButtons != null &&
|
||||
page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE
|
||||
) {
|
||||
for (pageButton in page.pageButtons) {
|
||||
val pageButtonView = LayoutInflater.from(activity)
|
||||
.inflate(
|
||||
@ -108,9 +110,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
||||
.alpha(0.38f)
|
||||
.setDuration(200)
|
||||
.start()
|
||||
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
||||
button.setTextColor(
|
||||
button.context.getColor(
|
||||
com.google.android.material.R.color.material_on_surface_disabled
|
||||
)
|
||||
)
|
||||
button.iconTint =
|
||||
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
||||
ColorStateList.valueOf(
|
||||
button.context.getColor(
|
||||
com.google.android.material.R.color.material_on_surface_disabled
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.applets
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import java.io.Serializable
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object MiiSelector {
|
||||
lateinit var data: MiiSelectorData
|
||||
val finishLock = Object()
|
||||
|
||||
private fun ExecuteImpl(config: MiiSelectorConfig) {
|
||||
private fun executeImpl(config: MiiSelectorConfig) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
data = MiiSelectorData(0, 0)
|
||||
val fragment = MiiSelectorDialogFragment.newInstance(config)
|
||||
@ -22,8 +22,8 @@ object MiiSelector {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Execute(config: MiiSelectorConfig): MiiSelectorData {
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
|
||||
fun execute(config: MiiSelectorConfig): MiiSelectorData {
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
|
||||
synchronized(finishLock) {
|
||||
try {
|
||||
finishLock.wait()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -7,27 +7,27 @@ package org.citra.citra_emu.applets
|
||||
import android.text.InputFilter
|
||||
import android.text.Spanned
|
||||
import androidx.annotation.Keep
|
||||
import java.io.Serializable
|
||||
import org.citra.citra_emu.CitraApplication.Companion.appContext
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.fragments.KeyboardDialogFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object SoftwareKeyboard {
|
||||
lateinit var data: KeyboardData
|
||||
val finishLock = Object()
|
||||
|
||||
private fun ExecuteImpl(config: KeyboardConfig) {
|
||||
private fun executeImpl(config: KeyboardConfig) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
data = KeyboardData(0, "")
|
||||
KeyboardDialogFragment.newInstance(config)
|
||||
.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun HandleValidationError(config: KeyboardConfig, error: ValidationError) {
|
||||
fun handleValidationError(config: KeyboardConfig, error: ValidationError) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
|
||||
val message: String = when (error) {
|
||||
ValidationError.FixedLengthRequired -> emulationActivity.getString(
|
||||
@ -54,12 +54,12 @@ object SoftwareKeyboard {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Execute(config: KeyboardConfig): KeyboardData {
|
||||
if (config.buttonConfig == ButtonConfig.None) {
|
||||
fun execute(config: KeyboardConfig): KeyboardData {
|
||||
if (config.buttonConfig == ButtonConfig.NONE) {
|
||||
Log.error("Unexpected button config None")
|
||||
return KeyboardData(0, "")
|
||||
}
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
|
||||
synchronized(finishLock) {
|
||||
try {
|
||||
finishLock.wait()
|
||||
@ -69,8 +69,9 @@ object SoftwareKeyboard {
|
||||
return data
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun ShowError(error: String) {
|
||||
fun showError(error: String) {
|
||||
NativeLibrary.displayAlertMsg(
|
||||
appContext.resources.getString(R.string.software_keyboard),
|
||||
error,
|
||||
@ -78,16 +79,19 @@ object SoftwareKeyboard {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private external fun ValidateFilters(text: String): ValidationError
|
||||
|
||||
@Suppress("FunctionName")
|
||||
external fun ValidateInput(text: String): ValidationError
|
||||
|
||||
// / Corresponds to Frontend::ButtonConfig
|
||||
interface ButtonConfig {
|
||||
companion object {
|
||||
const val Single = 0 /// Ok button
|
||||
const val Dual = 1 /// Cancel | Ok buttons
|
||||
const val Triple = 2 /// Cancel | I Forgot | Ok buttons
|
||||
const val None = 3 /// No button (returned by swkbdInputText in special cases)
|
||||
const val SINGLE = 0 // / Ok button
|
||||
const val DUAL = 1 // / Cancel | Ok buttons
|
||||
const val TRIPLE = 2 // / Cancel | I Forgot | Ok buttons
|
||||
const val NONE = 3 // / No button (returned by swkbdInputText in special cases)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,9 +21,10 @@ object StillImageCameraHelper {
|
||||
private var filePickerPath: String? = null
|
||||
|
||||
// Opens file picker for camera.
|
||||
@Suppress("unused")
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun OpenFilePicker(): String? {
|
||||
fun openFilePicker(): String? {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
|
||||
// At this point, we are assuming that we already have permissions as they are
|
||||
@ -44,15 +45,16 @@ object StillImageCameraHelper {
|
||||
|
||||
// Called from EmulationActivity.
|
||||
@JvmStatic
|
||||
fun OnFilePickerResult(result: String) {
|
||||
fun onFilePickerResult(result: String) {
|
||||
filePickerPath = result
|
||||
synchronized(filePickerLock) { filePickerLock.notifyAll() }
|
||||
}
|
||||
|
||||
// Blocking call. Load image from file and crop/resize it to fit in width x height.
|
||||
@Suppress("unused")
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
|
||||
fun loadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
|
||||
val context = CitraApplication.appContext
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(uri)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -9,11 +9,10 @@ import android.content.Intent
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
|
||||
override fun createIntent(context: Context, input: Boolean?): Intent {
|
||||
return Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
override fun createIntent(context: Context, input: Boolean?): Intent =
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.setType("application/octet-stream")
|
||||
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
|
||||
}
|
||||
|
||||
@ -4,15 +4,14 @@
|
||||
|
||||
package org.citra.citra_emu.display
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
@ -20,7 +19,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
class ScreenAdjustmentUtil(
|
||||
private val context: Context,
|
||||
private val windowManager: WindowManager,
|
||||
private val settings: Settings,
|
||||
private val settings: Settings
|
||||
) {
|
||||
fun swapScreen() {
|
||||
val isEnabled = !EmulationMenuSettings.swapScreens
|
||||
@ -34,17 +33,18 @@ class ScreenAdjustmentUtil(
|
||||
}
|
||||
|
||||
fun cycleLayouts() {
|
||||
|
||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
|
||||
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list
|
||||
val landscapeValues =
|
||||
if (landscapeLayoutsToCycle.isNotEmpty())
|
||||
if (landscapeLayoutsToCycle.isNotEmpty()) {
|
||||
landscapeLayoutsToCycle.toIntArray()
|
||||
else context.resources.getIntArray(
|
||||
} else {
|
||||
context.resources.getIntArray(
|
||||
R.array.landscapeValues
|
||||
)
|
||||
}
|
||||
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
|
||||
|
||||
if (NativeLibrary.isPortraitMode) {
|
||||
if (NativeLibrary.isPortraitMode()) {
|
||||
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
|
||||
val pos = portraitValues.indexOf(currentLayout)
|
||||
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
|
||||
@ -61,14 +61,32 @@ class ScreenAdjustmentUtil(
|
||||
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun changeScreenOrientation(layoutOption: Int) {
|
||||
IntSetting.SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun changeSecondaryOrientation(layoutOption: Int) {
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun enableSecondaryDisplay(layoutOption: Int) {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = true
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
changeSecondaryOrientation(layoutOption)
|
||||
}
|
||||
|
||||
fun disableSecondaryDisplay() {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
fun changeActivityOrientation(orientationOption: Int) {
|
||||
@ -83,7 +101,6 @@ class ScreenAdjustmentUtil(
|
||||
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
|
||||
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,8 @@ enum class ScreenLayout(val int: Int) {
|
||||
HYBRID_SCREEN(4),
|
||||
CUSTOM_LAYOUT(5);
|
||||
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): ScreenLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
||||
}
|
||||
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
|
||||
BELOW(7);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): SmallScreenPosition {
|
||||
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
||||
}
|
||||
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,23 +40,27 @@ enum class PortraitScreenLayout(val int: Int) {
|
||||
ORIGINAL(2);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): PortraitScreenLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
||||
}
|
||||
fun from(int: Int): PortraitScreenLayout =
|
||||
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
||||
}
|
||||
}
|
||||
|
||||
enum class SecondaryDisplayLayout(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
// NONE is no longer selectable in the interface, having been replaced with
|
||||
// the boolean ENABLE_SECONDARY_DISPLAY setting, but is left here for backwards compatibility
|
||||
NONE(0),
|
||||
TOP_SCREEN(1),
|
||||
BOTTOM_SCREEN(2),
|
||||
SIDE_BY_SIDE(3);
|
||||
SIDE_BY_SIDE(3),
|
||||
REVERSE_PRIMARY(4),
|
||||
ORIGINAL(5),
|
||||
HYBRID(6),
|
||||
LARGE_SCREEN(7)
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): SecondaryDisplayLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +73,7 @@ enum class StereoWhichDisplay(val int: Int) {
|
||||
SECONDARY_ONLY(3);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoWhichDisplay {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,8 +89,6 @@ enum class StereoMode(val int: Int) {
|
||||
CARDBOARD_VR(6);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoMode {
|
||||
return entries.firstOrNull { it.int == int } ?: OFF
|
||||
}
|
||||
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
|
||||
}
|
||||
}
|
||||
@ -8,19 +8,27 @@ import android.app.Presentation
|
||||
import android.content.Context
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.MotionEvent
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
||||
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
private var pres: SecondaryDisplayPresentation? = null
|
||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
private val vd: VirtualDisplay
|
||||
var preferredDisplayId = -1
|
||||
var currentDisplayId = -1
|
||||
|
||||
val availableDisplays: List<Display>
|
||||
get() = getSecondaryDisplays()
|
||||
|
||||
init {
|
||||
vd = displayManager.createVirtualDisplay(
|
||||
@ -35,31 +43,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
}
|
||||
|
||||
fun updateSurface() {
|
||||
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
|
||||
val surface = pres?.getSurfaceHolder()?.surface
|
||||
if (surface != null && surface.isValid) {
|
||||
NativeLibrary.secondarySurfaceChanged(surface)
|
||||
} else {
|
||||
Log.warning("SecondaryDisplay Attempted to update null or invalid surface")
|
||||
}
|
||||
}
|
||||
|
||||
fun destroySurface() {
|
||||
NativeLibrary.secondarySurfaceDestroyed()
|
||||
}
|
||||
|
||||
private fun getExternalDisplay(context: Context): Display? {
|
||||
private fun getSecondaryDisplays(): List<Display> {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val currentDisplayId = context.display.displayId
|
||||
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.display.displayId
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
|
||||
.defaultDisplay.displayId
|
||||
}
|
||||
val displays = dm.displays
|
||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
||||
val extDisplays = displays.filter {
|
||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
return displays.filter {
|
||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
||||
val isNotDefaultOrPresentable =
|
||||
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
|
||||
|
||||
isNotDefaultOrPresentable &&
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
}
|
||||
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
|
||||
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
|
||||
?: extDisplays.firstOrNull()
|
||||
return selected
|
||||
}
|
||||
|
||||
fun updateDisplay() {
|
||||
@ -68,21 +85,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
return
|
||||
}
|
||||
|
||||
// decide if we are going to the external display or the internal one
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||
display = vd.display
|
||||
val displayToUse = if (availableDisplays.isEmpty() ||
|
||||
// Theoretically, the NONE option is no longer selectable, but
|
||||
// I am leaving this in for backwards compatibility
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
|
||||
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
|
||||
) {
|
||||
currentDisplayId = -1
|
||||
vd.display
|
||||
} else if (preferredDisplayId >= 0 &&
|
||||
availableDisplays.any { it.displayId == preferredDisplayId }
|
||||
) {
|
||||
currentDisplayId = preferredDisplayId
|
||||
availableDisplays.first { it.displayId == preferredDisplayId }
|
||||
} else {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val default = dm.displays.first { it.displayId == Display.DEFAULT_DISPLAY }
|
||||
// prioritize displays that have a different name from the default display, as
|
||||
// some devices such as the Odin 2 create a permanent virtual display with the same
|
||||
// name as the default display that should be skipped in most cases
|
||||
currentDisplayId = availableDisplays.firstOrNull {
|
||||
it.name != default.name && !it.name.contains("Built", true)
|
||||
}?.displayId
|
||||
?: availableDisplays[0].displayId
|
||||
availableDisplays.first { it.displayId == currentDisplayId }
|
||||
}
|
||||
|
||||
// if our presentation is already on the right display, ignore
|
||||
if (pres?.display == display) return
|
||||
if (pres?.display == displayToUse) return
|
||||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
|
||||
try {
|
||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
||||
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
|
||||
pres?.show()
|
||||
}
|
||||
// catch BadTokenException and InvalidDisplayException,
|
||||
@ -119,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
}
|
||||
}
|
||||
class SecondaryDisplayPresentation(
|
||||
context: Context, display: Display, val parent: SecondaryDisplay
|
||||
context: Context,
|
||||
display: Display,
|
||||
val parent: SecondaryDisplay
|
||||
) : Presentation(context, display) {
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private var touchscreenPointerId = -1
|
||||
@ -137,16 +175,21 @@ class SecondaryDisplayPresentation(
|
||||
surfaceView = SurfaceView(context)
|
||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
|
||||
Log.debug("SecondaryDisplay Surface created")
|
||||
}
|
||||
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder, format: Int, width: Int, height: Int
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
Log.debug("SecondaryDisplay Surface changed: ${width}x$height")
|
||||
parent.updateSurface()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
Log.debug("SecondaryDisplay Surface destroyed")
|
||||
parent.destroySurface()
|
||||
}
|
||||
})
|
||||
@ -191,7 +234,5 @@ class SecondaryDisplayPresentation(
|
||||
}
|
||||
|
||||
// Publicly accessible method to get the SurfaceHolder
|
||||
fun getSurfaceHolder(): SurfaceHolder {
|
||||
return surfaceView.holder
|
||||
}
|
||||
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
|
||||
private var selectedCheatPosition = -1
|
||||
|
||||
fun initialize(titleId_: Long) {
|
||||
titleId = titleId_;
|
||||
titleId = titleId_
|
||||
load()
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -170,8 +170,7 @@ class CheatDetailsFragment : Fragment() {
|
||||
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View?, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -41,7 +41,8 @@ class CheatsAdapter(
|
||||
}
|
||||
|
||||
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
|
||||
RecyclerView.ViewHolder(binding.root), View.OnClickListener,
|
||||
RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener,
|
||||
CompoundButton.OnCheckedChangeListener {
|
||||
private lateinit var viewModel: CheatsViewModel
|
||||
private lateinit var cheat: Cheat
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -32,7 +32,9 @@ import org.citra.citra_emu.ui.TwoPaneOnBackPressedCallback
|
||||
import org.citra.citra_emu.ui.main.MainActivity
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
|
||||
class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||
class CheatsFragment :
|
||||
Fragment(),
|
||||
SlidingPaneLayout.PanelSlideListener {
|
||||
private var cheatListLastFocus: View? = null
|
||||
private var cheatDetailsLastFocus: View? = null
|
||||
|
||||
@ -157,12 +159,15 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||
}
|
||||
|
||||
private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
|
||||
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value!!
|
||||
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value
|
||||
if (!cheatSelected && binding.slidingPaneLayout.isOpen) {
|
||||
binding.slidingPaneLayout.close()
|
||||
}
|
||||
binding.slidingPaneLayout.lockMode =
|
||||
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
binding.slidingPaneLayout.lockMode = if (cheatSelected) {
|
||||
SlidingPaneLayout.LOCK_MODE_UNLOCKED
|
||||
} else {
|
||||
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
fun onListViewFocusChange(hasFocus: Boolean) {
|
||||
@ -203,7 +208,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
|
||||
// Set keyboard insets if the system supports smooth keyboard animations
|
||||
val mlpDetails = binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
|
||||
val mlpDetails = binding.cheatDetailsContainer.layoutParams
|
||||
as ViewGroup.MarginLayoutParams
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
if (keyboardInsets.bottom > 0) {
|
||||
mlpDetails.bottomMargin = keyboardInsets.bottom
|
||||
@ -231,14 +237,16 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||
runningAnimations: List<WindowInsetsAnimationCompat>
|
||||
): WindowInsetsCompat {
|
||||
val mlpDetails =
|
||||
binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
|
||||
binding.cheatDetailsContainer.layoutParams
|
||||
as ViewGroup.MarginLayoutParams
|
||||
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
||||
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
|
||||
binding.cheatDetailsContainer.layoutParams = mlpDetails
|
||||
return insets
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,5 +12,6 @@ enum class Hotkey(val button: Int) {
|
||||
QUICKSAVE(10005),
|
||||
QUICKLOAD(10006),
|
||||
TURBO_LIMIT(10007),
|
||||
ENABLE(10008);
|
||||
ENABLE(10008),
|
||||
COMBO_BUTTON(10009)
|
||||
}
|
||||
|
||||
@ -11,11 +11,12 @@ import androidx.preference.PreferenceManager
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.utils.ComboHelper
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
|
||||
class HotkeyUtility(
|
||||
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
|
||||
@ -71,10 +72,16 @@ class HotkeyUtility(
|
||||
var handled = false
|
||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||
val thisKeyIsComboButton = buttonSet.contains(Hotkey.COMBO_BUTTON.button)
|
||||
val thisKeyIsHotkey =
|
||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||
if (thisKeyIsEnableButton) {
|
||||
handled = true; hotkeyIsEnabled = false
|
||||
handled = true
|
||||
hotkeyIsEnabled = false
|
||||
}
|
||||
if (thisKeyIsComboButton) {
|
||||
ComboHelper.comboActivate(NativeLibrary.ButtonState.RELEASED)
|
||||
handled = true
|
||||
}
|
||||
|
||||
for (button in buttonSet) {
|
||||
@ -109,10 +116,15 @@ class HotkeyUtility(
|
||||
fun handleHotkey(bindedButton: Int): Boolean {
|
||||
when (bindedButton) {
|
||||
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
|
||||
|
||||
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
|
||||
|
||||
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
|
||||
|
||||
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
|
||||
|
||||
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
|
||||
|
||||
Hotkey.QUICKSAVE.button -> {
|
||||
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
|
||||
Toast.makeText(
|
||||
@ -136,6 +148,10 @@ class HotkeyUtility(
|
||||
).show()
|
||||
}
|
||||
|
||||
Hotkey.COMBO_BUTTON.button -> {
|
||||
ComboHelper.comboActivate(NativeLibrary.ButtonState.PRESSED)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
hotkeyIsPressed = true
|
||||
|
||||
@ -39,6 +39,7 @@ object SettingKeys {
|
||||
external fun use_disk_shader_cache(): String
|
||||
external fun shaders_accurate_mul(): String
|
||||
external fun use_vsync(): String
|
||||
external fun use_skip_duplicate_frames(): String
|
||||
external fun use_shader_jit(): String
|
||||
external fun resolution_factor(): String
|
||||
external fun frame_limit(): String
|
||||
@ -141,4 +142,6 @@ object SettingKeys {
|
||||
external fun android_hide_images(): String
|
||||
external fun screen_orientation(): String
|
||||
external fun performance_overlay_position(): String
|
||||
external fun enable_secondary_display(): String
|
||||
external fun combo_button_buttons(): String
|
||||
}
|
||||
@ -20,19 +20,63 @@ enum class BooleanSetting(
|
||||
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
|
||||
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
|
||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
|
||||
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(
|
||||
SettingKeys.toggle_unique_data_console_type(),
|
||||
Settings.SECTION_DEBUG,
|
||||
false
|
||||
),
|
||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(), Settings.SECTION_RENDERER, false),
|
||||
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
|
||||
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
|
||||
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
|
||||
PERF_OVERLAY_SHOW_FPS(
|
||||
SettingKeys.performance_overlay_show_fps(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
true
|
||||
),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME(
|
||||
SettingKeys.performance_overlay_show_frame_time(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
PERF_OVERLAY_SHOW_SPEED(
|
||||
SettingKeys.performance_overlay_show_speed(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
PERF_OVERLAY_SHOW_APP_RAM_USAGE(
|
||||
SettingKeys.performance_overlay_show_app_ram_usage(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
PERF_OVERLAY_SHOW_AVAILABLE_RAM(
|
||||
SettingKeys.performance_overlay_show_available_ram(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
PERF_OVERLAY_SHOW_BATTERY_TEMP(
|
||||
SettingKeys.performance_overlay_show_battery_temp(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
PERF_OVERLAY_BACKGROUND(
|
||||
SettingKeys.performance_overlay_background(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
false
|
||||
),
|
||||
DELAY_START_LLE_MODULES(
|
||||
SettingKeys.delay_start_for_lle_modules(),
|
||||
Settings.SECTION_DEBUG,
|
||||
true
|
||||
),
|
||||
DETERMINISTIC_ASYNC_OPERATIONS(
|
||||
SettingKeys.deterministic_async_operations(),
|
||||
Settings.SECTION_DEBUG,
|
||||
false
|
||||
),
|
||||
REQUIRED_ONLINE_LLE_MODULES(
|
||||
SettingKeys.enable_required_online_lle_modules(),
|
||||
Settings.SECTION_SYSTEM,
|
||||
false
|
||||
),
|
||||
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
|
||||
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
|
||||
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
|
||||
@ -44,22 +88,48 @@ enum class BooleanSetting(
|
||||
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
|
||||
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
|
||||
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
|
||||
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false),
|
||||
SIMULATE_HEADPHONES_PLUGGED(
|
||||
SettingKeys.simulate_headphones_plugged(),
|
||||
Settings.SECTION_AUDIO,
|
||||
false
|
||||
),
|
||||
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
|
||||
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
||||
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
||||
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
|
||||
USE_SKIP_DUPLICATE_FRAMES(
|
||||
SettingKeys.use_skip_duplicate_frames(),
|
||||
Settings.SECTION_RENDERER,
|
||||
false
|
||||
),
|
||||
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
|
||||
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
||||
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
|
||||
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
|
||||
DISABLE_RIGHT_EYE_RENDER(
|
||||
SettingKeys.disable_right_eye_render(),
|
||||
Settings.SECTION_RENDERER,
|
||||
false
|
||||
),
|
||||
USE_ARTIC_BASE_CONTROLLER(
|
||||
SettingKeys.use_artic_base_controller(),
|
||||
Settings.SECTION_CONTROLS,
|
||||
false
|
||||
),
|
||||
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
|
||||
COMPRESS_INSTALLED_CIA_CONTENT(
|
||||
SettingKeys.compress_cia_installs(),
|
||||
Settings.SECTION_STORAGE,
|
||||
false
|
||||
),
|
||||
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
|
||||
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
|
||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
|
||||
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
|
||||
ENABLE_SECONDARY_DISPLAY(SettingKeys.enable_secondary_display(), Settings.SECTION_LAYOUT, true),
|
||||
SIMULATE_3DS_GPU_TIMINGS(
|
||||
SettingKeys.simulate_3ds_gpu_timings(),
|
||||
Settings.SECTION_RENDERER,
|
||||
false
|
||||
);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
||||
|
||||
@ -12,7 +12,11 @@ enum class FloatSetting(
|
||||
override val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(), Settings.SECTION_LAYOUT, 2.25f),
|
||||
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
|
||||
SECOND_SCREEN_OPACITY(
|
||||
SettingKeys.custom_second_layer_opacity(),
|
||||
Settings.SECTION_RENDERER,
|
||||
100f
|
||||
),
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
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,
|
||||
@ -11,7 +13,22 @@ enum class IntListSetting(
|
||||
val canBeEmpty: Boolean = true
|
||||
) : AbstractListSetting<Int> {
|
||||
|
||||
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
|
||||
LAYOUTS_TO_CYCLE(
|
||||
SettingKeys.layouts_to_cycle(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
listOf(0, 1, 2, 3, 4, 5),
|
||||
canBeEmpty = false
|
||||
),
|
||||
|
||||
/**
|
||||
* Combo Buttons will be given options on MultiChoice setting initialization. This just makes selections empty by default.
|
||||
*/
|
||||
COMBO_BUTTON_BUTTONS(
|
||||
SettingKeys.combo_button_buttons(),
|
||||
Settings.SECTION_CONTROLS,
|
||||
listOf(),
|
||||
canBeEmpty = true
|
||||
);
|
||||
|
||||
private var backingList: List<Int> = defaultValue
|
||||
private var lastValidList: List<Int> = defaultValue
|
||||
@ -30,7 +47,6 @@ enum class IntListSetting(
|
||||
override val valueAsString: String
|
||||
get() = list.joinToString()
|
||||
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() {
|
||||
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||
@ -44,8 +60,7 @@ enum class IntListSetting(
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
|
||||
|
||||
fun from(key: String): IntListSetting? =
|
||||
values().firstOrNull { it.key == key }
|
||||
fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key }
|
||||
|
||||
fun clear() = values().forEach { it.list = it.defaultValue }
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ enum class IntSetting(
|
||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(), Settings.SECTION_LAYOUT, 480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(), Settings.SECTION_LAYOUT, 0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(), Settings.SECTION_LAYOUT, 0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 4),
|
||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(), Settings.SECTION_LAYOUT, 0),
|
||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(), Settings.SECTION_LAYOUT, 0),
|
||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(), Settings.SECTION_LAYOUT, 800),
|
||||
@ -45,7 +45,11 @@ enum class IntSetting(
|
||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(), Settings.SECTION_LAYOUT, 80),
|
||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(), Settings.SECTION_LAYOUT, 480),
|
||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(), Settings.SECTION_LAYOUT, 640),
|
||||
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_HEIGHT(
|
||||
SettingKeys.custom_portrait_bottom_height(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
480
|
||||
),
|
||||
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||
@ -54,7 +58,11 @@ enum class IntSetting(
|
||||
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
|
||||
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
|
||||
PERFORMANCE_OVERLAY_POSITION(
|
||||
SettingKeys.performance_overlay_position(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
0
|
||||
),
|
||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(), Settings.SECTION_RENDERER, 0),
|
||||
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
|
||||
|
||||
@ -78,7 +86,7 @@ enum class IntSetting(
|
||||
EMULATED_REGION,
|
||||
INIT_CLOCK,
|
||||
GRAPHICS_API,
|
||||
AUDIO_INPUT_TYPE,
|
||||
AUDIO_INPUT_TYPE
|
||||
)
|
||||
|
||||
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -26,9 +26,7 @@ class SettingSection(val name: String) {
|
||||
* @param key Used to retrieve the Setting.
|
||||
* @return A Setting object (you should probably cast this before using)
|
||||
*/
|
||||
fun getSetting(key: String): AbstractSetting? {
|
||||
return settings[key]
|
||||
}
|
||||
fun getSetting(key: String): AbstractSetting? = settings[key]
|
||||
|
||||
fun mergeSection(settingSection: SettingSection) {
|
||||
for (setting in settingSection.settings.values) {
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
package org.citra.citra_emu.features.settings.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import java.util.TreeMap
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import java.util.TreeMap
|
||||
|
||||
class Settings {
|
||||
private var gameId: String? = null
|
||||
@ -33,9 +33,7 @@ class Settings {
|
||||
|
||||
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
|
||||
|
||||
fun getSection(sectionName: String): SettingSection? {
|
||||
return sections[sectionName]
|
||||
}
|
||||
fun getSection(sectionName: String): SettingSection? = sections[sectionName]
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = sections.isEmpty()
|
||||
@ -142,8 +140,9 @@ class Settings {
|
||||
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
|
||||
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
|
||||
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
|
||||
const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
|
||||
const val HOTKEY_QUICKLOAD = "hotkey_quickpause"
|
||||
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
|
||||
const val HOTKEY_BUTTON_COMBO = "hotkey_button_combo"
|
||||
|
||||
val buttonKeys = listOf(
|
||||
KEY_BUTTON_A,
|
||||
@ -210,8 +209,9 @@ class Settings {
|
||||
HOTKEY_CLOSE_GAME,
|
||||
HOTKEY_PAUSE_OR_RESUME,
|
||||
HOTKEY_QUICKSAVE,
|
||||
HOTKEY_QUICKlOAD,
|
||||
HOTKEY_TURBO_LIMIT
|
||||
HOTKEY_QUICKLOAD,
|
||||
HOTKEY_TURBO_LIMIT,
|
||||
HOTKEY_BUTTON_COMBO
|
||||
)
|
||||
val hotkeyTitles = listOf(
|
||||
R.string.controller_hotkey_enable_button,
|
||||
@ -221,7 +221,8 @@ class Settings {
|
||||
R.string.emulation_toggle_pause,
|
||||
R.string.emulation_quicksave,
|
||||
R.string.emulation_quickload,
|
||||
R.string.turbo_limit_hotkey
|
||||
R.string.turbo_limit_hotkey,
|
||||
R.string.button_combo
|
||||
)
|
||||
|
||||
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
|
||||
|
||||
@ -15,9 +15,17 @@ enum class StringSetting(
|
||||
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
|
||||
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
|
||||
CAMERA_OUTER_LEFT_CONFIG(
|
||||
SettingKeys.camera_outer_left_config(),
|
||||
Settings.SECTION_CAMERA,
|
||||
"_back"
|
||||
),
|
||||
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
|
||||
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
|
||||
CAMERA_OUTER_RIGHT_CONFIG(
|
||||
SettingKeys.camera_outer_right_config(),
|
||||
Settings.SECTION_CAMERA,
|
||||
"_back"
|
||||
);
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
@ -13,7 +15,9 @@ class DateTimeSetting(
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_DATETIME_SETTING
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
@ -20,10 +20,8 @@ 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.Settings
|
||||
|
||||
class InputBindingSetting(
|
||||
val abstractSetting: AbstractSetting,
|
||||
titleId: Int
|
||||
) : SettingsItem(abstractSetting, titleId, 0) {
|
||||
class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
|
||||
SettingsItem(abstractSetting, titleId, 0) {
|
||||
private val context: Context get() = CitraApplication.appContext
|
||||
private val preferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
@ -39,8 +37,7 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if this key is for the 3DS Circle Pad
|
||||
*/
|
||||
fun isCirclePad(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
fun isCirclePad(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
|
||||
|
||||
@ -50,8 +47,7 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad
|
||||
*/
|
||||
fun isHorizontalOrientation(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
|
||||
@ -62,8 +58,7 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if this key is for the 3DS C-Stick
|
||||
*/
|
||||
fun isCStick(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
fun isCStick(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
|
||||
|
||||
@ -73,19 +68,18 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if this key is for the 3DS D-Pad
|
||||
*/
|
||||
fun isDPad(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
fun isDPad(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_VERTICAL -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
|
||||
* triggers on the 3DS, but we support them as such on a physical gamepad.
|
||||
*/
|
||||
fun isTrigger(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
fun isTrigger(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_BUTTON_L,
|
||||
Settings.KEY_BUTTON_R,
|
||||
Settings.KEY_BUTTON_ZL,
|
||||
@ -97,16 +91,12 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if a gamepad axis can be used to map this key.
|
||||
*/
|
||||
fun isAxisMappingSupported(): Boolean {
|
||||
return isCirclePad() || isCStick() || isDPad() || isTrigger()
|
||||
}
|
||||
fun isAxisMappingSupported(): Boolean = isCirclePad() || isCStick() || isDPad() || isTrigger()
|
||||
|
||||
/**
|
||||
* Returns true if a gamepad button can be used to map this key.
|
||||
*/
|
||||
fun isButtonMappingSupported(): Boolean {
|
||||
return !isAxisMappingSupported() || isTrigger()
|
||||
}
|
||||
fun isButtonMappingSupported(): Boolean = !isAxisMappingSupported() || isTrigger()
|
||||
|
||||
/**
|
||||
* Returns the Citra button code for the settings key.
|
||||
@ -135,8 +125,9 @@ class InputBindingSetting(
|
||||
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
|
||||
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
|
||||
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
|
||||
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
|
||||
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button
|
||||
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
|
||||
Settings.HOTKEY_BUTTON_COMBO -> Hotkey.COMBO_BUTTON.button
|
||||
else -> -1
|
||||
}
|
||||
|
||||
@ -177,9 +168,9 @@ class InputBindingSetting(
|
||||
} 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;
|
||||
return
|
||||
}
|
||||
buttonCodes.remove(buttonCode.toString());
|
||||
buttonCodes.remove(buttonCode.toString())
|
||||
preferences.edit().putStringSet(oldKey, buttonCodes).apply()
|
||||
}
|
||||
}
|
||||
@ -271,8 +262,7 @@ class InputBindingSetting(
|
||||
companion object {
|
||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
||||
|
||||
private fun toTitleCase(raw: String): String =
|
||||
raw.replace("_", " ").lowercase()
|
||||
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase()
|
||||
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
||||
|
||||
private const val BUTTON_NAME_L3 = "Button L3"
|
||||
@ -287,8 +277,7 @@ class InputBindingSetting(
|
||||
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
||||
)
|
||||
|
||||
fun getButtonName(keyCode: Int): String =
|
||||
buttonNameOverrides[keyCode]
|
||||
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode]
|
||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||
|
||||
private data class DefaultButtonMapping(
|
||||
@ -296,6 +285,7 @@ class InputBindingSetting(
|
||||
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,
|
||||
@ -306,45 +296,153 @@ class InputBindingSetting(
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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.
|
||||
@ -369,28 +467,84 @@ class InputBindingSetting(
|
||||
// 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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
@ -417,9 +571,11 @@ class InputBindingSetting(
|
||||
}
|
||||
|
||||
private val allBindingKeys: Set<String> by lazy {
|
||||
(Settings.buttonKeys + Settings.triggerKeys +
|
||||
(
|
||||
Settings.buttonKeys + Settings.triggerKeys +
|
||||
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
|
||||
Settings.dPadButtonKeys).toSet()
|
||||
Settings.dPadButtonKeys
|
||||
).toSet()
|
||||
}
|
||||
|
||||
fun clearAllBindings() {
|
||||
@ -465,7 +621,11 @@ class InputBindingSetting(
|
||||
* 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 faceButtons = if (isNintendoLayout) {
|
||||
nintendoFaceButtonMappings
|
||||
} else {
|
||||
xboxFaceButtonMappings
|
||||
}
|
||||
val buttonMappings = if (useAxisDpad) {
|
||||
faceButtons + commonButtonMappings
|
||||
} else {
|
||||
@ -502,15 +662,16 @@ class InputBindingSetting(
|
||||
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}"
|
||||
@Suppress("ktlint:standard:max-line-length")
|
||||
val reverseKey =
|
||||
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
|
||||
editor.putString(reverseKey, axisKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings key for the specified Citra button code.
|
||||
*/
|
||||
private fun getButtonKey(buttonCode: Int): String =
|
||||
when (buttonCode) {
|
||||
private fun getButtonKey(buttonCode: Int): String = when (buttonCode) {
|
||||
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
|
||||
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
|
||||
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
|
||||
@ -528,16 +689,19 @@ class InputBindingSetting(
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||
else -> ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mutable set of int button values this key should map to given an event
|
||||
*/
|
||||
fun getButtonSet(keyCode: KeyEvent): MutableSet<Int> {
|
||||
val key = getInputButtonKey(keyCode)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||
CitraApplication.appContext
|
||||
)
|
||||
var buttonCodes = try {
|
||||
preferences.getStringSet(key, mutableSetOf<String>())
|
||||
} catch (e: ClassCastException) {
|
||||
val prefInt = preferences.getInt(key, -1);
|
||||
val prefInt = preferences.getInt(key, -1)
|
||||
val migratedSet = if (prefInt != -1) {
|
||||
mutableSetOf(prefInt.toString())
|
||||
} else {
|
||||
@ -549,15 +713,17 @@ class InputBindingSetting(
|
||||
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
|
||||
}
|
||||
|
||||
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
|
||||
private fun getInputButtonKey(keyId: Int): String =
|
||||
"${INPUT_MAPPING_PREFIX}_HostAxis_$keyId"
|
||||
|
||||
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
|
||||
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
|
||||
fun getInputButtonKey(event: KeyEvent): String =
|
||||
getInputButtonKey(translateEventToKeyId(event))
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad axis.
|
||||
*/
|
||||
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${axis}"
|
||||
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_$axis"
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad axis button (stick or trigger).
|
||||
@ -575,7 +741,6 @@ class InputBindingSetting(
|
||||
fun getInputAxisOrientationKey(axis: Int): String =
|
||||
"${getInputAxisKey(axis)}_GuestOrientation"
|
||||
|
||||
|
||||
/**
|
||||
* This function translates a keyEvent into an "keyid"
|
||||
* This key id is either the keyCode from the event, or
|
||||
@ -585,12 +750,10 @@ class InputBindingSetting(
|
||||
* This handles keys like the media-keys on google statia-controllers
|
||||
* that don't have a conventional "mapping" and report as "unknown"
|
||||
*/
|
||||
fun translateEventToKeyId(event: KeyEvent): Int {
|
||||
return if (event.keyCode == 0) {
|
||||
fun translateEventToKeyId(event: KeyEvent): Int = if (event.keyCode == 0) {
|
||||
event.scanCode
|
||||
} else {
|
||||
event.keyCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
class MultiChoiceSetting(
|
||||
@ -13,7 +15,9 @@ class MultiChoiceSetting(
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: List<Int>? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_MULTI_CHOICE
|
||||
|
||||
@ -42,5 +46,4 @@ class MultiChoiceSetting(
|
||||
intSetting.list = selection
|
||||
return intSetting
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
|
||||
@ -30,6 +31,9 @@ abstract class SettingsItem(
|
||||
|
||||
open var isEnabled: Boolean = true
|
||||
|
||||
@StringRes open var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
|
||||
val isActive: Boolean
|
||||
get() {
|
||||
return this.isEditable && this.isEnabled
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
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.AbstractShortSetting
|
||||
@ -16,7 +18,9 @@ class SingleChoiceSetting(
|
||||
val valuesId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: Int? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SINGLE_CHOICE
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
@ -20,7 +22,9 @@ class SliderSetting(
|
||||
val units: String,
|
||||
val key: String? = null,
|
||||
val defaultValue: Float? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SLIDER
|
||||
val selectedFloat: Float
|
||||
@ -29,8 +33,11 @@ class SliderSetting(
|
||||
|
||||
val ret = when (setting) {
|
||||
is AbstractIntSetting -> setting.int.toFloat()
|
||||
|
||||
is FloatSetting -> setting.float
|
||||
|
||||
is ScaledFloatSetting -> setting.float
|
||||
|
||||
else -> {
|
||||
Log.error("[SliderSetting] Error casting setting type.")
|
||||
-1f
|
||||
@ -38,6 +45,7 @@ class SliderSetting(
|
||||
}
|
||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing int. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
@ -13,7 +15,9 @@ class StringInputSetting(
|
||||
descriptionId: Int,
|
||||
val defaultValue: String,
|
||||
val characterLimit: Int = 0,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_INPUT
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
@ -16,7 +18,9 @@ class StringSingleChoiceSetting(
|
||||
val values: Array<String>?,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
|
||||
package org.citra.citra_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class SwitchSetting(
|
||||
setting: AbstractBooleanSetting,
|
||||
@ -14,7 +14,9 @@ class SwitchSetting(
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
val defaultValue: Boolean = false,
|
||||
override var isEnabled: Boolean = true
|
||||
override var isEnabled: Boolean = true,
|
||||
@StringRes override var disabledMessage: Int =
|
||||
R.string.setting_disabled_description_incompatible_setting
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SWITCH
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
@ -22,11 +21,11 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import java.io.IOException
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.ActivitySettingsBinding
|
||||
import java.io.IOException
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
@ -35,13 +34,15 @@ import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.InsetsHelper
|
||||
import org.citra.citra_emu.utils.RefreshRateUtil
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
|
||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
class SettingsActivity :
|
||||
AppCompatActivity(),
|
||||
SettingsActivityView {
|
||||
private val presenter = SettingsActivityPresenter(this)
|
||||
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
|
||||
@ -12,11 +12,11 @@ import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.TurboHelper
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
@ -72,11 +72,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
val nomediaFileExists: Boolean
|
||||
try {
|
||||
dataDirTreeUri = PermissionsHandler.citraDirectory
|
||||
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||
dataDirDocument =
|
||||
DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
|
||||
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
|
||||
nomediaFileExists = (nomediaFileDocument != null)
|
||||
} catch (e: Exception) {
|
||||
Log.error("[SettingsActivity]: Error occurred while trying to find .nomedia, error: " + e.message)
|
||||
Log.error(
|
||||
"[SettingsActivity]: Error occurred while trying to find .nomedia, error: " +
|
||||
e.message
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@ -97,7 +101,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
settings.saveSettings(activityView)
|
||||
// added to ensure that layout changes take effect as soon as settings window closes
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
updateAndroidImageVisibility()
|
||||
TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
@ -29,6 +30,9 @@ import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import java.lang.NumberFormatException
|
||||
import java.text.SimpleDateFormat
|
||||
import kotlin.math.roundToInt
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.DialogSliderBinding
|
||||
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||
@ -39,16 +43,15 @@ import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
|
||||
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.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
|
||||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SettingsItem
|
||||
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.SliderSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
|
||||
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
@ -69,14 +72,10 @@ import org.citra.citra_emu.fragments.AutoMapDialogFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import java.lang.NumberFormatException
|
||||
import java.text.SimpleDateFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SettingsAdapter(
|
||||
private val fragmentView: SettingsFragmentView,
|
||||
public val context: Context
|
||||
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
|
||||
class SettingsAdapter(private val fragmentView: SettingsFragmentView, public val context: Context) :
|
||||
RecyclerView.Adapter<SettingViewHolder?>(),
|
||||
DialogInterface.OnClickListener,
|
||||
DialogInterface.OnMultiChoiceClickListener {
|
||||
private var settings: ArrayList<SettingsItem>? = null
|
||||
private var clickedItem: SettingsItem? = null
|
||||
@ -148,17 +147,11 @@ class SettingsAdapter(
|
||||
getItem(position)?.let { holder.bind(it) }
|
||||
}
|
||||
|
||||
private fun getItem(position: Int): SettingsItem? {
|
||||
return settings?.get(position)
|
||||
}
|
||||
private fun getItem(position: Int): SettingsItem? = settings?.get(position)
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return settings?.size ?: 0
|
||||
}
|
||||
override fun getItemCount(): Int = settings?.size ?: 0
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return getItem(position)?.type ?: -1
|
||||
}
|
||||
override fun getItemViewType(position: Int): Int = getItem(position)?.type ?: -1
|
||||
|
||||
fun setSettingsList(newSettings: ArrayList<SettingsItem>?) {
|
||||
if (settings == null) {
|
||||
@ -196,22 +189,28 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
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
|
||||
(oldItem as MultiChoiceSetting).isEnabled ==
|
||||
(newItem as MultiChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
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 -> {
|
||||
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
|
||||
(oldItem as StringSingleChoiceSetting).isEnabled ==
|
||||
(newItem as StringSingleChoiceSetting).isEnabled
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_STRING_INPUT -> {
|
||||
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
|
||||
(oldItem as StringInputSetting).isEnabled ==
|
||||
(newItem as StringInputSetting).isEnabled
|
||||
}
|
||||
|
||||
else -> {
|
||||
@ -231,10 +230,11 @@ class SettingsAdapter(
|
||||
fragmentView.onSettingChanged()
|
||||
|
||||
// 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
|
||||
fragmentView.loadSettingsList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
|
||||
clickedItem = item
|
||||
@ -253,7 +253,7 @@ class SettingsAdapter(
|
||||
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
|
||||
clickedItem = item
|
||||
|
||||
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
|
||||
val value: BooleanArray = getSelectionForMultiChoiceValue(item)
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setMultiChoiceItems(item.choicesId, value, this)
|
||||
@ -298,7 +298,7 @@ class SettingsAdapter(
|
||||
val time = item.value.substringAfter(" ")
|
||||
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZZZZ")
|
||||
val gmt = formatter.parse("${date}T${time}+0000")
|
||||
val gmt = formatter.parse("${date}T$time+0000")
|
||||
gmt!!.time
|
||||
}
|
||||
|
||||
@ -354,7 +354,6 @@ class SettingsAdapter(
|
||||
clickedPosition = position
|
||||
sliderProgress = (item.selectedFloat * 100f).roundToInt() / 100f
|
||||
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
||||
textInputLayout = sliderBinding.textInput
|
||||
@ -376,9 +375,9 @@ class SettingsAdapter(
|
||||
value = sliderProgress
|
||||
textSliderValue?.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
var textValue = s.toString().toFloatOrNull();
|
||||
var textValue = s.toString().toFloatOrNull()
|
||||
if (item.setting !is FloatSetting) {
|
||||
textValue = textValue?.roundToInt()?.toFloat();
|
||||
textValue = textValue?.roundToInt()?.toFloat()
|
||||
}
|
||||
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
|
||||
textInputLayout?.error = "Inappropriate value"
|
||||
@ -425,6 +424,7 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
is FloatSetting -> (item.setting as FloatSetting).defaultValue
|
||||
|
||||
else -> item.defaultValue ?: 0f
|
||||
}
|
||||
onClick(dialog, which)
|
||||
@ -495,7 +495,9 @@ class SettingsAdapter(
|
||||
it.setSelectedValue(value)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
|
||||
else -> throw IllegalStateException(
|
||||
"Unrecognized type used for SingleChoiceSetting!"
|
||||
)
|
||||
}
|
||||
fragmentView?.putSetting(setting)
|
||||
fragmentView.loadSettingsList()
|
||||
@ -518,7 +520,9 @@ class SettingsAdapter(
|
||||
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
|
||||
else -> throw IllegalStateException(
|
||||
"Unrecognized type used for StringSingleChoiceSetting!"
|
||||
)
|
||||
}
|
||||
|
||||
fragmentView?.putSetting(setting)
|
||||
@ -575,7 +579,16 @@ class SettingsAdapter(
|
||||
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())
|
||||
val setting = it.setSelectedValue(
|
||||
(
|
||||
if (isChecked) {
|
||||
it.selectedValues + value
|
||||
} else {
|
||||
it.selectedValues -
|
||||
value
|
||||
}
|
||||
).sorted()
|
||||
)
|
||||
fragmentView?.putSetting(setting)
|
||||
fragmentView?.onSettingChanged()
|
||||
}
|
||||
@ -583,13 +596,13 @@ class SettingsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
when (setting) {
|
||||
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
|
||||
|
||||
is AbstractFloatSetting -> {
|
||||
if (setting is ScaledFloatSetting) {
|
||||
setting.float = setting.defaultValue * setting.scale
|
||||
@ -599,7 +612,9 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
|
||||
|
||||
is AbstractStringSetting -> setting.string = setting.defaultValue as String
|
||||
|
||||
is AbstractShortSetting -> setting.short = setting.defaultValue as Short
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
@ -627,15 +642,17 @@ class SettingsAdapter(
|
||||
return true
|
||||
}
|
||||
|
||||
fun onClickDisabledSetting(isRuntimeDisabled: Boolean) {
|
||||
val titleId = if (isRuntimeDisabled)
|
||||
fun onClickDisabledSetting(isRuntimeDisabled: Boolean, @StringRes disabledMessage: Int) {
|
||||
val titleId = if (isRuntimeDisabled) {
|
||||
R.string.setting_not_editable
|
||||
else
|
||||
} else {
|
||||
R.string.setting_disabled
|
||||
val messageId = if (isRuntimeDisabled)
|
||||
}
|
||||
val messageId = if (isRuntimeDisabled) {
|
||||
R.string.setting_not_editable_description
|
||||
else
|
||||
R.string.setting_disabled_description
|
||||
} else {
|
||||
disabledMessage
|
||||
}
|
||||
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId,
|
||||
@ -652,7 +669,10 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
fun onLongClickAutoMap(): Boolean {
|
||||
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
|
||||
showConfirmationDialog(
|
||||
R.string.controller_clear_all,
|
||||
R.string.controller_clear_all_confirm
|
||||
) {
|
||||
InputBindingSetting.clearAllBindings()
|
||||
fragmentView.loadSettingsList()
|
||||
fragmentView.onSettingChanged()
|
||||
@ -661,14 +681,20 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
fun onClickRegenerateConsoleId() {
|
||||
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
|
||||
showConfirmationDialog(
|
||||
R.string.regenerate_console_id,
|
||||
R.string.regenerate_console_id_description
|
||||
) {
|
||||
SystemSaveGame.regenerateConsoleId()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRegenerateMAC() {
|
||||
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
|
||||
showConfirmationDialog(
|
||||
R.string.regenerate_mac_address,
|
||||
R.string.regenerate_mac_address_description
|
||||
) {
|
||||
SystemSaveGame.regenerateMac()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
@ -732,18 +758,18 @@ class SettingsAdapter(
|
||||
}
|
||||
|
||||
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
|
||||
val value = item.selectedValues;
|
||||
val valuesId = item.valuesId;
|
||||
val value = item.selectedValues
|
||||
val valuesId = item.valuesId
|
||||
if (valuesId > 0) {
|
||||
val valuesArray = context.resources.getIntArray(valuesId);
|
||||
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;
|
||||
res[index] = true
|
||||
}
|
||||
}
|
||||
return res;
|
||||
return res
|
||||
}
|
||||
return BooleanArray(1){false};
|
||||
return BooleanArray(1) { false }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user