Compare commits

..

No commits in common. "master" and "2124" have entirely different histories.
master ... 2124

591 changed files with 24138 additions and 59444 deletions

View file

@ -12,9 +12,6 @@ fi
echo "Tag name is: $TAG_NAME"
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
mkdir -p build
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
docker save azahar-room:$TAG_NAME > build/$FILENAME
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage

View file

@ -6,7 +6,8 @@ cmake .. -GNinja \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON
ninja
ccache -s -v

View file

@ -1,133 +0,0 @@
.core-defs:
variables:
JNI_PATH: .
CORENAME: azahar
API_LEVEL: 21
BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_TESTS=OFF
CORE_ARGS: ${BASE_CORE_ARGS}
EXTRA_PATH: bin/Release
variables:
STATIC_RETROARCH_BRANCH: master
GIT_SUBMODULE_STRATEGY: recursive
# Inclusion templates, required for the build to work
include:
################################## DESKTOPS ############################## ##
# Windows 64-bit
- project: 'libretro-infrastructure/ci-templates'
file: '/windows-cmake-mingw.yml'
# Linux 64-bit
- project: 'libretro-infrastructure/ci-templates'
file: '/linux-cmake.yml'
# MacOS x86_64
- project: 'libretro-infrastructure/ci-templates'
file: '/osx-cmake-x86.yml'
# MacOS ARM64
- project: 'libretro-infrastructure/ci-templates'
file: '/osx-cmake-arm64.yml'
################################## CELLULAR ############################## ##
# Android
- project: 'libretro-infrastructure/ci-templates'
file: '/android-cmake.yml'
# iOS
- project: 'libretro-infrastructure/ci-templates'
file: '/ios-cmake.yml'
# tvOS
- project: 'libretro-infrastructure/ci-templates'
file: '/tvos-cmake.yml'
################################## CONSOLES ############################## ##
# Stages for building
stages:
- build-prepare
- build-shared
- build-static
##############################################################################
#################################### STAGES ##################################
##############################################################################
#
################################### DESKTOPS #################################
# Windows 64-bit
libretro-build-windows-x64:
extends:
- .core-defs
- .libretro-windows-cmake-x86_64
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF -G Ninja
# Linux 64-bit
libretro-build-linux-x64:
extends:
- .core-defs
- .libretro-linux-cmake-x86_64
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF
CC: /usr/bin/gcc-12
CXX: /usr/bin/g++-12
# MacOS x86_64
libretro-build-osx-x64:
tags:
- mac-apple-silicon
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
MACOSX_DEPLOYMENT_TARGET: "11.0"
extends:
- .core-defs
- .libretro-osx-cmake-x86_64
# MacOS ARM64
libretro-build-osx-arm64:
extends:
- .core-defs
- .libretro-osx-cmake-arm64
variables:
MACOSX_DEPLOYMENT_TARGET: "11.0"
################################### CELLULAR #################################
# Android ARMv8a
android-arm64-v8a:
extends:
- .libretro-android-cmake-arm64-v8a
- .core-defs
variables:
ANDROID_NDK_VERSION: 26.2.11394342
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
LIBNAME: ${CORENAME}_libretro.so
artifacts:
paths:
- $LIBNAME
# iOS arm64
libretro-build-ios-arm64:
extends:
- .libretro-ios-cmake-arm64
- .core-defs
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
IOS_MINVER: "14.0"
EXTRA_PATH: bin/RelWithDebInfo
# tvOS arm64
libretro-build-tvos-arm64:
extends:
- .libretro-tvos-cmake-arm64
- .core-defs
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
MINVER: "14.0"
EXTRA_PATH: bin/RelWithDebInfo
################################### CONSOLES #################################

View file

@ -1,13 +0,0 @@
#!/bin/bash -ex
# Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
fi
# Create .zip
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*

View file

@ -1,6 +1,6 @@
#!/bin/bash -ex
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
if [[ "$TARGET" == "appimage"* ]]; then
# Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang
@ -25,8 +25,9 @@ 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 \
-DENABLE_DISCORD_RPC=ON \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
strip -s bin/Release/*

View file

@ -2,14 +2,13 @@
ARTIFACTS_LIST=($ARTIFACTS)
BUILD_DIR=build
UNIVERSAL_DIR=$BUILD_DIR/universal
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
BUNDLE_DIR=build/bundle
mkdir build
# Set up the base bundle to combine into.
mkdir $UNIVERSAL_DIR
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
# Set up the base artifact to combine into.
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
mv $BASE_ARTIFACT $BUNDLE_DIR
# Executable binary paths that need to be combined.
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
@ -20,18 +19,21 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
unset IFS
# Combine all of the executable binaries and dylibs.
for BIN_PATH in "${BIN_PATHS[@]}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
done
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
for BIN_PATH in "${BIN_PATHS[@]}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
done
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
fi
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
fi
done
done
# Remove leftover libs so that they aren't distributed

View file

@ -4,19 +4,23 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
cmake ../.. -GNinja \
mkdir build && cd build
cmake .. -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DENABLE_DISCORD_RPC=ON \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
ninja bundle
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
ccache -s -v
CURRENT_ARCH=`arch`
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
ctest -VV -C Release
fi

View file

@ -1,25 +0,0 @@
#!/bin/bash -ex
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
# It works fine when using the image locally.
export PATH="/mxe/usr/bin:${PATH}"
mkdir build && cd build
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
x86_64-w64-mingw32.shared-cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_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

View file

@ -3,21 +3,20 @@
# Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$GITHUB_REF_NAME-$OS-$TARGET"
else
RELEASE_NAME=azahar-head
fi
# Archive and upload the artifacts.
mkdir -p artifacts
function pack_artifacts() {
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
ARTIFACTS_PATH="$1"
# Set up root directory for archive.
@ -36,10 +35,10 @@ function pack_artifacts() {
fi
# Create .zip/.tar.gz
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
if [ "$OS" = "windows" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
else
@ -57,23 +56,11 @@ if [ -n "$UNPACKED" ]; then
FILENAME=$(basename "$ARTIFACT")
EXTENSION="${FILENAME##*.}"
# TODO: Deduplicate
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
done
elif [ -n "$PACK_INDIVIDUALLY" ]; then
# Pack and upload the artifacts one-by-one.
for ARTIFACT in build/bundle/*; do
TARGET=$(basename "$ARTIFACT")
pack_artifacts "$ARTIFACT"
done
else

View file

@ -6,7 +6,7 @@ gcc -v
tx --version
mkdir build && cd build
cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
make translation
cd ..

View file

@ -10,7 +10,8 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_DISCORD_RPC=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
ninja bundle

View file

@ -1,16 +0,0 @@
- [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms.
---------
---
<!--
If you are contributing to Azahar for the first time please
keep the block of text between `---` and write your
PR description below it. Do not write anything inside
or change this block of text!
If you are a recurrent contributor, remove this entire
block of text and proceed as normal.
-->
![Ignore Until Your PR has been created!](../blob/master/.github/ignore_unless_human.png?raw=true)
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -7,48 +7,28 @@ 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@v6
- uses: actions/checkout@v4
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@v7
uses: actions/upload-artifact@v4
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:
linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: ["appimage", "appimage-wayland", "gcc-nopch"]
target: ["appimage", "appimage-wayland", "fresh"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
@ -58,159 +38,115 @@ jobs:
CCACHE_SLOPPINESS: time_macros
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@v6
if: ${{ env.SHOULD_RUN == 'true' }}
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ github.job }}-${{ matrix.target }}-
${{ runner.os }}-${{ matrix.target }}-
- name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
if: ${{ contains(matrix.target, 'appimage') }}
run: |
mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
if: ${{ matrix.target == 'appimage-wayland' }}
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@v7
if: ${{ contains(matrix.target, 'appimage') }}
uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-${{ matrix.target }}
name: ${{ env.OS }}-${{ env.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
strategy:
fail-fast: false
matrix:
target: ["clang", "gcc-nopch"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
OS: linux
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ github.job }}-${{ matrix.target }}-
- name: Build
run: ./.ci/linux.sh
macos:
runs-on: 'macos-26'
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ github.sha }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-
${{ runner.os }}-${{ matrix.target }}-
- name: Install tools
run: brew install ccache ninja spirv-tools
- name: Build (x86_64)
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
- name: Build (arm64)
run: BUILD_ARCH=arm64 ./.ci/macos.sh
- name: Build
run: ./.ci/macos.sh
- name: Prepare outputs for caching
run: cp -R build/bundle $OS-$TARGET
- name: Cache outputs for universal build
uses: actions/cache/save@v4
with:
path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos-universal:
runs-on: macos-26
needs: macos
env:
OS: macos
TARGET: universal
steps:
- uses: actions/checkout@v4
- name: Download x86_64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Download ARM64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Create universal app
run: ./.ci/macos-universal.sh
- name: Prepare for packing
run: |
mkdir build/bundle
cp -r build/x86_64/bundle build/bundle/x86_64
cp -r build/arm64/bundle build/bundle/arm64
cp -r build/universal/bundle build/bundle/universal
- name: Pack
env:
PACK_INDIVIDUALLY: 1
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
- 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/macos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}
name: ${{ env.OS }}-${{ env.TARGET }}
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:
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 }}
target: ["msvc", "msys2"]
defaults:
run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -218,16 +154,14 @@ 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@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -235,7 +169,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC
if: ${{ matrix.target == 'msvc' }}
uses: azahar-emu/msvc-dev-cmd@v1
uses: ilammy/msvc-dev-cmd@v1
- name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget
@ -256,62 +190,34 @@ 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@v4
uses: crazy-max/ghaction-chocolatey@v3
with:
args: install ptime wget
- name: Install NSIS
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
if: ${{ github.ref_type == 'tag' }}
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 (Native)
if: ${{ matrix.target != 'mxe' }}
- name: Build
run: ./.ci/windows.sh
- name: Build (MXE)
if: ${{ matrix.target == 'mxe' }}
run: ./.ci/mxe.sh
- name: Generate installer (Native)
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
- name: Generate installer
if: ${{ github.ref_type == 'tag' }}
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@v7
uses: actions/upload-artifact@v4
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
@ -323,18 +229,17 @@ 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@v6
- uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
@ -372,48 +277,23 @@ 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@v7
uses: actions/upload-artifact@v4
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:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
os: ubuntu-24.04
- target: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
container:
# 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 }}
image: docker:dind
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install tools
run: apk add bash
- name: Fix git ownership
run: git config --global --add safe.directory .
- name: Build Docker image
@ -422,23 +302,8 @@ jobs:
run: |
mkdir -p artifacts
mv build/*.dockerimage artifacts/
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
image: ${{ env.DOCKER_IMAGE_PATH }}
format: spdx-json
output-file: artifacts/docker-room.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
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
name: docker
path: artifacts/

View file

@ -1,51 +0,0 @@
name: Detect first-time contributors
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
issues: write
jobs:
detect:
runs-on: ubuntu-latest
if: >-
(github.repository == 'azahar-emu/azahar') &&
(github.event.pull_request.author_association != 'COLLABORATOR') &&
(github.event.pull_request.author_association != 'CONTRIBUTOR') &&
(github.event.pull_request.author_association != 'MANNEQUIN') &&
(github.event.pull_request.author_association != 'MEMBER') &&
(github.event.pull_request.author_association != 'OWNER')
steps:
- name: Detect PR if author is first-time contributor
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
// Add needs verification label so that the reopen action runs on comment.
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pr.number,
labels: ['needs verification'],
});
// Close the pull request and wait for verification.
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: 'closed',
});
// Show the new contributor how to verify (they need to write a short poem about the Wii and 3DS being lovers)
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: 'Welcome to the Azahar Emulator repository! Due to the surge of AI bots we have decided to add an extra verification step to new contributors. Please follow the exact instructions in your own written Pull Request description to reopen it.',
});

View file

@ -1,79 +0,0 @@
name: Verify first-time contributors
on:
issue_comment:
types: [created]
permissions:
pull-requests: write
issues: write
jobs:
verify:
runs-on: ubuntu-latest
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
steps:
- name: Verify and reopen PR
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const issue = context.payload.issue;
const comment = context.payload.comment;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: issue.number,
});
// Only allow verification of the comment user is the author
if (comment.user.login !== pr.user.login) {
return;
}
// Fetch user display and login names (lowercase)
const { data: user } = await github.rest.users.getByUsername({
username: pr.user.login,
});
const username = pr.user.login.toLowerCase();
const displayName = (user.name || '').toLowerCase();
// Make comment body lowercase and split words
const body = comment.body.toLowerCase().trim().replace(/[^a-z0-9_\-\s]/g, '').split(/\s+/);
// Check that the user verified themselves by writing a song about the NES and the SNES.
const verified =
(body.includes(username) ||
(displayName && body.includes(displayName))) &&
body.includes('azahar');
// Only reopen the PR and remove the label if verification succeeded
if (verified) {
await github.rest.pulls.update({
owner,
repo,
pull_number: issue.number,
state: 'open',
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: 'Verification successful! Pull request has been reopened. Please also edit your PR description to remove the block of text between `---` to make the description easier to read.',
});
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issue.number,
name: 'needs verification',
});
} catch {}
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: 'Verification failed! Pull request will remain closed.',
});
}

View file

@ -13,22 +13,10 @@ jobs:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
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

View file

@ -1,304 +0,0 @@
name: citra-libretro
on:
push:
branches: [ "*" ]
tags: [ "*" ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CORE_ARGS: -DENABLE_LIBRETRO=ON
permissions:
id-token: write
contents: read
attestations: write
jobs:
libretro-android:
runs-on: ubuntu-22.04
env:
OS: android
TARGET: arm64-v8a
API_LEVEL: 21
ANDROID_NDK_VERSION: 26.2.11394342
ANDROID_ABI: arm64-v8a
BUILD_DIR: build/android-arm64-v8a
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set tag name
run: |
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
fi
echo $GIT_TAG_NAME
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Update Android SDK CMake version
run: |
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
- name: Build
run: |
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-android.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-android.spdx.json
libretro-linux:
runs-on: ubuntu-22.04
env:
OS: linux
TARGET: x86_64
BUILD_DIR: build/linux-x86_64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-linux.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-linux.spdx.json
libretro-windows:
runs-on: ubuntu-latest
env:
OS: windows
TARGET: x86_64
BUILD_DIR: build/windows-x86_64
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
CMAKE: x86_64-w64-mingw32.static-cmake
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build in cross-container
run: |
docker pull $IMAGE
docker run --rm --user root \
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-w "${GITHUB_WORKSPACE}" \
$IMAGE \
bash -lc "\
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-windows.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-windows.spdx.json
libretro-macos:
runs-on: macos-26
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env:
OS: macos
TARGET: ${{ matrix.target }}
MACOSX_DEPLOYMENT_TARGET: 11.0
BUILD_DIR: build/osx-${{ matrix.target }}
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: brew install spirv-tools
- name: Build
run: |
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-macos-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
libretro-ios:
runs-on: macos-26
env:
OS: ios
TARGET: arm64
BUILD_DIR: build/ios-arm64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-ios.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-ios.spdx.json
libretro-tvos:
runs-on: macos-26
env:
OS: tvos
TARGET: arm64
BUILD_DIR: build/tvos-arm64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-tvos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-tvos.spdx.json

View file

@ -11,7 +11,7 @@ jobs:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch master branch

View file

@ -10,7 +10,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/stale@v10.2.0
- uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 90

View file

@ -7,10 +7,10 @@ on:
jobs:
transifex:
runs-on: ubuntu-latest
container: opensauce04/azahar-build-environment:latest
container: opensauce04/azahar-build-environment:transifex
if: ${{ github.repository == 'azahar-emu/azahar' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

10
.gitignore vendored
View file

@ -17,8 +17,7 @@ src/common/scm_rev.cpp
*.swp
*.kdev4
.markdown-preview.html
.idea/*
!.idea/inspectionProfiles
.idea/
.vs/
.vscode/
.cache/
@ -57,10 +56,3 @@ repo/
.ccache/
node_modules/
VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG
# verify-release.sh downloads
verify/

17
.gitmodules vendored
View file

@ -55,12 +55,18 @@
[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/azahar-emu/openal-soft
url = https://github.com/kcat/openal-soft
[submodule "glslang"]
path = externals/glslang
url = https://github.com/KhronosGroup/glslang
@ -97,12 +103,3 @@
[submodule "externals/xxHash"]
path = externals/xxHash
url = https://github.com/Cyan4973/xxHash.git
[submodule "externals/libretro-common"]
path = externals/libretro-common/libretro-common
url = https://github.com/libretro/libretro-common.git
[submodule "dllwalker"]
path = externals/dllwalker
url = https://github.com/azahar-emu/dllwalker
[submodule "externals/cryptopp"]
path = externals/cryptopp
url = https://github.com/cryptopp-modern/cryptopp-modern.git

View file

@ -1,20 +0,0 @@
# Azahar Emulator AI Use Policy
The following document outlines the acceptable and unacceptable uses of AI within the Azahar codebase.
It describes whether or not submissions which were exposed to large language models (LLMs) such as ChatGPT, Claude, DeepSeek, and similar models would be capable of being merged in a pull request or otherwise utilized.
- ✅ Use of AI to help developers discover or understand problems in the codebase is acceptable **under the condition that any discovered issue is independently verified by a human**.
- ✅ Use of AI to write code snippets of a sufficiently small size that they aren't reasonably copyrightable **with disclosure in the PR description** is acceptable.
- This will be handled on a case-by-case basis and is up to the interpretation of the maintainer, but generic algorithm snippets up to a maximum of approximately 5 lines of code are acceptable.
- ❌ Use of AI to write code for submission without disclosure is prohibited.
- ❌ Use of AI to write the entirety/ a significant portion of a contribution is prohibited.
- ❌ Use of AI to write snippets of code which are of a size such that they could reasonably be copyrightable is prohibited.
- ❌ Use of AI to rewrite incompatibly-licensed code for submission to Azahar is prohibited.
- ❌ Use of AI to autonomously submit pull requests or issues is prohibited.
Pull requests which violate these rules will be closed. Previously accepted submissions which are found to violate these rules will be retroactively removed from the codebase.
This document may be updated in the future if further clarification is required.
This policy is effective for code submitted on or after the 20th of March 2026.

View file

@ -13,39 +13,24 @@ cmake_policy(SET CMP0063 NEW)
cmake_policy(SET CMP0127 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
# Prefer building bundled dependencies as static instead of shared
set(BUILD_SHARED_LIBS OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
include(CMakeDependentOption)
include(FindPkgConfig)
project(citra LANGUAGES C CXX ASM)
# must be invoked after project() command when using CMAKE_TOOLCHAIN_FILE
include(FindPkgConfig)
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
enable_language(OBJC OBJCXX)
endif()
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
endif()
if (BSD STREQUAL "OpenBSD")
add_link_options(-z wxneeded)
endif()
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
# Some submodules like to pick their own default build type if not specified.
# Make sure we default to Release build type always, unless the generator has custom types.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()
if (APPLE AND NOT ENABLE_LIBRETRO)
if (APPLE)
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
@ -105,23 +90,13 @@ else()
set(DEFAULT_ENABLE_OPENGL ON)
endif()
# Track which options were explicitly set by the user (for libretro conflict detection)
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
set(_USER_SET_OPTIONS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
if(DEFINED ${_opt})
list(APPEND _USER_SET_OPTIONS ${_opt})
endif()
endforeach()
option(ENABLE_SDL2 "Enable using SDL2" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF)
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
# Set bundled qt as dependent options.
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
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)
@ -130,7 +105,6 @@ 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)
@ -139,10 +113,9 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
# 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(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
@ -150,8 +123,6 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON)
# Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -159,31 +130,6 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
# Handle incompatible options for libretro builds
if(ENABLE_LIBRETRO)
# Check for explicitly-set conflicting options
set(_CONFLICTS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
list(FIND _USER_SET_OPTIONS ${_opt} _idx)
if(NOT _idx EQUAL -1 AND ${_opt})
list(APPEND _CONFLICTS ${_opt})
endif()
endforeach()
if(_CONFLICTS)
string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}")
message(FATAL_ERROR
"ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n"
"These options were explicitly enabled but are not supported for libretro builds.\n"
"Remove these options or set them to OFF.")
endif()
# Force disable incompatible options (handles defaulted-on options)
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE)
endforeach()
endif()
# Pass the following values to C++ land
if (ENABLE_QT)
add_definitions(-DENABLE_QT)
@ -197,6 +143,9 @@ endif()
if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2)
endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
message(STATUS "SSE4.2 enabled for x86_64")
@ -274,7 +223,7 @@ function(check_submodules_present)
foreach(module ${gitmodules})
string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
message(SEND_ERROR "Git submodule ${module} not found.\n"
message(SEND_ERROR "Git submodule ${module} not found."
"Please run: git submodule update --init --recursive")
endif()
endforeach()
@ -351,9 +300,6 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
# set up output paths for executable binaries
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
if (ENABLE_LIBRETRO)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# System imported libraries
# ======================
@ -364,7 +310,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT)
if (NOT USE_SYSTEM_QT)
download_qt(6.9.3)
download_qt(6.9.2)
endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
@ -411,21 +357,13 @@ if (APPLE)
endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
if (USE_SYSTEM_MOLTENVK)
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
else()
if (ENABLE_VULKAN)
if (NOT USE_SYSTEM_MOLTENVK)
download_moltenvk()
if (IOS)
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
else()
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
endif()
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
endif()
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif()
@ -575,6 +513,8 @@ if (NOT ANDROID AND NOT IOS)
include(BundleTarget)
if (ENABLE_QT)
qt_bundle_target(citra_meta)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(citra_meta)
endif()
if (ENABLE_ROOM_STANDALONE)
bundle_target(citra_room_standalone)

View file

@ -0,0 +1,26 @@
# To use this as a script, make sure you pass in the variables BASE_DIR, SRC_DIR, BUILD_DIR, and TARGET_FILE
cmake_minimum_required(VERSION 3.15)
if(WIN32)
set(PLATFORM "windows")
elseif(APPLE)
set(PLATFORM "mac")
elseif(UNIX)
set(PLATFORM "linux")
else()
message(FATAL_ERROR "Cannot build installer for this unsupported platform")
endif()
list(APPEND CMAKE_MODULE_PATH "${BASE_DIR}/CMakeModules")
include(DownloadExternals)
download_qt(tools_ifw)
get_external_prefix(qt QT_PREFIX)
file(GLOB_RECURSE INSTALLER_BASE "${QT_PREFIX}/**/installerbase*")
file(GLOB_RECURSE BINARY_CREATOR "${QT_PREFIX}/**/binarycreator*")
set(CONFIG_FILE "${SRC_DIR}/config/config_${PLATFORM}.xml")
set(PACKAGES_DIR "${BUILD_DIR}/packages")
file(MAKE_DIRECTORY ${PACKAGES_DIR})
execute_process(COMMAND ${BINARY_CREATOR} -t ${INSTALLER_BASE} -n -c ${CONFIG_FILE} -p ${PACKAGES_DIR} ${TARGET_FILE})

View file

@ -198,10 +198,6 @@ 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()
@ -277,23 +273,15 @@ else()
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
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()
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()
endfunction()
@ -305,11 +293,6 @@ 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.
@ -348,7 +331,6 @@ 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}")

View file

@ -1,11 +0,0 @@
function(disable_pax_mprotect target)
if (BSD STREQUAL "NetBSD")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
VERBATIM
)
else()
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
endif()
endfunction()

View file

@ -171,16 +171,27 @@ 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 "${CMAKE_BINARY_DIR}/externals/MoltenVK")
if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/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)

View file

@ -1,52 +1,49 @@
macro(generate_build_info)
find_package(Git QUIET)
# Gets a UTC timstamp and sets the provided variable to it
function(get_timestamp _var)
string(TIMESTAMP timestamp UTC)
set(${_var} "${timestamp}" PARENT_SCOPE)
endfunction()
get_timestamp(BUILD_DATE)
# Gets a UTC timstamp and sets the provided variable to it
function(get_timestamp _var)
string(TIMESTAMP timestamp UTC)
set(${_var} "${timestamp}" PARENT_SCOPE)
endfunction()
get_timestamp(BUILD_DATE)
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
if (EXISTS "${SRC_DIR}/.git/objects")
# Find the package here with the known path so that the GetGit commands can find it as well
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
string(STRIP "${GIT_REV_RAW}" GIT_REV)
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
set(GIT_BRANCH "HEAD")
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
# Find the package here with the known path so that the GetGit commands can find it as well
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
# only use Git to check revision info when source is obtained via Git
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REF_SPEC GIT_REV)
git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH)
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
# unified source archive
file(READ "${SRC_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
string(STRIP "${GIT_REV_RAW}" GIT_REV)
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
set(GIT_BRANCH "HEAD")
else()
# self-packed archive?
set(GIT_REV "UNKNOWN")
set(GIT_DESC "UNKNOWN")
set(GIT_BRANCH "UNKNOWN")
endif()
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
# only use Git to check revision info when source is obtained via Git
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REF_SPEC GIT_REV)
git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH)
else()
# self-packed archive?
set(GIT_REV "UNKNOWN")
set(GIT_DESC "UNKNOWN")
set(GIT_BRANCH "UNKNOWN")
# Set build version
set(REPO_NAME "")
set(BUILD_VERSION "0")
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
set(GIT_TAG $ENV{GITHUB_REF_NAME})
endif()
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
string(STRIP ${GIT_TAG} GIT_TAG)
endif()
# Set build version
set(REPO_NAME "")
set(BUILD_VERSION "0")
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
set(GIT_TAG $ENV{GITHUB_REF_NAME})
endif()
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
string(STRIP ${GIT_TAG} GIT_TAG)
endif()
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
set(BUILD_VERSION "${GIT_TAG}")
set(BUILD_FULLNAME "${BUILD_VERSION}")
endif()
endmacro()
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
set(BUILD_VERSION "${GIT_TAG}")
set(BUILD_FULLNAME "${BUILD_VERSION}")
endif()

View file

@ -1,10 +1,8 @@
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
include(GenerateBuildInfo)
generate_build_info()
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
@ -12,10 +10,6 @@ set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
@ -24,7 +18,6 @@ set(HASH_FILES
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
"${VIDEO_CORE}/shader/generator/profile.h"
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@ -48,4 +41,4 @@ foreach (F IN LISTS HASH_FILES)
set(COMBINED "${COMBINED}${TMP}")
endforeach()
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)

View file

@ -1,283 +0,0 @@
## This file should be the *only place* where setting keys exist as strings.
# All references to setting strings should be derived from the
# `setting_keys.h` and `jni_setting_keys.cpp` files generated here.
# !!! Changes made here should be mirrored to SettingKeys.kt if used on Android
# Shared setting keys (multi-platform)
foreach(KEY IN ITEMS
"use_artic_base_controller"
"enable_gamemode"
"use_cpu_jit"
"cpu_clock_percentage"
"is_new_3ds"
"lle_applets"
"deterministic_async_operations"
"enable_required_online_lle_modules"
"use_virtual_sd"
"use_custom_storage"
"compress_cia_installs"
"async_fs_operations"
"region_value"
"init_clock"
"init_time"
"init_time_offset"
"init_ticks_type"
"init_ticks_override"
"plugin_loader"
"allow_plugin_loader"
"steps_per_hour"
"apply_region_free_patch"
"graphics_api"
"physical_device"
"use_gles"
"renderer_debug"
"dump_command_buffers"
"spirv_shader_gen"
"disable_spirv_optimizer"
"async_shader_compilation"
"async_presentation"
"use_hw_shader"
"use_disk_shader_cache"
"shaders_accurate_mul"
"use_vsync"
"use_display_refresh_rate_detection"
"use_shader_jit"
"resolution_factor"
"frame_limit"
"turbo_limit"
"texture_filter"
"texture_sampling"
"delay_game_render_thread_us"
"simulate_3ds_gpu_timings"
"layout_option"
"swap_screen"
"upright_screen"
"secondary_display_layout"
"large_screen_proportion"
"screen_gap"
"small_screen_position"
"custom_top_x"
"custom_top_y"
"custom_top_width"
"custom_top_height"
"custom_bottom_x"
"custom_bottom_y"
"custom_bottom_width"
"custom_bottom_height"
"custom_second_layer_opacity"
"aspect_ratio"
"screen_top_stretch"
"screen_top_leftright_padding"
"screen_top_topbottom_padding"
"screen_bottom_stretch"
"screen_bottom_leftright_padding"
"screen_bottom_topbottom_padding"
"portrait_layout_option"
"custom_portrait_top_x"
"custom_portrait_top_y"
"custom_portrait_top_width"
"custom_portrait_top_height"
"custom_portrait_bottom_x"
"custom_portrait_bottom_y"
"custom_portrait_bottom_width"
"custom_portrait_bottom_height"
"bg_red"
"bg_green"
"bg_blue"
"render_3d"
"factor_3d"
"swap_eyes_3d"
"render_3d_which_display"
"mono_render_option"
"cardboard_screen_size"
"cardboard_x_shift"
"cardboard_y_shift"
"filter_mode"
"pp_shader_name"
"anaglyph_shader_name"
"dump_textures"
"custom_textures"
"preload_textures"
"async_custom_loading"
"disable_right_eye_render"
"audio_emulation"
"enable_audio_stretching"
"enable_realtime_audio"
"volume"
"output_type"
"output_device"
"input_type"
"input_device"
"simulate_headphones_plugged"
"delay_start_for_lle_modules"
"use_gdbstub"
"gdbstub_port"
"instant_debug_log"
"enable_rpc_server"
"log_filter"
"log_regex_filter"
"toggle_unique_data_console_type"
"break_on_unmapped_memory_access"
"use_integer_scaling"
"layouts_to_cycle"
"camera_inner_flip"
"camera_outer_left_flip"
"camera_outer_right_flip"
"camera_inner_name"
"camera_inner_config"
"camera_outer_left_name"
"camera_outer_left_config"
"camera_outer_right_name"
"camera_outer_right_config"
"video_encoder"
"video_encoder_options"
"video_bitrate"
"audio_encoder"
"audio_encoder_options"
"audio_bitrate"
"last_artic_base_addr"
"motion_device"
"touch_device"
"udp_input_address"
"udp_input_port"
"udp_pad_index"
"record_frame_times"
"language" # FIXME: DUPLICATE KEY (libretro equivalent: language_value)
"web_api_url"
"citra_username"
"citra_token"
)
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
if (ANDROID)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
endif()
endforeach()
# Qt exclusive setting keys
# Note: A lot of these are very generic because our Qt settings are currently put under groups:
# E.g. UILayout\geometry
# TODO: We should probably get rid of these groups and use complete keys at some point. -OS
# FIXME: Some of these settings don't use the standard snake_case. When we can migrate, address that. -OS
if (ENABLE_QT)
foreach(KEY IN ITEMS
"nickname"
"ip"
"port"
"room_nickname"
"room_name"
"room_port"
"host_type"
"max_player"
"room_description"
"multiplayer_filter_text"
"multiplayer_filter_games_owned"
"multiplayer_filter_hide_empty"
"multiplayer_filter_hide_full"
"username_ban_list"
"username"
"ip_ban_list"
"romsPath"
"symbolsPath"
"movieRecordPath"
"moviePlaybackPath"
"videoDumpingPath"
"gameListRootDir"
"gameListDeepScan"
"path"
"deep_scan"
"expanded"
"recentFiles"
"output_format"
"format_options"
"theme"
"program_id"
"geometry"
"state"
"geometryRenderWindow"
"gameListHeaderState"
"microProfileDialogGeometry"
"name"
"bind"
"profile"
"use_touchpad"
"controller_touch_device"
"use_touch_from_button"
"touch_from_button_map"
"touch_from_button_maps" # Why are these two so similar? Basically typo bait
"nand_directory"
"sdmc_directory"
"game_id"
"KeySeq"
"gamedirs"
"libvorbis"
"Context"
"favorites"
)
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
endforeach()
endif()
# Android exclusive setting keys (standalone app only, not Android libretro)
if (ANDROID)
foreach(KEY IN ITEMS
"expand_to_cutout_area"
"performance_overlay_enable"
"performance_overlay_show_fps"
"performance_overlay_show_frame_time"
"performance_overlay_show_speed"
"performance_overlay_show_app_ram_usage"
"performance_overlay_show_available_ram"
"performance_overlay_show_battery_temp"
"performance_overlay_background"
"use_frame_limit" # FIXME: DUPLICATE KEY (shared equivalent: frame_limit)
"android_hide_images"
"screen_orientation"
"performance_overlay_position"
"enable_secondary_display"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
endforeach()
endif()
# Libretro exclusive setting keys
if (ENABLE_LIBRETRO)
foreach(KEY IN ITEMS
"language_value"
"swap_screen_mode"
"use_libretro_save_path"
"analog_function"
"analog_deadzone"
"enable_mouse_touchscreen"
"enable_touch_touchscreen"
"enable_touch_pointer_timeout"
"enable_motion"
"motion_sensitivity"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
endforeach()
endif()
# Trim trailing comma and newline from SETTING_KEY_LIST
string(LENGTH "${SETTING_KEY_LIST}" SETTING_KEY_LIST_LENGTH)
math(EXPR SETTING_KEY_LIST_NEW_LENGTH "${SETTING_KEY_LIST_LENGTH} - 1")
string(SUBSTRING "${SETTING_KEY_LIST}" 0 ${SETTING_KEY_LIST_NEW_LENGTH} SETTING_KEY_LIST)
# Configure files
configure_file("common/setting_keys.h.in" "common/setting_keys.h" @ONLY)
if (ENABLE_QT)
configure_file("citra_qt/setting_qkeys.h.in" "citra_qt/setting_qkeys.h" @ONLY)
endif()
if (ANDROID AND NOT ENABLE_LIBRETRO)
configure_file("android/app/src/main/jni/jni_setting_keys.cpp.in" "android/app/src/main/jni/jni_setting_keys.cpp" @ONLY)
endif()

1
CONTRIBUTING.md Normal file
View file

@ -0,0 +1 @@
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**

View file

@ -1,8 +1,6 @@
![Azahar Emulator](https://azahar-emu.org/resources/images/logo/azahar-name-and-logo.svg)
![Current Release](https://img.shields.io/github/v/release/azahar-emu/azahar?label=Current%20Release)
![Current Prerelease](https://img.shields.io/github/v/release/azahar-emu/azahar?include_prereleases&label=Current%20Prerelease)
![GitHub Release](https://img.shields.io/github/v/release/azahar-emu/azahar?label=Current%20Release)
![GitHub Downloads](https://img.shields.io/github/downloads/azahar-emu/azahar/total?logo=github&label=GitHub%20Downloads)
![Google Play Downloads](https://playbadges.pavi2410.com/badge/downloads?id=io.github.lime3ds.android&pretty&label=Play%20Store%20Downloads)
![Flathub Downloads](https://img.shields.io/flathub/downloads/org.azahar_emu.Azahar?logo=flathub&label=Flathub%20Downloads)

View file

@ -35,7 +35,6 @@
<string>cci</string>
<string>cxi</string>
<string>cia</string>
<string>3ds</string>
</array>
<key>CFBundleTypeName</key>
<string>Nintendo 3DS File</string>

@ -1 +1 @@
Subproject commit d9f1126e42b606d02ecc89b10cb9a336a3b2f5a3
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f

View file

@ -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 = ca@valencia:b+ca+ES+valencia, es_419:b+es+419, pt_BR:b+pt+BR, zh_CN:b+zh+CN, zh_TW:b+zh+TW
lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1594
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

2505
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

7918
dist/languages/es_419.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1422
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

1570
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1424
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

1525
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1898
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

1428
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1481
dist/languages/pt_BR.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1457
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1452
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load diff

1426
dist/languages/zh_TW.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,6 @@
<expanded-acronym>CTR Cart Image</expanded-acronym>
<icon name="azahar"/>
<glob pattern="*.cci"/>
<glob pattern="*.3ds"/>
<magic><match value="NCSD" type="string" offset="256"/></magic>
</mime-type>

View file

@ -4,19 +4,23 @@
# --- Builder ----------------
FROM opensauce04/azahar-build-environment:latest AS builder
RUN mkdir /var/azahar-src/
COPY ./ /var/azahar-src/
COPY . /var/azahar-src
RUN mkdir ./builddir/
WORKDIR ./builddir/
RUN cmake /var/azahar-src -G Ninja \
RUN mkdir builddir && cd builddir && \
cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON
RUN ninja
RUN mv ./bin/Release/azahar-room /usr/local/bin/
-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
# --- Final ------------------
FROM debian:trixie AS final

View file

@ -50,18 +50,15 @@ else()
endif()
# Catch2
if (ENABLE_TESTS)
add_library(catch2 INTERFACE)
if(USE_SYSTEM_CATCH2)
find_package(Catch2 3.0.0 REQUIRED)
else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
add_library(catch2 INTERFACE)
if(USE_SYSTEM_CATCH2)
find_package(Catch2 3.0.0 REQUIRED)
else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
# Crypto++
if(USE_SYSTEM_CRYPTOPP)
@ -69,9 +66,17 @@ 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 "")
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake)
endif()
# dds-ktx
@ -104,13 +109,7 @@ endif()
# Oaknut
if ("arm64" IN_LIST ARCHITECTURE)
if(USE_SYSTEM_OAKNUT)
find_package(oaknut REQUIRED)
add_library(oaknut INTERFACE)
target_link_libraries(oaknut INTERFACE merry::oaknut)
else()
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif()
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif()
# Dynarmic
@ -134,7 +133,7 @@ endif()
# getopt
if (MSVC)
add_subdirectory(getopt EXCLUDE_FROM_ALL)
add_subdirectory(getopt)
endif()
# inih
@ -143,7 +142,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir)
else()
add_subdirectory(inih EXCLUDE_FROM_ALL)
add_subdirectory(inih)
endif()
# MicroProfile
@ -166,7 +165,7 @@ if (NOT MSVC)
endif()
# Open Source Archives
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
add_subdirectory(open_source_archives)
# faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -205,12 +204,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
add_subdirectory(sdl2)
endif()
# libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
add_subdirectory(libusb EXCLUDE_FROM_ALL)
add_subdirectory(libusb)
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif()
@ -254,7 +253,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet)
else()
add_subdirectory(enet EXCLUDE_FROM_ALL)
add_subdirectory(enet)
target_include_directories(enet INTERFACE ./enet/include)
endif()
@ -276,7 +275,7 @@ if (ENABLE_CUBEB)
endif()
# DiscordRPC
if (ENABLE_DISCORD_RPC)
if (USE_DISCORD_PRESENCE)
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN)
@ -293,15 +292,6 @@ if (ENABLE_DISCORD_RPC)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()
# LibRetro
if (ENABLE_LIBRETRO)
add_library(libretro INTERFACE)
target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include)
if (ANDROID)
add_subdirectory(libretro-common EXCLUDE_FROM_ALL)
endif()
endif()
# JSON
add_library(json-headers INTERFACE)
if (USE_SYSTEM_JSON)
@ -319,8 +309,12 @@ endif()
# OpenSSL
if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
else()
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/")
@ -362,7 +356,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE)
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
add_subdirectory(gamemode)
endif()
# cpp-jwt
@ -385,13 +379,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else()
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
add_subdirectory(lodepng)
endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID)
# libyuv
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
add_subdirectory(libyuv)
target_include_directories(yuv INTERFACE ./libyuv/include)
endif()
@ -402,9 +396,6 @@ 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 "")
@ -420,7 +411,7 @@ endif()
# OpenGL dependencies
if (ENABLE_OPENGL)
# Glad
add_subdirectory(glad EXCLUDE_FROM_ALL)
add_subdirectory(glad)
endif()
# Vulkan dependencies
@ -460,7 +451,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 EXCLUDE_FROM_ALL)
add_subdirectory(glslang)
endif()
# sirit
@ -491,24 +482,11 @@ if (ENABLE_VULKAN)
else()
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
target_disable_warnings(vulkan-headers)
if (BSD STREQUAL "NetBSD")
# There may be a better way to do this with
# find_package(X11), but I couldn't get
# CMake to do it, so we're depending on
# the x11-links package and assuming the
# prefix location. -OS
target_include_directories(vulkan-headers INTERFACE
/usr/pkg/share/x11-links/include)
elseif (BSD STREQUAL "OpenBSD")
# This is fine to hardcode because it'll never change
target_include_directories(vulkan-headers INTERFACE
/usr/X11R6/include)
endif()
endif()
# adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
add_subdirectory(libadrenotools)
endif()
endif()
@ -524,4 +502,4 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|armv8")
else()
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SCALAR)
message(STATUS "Disabling SIMD for xxHash")
endif()
endif()

2
externals/boost vendored

@ -1 +1 @@
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca

View file

@ -14,7 +14,6 @@ option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
@ -41,7 +40,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
@ -68,7 +66,6 @@ set(LIB_VAR_LIST
DYNARMIC
FMT
XBYAK
OAKNUT
INIH
FFMPEG_HEADERS
GLSLANG

2
externals/cryptopp vendored

@ -1 +1 @@
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e

1
externals/cryptopp-cmake vendored Submodule

@ -0,0 +1 @@
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c

@ -1 +1 @@
Subproject commit 049826c3f42875c2f9599552f425e76435789cc7
Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a

1
externals/dllwalker vendored

@ -1 +0,0 @@
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e

View file

@ -1,16 +0,0 @@
add_library(libretro_common STATIC
libretro-common/compat/compat_posix_string.c
libretro-common/compat/fopen_utf8.c
libretro-common/encodings/encoding_utf.c
libretro-common/compat/compat_strl.c
libretro-common/file/file_path.c
libretro-common/streams/file_stream.c
libretro-common/streams/file_stream_transforms.c
libretro-common/string/stdstring.c
libretro-common/time/rtime.c
libretro-common/vfs/vfs_implementation.c
)
target_include_directories(libretro_common PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include
)

@ -1 +0,0 @@
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d

@ -1 +1 @@
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727

2
externals/sdl2/SDL vendored

@ -1 +1 @@
Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71
Subproject commit 2359383fc187386204c3bb22de89655a494cd128

View file

@ -1,8 +1,6 @@
# Enable modules to include each other's files
include_directories(.)
include(GenerateSettingKeys)
# CMake seems to only define _DEBUG on Windows
set_property(DIRECTORY APPEND PROPERTY
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
@ -112,14 +110,10 @@ else()
# In case a flag isn't supported on e.g. a certain architecture, don't error.
-Wno-unused-command-line-argument
# Build fortification options
-Wp,-D_GLIBCXX_ASSERTIONS
-fstack-protector-strong
-fstack-clash-protection
)
if (NOT ENABLE_LIBRETRO)
add_compile_options(
-Wp,-D_GLIBCXX_ASSERTIONS
-fstack-clash-protection
)
endif()
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
@ -183,9 +177,6 @@ endif()
if(ENABLE_DEVELOPER_OPTIONS)
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
endif()
if(ENABLE_BUILTIN_KEYBLOB)
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
endif()
add_subdirectory(common)
add_subdirectory(core)
@ -198,19 +189,18 @@ if (ENABLE_TESTS)
add_subdirectory(tests)
endif()
if (ENABLE_SDL2_FRONTEND)
add_subdirectory(citra_sdl)
endif()
if (ENABLE_QT)
add_subdirectory(citra_qt)
endif()
if (ENABLE_QT) # Or any other hypothetical future frontends
add_subdirectory(citra_cli)
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
add_subdirectory(citra_meta)
endif()
if (ENABLE_LIBRETRO)
add_subdirectory(citra_libretro)
endif()
if (ENABLE_ROOM)
add_subdirectory(citra_room)
endif()
@ -219,7 +209,7 @@ if (ENABLE_ROOM_STANDALONE)
add_subdirectory(citra_room_standalone)
endif()
if (ANDROID AND NOT ENABLE_LIBRETRO)
if (ANDROID)
add_subdirectory(android/app/src/main/jni)
target_include_directories(citra-android PRIVATE android/app/src/main)
endif()

View file

@ -1,20 +0,0 @@
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

View file

@ -34,8 +34,7 @@ captures/
# IntelliJ
*.iml
.idea/*
!.idea/inspectionProfiles
.idea/
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.

View file

@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<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>

View file

@ -12,7 +12,6 @@ plugins {
id("kotlin-parcelize")
kotlin("plugin.serialization") version "2.0.20"
id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint")
}
/**
@ -25,11 +24,12 @@ 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.3.13750724"
ndkVersion = "27.1.12297006"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
@ -63,7 +63,7 @@ android {
defaultConfig {
// The application ID refers to Lime3DS to allow for
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
applicationId = "org.azahar_emu.azahar"
applicationId = "io.github.lime3ds.android"
minSdk = 29
targetSdk = 35
versionCode = autoVersion
@ -80,9 +80,7 @@ android {
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page
// sizes
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
)
}
}
@ -127,8 +125,7 @@ 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(
@ -139,10 +136,8 @@ 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 on these devices.
// 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.
register("relWithDebInfoLite") {
initWith(getByName("relWithDebInfo"))
signingConfig = signingConfigs.getByName("debug")
@ -152,7 +147,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.
}
}
@ -178,7 +173,6 @@ android {
register("googlePlay") {
dimension = "version"
versionNameSuffix = "-googleplay"
applicationId = "io.github.lime3ds.android"
}
}
@ -220,9 +214,7 @@ 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)
}
@ -242,11 +234,6 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
tasks.named("preBuild") {
dependsOn(unzipVulkanValidationLayers)
dependsOn("ktlintCheck")
}
ktlint {
version = "1.8.0"
}
fun getGitVersion(): String {
@ -278,7 +265,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()
@ -304,7 +291,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)
@ -318,5 +305,5 @@ android.applicationVariants.configureEach {
}
}
}
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
}

View file

@ -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.GraphicsUtil
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler
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,7 +69,6 @@ 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 {

View file

@ -19,22 +19,19 @@ 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
@ -44,7 +41,7 @@ object NativeLibrary {
/**
* Default touchscreen device
*/
const val TOUCHSCREEN_DEVICE = "Touchscreen"
const val TouchScreenDevice = "Touchscreen"
@JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@ -135,24 +132,7 @@ object NativeLibrary {
* If not set, it auto-detects a location
*/
external fun setUserDirectory(directory: String)
data class InstalledGame(val path: String, val mediaType: Game.MediaType)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
return games.mapNotNull { entry ->
entry?.let {
val sep = it.lastIndexOf('|')
if (sep == -1) return@mapNotNull null
val path = it.substring(0, sep)
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
InstalledGame(path, mediaType!!)
}
}.toTypedArray()
}
private external fun getInstalledGamePathsImpl(): Array<String?>
external fun getInstalledGamePaths(): Array<String?>
// Create the config.ini file.
external fun createConfigFile()
@ -250,15 +230,6 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
uninstallTitle(titleId, mediaType.value)
external fun nativeFileExists(path: String): Boolean
external fun deleteOpenGLShaderCache(titleId: Long)
external fun deleteVulkanShaderCache(titleId: Long)
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()
@ -315,12 +286,6 @@ 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)
@ -345,12 +310,11 @@ object NativeLibrary {
return coreErrorAlertResult
}
@Keep
@JvmStatic
fun isPortraitMode(): Boolean = (
CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
)
@get:Keep
@get:JvmStatic
val isPortraitMode: Boolean
get() = CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
@Keep
@JvmStatic
@ -444,7 +408,7 @@ object NativeLibrary {
return
}
if (resultCode == CoreError.ShutdownRequested.value) {
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
emulationActivity.finish()
return
}
@ -463,58 +427,24 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity
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
)
}
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 alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setTitle(captionId)
.setMessage(
Html.fromHtml(
message,
Html.FROM_HTML_MODE_LEGACY
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
)
)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
@ -535,6 +465,20 @@ object NativeLibrary {
const val RESULT_CODE = "resultcode"
const val Success = 0
const val ErrorNotInitialized = 1
const val ErrorGetLoader = 2
const val ErrorSystemMode = 3
const val ErrorLoader = 4
const val ErrorLoader_ErrorEncrypted = 5
const val ErrorLoader_ErrorInvalidFormat = 6
const val ErrorLoader_ErrorGBATitle = 7
const val ErrorSystemFiles = 8
const val ErrorSavestate = 9
const val ErrorArticDisconnected = 10
const val ShutdownRequested = 11
const val ErrorUnknown = 12
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
val args = Bundle()
args.putInt(RESULT_CODE, resultCode)
@ -645,7 +589,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
}
@ -683,17 +627,19 @@ object NativeLibrary {
// Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
compressFileNative(inputPath, outputPath)
)
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath)
)
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@ -727,76 +673,62 @@ 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
fun getNativePath(uri: Uri): String {
fun getUserDirectory(uriOverride: Uri? = null): String {
BuildUtil.assertNotGooglePlay()
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val dirSep = "/"
val udUri = uriOverride ?:
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
val udPathSegment = udUri.lastPathSegment!!
val udVirtualPath = udPathSegment.substringAfter(":")
val uriString = uri.toString()
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
return uriString
}
if (uri.scheme == "file") {
return uri.path!!
}
val pathSegment = uri.lastPathSegment ?: return ""
val virtualPath = pathSegment.substringAfter(":")
if (pathSegment.startsWith("primary:")) { // User directory is located in primary storage
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
return primaryStoragePath + dirSep + virtualPath
return primaryStoragePath + dirSep + udVirtualPath + dirSep
} else { // User directory probably located on a removable storage device
val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
CitraApplication.appContext,
storageIdString
)
val storageIdString = udPathSegment.substringBefore(":")
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
if (removablePath == null) {
android.util.Log.e(
"NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
if (udRemovablePath == null) {
android.util.Log.e("NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
)
return ""
}
return removablePath + dirSep + virtualPath
return udRemovablePath + dirSep + udVirtualPath + dirSep
}
}
@Keep
@JvmStatic
fun getUserDirectory(): String {
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
return getNativePath(userDirectoryUri)
}
@Keep
@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
@ -804,23 +736,21 @@ object NativeLibrary {
@Keep
@JvmStatic
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
fun fileExists(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path)
} else {
FileUtil.exists(path)
}
@Keep
@JvmStatic
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)
}
fun isDirectory(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path)
} else {
FileUtil.isDirectory(path)
}
@Keep
@JvmStatic
@ -828,18 +758,19 @@ 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
@ -874,35 +805,19 @@ object NativeLibrary {
@Keep
@JvmStatic
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path)
} else {
FileUtil.deleteDocument(path)
}
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
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 InstallStatus {
@ -953,11 +868,7 @@ 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)

View file

@ -8,9 +8,9 @@ import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
@ -21,7 +21,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.core.os.BundleCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@ -31,7 +30,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
@ -44,11 +43,10 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.fragments.EmulationFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
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 +62,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility
lateinit var secondaryDisplayManager: SecondaryDisplay
private lateinit var secondaryDisplay: SecondaryDisplay
private val onShutdown = Runnable {
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
@ -81,9 +79,7 @@ class EmulationActivity : AppCompatActivity() {
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
}
private var isRotationBlocked: Boolean = true
private var isEmulationRunning: Boolean = false
private var isEmulationReady: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -92,20 +88,12 @@ class EmulationActivity : AppCompatActivity() {
ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings()
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
// Block orientation until emulation is ready to prevent unneccesary
// surface recreation until the renderer is ready.
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
super.onCreate(savedInstanceState)
secondaryDisplayManager = SecondaryDisplay(this)
secondaryDisplayManager.updateDisplay()
secondaryDisplay = SecondaryDisplay(this)
secondaryDisplay.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root)
@ -130,6 +118,8 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true
instance = this
applyOrientationSettings() // Check for orientation settings at startup
val game = try {
intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
@ -145,76 +135,39 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStart(game.titleId)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
NativeLibrary.stopEmulation()
NativeLibrary.playTimeManagerStop()
isEmulationReady = false
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
emulationViewModel.setEmulationStarted(false)
val game = intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
}
if (game != null) {
NativeLibrary.playTimeManagerStart(game.titleId)
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
}
// On some devices, the system bars will not disappear on first boot or after some
// rotations. Here we set full screen immersive repeatedly in onResume and in
// onWindowFocusChanged to prevent the unwanted status bar state.
override fun onResume() {
enableFullscreenImmersive()
if (isEmulationReady) {
// If emulation is ready then unblock rotation
isRotationBlocked = false
applyOrientationSettings()
emulationViewModel.setEmulationStarted(true)
} else {
if (!isRotationBlocked) {
applyOrientationSettings()
}
}
super.onResume()
enableFullscreenImmersive()
applyOrientationSettings() // Check for orientation settings changes on runtime
}
override fun onStop() {
secondaryDisplayManager.releasePresentation()
secondaryDisplay.releasePresentation()
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
enableFullscreenImmersive()
super.onWindowFocusChanged(hasFocus)
enableFullscreenImmersive()
}
public override fun onRestart() {
super.onRestart()
secondaryDisplayManager.updateDisplay()
secondaryDisplay.updateDisplay()
NativeLibrary.reloadCameraDevices()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean("isEmulationRunning", isEmulationRunning)
outState.putBoolean("isEmulationReady", isEmulationReady)
outState.putBoolean("isRotationBlocked", isRotationBlocked)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
}
override fun onDestroy() {
@ -222,8 +175,8 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStop()
isEmulationRunning = false
instance = null
secondaryDisplayManager.releasePresentation()
secondaryDisplayManager.releaseVD()
secondaryDisplay.releasePresentation()
secondaryDisplay.releaseVD()
super.onDestroy()
}
@ -268,11 +221,6 @@ class EmulationActivity : AppCompatActivity() {
fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true)
isEmulationReady = true
if (isRotationBlocked) {
isRotationBlocked = false
applyOrientationSettings()
}
Toast.makeText(
applicationContext,
getString(R.string.emulation_menu_help),
@ -319,36 +267,41 @@ class EmulationActivity : AppCompatActivity() {
return super.dispatchKeyEvent(event)
}
when (event.action) {
val button =
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> {
hotkeyUtility.handleHotkey(button)
// On some devices, the back gesture / button press is not intercepted by androidx
// and fails to open the emulation menu. So we're stuck running deprecated code to
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
// If the hotkey is pressed, we don't want to open the drawer
if (!hotkeyUtility.hotkeyIsPressed) {
if (!hotkeyUtility.HotkeyIsPressed) {
onBackPressed()
return true
}
}
return hotkeyUtility.handleKeyPress(event)
// Normal key events.
NativeLibrary.ButtonState.PRESSED
}
KeyEvent.ACTION_UP -> {
return hotkeyUtility.handleKeyRelease(event)
}
else -> {
return false
hotkeyUtility.HotkeyIsPressed = false
NativeLibrary.ButtonState.RELEASED
}
else -> return false
}
val input = event.device
?: // Controller was disconnected
return false
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
}
private fun onAmiiboSelected(selectedFile: String) {
val success = NativeLibrary.loadAmiibo(selectedFile)
if (!success) {
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
MessageDialogFragment.newInstance(
R.string.amiibo_load_error,
R.string.amiibo_load_error_message
@ -360,8 +313,7 @@ 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)
}
@ -390,19 +342,16 @@ 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 -> {
@ -456,7 +405,7 @@ class EmulationActivity : AppCompatActivity() {
// Triggers L/R and ZL/ZR
if (isTriggerPressedLMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.TRIGGER_L,
if (isTriggerPressedL) {
NativeLibrary.ButtonState.PRESSED
@ -467,7 +416,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedRMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.TRIGGER_R,
if (isTriggerPressedR) {
NativeLibrary.ButtonState.PRESSED
@ -478,7 +427,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedZLMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.BUTTON_ZL,
if (isTriggerPressedZL) {
NativeLibrary.ButtonState.PRESSED
@ -489,7 +438,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedZRMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.BUTTON_ZR,
if (isTriggerPressedZR) {
NativeLibrary.ButtonState.PRESSED
@ -502,72 +451,72 @@ class EmulationActivity : AppCompatActivity() {
// Work-around to allow D-pad axis to be bound to emulated buttons
if (axisValuesDPad[0] == 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[0] < 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.PRESSED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[0] > 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.PRESSED
)
}
if (axisValuesDPad[1] == 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[1] < 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.PRESSED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[1] > 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.TouchScreenDevice,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.PRESSED
)
@ -575,21 +524,13 @@ class EmulationActivity : AppCompatActivity() {
return true
}
val openAmiiboFileLauncher =
val openFileLauncher =
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])
} else {
val fileUri = selectedFiles[0].toUri()
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
onAmiiboSelected(nativePath)
}
onAmiiboSelected(selectedFiles[0])
}
val openImageLauncher =
@ -598,12 +539,14 @@ class EmulationActivity : AppCompatActivity() {
return@registerForActivityResult
}
onFilePickerResult(result.toString())
OnFilePickerResult(result.toString())
}
companion object {
private var instance: EmulationActivity? = null
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
fun isRunning(): Boolean {
return instance?.isEmulationRunning ?: false
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra 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,11 +105,15 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
override fun areItemsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean = oldItem.first == newItem.first
): Boolean {
return oldItem.first == newItem.first
}
override fun areContentsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean = oldItem.second == newItem.second
): Boolean {
return oldItem.second == newItem.second
}
}
}

View file

@ -4,25 +4,24 @@
package org.citra.citra_emu.adapters
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.content.Intent
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.widget.ImageView
import android.widget.PopupMenu
import android.content.Context
import android.content.SharedPreferences
import android.widget.TextView
import android.widget.ImageView
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
@ -30,23 +29,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 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.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
@ -55,22 +54,18 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter(
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
@ -141,7 +136,7 @@ class GameAdapter(
val holder = view.tag as GameViewHolder
gameExists(holder)
if (!holder.game.valid) {
if (holder.game.titleId == 0L) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.properties)
.setMessage(R.string.properties_not_loaded)
@ -158,21 +153,12 @@ class GameAdapter(
if (holder.game.isInstalled) {
return true
}
val path = holder.game.path
val pathUri = path.toUri()
var gameExists: Boolean
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
gameExists =
DocumentFile.fromSingleUri(
CitraApplication.appContext,
pathUri
)?.exists() == true
} else {
val nativePath = NativeLibrary.getNativePath(pathUri)
gameExists = NativeLibrary.nativeFileExists(nativePath)
}
val gameExists = DocumentFile.fromSingleUri(
CitraApplication.appContext,
Uri.parse(holder.game.path)
)?.exists() == true
return if (!gameExists) {
Log.error("[GameAdapter] ROM file does not exist: $path")
Toast.makeText(
CitraApplication.appContext,
R.string.loader_error_file_not_found,
@ -214,18 +200,15 @@ 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 {
@ -265,45 +248,16 @@ 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')}"
)
}
@ -333,36 +287,13 @@ 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
}
@ -376,11 +307,7 @@ 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)
@ -396,45 +323,21 @@ class GameAdapter(
}
}
val titleId = game.titleId
val dlcTitleId = titleId or 0x8C00000000L
val updateTitleId = titleId or 0xE00000000L
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> 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 -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
.toString())
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
.toString())
}
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 {
@ -445,12 +348,7 @@ 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)
@ -462,22 +360,12 @@ 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) {
@ -518,7 +406,7 @@ class GameAdapter(
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Default to false for zoomed in shortcut icons
preferences.edit {
preferences.edit() {
putBoolean(
"shouldStretchIcon",
false
@ -550,15 +438,10 @@ 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 {
@ -566,11 +449,9 @@ 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)
@ -591,9 +472,7 @@ 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(
@ -606,90 +485,22 @@ 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))
var selectedIndex = -1
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_cache_select_backend)
.setSingleChoiceItems(options, -1) { dialog, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
Toast.LENGTH_LONG
)
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
}
activity.runOnUiThread {
progToast.cancel()
Toast.makeText(
CitraApplication.appContext,
R.string.shader_cache_deleted,
Toast.LENGTH_SHORT
).show()
}
}
}
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.create()
dialog.setOnShowListener {
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
positiveButton.isEnabled = false
val listView = dialog.listView
listView.setOnItemClickListener { _, _, position, _ ->
selectedIndex = position
positiveButton.isEnabled = true
}
}
dialog.show()
}
val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -738,16 +549,18 @@ class GameAdapter(
}
}
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }
private fun isValidGame(extension: String): Boolean {
return Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
// The title is taken into account to support 3DSX, which all have the titleID 0.
// This only works now because we always return the English title, adjust if that changes.
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
return oldItem.titleId == newItem.titleId
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,24 +9,27 @@ 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)
@ -34,7 +37,9 @@ class HomeSettingAdapter(
return HomeOptionViewHolder(binding)
}
override fun getItemCount(): Int = options.size
override fun getItemCount(): Int {
return options.size
}
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position])

View file

@ -8,17 +8,18 @@ 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>) :
@ -34,8 +35,7 @@ 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,9 +49,7 @@ 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(
@ -110,17 +108,9 @@ 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))
}
}
}

View file

@ -1,20 +1,20 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra 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)
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra 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,9 +69,8 @@ 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,
@ -79,23 +78,20 @@ 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,
@ -132,7 +128,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(

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -21,10 +21,9 @@ 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
@ -45,21 +44,19 @@ 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)
.size(width, height)
.allowHardware(false)
.build()
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
width,

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,10 +9,11 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
override fun createIntent(context: Context, input: Boolean?): Intent =
Intent(Intent.ACTION_OPEN_DOCUMENT)
override fun createIntent(context: Context, input: Boolean?): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT)
.setType("application/octet-stream")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
}

View file

@ -4,13 +4,13 @@
package org.citra.citra_emu.display
import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.app.Activity
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.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
@ -19,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
@ -31,20 +31,11 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
}
fun cycleLayouts() {
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty()) {
landscapeLayoutsToCycle.toIntArray()
} else {
context.resources.getIntArray(
R.array.landscapeValues
)
}
val landscapeValues = 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,32 +52,14 @@ 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())
}
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)
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeActivityOrientation(orientationOption: Int) {
@ -101,6 +74,7 @@ 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)
}
}

View file

@ -13,8 +13,11 @@ enum class ScreenLayout(val int: Int) {
HYBRID_SCREEN(4),
CUSTOM_LAYOUT(5);
companion object {
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
}
}
@ -29,7 +32,9 @@ enum class SmallScreenPosition(val int: Int) {
BELOW(7);
companion object {
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
fun from(int: Int): SmallScreenPosition {
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
}
}
@ -40,27 +45,23 @@ enum class PortraitScreenLayout(val int: Int) {
ORIGINAL(2);
companion object {
fun from(int: Int): PortraitScreenLayout =
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
fun from(int: Int): PortraitScreenLayout {
return 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),
REVERSE_PRIMARY(4),
ORIGINAL(5),
HYBRID(6),
LARGE_SCREEN(7)
;
SIDE_BY_SIDE(3);
companion object {
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
fun from(int: Int): SecondaryDisplayLayout {
return entries.firstOrNull { it.int == int } ?: NONE
}
}
}
@ -73,22 +74,26 @@ enum class StereoWhichDisplay(val int: Int) {
SECONDARY_ONLY(3);
companion object {
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
fun from(int: Int): StereoWhichDisplay {
return 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 = entries.firstOrNull { it.int == int } ?: OFF
fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
}
}
}

View file

@ -6,29 +6,24 @@ package org.citra.citra_emu.display
import android.app.Presentation
import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle
import android.view.Display
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import org.citra.citra_emu.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
import org.citra.citra_emu.display.SecondaryDisplayLayout
import org.citra.citra_emu.NativeLibrary
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(
@ -43,98 +38,48 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
}
fun updateSurface() {
val surface = pres?.getSurfaceHolder()?.surface
if (surface != null && surface.isValid) {
NativeLibrary.secondarySurfaceChanged(surface)
} else {
Log.warning("SecondaryDisplay Attempted to update null or invalid surface")
}
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
}
fun destroySurface() {
NativeLibrary.secondarySurfaceDestroyed()
}
private fun getSecondaryDisplays(): List<Display> {
private fun getExternalDisplay(context: Context): Display? {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
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 currentDisplayId = context.display.displayId
val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return displays.filter {
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
return displays.firstOrNull {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable =
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable &&
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
}
}
fun updateDisplay() {
// return early if the parent context is dead or dying
if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
return
}
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 }
// 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
}
// if our presentation is already on the right display, ignore
if (pres?.display == displayToUse) return
if (pres?.display == display) return
// otherwise, make a new presentation
releasePresentation()
try {
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
pres?.show()
}
// catch BadTokenException and InvalidDisplayException,
// the display became invalid asynchronously, so we can assign to null
// until onDisplayAdded/Removed/Changed is called and logic retriggered
catch (_: WindowManager.BadTokenException) {
pres = null
} catch (_: WindowManager.InvalidDisplayException) {
pres = null
}
pres = SecondaryDisplayPresentation(context, display!!, this)
pres?.show()
}
fun releasePresentation() {
try {
pres?.dismiss()
} catch (_: Exception) { }
pres?.dismiss()
pres = null
}
@ -155,9 +100,7 @@ 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
@ -175,21 +118,16 @@ 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()
}
})
@ -234,5 +172,7 @@ class SecondaryDisplayPresentation(
}
// Publicly accessible method to get the SurfaceHolder
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
fun getSurfaceHolder(): SurfaceHolder {
return surfaceView.holder
}
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra 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()
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -170,23 +170,24 @@ 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
}
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra 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

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -41,8 +41,7 @@ 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

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -32,9 +32,7 @@ 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
@ -159,15 +157,12 @@ class CheatsFragment :
}
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) {
@ -208,8 +203,7 @@ class CheatsFragment :
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
@ -237,16 +231,14 @@ class CheatsFragment :
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
}
}
)
})
}
}
}

View file

@ -11,6 +11,5 @@ enum class Hotkey(val button: Int) {
PAUSE_OR_RESUME(10004),
QUICKSAVE(10005),
QUICKLOAD(10006),
TURBO_LIMIT(10007),
ENABLE(10008)
TURBO_LIMIT(10007);
}

Some files were not shown because too many files have changed in this diff Show more