mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-07-10 01:34:41 -06:00
Compare commits
109 Commits
| 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 | ||
|
|
e11f3da493 | ||
|
|
8ffb94b06c | ||
|
|
267887d7a9 | ||
|
|
778ca369cd | ||
|
|
dbe7fd979f | ||
|
|
1c7c7a5f1b | ||
|
|
bf59d26c48 | ||
|
|
652fc02175 | ||
|
|
854e198196 | ||
|
|
5ecd402811 | ||
|
|
0ce2a30d20 | ||
|
|
644a181aff | ||
|
|
422c7865a3 | ||
|
|
ca99574700 | ||
|
|
f902010f04 | ||
|
|
929a51afc6 | ||
|
|
260f08c497 | ||
|
|
921ea178b9 | ||
|
|
b540725090 | ||
|
|
5ddbaeae23 | ||
|
|
b081f800a4 | ||
|
|
76a71d76d4 | ||
|
|
83eef0012e | ||
|
|
ec6a0dd1c8 | ||
|
|
eb498e5ecd | ||
|
|
4fa793b945 | ||
|
|
5d84dfed91 | ||
|
|
eee7f076ee | ||
|
|
996abd1eaf | ||
|
|
91128d6625 | ||
|
|
37b6c91de6 | ||
|
|
b6c54ac8c7 |
@ -12,6 +12,9 @@ fi
|
||||
|
||||
echo "Tag name is: $TAG_NAME"
|
||||
|
||||
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
mkdir -p build
|
||||
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
|
||||
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
||||
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
||||
|
||||
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
||||
|
||||
@ -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
|
||||
|
||||
25
.ci/mxe.sh
Executable file
25
.ci/mxe.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
|
||||
# It works fine when using the image locally.
|
||||
export PATH="/mxe/usr/bin:${PATH}"
|
||||
|
||||
mkdir build && cd build
|
||||
|
||||
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
||||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||
fi
|
||||
|
||||
x86_64-w64-mingw32.shared-cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
-DUSE_SYSTEM_BOOST=ON \
|
||||
-DUSE_SYSTEM_CRYPTOPP=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
|
||||
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
|
||||
make bundle
|
||||
|
||||
ccache -s -v
|
||||
@ -36,10 +36,10 @@ function pack_artifacts() {
|
||||
fi
|
||||
|
||||
# Create .zip/.tar.gz
|
||||
if [ "$OS" = "windows" ]; then
|
||||
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||
else
|
||||
|
||||
@ -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
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,4 +1,5 @@
|
||||
- [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms.
|
||||
---------
|
||||
|
||||
---
|
||||
<!--
|
||||
|
||||
202
.github/workflows/build.yml
vendored
202
.github/workflows/build.yml
vendored
@ -7,21 +7,41 @@ on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
source:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: ./
|
||||
format: spdx-json
|
||||
output-file: artifacts/source.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.tar.xz
|
||||
sbom-path: artifacts/source.spdx.json
|
||||
|
||||
linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
@ -39,14 +59,15 @@ jobs:
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -64,12 +85,27 @@ jobs:
|
||||
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||
- name: Generate SBOM
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ github.job }}-${{ matrix.target }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.AppImage
|
||||
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
|
||||
linux-arm64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
@ -87,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 }}
|
||||
@ -106,13 +142,15 @@ jobs:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: macos
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
@ -136,18 +174,43 @@ jobs:
|
||||
env:
|
||||
PACK_INDIVIDUALLY: 1
|
||||
run: ./.ci/pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/macos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
sbom-path: artifacts/macos.spdx.json
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["msvc", "msys2"]
|
||||
include:
|
||||
- target: msvc
|
||||
os: windows-latest
|
||||
- target: msys2
|
||||
os: windows-latest
|
||||
- target: mxe
|
||||
os: ubuntu-latest
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
||||
@ -155,14 +218,16 @@ jobs:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -170,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
|
||||
@ -191,34 +256,62 @@ jobs:
|
||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Install extra tools (MSYS2)
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
uses: crazy-max/ghaction-chocolatey@v3
|
||||
uses: crazy-max/ghaction-chocolatey@v4
|
||||
with:
|
||||
args: install ptime wget
|
||||
- name: Install NSIS
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
|
||||
ptime D:/a/_temp/nsis-setup.exe /S
|
||||
shell: pwsh
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
- name: Build (Native)
|
||||
if: ${{ matrix.target != 'mxe' }}
|
||||
run: ./.ci/windows.sh
|
||||
- name: Generate installer
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
- name: Build (MXE)
|
||||
if: ${{ matrix.target == 'mxe' }}
|
||||
run: ./.ci/mxe.sh
|
||||
- name: Generate installer (Native)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
cd src\installer
|
||||
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir ..\..\artifacts 2> NUL
|
||||
move /y *.exe ..\..\artifacts\
|
||||
shell: cmd
|
||||
- name: Generate installer (MXE)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
|
||||
run: |
|
||||
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
|
||||
cd src/installer
|
||||
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir -p ../../artifacts
|
||||
mv ./*.exe ../../artifacts/
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
artifacts/*.exe
|
||||
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
@ -230,17 +323,18 @@ jobs:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: android
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@ -278,23 +372,48 @@ jobs:
|
||||
working-directory: src/android/app
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: src/android
|
||||
format: spdx-json
|
||||
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
src/android/app/artifacts/*.apk
|
||||
src/android/app/artifacts/*.aab
|
||||
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64
|
||||
os: ubuntu-24.04
|
||||
- target: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: docker:dind
|
||||
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
||||
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: apk add bash
|
||||
- name: Fix git ownership
|
||||
run: git config --global --add safe.directory .
|
||||
- name: Build Docker image
|
||||
@ -303,8 +422,23 @@ jobs:
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
mv build/*.dockerimage artifacts/
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
name: docker
|
||||
image: ${{ env.DOCKER_IMAGE_PATH }}
|
||||
format: spdx-json
|
||||
output-file: artifacts/docker-room.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.dockerimage
|
||||
sbom-path: artifacts/docker-room.spdx.json
|
||||
|
||||
@ -20,7 +20,7 @@ jobs:
|
||||
(github.event.pull_request.author_association != 'OWNER')
|
||||
steps:
|
||||
- name: Detect PR if author is first-time contributor
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
@ -14,7 +14,7 @@ jobs:
|
||||
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
|
||||
steps:
|
||||
- name: Verify and reopen PR
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
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
|
||||
178
.github/workflows/libretro.yml
vendored
178
.github/workflows/libretro.yml
vendored
@ -11,8 +11,13 @@ on:
|
||||
env:
|
||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
android:
|
||||
libretro-android:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: android
|
||||
@ -23,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
|
||||
@ -32,6 +37,10 @@ jobs:
|
||||
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
fi
|
||||
echo $GIT_TAG_NAME
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- name: Update Android SDK CMake version
|
||||
run: |
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
|
||||
@ -41,14 +50,33 @@ jobs:
|
||||
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-android.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
linux:
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-android.spdx.json
|
||||
|
||||
libretro-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: linux
|
||||
@ -57,21 +85,44 @@ 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
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-linux.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
windows:
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-linux.spdx.json
|
||||
|
||||
libretro-windows:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OS: windows
|
||||
@ -79,10 +130,10 @@ jobs:
|
||||
BUILD_DIR: build/windows-x86_64
|
||||
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
|
||||
CMAKE: x86_64-w64-mingw32.static-cmake
|
||||
IMAGE: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
|
||||
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
|
||||
@ -94,17 +145,36 @@ jobs:
|
||||
$IMAGE \
|
||||
bash -lc "\
|
||||
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
|
||||
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)"
|
||||
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
|
||||
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-windows.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
macos:
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-windows.spdx.json
|
||||
libretro-macos:
|
||||
runs-on: macos-26
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
env:
|
||||
@ -114,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
|
||||
@ -123,14 +193,33 @@ jobs:
|
||||
run: |
|
||||
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
ios:
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
|
||||
libretro-ios:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: ios
|
||||
@ -139,21 +228,40 @@ 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
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-ios.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
tvos:
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-ios.spdx.json
|
||||
|
||||
libretro-tvos:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: tvos
|
||||
@ -162,17 +270,35 @@ 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
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-tvos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: ./*.zip
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-tvos.spdx.json
|
||||
|
||||
2
.github/workflows/license-header.yml
vendored
2
.github/workflows/license-header.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch master branch
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 90
|
||||
|
||||
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
@ -7,10 +7,10 @@ on:
|
||||
jobs:
|
||||
transifex:
|
||||
runs-on: ubuntu-latest
|
||||
container: opensauce04/azahar-build-environment:transifex
|
||||
container: opensauce04/azahar-build-environment:latest
|
||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -17,7 +17,8 @@ src/common/scm_rev.cpp
|
||||
*.swp
|
||||
*.kdev4
|
||||
.markdown-preview.html
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
@ -60,3 +61,6 @@ VULKAN_SDK/
|
||||
# Version info files
|
||||
GIT-COMMIT
|
||||
GIT-TAG
|
||||
|
||||
# verify-release.sh downloads
|
||||
verify/
|
||||
14
.gitmodules
vendored
14
.gitmodules
vendored
@ -55,18 +55,12 @@
|
||||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "cryptopp-cmake"]
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
[submodule "cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
[submodule "dds-ktx"]
|
||||
path = externals/dds-ktx
|
||||
url = https://github.com/septag/dds-ktx
|
||||
[submodule "openal-soft"]
|
||||
path = externals/openal-soft
|
||||
url = https://github.com/kcat/openal-soft
|
||||
url = https://github.com/azahar-emu/openal-soft
|
||||
[submodule "glslang"]
|
||||
path = externals/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
@ -106,3 +100,9 @@
|
||||
[submodule "externals/libretro-common"]
|
||||
path = externals/libretro-common/libretro-common
|
||||
url = https://github.com/libretro/libretro-common.git
|
||||
[submodule "dllwalker"]
|
||||
path = externals/dllwalker
|
||||
url = https://github.com/azahar-emu/dllwalker
|
||||
[submodule "externals/cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/cryptopp-modern/cryptopp-modern.git
|
||||
|
||||
@ -13,6 +13,9 @@ cmake_policy(SET CMP0063 NEW)
|
||||
cmake_policy(SET CMP0127 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
|
||||
|
||||
# Prefer building bundled dependencies as static instead of shared
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
@ -25,6 +28,11 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
|
||||
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
|
||||
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
|
||||
endif()
|
||||
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
add_link_options(-z wxneeded)
|
||||
endif()
|
||||
@ -99,7 +107,7 @@ endif()
|
||||
|
||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
|
||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||
set(_USER_SET_OPTIONS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
@ -113,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)
|
||||
@ -122,6 +130,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
|
||||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||
@ -133,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)
|
||||
|
||||
@ -402,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)
|
||||
if (USE_SYSTEM_MOLTENVK)
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
else()
|
||||
download_moltenvk()
|
||||
if (IOS)
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
|
||||
else()
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
|
||||
endif()
|
||||
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
|
||||
endif()
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
|
||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
||||
endif()
|
||||
|
||||
@ -198,6 +198,10 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
|
||||
# On Linux, always bundle an AppImage.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (IS_MINGW)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (IN_PLACE)
|
||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||
endif()
|
||||
@ -273,15 +277,23 @@ else()
|
||||
|
||||
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
if (MINGW)
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
# The target here is arbitrary
|
||||
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
|
||||
POST_BUILD)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@ -293,6 +305,11 @@ else()
|
||||
create_base_bundle_target()
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
|
||||
# We don't really need to "bundle" MXE builds, so don't do anything
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||
if (bundle_qt AND APPLE)
|
||||
# For Qt targets on Apple, expect an app bundle.
|
||||
@ -331,6 +348,7 @@ else()
|
||||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||
"-DBUNDLE_QT=${bundle_qt}"
|
||||
"-DIN_PLACE=${in_place}"
|
||||
"-DIS_MINGW=${MINGW}"
|
||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -17,6 +17,7 @@ foreach(KEY IN ITEMS
|
||||
"use_virtual_sd"
|
||||
"use_custom_storage"
|
||||
"compress_cia_installs"
|
||||
"async_fs_operations"
|
||||
"region_value"
|
||||
"init_clock"
|
||||
"init_time"
|
||||
@ -31,6 +32,7 @@ foreach(KEY IN ITEMS
|
||||
"physical_device"
|
||||
"use_gles"
|
||||
"renderer_debug"
|
||||
"pica_debugging"
|
||||
"dump_command_buffers"
|
||||
"spirv_shader_gen"
|
||||
"disable_spirv_optimizer"
|
||||
@ -40,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"
|
||||
@ -48,6 +51,7 @@ foreach(KEY IN ITEMS
|
||||
"texture_filter"
|
||||
"texture_sampling"
|
||||
"delay_game_render_thread_us"
|
||||
"simulate_3ds_gpu_timings"
|
||||
"layout_option"
|
||||
"swap_screen"
|
||||
"upright_screen"
|
||||
@ -107,6 +111,7 @@ foreach(KEY IN ITEMS
|
||||
"output_device"
|
||||
"input_type"
|
||||
"input_device"
|
||||
"simulate_headphones_plugged"
|
||||
"delay_start_for_lle_modules"
|
||||
"use_gdbstub"
|
||||
"gdbstub_port"
|
||||
@ -115,6 +120,7 @@ foreach(KEY IN ITEMS
|
||||
"log_filter"
|
||||
"log_regex_filter"
|
||||
"toggle_unique_data_console_type"
|
||||
"break_on_unmapped_memory_access"
|
||||
"use_integer_scaling"
|
||||
"layouts_to_cycle"
|
||||
"camera_inner_flip"
|
||||
@ -234,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}\",")
|
||||
|
||||
@ -1 +0,0 @@
|
||||
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**
|
||||
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
|
||||
|
||||
825
dist/languages/ca_ES_valencia.ts
vendored
825
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load Diff
813
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
813
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load Diff
831
dist/languages/de.ts
vendored
831
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
827
dist/languages/el.ts
vendored
827
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
827
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
827
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load Diff
955
dist/languages/es_419.ts
vendored
955
dist/languages/es_419.ts
vendored
File diff suppressed because it is too large
Load Diff
811
dist/languages/fi.ts
vendored
811
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
825
dist/languages/fr.ts
vendored
825
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
813
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
813
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load Diff
811
dist/languages/id.ts
vendored
811
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
833
dist/languages/it.ts
vendored
833
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
833
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
833
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
File diff suppressed because it is too large
Load Diff
815
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
815
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
File diff suppressed because it is too large
Load Diff
813
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
813
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
File diff suppressed because it is too large
Load Diff
837
dist/languages/nb.ts
vendored
837
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
813
dist/languages/nl.ts
vendored
813
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
833
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
833
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load Diff
843
dist/languages/pt_BR.ts
vendored
843
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
815
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
815
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
File diff suppressed because it is too large
Load Diff
819
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
819
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
File diff suppressed because it is too large
Load Diff
825
dist/languages/sv.ts
vendored
825
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load Diff
821
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
821
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
File diff suppressed because it is too large
Load Diff
813
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
813
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load Diff
825
dist/languages/zh_CN.ts
vendored
825
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
811
dist/languages/zh_TW.ts
vendored
811
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -4,23 +4,19 @@
|
||||
# --- Builder ----------------
|
||||
FROM opensauce04/azahar-build-environment:latest AS builder
|
||||
|
||||
COPY . /var/azahar-src
|
||||
RUN mkdir /var/azahar-src/
|
||||
COPY ./ /var/azahar-src/
|
||||
|
||||
RUN mkdir builddir && cd builddir && \
|
||||
cmake /var/azahar-src -G Ninja \
|
||||
RUN mkdir ./builddir/
|
||||
WORKDIR ./builddir/
|
||||
RUN cmake /var/azahar-src -G Ninja \
|
||||
-DENABLE_QT=OFF \
|
||||
-DENABLE_GDBSTUB=OFF \
|
||||
-DENABLE_TESTS=OFF \
|
||||
-DENABLE_ROOM=ON \
|
||||
-DENABLE_ROOM_STANDALONE=ON \
|
||||
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
|
||||
-DENABLE_VULKAN=OFF \
|
||||
-DENABLE_SDL2=OFF \
|
||||
-DENABLE_LIBUSB=OFF \
|
||||
-DENABLE_CUBEB=OFF \
|
||||
-DENABLE_OPENAL=OFF && \
|
||||
ninja && \
|
||||
mv bin/Release/azahar-room /usr/local/bin/ && \
|
||||
cd .. && rm -rf builddir
|
||||
-DENABLE_ROOM_STANDALONE=ON
|
||||
RUN ninja
|
||||
RUN mv ./bin/Release/azahar-room /usr/local/bin/
|
||||
|
||||
# --- Final ------------------
|
||||
FROM debian:trixie AS final
|
||||
|
||||
49
externals/CMakeLists.txt
vendored
49
externals/CMakeLists.txt
vendored
@ -57,7 +57,7 @@ if (ENABLE_TESTS)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
include(Catch)
|
||||
@ -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)
|
||||
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# dds-ktx
|
||||
@ -142,7 +134,7 @@ endif()
|
||||
|
||||
# getopt
|
||||
if (MSVC)
|
||||
add_subdirectory(getopt)
|
||||
add_subdirectory(getopt EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# inih
|
||||
@ -151,7 +143,7 @@ if(USE_SYSTEM_INIH)
|
||||
add_library(inih INTERFACE)
|
||||
target_link_libraries(inih INTERFACE inih::inih inih::inir)
|
||||
else()
|
||||
add_subdirectory(inih)
|
||||
add_subdirectory(inih EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# MicroProfile
|
||||
@ -174,7 +166,7 @@ if (NOT MSVC)
|
||||
endif()
|
||||
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives)
|
||||
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
|
||||
|
||||
# faad2
|
||||
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||
@ -213,12 +205,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
|
||||
|
||||
# SDL2
|
||||
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
|
||||
add_subdirectory(sdl2)
|
||||
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# libusb
|
||||
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
||||
add_subdirectory(libusb)
|
||||
add_subdirectory(libusb EXCLUDE_FROM_ALL)
|
||||
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
|
||||
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
|
||||
endif()
|
||||
@ -262,7 +254,7 @@ if(USE_SYSTEM_ENET)
|
||||
add_library(enet INTERFACE)
|
||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||
else()
|
||||
add_subdirectory(enet)
|
||||
add_subdirectory(enet EXCLUDE_FROM_ALL)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
endif()
|
||||
|
||||
@ -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)
|
||||
@ -327,12 +319,8 @@ endif()
|
||||
# OpenSSL
|
||||
if (USE_SYSTEM_OPENSSL)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
else()
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
@ -374,7 +362,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_subdirectory(gamemode)
|
||||
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
@ -397,13 +385,13 @@ if(USE_SYSTEM_LODEPNG)
|
||||
find_package(lodepng REQUIRED)
|
||||
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
|
||||
else()
|
||||
add_subdirectory(lodepng)
|
||||
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
||||
if(ANDROID)
|
||||
# libyuv
|
||||
add_subdirectory(libyuv)
|
||||
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
|
||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||
endif()
|
||||
|
||||
@ -414,6 +402,9 @@ if (ENABLE_OPENAL)
|
||||
find_package(OpenAL REQUIRED)
|
||||
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
|
||||
else()
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
|
||||
endif()
|
||||
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
||||
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
||||
set(ALSOFT_INSTALL OFF CACHE BOOL "")
|
||||
@ -429,7 +420,7 @@ endif()
|
||||
# OpenGL dependencies
|
||||
if (ENABLE_OPENGL)
|
||||
# Glad
|
||||
add_subdirectory(glad)
|
||||
add_subdirectory(glad EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# Vulkan dependencies
|
||||
@ -469,7 +460,7 @@ if (ENABLE_VULKAN)
|
||||
set(ENABLE_CTEST OFF CACHE BOOL "")
|
||||
set(ENABLE_HLSL OFF CACHE BOOL "")
|
||||
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
||||
add_subdirectory(glslang)
|
||||
add_subdirectory(glslang EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# sirit
|
||||
@ -517,7 +508,7 @@ if (ENABLE_VULKAN)
|
||||
|
||||
# adrenotools
|
||||
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(libadrenotools)
|
||||
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
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
|
||||
1
externals/dllwalker
vendored
Submodule
1
externals/dllwalker
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
@ -1 +1 @@
|
||||
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f
|
||||
Subproject commit e77b1ba0b7da7cbe93021b01a663acfe7c4dd516
|
||||
2
externals/openal-soft
vendored
2
externals/openal-soft
vendored
@ -1 +1 @@
|
||||
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727
|
||||
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906
|
||||
@ -203,6 +203,7 @@ if (ENABLE_QT)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT) # Or any other hypothetical future frontends
|
||||
add_subdirectory(citra_cli)
|
||||
add_subdirectory(citra_meta)
|
||||
endif()
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,12 +25,11 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
|
||||
|
||||
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.citra.citra_emu"
|
||||
|
||||
compileSdkVersion = "android-35"
|
||||
ndkVersion = "27.1.12297006"
|
||||
ndkVersion = "27.3.13750724"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
@ -80,7 +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
|
||||
"-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 {
|
||||
@ -266,7 +278,7 @@ fun getGitHash(): String =
|
||||
fun getBranch(): String =
|
||||
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
|
||||
|
||||
fun runGitCommand(command: ProcessBuilder) : String? {
|
||||
fun runGitCommand(command: ProcessBuilder): String? {
|
||||
try {
|
||||
command.directory(project.rootDir)
|
||||
val process = command.start()
|
||||
@ -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 {
|
||||
|
||||
@ -19,20 +19,22 @@ import android.view.Surface
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import 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
|
||||
@ -42,7 +44,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Default touchscreen device
|
||||
*/
|
||||
const val TouchScreenDevice = "Touchscreen"
|
||||
const val TOUCHSCREEN_DEVICE = "Touchscreen"
|
||||
|
||||
@JvmField
|
||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||
@ -134,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()
|
||||
|
||||
@ -252,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
|
||||
|
||||
@ -317,6 +315,12 @@ object NativeLibrary {
|
||||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorCoreExceptionRaised -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorUnknown -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
@ -341,11 +345,12 @@ object NativeLibrary {
|
||||
return coreErrorAlertResult
|
||||
}
|
||||
|
||||
@get:Keep
|
||||
@get:JvmStatic
|
||||
val isPortraitMode: Boolean
|
||||
get() = CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isPortraitMode(): Boolean = (
|
||||
CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -439,7 +444,7 @@ object NativeLibrary {
|
||||
return
|
||||
}
|
||||
|
||||
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
||||
if (resultCode == CoreError.ShutdownRequested.value) {
|
||||
emulationActivity.finish()
|
||||
return
|
||||
}
|
||||
@ -458,24 +463,58 @@ object NativeLibrary {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
|
||||
var captionId = R.string.loader_error_invalid_format
|
||||
val result = requireArguments().getInt(RESULT_CODE)
|
||||
if (result == ErrorLoader_ErrorEncrypted) {
|
||||
captionId = R.string.loader_error_encrypted
|
||||
}
|
||||
if (result == ErrorArticDisconnected) {
|
||||
captionId = R.string.artic_base
|
||||
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||
val title: String
|
||||
val message: String
|
||||
when (coreError) {
|
||||
CoreError.ErrorGetLoader,
|
||||
CoreError.ErrorLoaderErrorInvalidFormat,
|
||||
CoreError.ErrorSystemMode -> {
|
||||
title = getString(R.string.loader_error_invalid_format)
|
||||
message = getString(R.string.loader_error_invalid_format_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoaderErrorEncrypted -> {
|
||||
title = getString(R.string.loader_error_encrypted)
|
||||
message = getString(R.string.loader_error_encrypted_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorArticDisconnected -> {
|
||||
title = getString(R.string.artic_base)
|
||||
message = getString(R.string.artic_server_comm_error)
|
||||
}
|
||||
|
||||
CoreError.ErrorN3DSApplication -> {
|
||||
title = getString(R.string.loader_error_invalid_system_mode)
|
||||
message = getString(R.string.loader_error_invalid_system_mode_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoaderErrorPatches -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_applying_patches_description)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(captionId)
|
||||
.setTitle(title)
|
||||
.setMessage(
|
||||
Html.fromHtml(
|
||||
if (result == ErrorArticDisconnected)
|
||||
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
|
||||
else
|
||||
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
message,
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
@ -496,21 +535,6 @@ object NativeLibrary {
|
||||
|
||||
const val RESULT_CODE = "resultcode"
|
||||
|
||||
const val Success = 0
|
||||
const val ErrorNotInitialized = 1
|
||||
const val ErrorGetLoader = 2
|
||||
const val ErrorSystemMode = 3
|
||||
const val ErrorLoader = 4
|
||||
const val ErrorLoader_ErrorEncrypted = 5
|
||||
const val ErrorLoader_ErrorInvalidFormat = 6
|
||||
const val ErrorLoader_ErrorGBATitle = 7
|
||||
const val ErrorSystemFiles = 8
|
||||
const val ErrorSavestate = 9
|
||||
const val ErrorArticDisconnected = 10
|
||||
const val ErrorN3DSApplication = 11
|
||||
const val ShutdownRequested = 12
|
||||
const val ErrorUnknown = 13
|
||||
|
||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putInt(RESULT_CODE, resultCode)
|
||||
@ -621,7 +645,7 @@ object NativeLibrary {
|
||||
fun loadStateIfAvailable(slot: Int): Boolean {
|
||||
var available = false
|
||||
getSavestateInfo()?.forEach {
|
||||
if (it.slot == slot){
|
||||
if (it.slot == slot) {
|
||||
available = true
|
||||
return@forEach
|
||||
}
|
||||
@ -659,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
|
||||
|
||||
@ -705,21 +727,19 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun openContentUri(path: String, openMode: String): Int =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openMode)
|
||||
}
|
||||
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openMode)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getFilesName(path: String): Array<String?> =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFilesName(path)
|
||||
} else {
|
||||
FileUtil.getFilesName(path)
|
||||
}
|
||||
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFilesName(path)
|
||||
} else {
|
||||
FileUtil.getFilesName(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -745,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 ""
|
||||
@ -768,12 +792,11 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String): Long =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
}
|
||||
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -781,21 +804,23 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun fileExists(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
}
|
||||
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
}
|
||||
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -803,19 +828,18 @@ object NativeLibrary {
|
||||
sourcePath: String,
|
||||
destinationParentPath: String,
|
||||
destinationFilename: String
|
||||
): Boolean =
|
||||
if (FileUtil.isNativePath(sourcePath) &&
|
||||
FileUtil.isNativePath(destinationParentPath)
|
||||
) {
|
||||
CitraApplication.documentsTree
|
||||
.copyFile(sourcePath, destinationParentPath, destinationFilename)
|
||||
} else {
|
||||
FileUtil.copyFile(
|
||||
Uri.parse(sourcePath),
|
||||
Uri.parse(destinationParentPath),
|
||||
destinationFilename
|
||||
)
|
||||
}
|
||||
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
|
||||
FileUtil.isNativePath(destinationParentPath)
|
||||
) {
|
||||
CitraApplication.documentsTree
|
||||
.copyFile(sourcePath, destinationParentPath, destinationFilename)
|
||||
} else {
|
||||
FileUtil.copyFile(
|
||||
Uri.parse(sourcePath),
|
||||
Uri.parse(destinationParentPath),
|
||||
destinationFilename
|
||||
)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
@ -850,19 +874,35 @@ object NativeLibrary {
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun deleteDocument(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.deleteDocument(path)
|
||||
} else {
|
||||
FileUtil.deleteDocument(path)
|
||||
}
|
||||
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.deleteDocument(path)
|
||||
} else {
|
||||
FileUtil.deleteDocument(path)
|
||||
}
|
||||
|
||||
enum class CoreError {
|
||||
ErrorSystemFiles,
|
||||
ErrorSavestate,
|
||||
ErrorArticDisconnected,
|
||||
ErrorN3DSApplication,
|
||||
ErrorUnknown
|
||||
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
|
||||
Success(0, R.string.core_error_success),
|
||||
ErrorNotInitialized(1, R.string.core_error_not_initialized),
|
||||
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||
ErrorLoader(4, R.string.core_error_loader),
|
||||
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),
|
||||
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
|
||||
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
|
||||
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
|
||||
ShutdownRequested(16, R.string.core_error_shutdown_requested),
|
||||
ErrorUnknown(17, R.string.core_error_unknown);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
|
||||
}
|
||||
}
|
||||
|
||||
enum class InstallStatus {
|
||||
@ -913,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,15 +214,18 @@ 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) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.imageCartridge.visibility =
|
||||
if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
|
||||
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 {
|
||||
putExtra("launchedFromShortcut", true)
|
||||
})
|
||||
.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,31 +606,42 @@ 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)
|
||||
.setSingleChoiceItems(options, -1) { dialog, which ->
|
||||
selectedIndex = which
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) {_, _ ->
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val progToast = Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.deleting_shader_cache,
|
||||
@ -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()
|
||||
.noneMatch { extension == it.lowercase() }
|
||||
}
|
||||
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()
|
||||
@ -43,5 +43,5 @@ object MiiSelector {
|
||||
lateinit var miiNames: Array<String>
|
||||
}
|
||||
|
||||
class MiiSelectorData (var returnCode: Long, var index: Int)
|
||||
class MiiSelectorData(var returnCode: Long, var index: Int)
|
||||
}
|
||||
|
||||
@ -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,20 +79,23 @@ object SoftwareKeyboard {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private external fun ValidateFilters(text: String): ValidationError
|
||||
|
||||
@Suppress("FunctionName")
|
||||
external fun ValidateInput(text: String): ValidationError
|
||||
|
||||
/// Corresponds to Frontend::ButtonConfig
|
||||
// / 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to Frontend::ValidationError
|
||||
// / Corresponds to Frontend::ValidationError
|
||||
enum class ValidationError {
|
||||
None,
|
||||
|
||||
@ -128,7 +132,7 @@ object SoftwareKeyboard {
|
||||
lateinit var buttonText: Array<String>
|
||||
}
|
||||
|
||||
/// Corresponds to Frontend::KeyboardData
|
||||
// / Corresponds to Frontend::KeyboardData
|
||||
class KeyboardData(var button: Int, var text: String)
|
||||
class Filter : InputFilter {
|
||||
override fun filter(
|
||||
|
||||
@ -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(
|
||||
R.array.landscapeValues
|
||||
)
|
||||
} 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,26 +73,22 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
enum class StereoMode(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
// These must match what is defined in src/common/settings.h
|
||||
|
||||
OFF(0),
|
||||
SIDE_BY_SIDE(1),
|
||||
SIDE_BY_SIDE_FULL(2),
|
||||
ANAGLYPH(3),
|
||||
INTERLACED(4),
|
||||
REVERSE_INTERLACED (5),
|
||||
CARDBOARD_VR (6);
|
||||
REVERSE_INTERLACED(5),
|
||||
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 displays = dm.displays
|
||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
||||
val extDisplays = displays.filter {
|
||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
||||
isNotDefaultOrPresentable &&
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
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)
|
||||
return displays.filter {
|
||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||
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,24 +170,23 @@ class CheatDetailsFragment : Fragment() {
|
||||
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View?, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View?, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
|
||||
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarCheatDetails.layoutParams = mlpAppBar
|
||||
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarCheatDetails.layoutParams = mlpAppBar
|
||||
|
||||
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -128,7 +128,7 @@ class CheatListFragment : Fragment() {
|
||||
left = leftInsets,
|
||||
right = rightInsets,
|
||||
bottom = barInsets.bottom +
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
|
||||
)
|
||||
|
||||
val mlpFab = binding.fab.layoutParams as MarginLayoutParams
|
||||
|
||||
@ -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,
|
||||
@ -41,7 +42,7 @@ class HotkeyUtility(
|
||||
// Now process all internal buttons associated with this keypress
|
||||
for (button in buttonSet) {
|
||||
currentlyPressedButtons.add(button)
|
||||
//option 1 - this is the enable command, which was already handled
|
||||
// option 1 - this is the enable command, which was already handled
|
||||
if (button == Hotkey.ENABLE.button) {
|
||||
handled = true
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -18,6 +18,7 @@ object SettingKeys {
|
||||
external fun enable_required_online_lle_modules(): String
|
||||
external fun use_virtual_sd(): String
|
||||
external fun compress_cia_installs(): String
|
||||
external fun async_fs_operations(): String
|
||||
external fun region_value(): String
|
||||
external fun init_clock(): String
|
||||
external fun init_time(): String
|
||||
@ -38,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
|
||||
@ -45,6 +47,7 @@ object SettingKeys {
|
||||
external fun texture_filter(): String
|
||||
external fun texture_sampling(): String
|
||||
external fun delay_game_render_thread_us(): String
|
||||
external fun simulate_3ds_gpu_timings(): String
|
||||
external fun layout_option(): String
|
||||
external fun swap_screen(): String
|
||||
external fun upright_screen(): String
|
||||
@ -92,6 +95,7 @@ object SettingKeys {
|
||||
external fun audio_emulation(): String
|
||||
external fun enable_audio_stretching(): String
|
||||
external fun enable_realtime_audio(): String
|
||||
external fun simulate_headphones_plugged(): String
|
||||
external fun volume(): String
|
||||
external fun output_type(): String
|
||||
external fun output_device(): String
|
||||
@ -138,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),
|
||||
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, 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,19 +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
|
||||
),
|
||||
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);
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
|
||||
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
|
||||
|
||||
@ -91,6 +164,7 @@ enum class BooleanSetting(
|
||||
SHADERS_ACCURATE_MUL,
|
||||
USE_ARTIC_BASE_CONTROLLER,
|
||||
COMPRESS_INSTALLED_CIA_CONTENT,
|
||||
ASYNC_FS_OPERATIONS,
|
||||
ANDROID_HIDE_IMAGES,
|
||||
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
|
||||
APPLY_REGION_FREE_PATCH
|
||||
|
||||
@ -11,8 +11,12 @@ enum class FloatSetting(
|
||||
override val section: String,
|
||||
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),
|
||||
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(), Settings.SECTION_LAYOUT, 2.25f),
|
||||
SECOND_SCREEN_OPACITY(
|
||||
SettingKeys.custom_second_layer_opacity(),
|
||||
Settings.SECTION_RENDERER,
|
||||
100f
|
||||
),
|
||||
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
|
||||
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
|
||||
|
||||
@ -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,10 +13,25 @@ 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
|
||||
private var lastValidList: List<Int> = defaultValue
|
||||
|
||||
override var list: List<Int>
|
||||
get() = backingList
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
@ -26,26 +26,30 @@ enum class IntSetting(
|
||||
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
|
||||
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
|
||||
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
|
||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
|
||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
|
||||
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(), Settings.SECTION_LAYOUT, 0),
|
||||
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(), Settings.SECTION_LAYOUT, 0),
|
||||
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(), Settings.SECTION_LAYOUT, 0),
|
||||
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(), Settings.SECTION_LAYOUT, 800),
|
||||
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(), Settings.SECTION_LAYOUT, 480),
|
||||
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(), Settings.SECTION_LAYOUT, 80),
|
||||
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(), Settings.SECTION_LAYOUT, 480),
|
||||
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(), Settings.SECTION_LAYOUT, 640),
|
||||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(), Settings.SECTION_LAYOUT, 480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(), Settings.SECTION_LAYOUT, 0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(), Settings.SECTION_LAYOUT, 0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 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),
|
||||
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(), Settings.SECTION_LAYOUT, 480),
|
||||
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(), Settings.SECTION_LAYOUT, 80),
|
||||
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(), Settings.SECTION_LAYOUT, 480),
|
||||
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(), Settings.SECTION_LAYOUT, 640),
|
||||
PORTRAIT_BOTTOM_HEIGHT(
|
||||
SettingKeys.custom_portrait_bottom_height(),
|
||||
Settings.SECTION_LAYOUT,
|
||||
480
|
||||
),
|
||||
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
|
||||
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
|
||||
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
|
||||
@ -54,8 +58,12 @@ 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),
|
||||
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,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);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
@ -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,
|
||||
@ -182,7 +181,7 @@ class Settings {
|
||||
KEY_BUTTON_RIGHT
|
||||
)
|
||||
val axisTitles = listOf(
|
||||
R.string.controller_axis_vertical,
|
||||
R.string.controller_axis_vertical,
|
||||
R.string.controller_axis_horizontal
|
||||
)
|
||||
val dPadTitles = listOf(
|
||||
@ -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,9 +1,9 @@
|
||||
// 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
|
||||
|
||||
class HeaderSetting(titleId: Int,descId: Int = 0) : SettingsItem(null, titleId, descId) {
|
||||
class HeaderSetting(titleId: Int, descId: Int = 0) : SettingsItem(null, titleId, descId) {
|
||||
override val type = TYPE_HEADER
|
||||
}
|
||||
|
||||
@ -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,74 +37,66 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Returns true if this key is for the 3DS Circle Pad
|
||||
*/
|
||||
fun isCirclePad(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
|
||||
fun isCirclePad(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad
|
||||
*/
|
||||
fun isHorizontalOrientation(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
|
||||
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key is for the 3DS C-Stick
|
||||
*/
|
||||
fun isCStick(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
|
||||
fun isCStick(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
|
||||
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key is for the 3DS D-Pad
|
||||
*/
|
||||
fun isDPad(): Boolean =
|
||||
when (abstractSetting.key) {
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_VERTICAL -> true
|
||||
fun isDPad(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_DPAD_AXIS_HORIZONTAL,
|
||||
Settings.KEY_DPAD_AXIS_VERTICAL -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
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) {
|
||||
Settings.KEY_BUTTON_L,
|
||||
Settings.KEY_BUTTON_R,
|
||||
Settings.KEY_BUTTON_ZL,
|
||||
Settings.KEY_BUTTON_ZR -> true
|
||||
fun isTrigger(): Boolean = when (abstractSetting.key) {
|
||||
Settings.KEY_BUTTON_L,
|
||||
Settings.KEY_BUTTON_R,
|
||||
Settings.KEY_BUTTON_ZL,
|
||||
Settings.KEY_BUTTON_ZR -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,10 +168,10 @@ 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());
|
||||
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
|
||||
buttonCodes.remove(buttonCode.toString())
|
||||
preferences.edit().putStringSet(oldKey, buttonCodes).apply()
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +188,7 @@ class InputBindingSetting(
|
||||
// Cleanup old mapping for this setting
|
||||
removeOldMapping()
|
||||
|
||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
|
||||
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) { it.toString() })
|
||||
|
||||
// Write next reverse mapping for future cleanup
|
||||
editor.putString(reverseKey, key)
|
||||
@ -217,7 +208,7 @@ class InputBindingSetting(
|
||||
preferences.edit()
|
||||
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
|
||||
.putInt(getInputAxisButtonKey(axis), value)
|
||||
.putBoolean(getInputAxisInvertedKey(axis),inverted)
|
||||
.putBoolean(getInputAxisInvertedKey(axis), inverted)
|
||||
// Write next reverse mapping for future cleanup
|
||||
.putString(reverseKey, getInputAxisKey(axis))
|
||||
.apply()
|
||||
@ -271,9 +262,8 @@ class InputBindingSetting(
|
||||
companion object {
|
||||
private const val INPUT_MAPPING_PREFIX = "InputMapping"
|
||||
|
||||
private fun toTitleCase(raw: String): String =
|
||||
raw.replace("_", " ").lowercase()
|
||||
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
||||
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase()
|
||||
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
||||
|
||||
private const val BUTTON_NAME_L3 = "Button L3"
|
||||
private const val BUTTON_NAME_R3 = "Button R3"
|
||||
@ -287,15 +277,15 @@ class InputBindingSetting(
|
||||
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
|
||||
)
|
||||
|
||||
fun getButtonName(keyCode: Int): String =
|
||||
buttonNameOverrides[keyCode]
|
||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode]
|
||||
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
|
||||
|
||||
private data class DefaultButtonMapping(
|
||||
val settingKey: String,
|
||||
val hostKeyCode: Int,
|
||||
val guestButtonCode: Int
|
||||
)
|
||||
|
||||
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
|
||||
private data class DefaultAxisMapping(
|
||||
val settingKey: String,
|
||||
@ -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.
|
||||
@ -360,37 +458,93 @@ class InputBindingSetting(
|
||||
// KEYCODE_UNKNOWN with these scan codes because Android's input layer doesn't
|
||||
// translate them to KEYCODE_DPAD_*. translateEventToKeyId() falls back to
|
||||
// the scan code in that case.
|
||||
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
|
||||
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
|
||||
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
|
||||
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
|
||||
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
|
||||
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
|
||||
private const val LINUX_BTN_DPAD_RIGHT = 0x223 // 547
|
||||
|
||||
// Joy-Con face buttons: A/B are swapped by Android's evdev layer, but X/Y are not.
|
||||
// This is different from both the standard Xbox table (full swap) and the
|
||||
// Nintendo table (no swap).
|
||||
private val joyconFaceButtonMappings = listOf(
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
|
||||
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
|
||||
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.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
|
||||
Settings.dPadButtonKeys).toSet()
|
||||
(
|
||||
Settings.buttonKeys + Settings.triggerKeys +
|
||||
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
|
||||
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,42 +662,46 @@ 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) {
|
||||
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
|
||||
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
|
||||
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
|
||||
NativeLibrary.ButtonType.BUTTON_Y -> Settings.KEY_BUTTON_Y
|
||||
NativeLibrary.ButtonType.TRIGGER_L -> Settings.KEY_BUTTON_L
|
||||
NativeLibrary.ButtonType.TRIGGER_R -> Settings.KEY_BUTTON_R
|
||||
NativeLibrary.ButtonType.BUTTON_ZL -> Settings.KEY_BUTTON_ZL
|
||||
NativeLibrary.ButtonType.BUTTON_ZR -> Settings.KEY_BUTTON_ZR
|
||||
NativeLibrary.ButtonType.BUTTON_SELECT -> Settings.KEY_BUTTON_SELECT
|
||||
NativeLibrary.ButtonType.BUTTON_START -> Settings.KEY_BUTTON_START
|
||||
NativeLibrary.ButtonType.BUTTON_HOME -> Settings.KEY_BUTTON_HOME
|
||||
NativeLibrary.ButtonType.DPAD_UP -> Settings.KEY_BUTTON_UP
|
||||
NativeLibrary.ButtonType.DPAD_DOWN -> Settings.KEY_BUTTON_DOWN
|
||||
NativeLibrary.ButtonType.DPAD_LEFT -> Settings.KEY_BUTTON_LEFT
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
|
||||
else -> ""
|
||||
}
|
||||
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
|
||||
NativeLibrary.ButtonType.BUTTON_Y -> Settings.KEY_BUTTON_Y
|
||||
NativeLibrary.ButtonType.TRIGGER_L -> Settings.KEY_BUTTON_L
|
||||
NativeLibrary.ButtonType.TRIGGER_R -> Settings.KEY_BUTTON_R
|
||||
NativeLibrary.ButtonType.BUTTON_ZL -> Settings.KEY_BUTTON_ZL
|
||||
NativeLibrary.ButtonType.BUTTON_ZR -> Settings.KEY_BUTTON_ZR
|
||||
NativeLibrary.ButtonType.BUTTON_SELECT -> Settings.KEY_BUTTON_SELECT
|
||||
NativeLibrary.ButtonType.BUTTON_START -> Settings.KEY_BUTTON_START
|
||||
NativeLibrary.ButtonType.BUTTON_HOME -> Settings.KEY_BUTTON_HOME
|
||||
NativeLibrary.ButtonType.DPAD_UP -> Settings.KEY_BUTTON_UP
|
||||
NativeLibrary.ButtonType.DPAD_DOWN -> Settings.KEY_BUTTON_DOWN
|
||||
NativeLibrary.ButtonType.DPAD_LEFT -> Settings.KEY_BUTTON_LEFT
|
||||
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> {
|
||||
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) {
|
||||
event.scanCode
|
||||
} else {
|
||||
event.keyCode
|
||||
}
|
||||
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
|
||||
|
||||
@ -25,7 +29,7 @@ class MultiChoiceSetting(
|
||||
try {
|
||||
val setting = setting as IntListSetting
|
||||
return setting.list
|
||||
}catch (_: ClassCastException) {
|
||||
} catch (_: ClassCastException) {
|
||||
}
|
||||
return defaultValue!!
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user