Compare commits

..

42 commits

Author SHA1 Message Date
OpenSauce04
8e8cb3d97a Updated compatibility list 2026-01-06 18:40:37 +00:00
OpenSauce04
d25eacdf83 RemovableStorageHelper.kt: Add /storage/ as external storage mount path 2026-01-06 18:39:18 +00:00
OpenSauce04
e3b04fe22e android_storage: No longer always use full rename+move process
This was intentionally included for testing, as per the comments.
2026-01-05 13:49:49 +00:00
OpenSauce
7a436ca7e8 renderer_vulkan: Fix incorrect MaxTexelBufferElements return type (#1563) 2026-01-03 00:03:22 +00:00
Chance
a63d6a3834 Fix simple typo that prevented debug builds (MSVC) 2025-12-31 20:56:00 +00:00
OpenSauce
d246f5b436 android: Fix rename + move behaviour for Google Play build (#1545)
* android: Fix rename + move behaviour for Google Play build

* android_storage: Handle rename/move with same source and destination
2025-12-29 17:58:31 +00:00
OpenSauce04
9a926e44c1 ci: Only build Android googleplay target for tags
I couldn't find any method of entirely skipping a single target in a matrix, so skipping each job is the next best thing.
2025-12-29 17:19:54 +00:00
OpenSauce04
f906242a23 ci: Added build job for Android Google Play flavor 2025-12-29 17:19:54 +00:00
OpenSauce04
33e7ed5c5c android: Implemented googlePlay build variants 2025-12-29 17:19:54 +00:00
OpenSauce04
f911af4197 MainActivity.kt: Added missing import 2025-12-24 19:02:06 +00:00
OpenSauce
37dd01fd51 android: Begin migration to raw fs access, starting with Rename reimplementation (#1511)
* Add setup step to grant `MANAGE_EXTERNAL_STORAGE`

* WIP re-implementation of Android `Rename` using native filesystem manipulation

* Applied clang-format and updated license headers

* Support user directories on removable storage devices

* If `MANAGE_EXTERNAL_STORAGE` is lost, re-request it

* Fix missing permission dialog appearing during initial setup

* Added empty code branches to prep for old Android support

* Fixed permission setup completion not accounting for external storage permission

* Implement code for Android <11

* Fixed emulation error if a renamed file is then opened for R/W

* Detect if the current user directory cannot be located, and prompt re-selection

* If an invalid user directory is selected, reject and re-prompt
2025-12-24 12:21:15 +00:00
OpenSauce04
ba5215242f macos: Fixed real camera not activating during emulation
This was due to a plugin which is required for handling camera permissions being missing
2025-12-23 23:28:20 +00:00
OpenSauce04
8e4d4efce4 Updated Linux section of readme with Wayland information 2025-12-22 12:40:50 +00:00
OpenSauce04
14dccb3c47 ci: Split AppImage build into two, with and without Wayland support 2025-12-22 12:40:50 +00:00
OpenSauce04
5431705e79 android: Upgrade AGP to 8.13.1 2025-12-09 23:05:49 +00:00
OpenSauce04
633ae42fbb video_core: Fixed occasional launch crash on certain platforms due to unsafe SDL_Init 2025-12-09 23:02:41 +00:00
OpenSauce04
0ed6b7558e android: Added relWithDebInfoLite build profile 2025-12-09 23:02:27 +00:00
OpenSauce04
bf1a95f438 cmake: Fixed AppImage build failure caused by upstream changes to linuxdeploy
Also improved error message when linuxdeploy run fails
2025-12-09 23:02:19 +00:00
David Griswold
01dc2bb776 android: Add Display Listener methods for smoother secondary display updates 2025-10-03 18:05:22 +01:00
OpenSauce04
4ac3cab012 externals: Updated fmt to 12.0.0
This fixes a build failure with Clang 21
2025-10-03 16:28:27 +01:00
huesos_96
897447e9bd Android: Dual screen fixes for Handhelds that have 2 screens like Ayaneo Pocket DS (#1341)
* Prevent SecondaryDisplay from stealing focus

The SecondaryDisplay Activity was stealing focus from the main
Activity when it was launched.

Set the `FLAG_NOT_FOCUSABLE` and `FLAG_NOT_TOUCH_MODAL` window flags
to prevent the SecondaryDisplay from gaining focus.

* Implement touch controls for secondary display

This commit introduces touch input handling for the secondary display.

The following changes were made:
- Added `onSecondaryTouchEvent` and `onSecondaryTouchMoved` to `NativeLibrary.kt` and `native.cpp` to process touch events on the secondary display.
- Implemented `onTouchListener` in `SecondaryDisplay.kt` to capture touch events and forward them to the native layer.
- Handles `ACTION_DOWN`, `ACTION_POINTER_DOWN`, `ACTION_MOVE`, `ACTION_UP`, `ACTION_POINTER_UP`, and `ACTION_CANCEL` motion events.
- Tracks the active pointer to ensure correct touch event handling.

* Refactor display logic for multi-display support

This commit introduces a `DisplayHelper` class to centralize display-related logic, particularly for handling scenarios where the application might be launched on an external display.

Key changes:
- Added `DisplayHelper.kt` to manage internal and external display identification based on launch conditions.
- `MainActivity` and `EmulationActivity` now use `DisplayHelper.checkLaunchDisplay()` to determine the initial display.
- `SecondaryDisplay` now uses `DisplayHelper.getExternalDisplay()` to correctly identify the target display for the secondary presentation.
- `InputOverlay` now queries `DisplayHelper.isBottomOnPrimary()` to determine if touch input should be processed for the primary display based on the current screen layout.
- `SecondaryDisplay` now queries `DisplayHelper.isBottomOnSecondary()` to conditionally pass touch events to the native layer based on which screen (primary or secondary) is currently displaying the 3DS bottom screen.

These changes ensure that the application behaves correctly when launched on either the internal or an external display, and that touch input is routed appropriately based on the user's chosen screen layout for the dual screens.

* Removed primary-screen checks so the input overlay always forwards touch events, ensuring all touches reach the native handler even when multiple displays are active

* Remove DisplayHelper class and adjust external display logic

* Formatting adjustments

---------

Co-authored-by: DavidRGriswold <novachild@gmail.com>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-10-03 14:46:05 +01:00
OpenSauce04
724576cc61 ci: Update all macOS runners to macOS 15 Sequoia 2025-10-03 14:18:27 +01:00
OpenSauce04
3e1b86548a cmake: Remove SYSTEM from target_link_libraries 2025-09-16 16:06:12 +01:00
OpenSauce04
246e06d1a4 vk_pipeline_cache: Fix directory creation failure if shaders/vulkan/ is missing 2025-09-13 01:20:32 +01:00
OpenSauce04
a607e3dd22 tools: Added Github cache purge script 2025-09-13 01:20:32 +01:00
OpenSauce04
a65114eabf Updated compatibility list 2025-09-05 22:22:32 +01:00
OpenSauce04
6ac0733002 tools: Updated guidance regarding translation updates 2025-09-05 21:56:32 +01:00
David Griswold
8519e92eae android: Re-fixed game termination bug (#1357)
* EmulationActivity and EmulationFragment clear only their own hooks

* EmulationLifecycleUtil: Rename `remove()` to `removeHook()`

* EmulationLifecycleUtil: Removed unused function `clear()`

* Corrected somewhat incorrect usage of the word "hook"

* Define `onShutdown` and `onPause` hook functions in constructors

* Formatting nitpicks

* Updated license header

* Re-added log messages for attempting to add duplicate hooks

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-09-05 21:40:05 +01:00
OpenSauce04
7f2ac35870 Revert "Fix android termination bug (#1354)"
This reverts commit 70f9379eef.
2025-09-05 21:40:05 +01:00
OpenSauce04
1e2dd5ea78 SecondaryDisplay.kt: Remove redundant SurfaceTexture, preventing log spam 2025-09-05 21:40:05 +01:00
David Griswold
beba099fed Fix android termination bug (#1354)
* move hook additions to onCreateView

* Updated license header

* Formatting nitpick

* Added prefix to log messages

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-09-04 22:59:51 +01:00
OpenSauce04
c888c40b3e macos: Set UIDesignRequiresCompatibility to true 2025-09-03 23:02:34 +01:00
OpenSauce04
57995cd89c android: Bump Vulkan Validation Layers to SDK 1.4.313.0 2025-09-03 22:31:26 +01:00
DavidRGriswold
29a77b342b android: Prevent crash when editing a slider option with an out of bounds value
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-09-03 13:18:43 +01:00
OpenSauce04
3ef5bc0bfe macos: Patch QMetalLayer.setNeedsDisplayInRect at runtime to avoid freezing on recent Qt 2025-09-03 03:16:00 +01:00
OpenSauce04
d94657a44d cmake: On Windows, download MSVC 2022 Qt versions instead of MSVC 2019 2025-09-03 03:16:00 +01:00
OpenSauce04
ee58988897 cmake: Bump downloaded Qt version to 6.9.2
Also bumps aqtinstall to 3.3.0
2025-09-03 03:16:00 +01:00
OpenSauce04
ec7f00c9a4 cmake: Added check for minimum AppleClang version 2025-09-02 14:06:53 +01:00
OpenSauce04
164b9329c7 cmake: Corrected widespread incorrect usage of the SYSTEM property 2025-09-01 00:43:01 +01:00
OpenSauce04
2292f3ab1b Updated translations via Transifex 2025-08-20 13:57:05 +01:00
PabloMK7
3ab6a304cd am: fix save data being deleted on CIA install failure (#1319) 2025-08-20 13:51:06 +01:00
OpenSauce04
2e3d926dd5 Updated language translations via Transifex 2025-08-15 17:47:35 +01:00
575 changed files with 28910 additions and 67214 deletions

View file

@ -1,20 +0,0 @@
#!/bin/bash -ex
GITREV="`git show -s --format='%h'`" || true
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
TAG_NAME=$GITHUB_REF_NAME
elif [[ -n $GITREV ]]; then
TAG_NAME=$GITREV
else
TAG_NAME=unknown
fi
echo "Tag name is: $TAG_NAME"
docker build --no-cache -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

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 #!/bin/bash -ex
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then if [[ "$TARGET" == "appimage"* ]]; then
# Compile the AppImage we distribute with Clang. # Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang -DCMAKE_C_COMPILER=clang

View file

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

View file

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

View file

@ -1,26 +0,0 @@
#!/bin/bash -ex
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
# It works fine when using the image locally.
export PATH="/mxe/usr/bin:${PATH}"
mkdir build && cd build
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
x86_64-w64-mingw32.shared-cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DUSE_SYSTEM_BOOST=ON \
-DUSE_SYSTEM_CRYPTOPP=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
make bundle
ccache -s -v

View file

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

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,27 @@ on:
pull_request: pull_request:
branches: [ master ] branches: [ master ]
permissions:
id-token: write
contents: read
attestations: write
jobs: jobs:
source: source:
if: ${{ !github.head_ref }} if: ${{ !github.head_ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Pack - name: Pack
run: ./.ci/source.sh run: ./.ci/source.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: ./
format: spdx-json
output-file: artifacts/source.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: source name: source
path: artifacts/ path: artifacts/
- name: Attest artifacts linux:
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.tar.xz
sbom-path: artifacts/source.spdx.json
linux-x86_64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
target: ["appimage", "appimage-wayland", "gcc-nopch"] target: ["appimage", "appimage-wayland", "fresh"]
container: container:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
@ -58,159 +37,112 @@ jobs:
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }} uses: actions/cache@v4
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ github.job }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Build - name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory - name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }} if: ${{ contains(matrix.target, 'appimage') }}
run: | run: |
mkdir -p artifacts mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/ mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage - name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }} if: ${{ matrix.target == 'appimage-wayland' }}
run: | run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Generate SBOM
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }} if: ${{ contains(matrix.target, 'appimage') }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ github.job }}-${{ matrix.target }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
- name: Attest artifacts macos:
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }} runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-15' }}
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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
target: ["clang", "gcc-nopch"] target: ["x86_64", "arm64"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
env: env:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
OS: linux OS: macos
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ github.job }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Build
run: ./.ci/linux.sh
macos:
runs-on: 'macos-26'
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-
- name: Install tools - name: Install tools
run: brew install ccache ninja spirv-tools run: brew install ccache ninja spirv-tools
- name: Build (x86_64) - name: Build
run: BUILD_ARCH=x86_64 ./.ci/macos.sh run: ./.ci/macos.sh
- name: Build (arm64) - name: Prepare outputs for caching
run: BUILD_ARCH=arm64 ./.ci/macos.sh run: cp -R build/bundle $OS-$TARGET
- name: Cache outputs for universal build
uses: actions/cache/save@v4
with:
path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos-universal:
runs-on: macos-15
needs: macos
env:
OS: macos
TARGET: universal
steps:
- uses: actions/checkout@v4
- name: Download x86_64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Download ARM64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Create universal app - name: Create universal app
run: ./.ci/macos-universal.sh run: ./.ci/macos-universal.sh
- name: Prepare for packing
run: |
mkdir build/bundle
cp -r build/x86_64/bundle build/bundle/x86_64
cp -r build/arm64/bundle build/bundle/arm64
cp -r build/universal/bundle build/bundle/universal
- name: Pack
env: env:
PACK_INDIVIDUALLY: 1 ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
- name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/macos.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
sbom-path: artifacts/macos.spdx.json
windows: windows:
runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: target: ["msvc", "msys2"]
- target: msvc
os: windows-latest
- target: msys2
os: windows-latest
- target: mxe
os: ubuntu-latest
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
defaults: defaults:
run: run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0} shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -218,16 +150,14 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: windows OS: windows
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }} uses: actions/cache@v4
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -235,7 +165,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC - name: Set up MSVC
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
uses: azahar-emu/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
- name: Install extra tools (MSVC) - name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget run: choco install ccache ninja ptime wget
@ -256,63 +186,34 @@ jobs:
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2) - name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }} if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v4 uses: crazy-max/ghaction-chocolatey@v3
with: with:
args: install ptime wget args: install ptime wget
- name: Install NSIS - name: Install NSIS
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }} if: ${{ github.ref_type == 'tag' }}
run: | run: |
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
ptime D:/a/_temp/nsis-setup.exe /S ptime D:/a/_temp/nsis-setup.exe /S
shell: pwsh shell: pwsh
- name: Disable line ending translation - name: Disable line ending translation
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Build (Native) - name: Build
if: ${{ matrix.target != 'mxe' }}
run: ./.ci/windows.sh run: ./.ci/windows.sh
- name: Build (MXE) - name: Generate installer
if: ${{ matrix.target == 'mxe' }} if: ${{ github.ref_type == 'tag' }}
run: ./.ci/mxe.sh
- name: Generate installer (Native)
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: | run: |
cd src\installer cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi "C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir ..\..\artifacts 2> NUL mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\ move /y *.exe ..\..\artifacts\
shell: cmd shell: cmd
- name: Generate installer (MXE)
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
run: |
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
cd src/installer
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir -p ../../artifacts
mv ./*.exe ../../artifacts/
- name: Pack - name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
artifacts/*.exe
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
android: android:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -323,18 +224,17 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: android OS: android
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }} SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v5 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -372,73 +272,9 @@ jobs:
working-directory: src/android/app working-directory: src/android/app
env: env:
UNPACKED: 1 UNPACKED: 1
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: src/android
format: spdx-json
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload - name: Upload
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/ path: src/android/app/artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
src/android/app/artifacts/*.apk
src/android/app/artifacts/*.aab
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
docker:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
os: ubuntu-24.04
- target: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
container:
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Fix git ownership
run: git config --global --add safe.directory .
- name: Build Docker image
run: ./.ci/docker.sh
- name: Move Docker image to artifacts directory
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
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

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,7 +13,7 @@ jobs:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Build - name: Build

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

View file

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

View file

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

View file

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

10
.gitignore vendored
View file

@ -10,9 +10,6 @@ src/installer/*.exe
src/common/scm_rev.cpp src/common/scm_rev.cpp
.travis.descriptor.json .travis.descriptor.json
# Docker image files
*.dockerimage
# Project/editor files # Project/editor files
*.swp *.swp
*.kdev4 *.kdev4
@ -56,10 +53,3 @@ repo/
.ccache/ .ccache/
node_modules/ node_modules/
VULKAN_SDK/ VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG
# verify-release.sh downloads
verify/

20
.gitmodules vendored
View file

@ -55,12 +55,18 @@
[submodule "sdl2"] [submodule "sdl2"]
path = externals/sdl2/SDL path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL url = https://github.com/libsdl-org/SDL
[submodule "cryptopp-cmake"]
path = externals/cryptopp-cmake
url = https://github.com/abdes/cryptopp-cmake.git
[submodule "cryptopp"]
path = externals/cryptopp
url = https://github.com/weidai11/cryptopp.git
[submodule "dds-ktx"] [submodule "dds-ktx"]
path = externals/dds-ktx path = externals/dds-ktx
url = https://github.com/septag/dds-ktx url = https://github.com/septag/dds-ktx
[submodule "openal-soft"] [submodule "openal-soft"]
path = externals/openal-soft path = externals/openal-soft
url = https://github.com/azahar-emu/openal-soft url = https://github.com/kcat/openal-soft
[submodule "glslang"] [submodule "glslang"]
path = externals/glslang path = externals/glslang
url = https://github.com/KhronosGroup/glslang url = https://github.com/KhronosGroup/glslang
@ -94,15 +100,3 @@
[submodule "spirv-headers"] [submodule "spirv-headers"]
path = externals/spirv-headers path = externals/spirv-headers
url = https://github.com/KhronosGroup/SPIRV-Headers url = https://github.com/KhronosGroup/SPIRV-Headers
[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) cmake_policy(SET CMP0127 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
# Prefer building bundled dependencies as static instead of shared
set(BUILD_SHARED_LIBS OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals) include(DownloadExternals)
include(CMakeDependentOption) include(CMakeDependentOption)
include(FindPkgConfig)
project(citra LANGUAGES C CXX ASM) 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") if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
endif() endif()
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
endif()
if (BSD STREQUAL "OpenBSD")
add_link_options(-z wxneeded)
endif()
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
# Some submodules like to pick their own default build type if not specified. # Some submodules like to pick their own default build type if not specified.
# Make sure we default to Release build type always, unless the generator has custom types. # 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) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif() endif()
if (APPLE AND NOT ENABLE_LIBRETRO) if (APPLE)
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out. # 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_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>") set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
@ -105,18 +90,8 @@ else()
set(DEFAULT_ENABLE_OPENGL ON) set(DEFAULT_ENABLE_OPENGL ON)
endif() 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) 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) option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
# Set bundled qt as dependent options. # Set bundled qt as dependent options.
@ -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_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON) option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON) option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
@ -139,8 +113,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
# NetBSD doesn't support Vulkan yet, remove this check when it does. option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
@ -148,10 +121,6 @@ option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON) 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 # Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -159,31 +128,6 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" 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 # Pass the following values to C++ land
if (ENABLE_QT) if (ENABLE_QT)
add_definitions(-DENABLE_QT) add_definitions(-DENABLE_QT)
@ -197,6 +141,9 @@ endif()
if (ENABLE_SDL2) if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2) add_definitions(-DENABLE_SDL2)
endif() endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")) if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
message(STATUS "SSE4.2 enabled for x86_64") message(STATUS "SSE4.2 enabled for x86_64")
@ -274,7 +221,7 @@ function(check_submodules_present)
foreach(module ${gitmodules}) foreach(module ${gitmodules})
string(REGEX REPLACE "path *= *" "" module ${module}) string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") 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") "Please run: git submodule update --init --recursive")
endif() endif()
endforeach() endforeach()
@ -351,9 +298,6 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
# set up output paths for executable binaries # set up output paths for executable binaries
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
if (ENABLE_LIBRETRO)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# System imported libraries # System imported libraries
# ====================== # ======================
@ -364,7 +308,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT) if (ENABLE_QT)
if (NOT USE_SYSTEM_QT) if (NOT USE_SYSTEM_QT)
download_qt(6.9.3) download_qt(6.9.2)
endif() endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
@ -411,21 +355,13 @@ if (APPLE)
endif() endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED) find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED) find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY}) set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO) if (ENABLE_VULKAN)
if (USE_SYSTEM_MOLTENVK) if (NOT USE_SYSTEM_MOLTENVK)
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
else()
download_moltenvk() download_moltenvk()
if (IOS)
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
else()
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
endif()
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
endif() endif()
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY}) set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif() endif()
@ -575,6 +511,8 @@ if (NOT ANDROID AND NOT IOS)
include(BundleTarget) include(BundleTarget)
if (ENABLE_QT) if (ENABLE_QT)
qt_bundle_target(citra_meta) qt_bundle_target(citra_meta)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(citra_meta)
endif() endif()
if (ENABLE_ROOM_STANDALONE) if (ENABLE_ROOM_STANDALONE)
bundle_target(citra_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. # On Linux, always bundle an AppImage.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IS_MINGW)
return()
endif()
if (IN_PLACE) if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.") message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif() endif()
@ -277,23 +273,15 @@ else()
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs. # On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (MINGW) add_custom_command(
add_custom_command( TARGET bundle
TARGET bundle COMMAND ${CMAKE_COMMAND}
# The target here is arbitrary "-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/" "-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
POST_BUILD) "-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
else() -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
add_custom_command( WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
TARGET bundle POST_BUILD)
COMMAND ${CMAKE_COMMAND}
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
POST_BUILD)
endif()
endif() endif()
endfunction() endfunction()
@ -305,11 +293,6 @@ else()
create_base_bundle_target() create_base_bundle_target()
endif() endif()
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
# We don't really need to "bundle" MXE builds, so don't do anything
return()
endif()
set(bundle_executable_path "$<TARGET_FILE:${target_name}>") set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (bundle_qt AND APPLE) if (bundle_qt AND APPLE)
# For Qt targets on Apple, expect an app bundle. # For Qt targets on Apple, expect an app bundle.
@ -348,7 +331,6 @@ else()
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\"" "-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}" "-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}" "-DIN_PLACE=${in_place}"
"-DIS_MINGW=${MINGW}"
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun" "-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

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,8 +171,15 @@ function(download_qt target)
endfunction() endfunction()
function(download_moltenvk) function(download_moltenvk)
if (IOS)
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
else()
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
endif()
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK") if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS ${MOLTENVK_TAR}) if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
${MOLTENVK_TAR} SHOW_PROGRESS) ${MOLTENVK_TAR} SHOW_PROGRESS)
@ -181,6 +188,10 @@ function(download_moltenvk)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif() endif()
# Add the MoltenVK library path to the prefix so find_library can locate it.
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction() endfunction()
function(get_external_prefix lib_name prefix_var) function(get_external_prefix lib_name prefix_var)

View file

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

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) 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) # The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core") set(VIDEO_CORE "${SRC_DIR}/src/video_core")
set(HASH_FILES set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp" "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h" "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
@ -12,10 +10,6 @@ set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h" "${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp" "${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h" "${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.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h" "${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp" "${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/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp" "${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h" "${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.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h" "${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp" "${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@ -48,4 +41,4 @@ foreach (F IN LISTS HASH_FILES)
set(COMBINED "${COMBINED}${TMP}") set(COMBINED "${COMBINED}${TMP}")
endforeach() endforeach()
string(MD5 SHADER_CACHE_VERSION "${COMBINED}") string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY) configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)

View file

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

1
CONTRIBUTING.md Normal file
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,11 +1,7 @@
![Azahar Emulator](https://azahar-emu.org/resources/images/logo/azahar-name-and-logo.svg) ![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) ![GitHub 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 Downloads](https://img.shields.io/github/downloads/azahar-emu/azahar/total?logo=github&label=GitHub%20Downloads) <!-- ![Flathub Downloads](https://img.shields.io/flathub/downloads/org.azahar-emu.azahar?logo=Flathub&label=Flathub%20Downloads) -->
![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)
![CI Build Status](https://github.com/azahar-emu/azahar/actions/workflows/build.yml/badge.svg) ![CI Build Status](https://github.com/azahar-emu/azahar/actions/workflows/build.yml/badge.svg)
<b>Azahar</b> is an open-source 3DS emulator project based on Citra. <b>Azahar</b> is an open-source 3DS emulator project based on Citra.
@ -18,14 +14,11 @@ The goal of this project is to be the de-facto platform for future development.
### Windows ### Windows
Azahar is available as both an installer and a zip archive. Download the latest release from [Releases](https://github.com/azahar-emu/azahar/releases).
Download the latest release in your preferred format from the [Releases](https://github.com/azahar-emu/azahar/releases) page. If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2.
If you are unsure of whether you want to use MSVC or MSYS2, use MSYS2.
--- ---
### MacOS ### MacOS
To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page. To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
@ -36,30 +29,23 @@ Alternatively, if you wish to download a build specifically for your Mac, you ca
- `macos-x86_64` for Intel Macs - `macos-x86_64` for Intel Macs
--- ---
### Android ### Android
The recommended method of downloading Azahar on Android is via the Google Play store:
There are two variants of Azahar available on Android, those being the Vanilla and Google Play builds.
The Vanilla build is technically superior, as it uses an alternative method of file management which is faster, but isn't permitted on the Google Play store.
For most users, we currently recommended downloading Azahar on Android via the Google Play Store for ease of accessibility:
<a href='https://play.google.com/store/apps/details?id=io.github.lime3ds.android'><img width='180' alt='Get it on Google Play' src='https://raw.githubusercontent.com/pioug/google-play-badges/06ccd9252af1501613da2ca28eaffe31307a4e6d/svg/English.svg'/></a> <a href='https://play.google.com/store/apps/details?id=io.github.lime3ds.android'><img width='180' alt='Get it on Google Play' src='https://raw.githubusercontent.com/pioug/google-play-badges/06ccd9252af1501613da2ca28eaffe31307a4e6d/svg/English.svg'/></a>
Alternatively, you can install the app using Obtainium, allowing you to use the Vanilla variant: Alternatively, you can install the app using Obtainium:
1. Download and install Obtainium from [here](https://github.com/ImranR98/Obtainium/releases) (use the file named `app-release.apk`) 1. Download and install Obtainium from [here](https://github.com/ImranR98/Obtainium/releases) (use the file named `app-release.apk`)
2. Open Obtainium and click 'Add App' 2. Open Obtainium and click 'Add App'
3. Type `https://github.com/azahar-emu/azahar` into the 'App Source URL' section 3. Type `https://github.com/azahar-emu/azahar` into the 'App Source URL' section
4. Click 'Add' 4. Click 'Add'
5. Click 'Install', and select the preferred variant 5. Click 'Install'
If you wish, you can also simply install the latest APK from the [Releases](https://github.com/azahar-emu/azahar/releases) page. If you wish, you can also simply install the latest APK from the [Releases](https://github.com/azahar-emu/azahar/releases) page.
Keep in mind that you will not recieve automatic updates when installing via the APK. Keep in mind that you will not recieve automatic updates when installing via the APK.
--- ---
### Linux ### Linux
The recommended format for using Azahar on Linux is the Flatpak available on Flathub: The recommended format for using Azahar on Linux is the Flatpak available on Flathub:
@ -68,13 +54,11 @@ The recommended format for using Azahar on Linux is the Flatpak available on Fla
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page. Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
There are two variants of the AppImage available, those being `azahar.AppImage` and `azahar-wayland.AppImage`.
If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)). If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)).
Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended. Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended.
The Flatpak build of Azahar also has native Wayland support disabled by default. If you require native Wayland support, it can be enabled using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal). If you are using the Flatpak and run into issues with Wayland, you can disable Wayland support for the Azahar Flatpak using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal).
# Build instructions # Build instructions
@ -106,23 +90,18 @@ To do so, simply read https://github.com/azahar-emu/compatibility-list/blob/mast
Contributing compatibility data helps more accurately reflect the current capabilities of the emulator, so it would be highly appreciated if you could go through the reporting process after completing a game. Contributing compatibility data helps more accurately reflect the current capabilities of the emulator, so it would be highly appreciated if you could go through the reporting process after completing a game.
# Minimum requirements # Minimum requirements
Below are the minimum requirements to run Azahar: Below are the minimum requirements to run Azahar:
### Desktop ### Desktop
``` ```
Operating System: Windows 10 (64-bit), MacOS 13.4 (Ventura), or modern 64-bit Linux Operating System: Windows 10 (64-bit), MacOS 13.4 (Ventura), or modern 64-bit Linux
CPU: x86-64/ARM64 CPU (Windows for ARM not supported). CPU: x86-64/ARM64 CPU (Windows for ARM not supported). Single core performance higher than 1,800 on Passmark
Single core performance higher than 1,800 on Passmark.
SSE4.2 required on x86_64.
GPU: OpenGL 4.3 or Vulkan 1.1 support GPU: OpenGL 4.3 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended Memory: 2GB of RAM. 4GB is recommended
``` ```
### Android ### Android
``` ```
Operating System: Android 10.0+ (64-bit) Operating System: Android 9.0+ (64-bit)
CPU: Snapdragon 835 SoC or better CPU: Snapdragon 835 SoC or better
GPU: OpenGL ES 3.2 or Vulkan 1.1 support GPU: OpenGL ES 3.2 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended Memory: 2GB of RAM. 4GB is recommended
@ -135,7 +114,6 @@ We share public roadmaps for upcoming releases in the form of GitHub milestones.
You can find these at https://github.com/azahar-emu/azahar/milestones. You can find these at https://github.com/azahar-emu/azahar/milestones.
# Join the conversation # Join the conversation
We have a community Discord server where you can chat about the project, keep up to date with the latest announcements, or coordinate emulator development. We have a community Discord server where you can chat about the project, keep up to date with the latest announcements, or coordinate emulator development.
Join at https://discord.gg/4ZjMpAp3M6 [![](https://dcbadge.vercel.app/api/server/4ZjMpAp3M6)](https://discord.gg/4ZjMpAp3M6)

View file

@ -35,7 +35,6 @@
<string>cci</string> <string>cci</string>
<string>cxi</string> <string>cxi</string>
<string>cia</string> <string>cia</string>
<string>3ds</string>
</array> </array>
<key>CFBundleTypeName</key> <key>CFBundleTypeName</key>
<string>Nintendo 3DS File</string> <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 file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID type = ANDROID
lang_map = ca@valencia:b+ca+ES+valencia, es_419:b+es+419, pt_BR:b+pt+BR, zh_CN:b+zh+CN, zh_TW:b+zh+TW lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1947
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

2886
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

7928
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

1865
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

1917
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

1873
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

2030
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

2265
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

1897
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

1978
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

1937
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

1913
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load diff

1885
dist/languages/zh_TW.ts vendored

File diff suppressed because it is too large Load diff

1
dist/license.md vendored
View file

@ -16,7 +16,6 @@ qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/128x128/cartridge.png | CC0 1.0 | Designed by PabloMK7
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,16 +1,13 @@
[Icon Theme] [Icon Theme]
Name=default Name=default
Comment=default theme Comment=default theme
Directories=16x16,48x48,128x128,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]
Size=16 Size=16
[48x48] [48x48]
Size=48 Size=48
[128x128]
Size=128
[256x256] [256x256]
Size=256 Size=256

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,16 +1,13 @@
[Icon Theme] [Icon Theme]
Name=default Name=default
Comment=default theme Comment=default theme
Directories=16x16,48x48,128x128,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]
Size=16 Size=16
[48x48] [48x48]
Size=48 Size=48
[128x128]
Size=128
[256x256] [256x256]
Size=256 Size=256

View file

@ -13,7 +13,6 @@
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file> <file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="128x128/cartridge.png">icons/128x128/cartridge.png</file>
<file alias="256x256/azahar.png">icons/256x256/azahar.png</file> <file alias="256x256/azahar.png">icons/256x256/azahar.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file> <file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
@ -32,7 +31,6 @@
<file alias="48x48/no_avatar.png">icons_light/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">icons_light/48x48/no_avatar.png</file>
<file alias="48x48/plus.png">icons_light/48x48/plus.png</file> <file alias="48x48/plus.png">icons_light/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons_light/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons_light/48x48/sd_card.png</file>
<file alias="128x128/cartridge.png">icons_light/128x128/cartridge.png</file>
<file alias="256x256/azahar.png">icons_light/256x256/azahar.png</file> <file alias="256x256/azahar.png">icons_light/256x256/azahar.png</file>
<file alias="48x48/star.png">icons_light/48x48/star.png</file> <file alias="48x48/star.png">icons_light/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons_light/256x256/plus_folder.png</file> <file alias="256x256/plus_folder.png">icons_light/256x256/plus_folder.png</file>

View file

@ -1,27 +0,0 @@
# This Dockerfile assumes that it is being built from the project root directory, e.g.:
# $ docker build -f docker/azahar-room/Dockerfile -t azahar-room .
# --- Builder ----------------
FROM opensauce04/azahar-build-environment:latest AS builder
RUN mkdir /var/azahar-src/
COPY ./ /var/azahar-src/
RUN mkdir ./builddir/
WORKDIR ./builddir/
RUN cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON
RUN ninja
RUN mv ./bin/Release/azahar-room /usr/local/bin/
# --- Final ------------------
FROM debian:trixie AS final
RUN apt-get update && apt-get -y full-upgrade
RUN apt-get install -y iputils-ping net-tools
COPY --from=builder /usr/local/bin/azahar-room /usr/local/bin/azahar-room

View file

@ -50,18 +50,15 @@ else()
endif() endif()
# Catch2 # Catch2
if (ENABLE_TESTS) add_library(catch2 INTERFACE)
add_library(catch2 INTERFACE) if(USE_SYSTEM_CATCH2)
if(USE_SYSTEM_CATCH2) find_package(Catch2 3.0.0 REQUIRED)
find_package(Catch2 3.0.0 REQUIRED) else()
else() set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "") set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "") add_subdirectory(catch2)
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
endif() endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
# Crypto++ # Crypto++
if(USE_SYSTEM_CRYPTOPP) if(USE_SYSTEM_CRYPTOPP)
@ -69,9 +66,17 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE) add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else() else()
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
endif()
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "") set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
add_subdirectory(cryptopp EXCLUDE_FROM_ALL) set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake)
endif() endif()
# dds-ktx # dds-ktx
@ -104,13 +109,7 @@ endif()
# Oaknut # Oaknut
if ("arm64" IN_LIST ARCHITECTURE) if ("arm64" IN_LIST ARCHITECTURE)
if(USE_SYSTEM_OAKNUT) add_subdirectory(oaknut EXCLUDE_FROM_ALL)
find_package(oaknut REQUIRED)
add_library(oaknut INTERFACE)
target_link_libraries(oaknut INTERFACE merry::oaknut)
else()
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif()
endif() endif()
# Dynarmic # Dynarmic
@ -134,7 +133,7 @@ endif()
# getopt # getopt
if (MSVC) if (MSVC)
add_subdirectory(getopt EXCLUDE_FROM_ALL) add_subdirectory(getopt)
endif() endif()
# inih # inih
@ -143,7 +142,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE) add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir) target_link_libraries(inih INTERFACE inih::inih inih::inir)
else() else()
add_subdirectory(inih EXCLUDE_FROM_ALL) add_subdirectory(inih)
endif() endif()
# MicroProfile # MicroProfile
@ -166,7 +165,7 @@ if (NOT MSVC)
endif() endif()
# Open Source Archives # Open Source Archives
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL) add_subdirectory(open_source_archives)
# faad2 # faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL) add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -205,12 +204,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2 # SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2) if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2 EXCLUDE_FROM_ALL) add_subdirectory(sdl2)
endif() endif()
# libusb # libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB) if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
add_subdirectory(libusb EXCLUDE_FROM_ALL) add_subdirectory(libusb)
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE) set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE) set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif() endif()
@ -244,8 +243,13 @@ else()
) )
target_link_libraries(zstd_seekable PUBLIC libzstd_static) target_link_libraries(zstd_seekable PUBLIC libzstd_static)
add_library(zstd INTERFACE) target_link_libraries(libzstd_static INTERFACE zstd_seekable)
target_link_libraries(zstd INTERFACE libzstd_static zstd_seekable)
add_library(zstd ALIAS libzstd_static)
install(TARGETS zstd_seekable
EXPORT zstdExports
)
endif() endif()
# ENet # ENet
@ -254,7 +258,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE) add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet) target_link_libraries(enet INTERFACE libenet::libenet)
else() else()
add_subdirectory(enet EXCLUDE_FROM_ALL) add_subdirectory(enet)
target_include_directories(enet INTERFACE ./enet/include) target_include_directories(enet INTERFACE ./enet/include)
endif() endif()
@ -293,15 +297,6 @@ if (USE_DISCORD_PRESENCE)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif() 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 # JSON
add_library(json-headers INTERFACE) add_library(json-headers INTERFACE)
if (USE_SYSTEM_JSON) if (USE_SYSTEM_JSON)
@ -319,8 +314,12 @@ endif()
# OpenSSL # OpenSSL
if (USE_SYSTEM_OPENSSL) if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1) find_package(OpenSSL 1.1)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) if (OPENSSL_FOUND)
else() set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL # LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/") set(OPENSSLDIR "/etc/ssl/")
@ -362,7 +361,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
add_subdirectory(gamemode EXCLUDE_FROM_ALL) add_subdirectory(gamemode)
endif() endif()
# cpp-jwt # cpp-jwt
@ -385,13 +384,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED) find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng) target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else() else()
add_subdirectory(lodepng EXCLUDE_FROM_ALL) add_subdirectory(lodepng)
endif() endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG # (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID) if(ANDROID)
# libyuv # libyuv
add_subdirectory(libyuv EXCLUDE_FROM_ALL) add_subdirectory(libyuv)
target_include_directories(yuv INTERFACE ./libyuv/include) target_include_directories(yuv INTERFACE ./libyuv/include)
endif() endif()
@ -402,9 +401,6 @@ if (ENABLE_OPENAL)
find_package(OpenAL REQUIRED) find_package(OpenAL REQUIRED)
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL) target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
else() else()
if (BSD STREQUAL "OpenBSD")
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
endif()
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
set(ALSOFT_EXAMPLES OFF CACHE BOOL "") set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
set(ALSOFT_INSTALL OFF CACHE BOOL "") set(ALSOFT_INSTALL OFF CACHE BOOL "")
@ -420,7 +416,7 @@ endif()
# OpenGL dependencies # OpenGL dependencies
if (ENABLE_OPENGL) if (ENABLE_OPENGL)
# Glad # Glad
add_subdirectory(glad EXCLUDE_FROM_ALL) add_subdirectory(glad)
endif() endif()
# Vulkan dependencies # Vulkan dependencies
@ -460,7 +456,7 @@ if (ENABLE_VULKAN)
set(ENABLE_CTEST OFF CACHE BOOL "") set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "") set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "") set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang EXCLUDE_FROM_ALL) add_subdirectory(glslang)
endif() endif()
# sirit # sirit
@ -491,37 +487,10 @@ if (ENABLE_VULKAN)
else() else()
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include) target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
target_disable_warnings(vulkan-headers) target_disable_warnings(vulkan-headers)
if (BSD STREQUAL "NetBSD")
# There may be a better way to do this with
# find_package(X11), but I couldn't get
# CMake to do it, so we're depending on
# the x11-links package and assuming the
# prefix location. -OS
target_include_directories(vulkan-headers INTERFACE
/usr/pkg/share/x11-links/include)
elseif (BSD STREQUAL "OpenBSD")
# This is fine to hardcode because it'll never change
target_include_directories(vulkan-headers INTERFACE
/usr/X11R6/include)
endif()
endif() endif()
# adrenotools # adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE) if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL) add_subdirectory(libadrenotools)
endif() endif()
endif() endif()
set(XXHASH_BUILD_XXHSUM OFF)
add_subdirectory(xxHash/cmake_unofficial EXCLUDE_FROM_ALL)
target_compile_definitions(xxhash PRIVATE XXH_FORCE_MEMORY_ACCESS=2)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SSE2)
message(STATUS "Enabling SSE2 for xxHash")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|armv8")
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_NEON)
message(STATUS "Enabling NEON for xxHash")
else()
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SCALAR)
message(STATUS "Disabling SIMD for xxHash")
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_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF) option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF) option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF) option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF) option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF) option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
@ -41,7 +40,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF) CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
@ -68,7 +66,6 @@ set(LIB_VAR_LIST
DYNARMIC DYNARMIC
FMT FMT
XBYAK XBYAK
OAKNUT
INIH INIH
FFMPEG_HEADERS FFMPEG_HEADERS
GLSLANG GLSLANG

2
externals/cryptopp vendored

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

1
externals/cryptopp-cmake vendored Submodule

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

1
externals/dllwalker vendored

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

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f Subproject commit 278405bd71999ed3f3c77c5f78344a06fef798b9

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

2
externals/teakra vendored

@ -1 +1 @@
Subproject commit 3d697a18df504f4677b65129d9ab14c7c597e3eb Subproject commit 01db7cdd00aabcce559a8dddce8798dabb71949b

1
externals/xxHash vendored

@ -1 +0,0 @@
Subproject commit e626a72bc2321cd320e953a0ccf1584cad60f363

View file

@ -357,4 +357,3 @@ plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 fro
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
sd_card.png | CC BY-ND 3.0 | https://icons8.com sd_card.png | CC BY-ND 3.0 | https://icons8.com
star.png | CC BY-ND 3.0 | https://icons8.com star.png | CC BY-ND 3.0 | https://icons8.com
cartridge.png | CC0 1.0 | Designed by PabloMK7

View file

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

View file

@ -24,11 +24,12 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs" val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
@Suppress("UnstableApiUsage")
android { android {
namespace = "org.citra.citra_emu" namespace = "org.citra.citra_emu"
compileSdkVersion = "android-35" compileSdkVersion = "android-35"
ndkVersion = "27.3.13750724" ndkVersion = "27.1.12297006"
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
@ -62,8 +63,8 @@ android {
defaultConfig { defaultConfig {
// The application ID refers to Lime3DS to allow for // The application ID refers to Lime3DS to allow for
// the Play Store listing, which was originally set up for Lime3DS, to still be used. // the Play Store listing, which was originally set up for Lime3DS, to still be used.
applicationId = "org.azahar_emu.azahar" applicationId = "io.github.lime3ds.android"
minSdk = 29 minSdk = 28
targetSdk = 35 targetSdk = 35
versionCode = autoVersion versionCode = autoVersion
versionName = getGitVersion() versionName = getGitVersion()
@ -79,8 +80,7 @@ android {
"-DENABLE_QT=0", // Don't use QT "-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL "-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
) )
} }
} }
@ -173,7 +173,6 @@ android {
register("googlePlay") { register("googlePlay") {
dimension = "version" dimension = "version"
versionNameSuffix = "-googleplay" versionNameSuffix = "-googleplay"
applicationId = "io.github.lime3ds.android"
} }
} }

View file

@ -19,19 +19,15 @@ import android.view.Surface
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RemovableStorageHelper import org.citra.citra_emu.utils.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Date import java.util.Date
@ -134,39 +130,13 @@ object NativeLibrary {
* If not set, it auto-detects a location * If not set, it auto-detects a location
*/ */
external fun setUserDirectory(directory: String) external fun setUserDirectory(directory: String)
external fun getInstalledGamePaths(): Array<String?>
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
return games.mapNotNull { entry ->
entry?.let {
val sep = it.lastIndexOf('|')
if (sep == -1) return@mapNotNull null
val path = it.substring(0, sep)
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
InstalledGame(path, mediaType!!)
}
}.toTypedArray()
}
private external fun getInstalledGamePathsImpl(): Array<String?>
// Create the config.ini file. // Create the config.ini file.
external fun createConfigFile() external fun createConfigFile()
external fun createLogFile() external fun createLogFile()
external fun logUserDirectory(directory: String) external fun logUserDirectory(directory: String)
/**
* Set the inserted cartridge that will appear
* in the home menu. Empty string to clear.
*/
external fun setInsertedCartridge(path: String)
/** /**
* Begins emulation. * Begins emulation.
*/ */
@ -252,16 +222,6 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
external fun nativeFileExists(path: String): Boolean
external fun deleteOpenGLShaderCache(titleId: Long)
external fun deleteVulkanShaderCache(titleId: Long)
private var coreErrorAlertResult = false private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object() private val coreErrorAlertLock = Object()
@ -312,18 +272,6 @@ object NativeLibrary {
canContinue = false canContinue = false
} }
CoreError.ErrorN3DSApplication -> {
title = emulationActivity.getString(R.string.invalid_system_mode)
message = emulationActivity.getString(R.string.invalid_system_mode_message)
canContinue = false
}
CoreError.ErrorCoreExceptionRaised -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = false
}
CoreError.ErrorUnknown -> { CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error) title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message) message = emulationActivity.getString(R.string.fatal_error_message)
@ -446,7 +394,7 @@ object NativeLibrary {
return return
} }
if (resultCode == CoreError.ShutdownRequested.value) { if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
emulationActivity.finish() emulationActivity.finish()
return return
} }
@ -465,51 +413,23 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
var coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE)) var captionId = R.string.loader_error_invalid_format
val title: String val result = requireArguments().getInt(RESULT_CODE)
val message: String if (result == ErrorLoader_ErrorEncrypted) {
when (coreError) { captionId = R.string.loader_error_encrypted
CoreError.ErrorGetLoader, CoreError.ErrorLoader_ErrorInvalidFormat, CoreError.ErrorSystemMode -> { }
title = getString(R.string.loader_error_invalid_format) if (result == ErrorArticDisconnected) {
message = getString(R.string.loader_error_invalid_format_description) captionId = R.string.artic_base
}
CoreError.ErrorLoader_ErrorEncrypted -> {
title = getString(R.string.loader_error_encrypted)
message = getString(R.string.loader_error_encrypted_description)
}
CoreError.ErrorArticDisconnected -> {
title = getString(R.string.artic_base)
message = getString(R.string.artic_server_comm_error)
}
CoreError.ErrorN3DSApplication -> {
title = getString(R.string.loader_error_invalid_system_mode)
message = getString(R.string.loader_error_invalid_system_mode_description)
}
CoreError.ErrorLoader_ErrorPatches -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_applying_patches_description)
}
CoreError.ErrorLoader_ErrorPatchesInvalidTitle -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_patch_wrong_application)
}
else -> {
title = getString(R.string.loader_error_generic_title)
message = getString(R.string.loader_error_generic,
getString(coreError.stringRes), coreError.value)
}
} }
val alert = MaterialAlertDialogBuilder(requireContext()) val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(title) .setTitle(captionId)
.setMessage( .setMessage(
Html.fromHtml(message, Html.fromHtml(
if (result == ErrorArticDisconnected)
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
else
CitraApplication.appContext.resources.getString(R.string.redump_games),
Html.FROM_HTML_MODE_LEGACY Html.FROM_HTML_MODE_LEGACY
) )
) )
@ -531,6 +451,20 @@ object NativeLibrary {
const val RESULT_CODE = "resultcode" const val RESULT_CODE = "resultcode"
const val Success = 0
const val ErrorNotInitialized = 1
const val ErrorGetLoader = 2
const val ErrorSystemMode = 3
const val ErrorLoader = 4
const val ErrorLoader_ErrorEncrypted = 5
const val ErrorLoader_ErrorInvalidFormat = 6
const val ErrorLoader_ErrorGBATitle = 7
const val ErrorSystemFiles = 8
const val ErrorSavestate = 9
const val ErrorArticDisconnected = 10
const val ShutdownRequested = 11
const val ErrorUnknown = 12
fun newInstance(resultCode: Int): EmulationErrorDialogFragment { fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
val args = Bundle() val args = Bundle()
args.putInt(RESULT_CODE, resultCode) args.putInt(RESULT_CODE, resultCode)
@ -660,47 +594,6 @@ object NativeLibrary {
*/ */
external fun logDeviceInfo() external fun logDeviceInfo()
enum class CompressStatus(val value: Int) {
SUCCESS(0),
COMPRESS_UNSUPPORTED(1),
COMPRESS_ALREADY_COMPRESSED(2),
COMPRESS_FAILED(3),
DECOMPRESS_UNSUPPORTED(4),
DECOMPRESS_NOT_COMPRESSED(5),
DECOMPRESS_FAILED(6),
INSTALLED_APPLICATION(7);
companion object {
fun fromValue(value: Int): CompressStatus =
CompressStatus.entries.first { it.value == value }
}
}
// Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
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 {
return CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath)
)
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@Keep
@JvmStatic
fun onCompressProgress(total: Long, current: Long) {
CompressProgressDialogViewModel.update(total, current)
}
@Keep @Keep
@JvmStatic @JvmStatic
fun createFile(directory: String, filename: String): Boolean = fun createFile(directory: String, filename: String): Boolean =
@ -743,47 +636,32 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getNativePath(uri: Uri): String { fun getUserDirectory(uriOverride: Uri? = null): String {
BuildUtil.assertNotGooglePlay() val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val dirSep = "/" val dirSep = "/"
val udUri = uriOverride ?:
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
val udPathSegment = udUri.lastPathSegment!!
val udVirtualPath = udPathSegment.substringAfter(":")
val uriString = uri.toString() if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
return uriString
}
if (uri.scheme == "file") {
return uri.path!!
}
val pathSegment = uri.lastPathSegment ?: return ""
val virtualPath = pathSegment.substringAfter(":")
if (pathSegment.startsWith("primary:")) { // User directory is located in primary storage
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
return primaryStoragePath + dirSep + virtualPath return primaryStoragePath + dirSep + udVirtualPath + dirSep
} else { // User directory probably located on a removable storage device } else { // User directory probably located on a removable storage device
val storageIdString = pathSegment.substringBefore(":") val storageIdString = udPathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString) val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
if (removablePath == null) { if (udRemovablePath == null) {
android.util.Log.e("NativeLibrary", android.util.Log.e("NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)" "Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
) )
return "" return ""
} }
return removablePath + dirSep + virtualPath return udRemovablePath + dirSep + udVirtualPath + dirSep
} }
}
@Keep
@JvmStatic
fun getUserDirectory(): String {
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
return getNativePath(userDirectoryUri)
} }
@Keep @Keep
@ -877,31 +755,11 @@ object NativeLibrary {
FileUtil.deleteDocument(path) FileUtil.deleteDocument(path)
} }
enum class CoreError(val value: Int, @StringRes val stringRes: Int) { enum class CoreError {
Success(0, R.string.core_error_success), ErrorSystemFiles,
ErrorNotInitialized(1, R.string.core_error_not_initialized), ErrorSavestate,
ErrorGetLoader(2, R.string.core_error_get_loader), ErrorArticDisconnected,
ErrorSystemMode(3, R.string.core_error_system_mode), ErrorUnknown
ErrorLoader(4, R.string.core_error_loader),
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
ErrorLoader_ErrorPatches(8, R.string.core_error_loader_error_patches),
ErrorLoader_ErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
ErrorSystemFiles(10, R.string.core_error_system_files),
ErrorSavestate(11, R.string.core_error_savestate),
ErrorArticDisconnected(12, R.string.core_error_artic_disconnected),
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
ShutdownRequested(16, R.string.core_error_shutdown_requested),
ErrorUnknown(17, R.string.core_error_unknown);
companion object {
fun fromInt(value: Int): CoreError {
return entries.find { it.value == value } ?: ErrorUnknown
}
}
} }
enum class InstallStatus { enum class InstallStatus {

View file

@ -8,7 +8,6 @@ import android.Manifest.permission
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -21,7 +20,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.core.os.BundleCompat import androidx.core.os.BundleCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -44,13 +42,11 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.fragments.EmulationFragment import org.citra.citra_emu.fragments.EmulationFragment
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.viewmodel.EmulationViewModel import org.citra.citra_emu.viewmodel.EmulationViewModel
@ -81,31 +77,19 @@ class EmulationActivity : AppCompatActivity() {
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
} }
private var isRotationBlocked: Boolean = true
private var isEmulationRunning: Boolean = false private var isEmulationRunning: Boolean = false
private var isEmulationReady: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true)
ThemeUtil.setTheme(this) ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings() settingsViewModel.settings.loadSettings()
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
// Block orientation until emulation is ready to prevent unneccesary
// surface recreation until the renderer is ready.
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
secondaryDisplay = SecondaryDisplay(this) secondaryDisplay = SecondaryDisplay(this)
secondaryDisplay.updateDisplay() secondaryDisplay.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root) setContentView(binding.root)
@ -130,6 +114,8 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true isEmulationRunning = true
instance = this instance = this
applyOrientationSettings() // Check for orientation settings at startup
val game = try { val game = try {
intent.extras?.let { extras -> intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java) BundleCompat.getParcelable(extras, "game", Game::class.java)
@ -145,46 +131,13 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStart(game.titleId) NativeLibrary.playTimeManagerStart(game.titleId)
} }
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
NativeLibrary.stopEmulation()
NativeLibrary.playTimeManagerStop()
isEmulationReady = false
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
emulationViewModel.setEmulationStarted(false)
val game = intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
}
if (game != null) {
NativeLibrary.playTimeManagerStart(game.titleId)
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
}
// On some devices, the system bars will not disappear on first boot or after some // On some devices, the system bars will not disappear on first boot or after some
// rotations. Here we set full screen immersive repeatedly in onResume and in // rotations. Here we set full screen immersive repeatedly in onResume and in
// onWindowFocusChanged to prevent the unwanted status bar state. // onWindowFocusChanged to prevent the unwanted status bar state.
override fun onResume() { override fun onResume() {
enableFullscreenImmersive()
if (isEmulationReady) {
// If emulation is ready then unblock rotation
isRotationBlocked = false
applyOrientationSettings()
emulationViewModel.setEmulationStarted(true)
} else {
if (!isRotationBlocked) {
applyOrientationSettings()
}
}
super.onResume() super.onResume()
enableFullscreenImmersive()
applyOrientationSettings() // Check for orientation settings changes on runtime
} }
override fun onStop() { override fun onStop() {
@ -193,8 +146,8 @@ class EmulationActivity : AppCompatActivity() {
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
enableFullscreenImmersive()
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
enableFullscreenImmersive()
} }
public override fun onRestart() { public override fun onRestart() {
@ -206,15 +159,11 @@ class EmulationActivity : AppCompatActivity() {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean("isEmulationRunning", isEmulationRunning) outState.putBoolean("isEmulationRunning", isEmulationRunning)
outState.putBoolean("isEmulationReady", isEmulationReady)
outState.putBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false) isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onDestroy() { override fun onDestroy() {
@ -268,11 +217,6 @@ class EmulationActivity : AppCompatActivity() {
fun onEmulationStarted() { fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true) emulationViewModel.setEmulationStarted(true)
isEmulationReady = true
if (isRotationBlocked) {
isRotationBlocked = false
applyOrientationSettings()
}
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
getString(R.string.emulation_menu_help), getString(R.string.emulation_menu_help),
@ -319,34 +263,41 @@ class EmulationActivity : AppCompatActivity() {
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
} }
when (event.action) { val button =
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> { KeyEvent.ACTION_DOWN -> {
hotkeyUtility.handleHotkey(button)
// On some devices, the back gesture / button press is not intercepted by androidx // On some devices, the back gesture / button press is not intercepted by androidx
// and fails to open the emulation menu. So we're stuck running deprecated code to // and fails to open the emulation menu. So we're stuck running deprecated code to
// cover for either a fault on androidx's side or in OEM skins (MIUI at least) // cover for either a fault on androidx's side or in OEM skins (MIUI at least)
if (event.keyCode == KeyEvent.KEYCODE_BACK) { if (event.keyCode == KeyEvent.KEYCODE_BACK) {
// If the hotkey is pressed, we don't want to open the drawer // If the hotkey is pressed, we don't want to open the drawer
if (!hotkeyUtility.hotkeyIsPressed) { if (!hotkeyUtility.HotkeyIsPressed) {
onBackPressed() onBackPressed()
return true
} }
} }
return hotkeyUtility.handleKeyPress(event)
// Normal key events.
NativeLibrary.ButtonState.PRESSED
} }
KeyEvent.ACTION_UP -> { KeyEvent.ACTION_UP -> {
return hotkeyUtility.handleKeyRelease(event) hotkeyUtility.HotkeyIsPressed = false
} NativeLibrary.ButtonState.RELEASED
else -> {
return false;
} }
else -> return false
} }
val input = event.device
?: // Controller was disconnected
return false
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
} }
private fun onAmiiboSelected(selectedFile: String) { private fun onAmiiboSelected(selectedFile: String) {
val success = NativeLibrary.loadAmiibo(selectedFile) val success = NativeLibrary.loadAmiibo(selectedFile)
if (!success) { if (!success) {
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.amiibo_load_error, R.string.amiibo_load_error,
R.string.amiibo_load_error_message R.string.amiibo_load_error_message
@ -387,7 +338,6 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation = val guestOrientation =
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
if (nextMapping == -1 || guestOrientation == -1) { if (nextMapping == -1 || guestOrientation == -1) {
// Axis is unmapped // Axis is unmapped
continue continue
@ -396,8 +346,6 @@ class EmulationActivity : AppCompatActivity() {
// Skip joystick wobble // Skip joystick wobble
value = 0f value = 0f
} }
if (inverted) value = -value;
when (nextMapping) { when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> { NativeLibrary.ButtonType.STICK_LEFT -> {
axisValuesCirclePad[guestOrientation] = value axisValuesCirclePad[guestOrientation] = value
@ -569,19 +517,13 @@ class EmulationActivity : AppCompatActivity() {
return true return true
} }
val openAmiiboFileLauncher = val openFileLauncher =
registerForActivityResult(OpenFileResultContract()) { result: Intent? -> registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles( val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, applicationContext, listOf<String>("bin") result, applicationContext, listOf<String>("bin")
) ?: return@registerForActivityResult ) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) { onAmiiboSelected(selectedFiles[0])
onAmiiboSelected(selectedFiles[0])
} else {
val fileUri = selectedFiles[0].toUri()
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
onAmiiboSelected(nativePath)
}
} }
val openImageLauncher = val openImageLauncher =

View file

@ -13,7 +13,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.widget.TextView import android.widget.TextView
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
@ -36,7 +35,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu import android.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -55,27 +53,17 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.viewmodel.GamesViewModel import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter( class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater, private val openImageLauncher: ActivityResultLauncher<String>?) :
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()), ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener, View.OnLongClickListener { View.OnClickListener, View.OnLongClickListener {
private var lastClickTime = 0L private var lastClickTime = 0L
private var imagePath: String? = null private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null private var dialogShortcutBinding: DialogShortcutBinding? = null
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
fun handleShortcutImageResult(uri: Uri?) { fun handleShortcutImageResult(uri: Uri?) {
val path = uri?.toString() val path = uri?.toString()
if (path != null) { if (path != null) {
@ -139,7 +127,7 @@ class GameAdapter(
val holder = view.tag as GameViewHolder val holder = view.tag as GameViewHolder
gameExists(holder) gameExists(holder)
if (!holder.game.valid) { if (holder.game.titleId == 0L) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(R.string.properties) .setTitle(R.string.properties)
.setMessage(R.string.properties_not_loaded) .setMessage(R.string.properties_not_loaded)
@ -156,21 +144,12 @@ class GameAdapter(
if (holder.game.isInstalled) { if (holder.game.isInstalled) {
return true return true
} }
val path = holder.game.path
val pathUri = path.toUri() val gameExists = DocumentFile.fromSingleUri(
var gameExists: Boolean CitraApplication.appContext,
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) { Uri.parse(holder.game.path)
gameExists = )?.exists() == true
DocumentFile.fromSingleUri(
CitraApplication.appContext,
pathUri
)?.exists() == true
} else {
val nativePath = NativeLibrary.getNativePath(pathUri)
gameExists = NativeLibrary.nativeFileExists(nativePath)
}
return if (!gameExists) { return if (!gameExists) {
Log.error("[GameAdapter] ROM file does not exist: $path")
Toast.makeText( Toast.makeText(
CitraApplication.appContext, CitraApplication.appContext,
R.string.loader_error_file_not_found, R.string.loader_error_file_not_found,
@ -212,11 +191,6 @@ class GameAdapter(
binding.textGameTitle.text = game.title binding.textGameTitle.text = game.title
binding.textCompany.text = game.company binding.textCompany.text = game.company
binding.textGameRegion.text = game.regions binding.textGameRegion.text = game.regions
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
View.GONE
} else {
View.VISIBLE
}
val backgroundColorId = val backgroundColorId =
if ( if (
@ -335,16 +309,14 @@ class GameAdapter(
} }
} }
val titleId = game.titleId
val dlcTitleId = titleId or 0x8C00000000L
val updateTitleId = titleId or 0xE00000000L
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = { val uninstallAction: () -> Unit = {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType) R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC) R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC) .toString())
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
.toString())
} }
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
@ -368,29 +340,12 @@ class GameAdapter(
val bottomSheetDialog = BottomSheetDialog(context) val bottomSheetDialog = BottomSheetDialog(context)
bottomSheetDialog.setContentView(bottomSheetView) bottomSheetDialog.setContentView(bottomSheetView)
val insertable = game.isInsertable
val inserted = insertable && (preferences.getString("insertedCartridge", "") == game.path)
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title 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_company).text = game.company
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions 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_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_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_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) }
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
insertButton.setOnClickListener {
if (inserted) {
preferences.edit().putString("insertedCartridge", "").apply()
} else {
preferences.edit().putString("insertedCartridge", game.path).apply()
}
bottomSheetDialog.dismiss()
notifyItemRangeChanged(0, currentList.size)
}
GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon)) GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon))
bottomSheetView.findViewById<MaterialButton>(R.id.about_game_play).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(R.id.about_game_play).setOnClickListener {
@ -486,27 +441,6 @@ class GameAdapter(
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
if (game.isInstalled) {
compressDecompressButton.setOnClickListener {
Toast.makeText(
context,
context.getString(R.string.compress_decompress_installed_app),
Toast.LENGTH_LONG
).show()
}
compressDecompressButton.alpha = 0.38f
} else {
compressDecompressButton.setOnClickListener {
val shouldCompress = !game.isCompressed
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
val baseName = holder.game.filename.substringBeforeLast('.')
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
bottomSheetDialog.dismiss()
}
}
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game) showOpenContextMenu(it, game)
} }
@ -515,63 +449,6 @@ class GameAdapter(
showUninstallContextMenu(it, game, bottomSheetDialog) showUninstallContextMenu(it, game, bottomSheetDialog)
} }
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
var selectedIndex = -1
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_cache_select_backend)
.setSingleChoiceItems(options, -1) { dialog, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) {_, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
Toast.LENGTH_LONG
)
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
}
activity.runOnUiThread {
progToast.cancel()
Toast.makeText(
CitraApplication.appContext,
R.string.shader_cache_deleted,
Toast.LENGTH_SHORT
).show()
}
}
}
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.create()
dialog.setOnShowListener {
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
positiveButton.isEnabled = false
val listView = dialog.listView
listView.setOnItemClickListener { _, _, position, _ ->
selectedIndex = position
positiveButton.isEnabled = true
}
}
dialog.show()
}
val bottomSheetBehavior = bottomSheetDialog.getBehavior() val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -627,9 +504,7 @@ class GameAdapter(
private class DiffCallback : DiffUtil.ItemCallback<Game>() { private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
// The title is taken into account to support 3DSX, which all have the titleID 0. return oldItem.titleId == newItem.titleId
// This only works now because we always return the English title, adjust if that changes.
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
} }
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -57,7 +57,6 @@ object StillImageCameraHelper {
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.data(uri) .data(uri)
.size(width, height) .size(width, height)
.allowHardware(false)
.build() .build()
return context.imageLoader.executeBlocking(request).drawable?.toBitmap( return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
width, width,

View file

@ -12,7 +12,6 @@ import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
@ -32,16 +31,8 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
} }
fun cycleLayouts() { fun cycleLayouts() {
val landscapeValues = context.resources.getIntArray(R.array.landscapeValues)
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty())
landscapeLayoutsToCycle.toIntArray()
else context.resources.getIntArray(
R.array.landscapeValues
)
val portraitValues = context.resources.getIntArray(R.array.portraitValues) val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) { if (NativeLibrary.isPortraitMode) {

View file

@ -63,37 +63,4 @@ enum class SecondaryDisplayLayout(val int: Int) {
return entries.firstOrNull { it.int == int } ?: NONE return entries.firstOrNull { it.int == int } ?: NONE
} }
} }
}
enum class StereoWhichDisplay(val int: Int) {
// These must match what is defined in src/common/settings.h
NONE(0), // equivalent to StereoRenderOption = Off
BOTH(1),
PRIMARY_ONLY(2),
SECONDARY_ONLY(3);
companion object {
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
OFF(0),
SIDE_BY_SIDE(1),
SIDE_BY_SIDE_FULL(2),
ANAGLYPH(3),
INTERLACED(4),
REVERSE_INTERLACED (5),
CARDBOARD_VR (6);
companion object {
fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
}
} }

View file

@ -6,15 +6,18 @@ package org.citra.citra_emu.display
import android.app.Presentation import android.app.Presentation
import android.content.Context import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.os.Bundle import android.os.Bundle
import android.view.Display import android.view.Display
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.WindowManager import android.view.WindowManager
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.display.SecondaryDisplayLayout
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
@ -44,30 +47,12 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
private fun getExternalDisplay(context: Context): Display? { private fun getExternalDisplay(context: Context): Display? {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = context.display.displayId val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY
val displays = dm.displays val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" }
val extDisplays = displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable &&
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
}
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
?: extDisplays.firstOrNull()
return selected
} }
fun updateDisplay() { fun updateDisplay() {
// return early if the parent context is dead or dying
if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
return
}
// decide if we are going to the external display or the internal one // decide if we are going to the external display or the internal one
var display = getExternalDisplay(context) var display = getExternalDisplay(context)
if (display == null || if (display == null ||
@ -80,25 +65,12 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
// otherwise, make a new presentation // otherwise, make a new presentation
releasePresentation() releasePresentation()
pres = SecondaryDisplayPresentation(context, display!!, this)
try { pres?.show()
pres = SecondaryDisplayPresentation(context, display!!, this)
pres?.show()
}
// catch BadTokenException and InvalidDisplayException,
// the display became invalid asynchronously, so we can assign to null
// until onDisplayAdded/Removed/Changed is called and logic retriggered
catch (_: WindowManager.BadTokenException) {
pres = null
} catch (_: WindowManager.InvalidDisplayException) {
pres = null
}
} }
fun releasePresentation() { fun releasePresentation() {
try { pres?.dismiss()
pres?.dismiss()
} catch (_: Exception) { }
pres = null pres = null
} }

View file

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

View file

@ -5,140 +5,50 @@
package org.citra.citra_emu.features.hotkeys package org.citra.citra_emu.features.hotkeys
import android.content.Context import android.content.Context
import android.view.KeyEvent
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.Settings
class HotkeyUtility( class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val screenAdjustmentUtil: ScreenAdjustmentUtil,
private val context: Context private val context: Context) {
) {
private val hotkeyButtons = Hotkey.entries.map { it.button } private val hotkeyButtons = Hotkey.entries.map { it.button }
private var hotkeyIsEnabled = false var HotkeyIsPressed = false
var hotkeyIsPressed = false
private val currentlyPressedButtons = mutableSetOf<Int>()
fun handleKeyPress(keyEvent: KeyEvent): Boolean {
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val enableButton =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
.getString(Settings.HOTKEY_ENABLE, "")
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
hotkeyIsEnabled = hotkeyIsEnabled || enableButton == "" || thisKeyIsEnableButton
// Now process all internal buttons associated with this keypress
for (button in buttonSet) {
currentlyPressedButtons.add(button)
//option 1 - this is the enable command, which was already handled
if (button == Hotkey.ENABLE.button) {
handled = true
}
// option 2 - this is a different hotkey command
else if (hotkeyButtons.contains(button)) {
if (hotkeyIsEnabled) {
handled = handleHotkey(button) || handled
}
}
// option 3 - this is a normal key
else {
// if this key press is ALSO associated with a hotkey that will process, skip
// the normal key event.
if (!thisKeyIsHotkey || !hotkeyIsEnabled) {
handled = NativeLibrary.onGamePadEvent(
keyEvent.device.descriptor,
button,
NativeLibrary.ButtonState.PRESSED
) || handled
}
}
}
return handled
}
fun handleKeyRelease(keyEvent: KeyEvent): Boolean {
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) {
handled = true; hotkeyIsEnabled = false
}
for (button in buttonSet) {
// this is a hotkey button
if (hotkeyButtons.contains(button)) {
currentlyPressedButtons.remove(button)
if (!currentlyPressedButtons.any { hotkeyButtons.contains(it) }) {
// all hotkeys are no longer pressed
hotkeyIsPressed = false
}
} else {
// if this key ALSO sends a hotkey command that we already/will handle,
// or if we did not register the press of this button, e.g. if this key
// was also a hotkey pressed after enable, but released after enable button release, then
// skip the normal key event
if ((!thisKeyIsHotkey || !hotkeyIsEnabled) && currentlyPressedButtons.contains(
button
)
) {
handled = NativeLibrary.onGamePadEvent(
keyEvent.device.descriptor,
button,
NativeLibrary.ButtonState.RELEASED
) || handled
currentlyPressedButtons.remove(button)
}
}
}
return handled
}
fun handleHotkey(bindedButton: Int): Boolean { fun handleHotkey(bindedButton: Int): Boolean {
when (bindedButton) { if(hotkeyButtons.contains(bindedButton)) {
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen() when (bindedButton) {
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true) Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.QUICKSAVE.button -> { Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) Hotkey.QUICKSAVE.button -> {
Toast.makeText( NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
context, Toast.makeText(context,
context.getString(R.string.saving), context.getString(R.string.saving),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT).show()
).show()
}
Hotkey.QUICKLOAD.button -> {
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
val stringRes = if (wasLoaded) {
R.string.loading
} else {
R.string.quickload_not_found
} }
Toast.makeText( Hotkey.QUICKLOAD.button -> {
context, val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
context.getString(stringRes), val stringRes = if(wasLoaded) {
Toast.LENGTH_SHORT R.string.loading
).show() } else {
R.string.quickload_not_found
}
Toast.makeText(context,
context.getString(stringRes),
Toast.LENGTH_SHORT).show()
}
else -> {}
} }
HotkeyIsPressed = true
else -> {} return true
} }
hotkeyIsPressed = true return false
return true
} }
} }

View file

@ -1,144 +0,0 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings
// This list should mirror the list in GenerateSettingKeys.cmake,
// specifically the Shared and Android setting keys.
@Suppress("KotlinJniMissingFunction", "FunctionName")
object SettingKeys {
// Shared
external fun use_artic_base_controller(): String
external fun use_cpu_jit(): String
external fun cpu_clock_percentage(): String
external fun is_new_3ds(): String
external fun lle_applets(): String
external fun deterministic_async_operations(): String
external fun enable_required_online_lle_modules(): String
external fun use_virtual_sd(): String
external fun compress_cia_installs(): String
external fun async_fs_operations(): String
external fun region_value(): String
external fun init_clock(): String
external fun init_time(): String
external fun init_ticks_type(): String
external fun init_ticks_override(): String
external fun plugin_loader(): String
external fun allow_plugin_loader(): String
external fun steps_per_hour(): String
external fun apply_region_free_patch(): String
external fun graphics_api(): String
external fun use_gles(): String
external fun renderer_debug(): String
external fun spirv_shader_gen(): String
external fun disable_spirv_optimizer(): String
external fun async_shader_compilation(): String
external fun async_presentation(): String
external fun use_hw_shader(): String
external fun use_disk_shader_cache(): String
external fun shaders_accurate_mul(): String
external fun use_vsync(): String
external fun use_shader_jit(): String
external fun resolution_factor(): String
external fun frame_limit(): String
external fun turbo_limit(): String
external fun texture_filter(): String
external fun texture_sampling(): String
external fun delay_game_render_thread_us(): String
external fun simulate_3ds_gpu_timings(): String
external fun layout_option(): String
external fun swap_screen(): String
external fun upright_screen(): String
external fun secondary_display_layout(): String
external fun large_screen_proportion(): String
external fun screen_gap(): String
external fun small_screen_position(): String
external fun custom_top_x(): String
external fun custom_top_y(): String
external fun custom_top_width(): String
external fun custom_top_height(): String
external fun custom_bottom_x(): String
external fun custom_bottom_y(): String
external fun custom_bottom_width(): String
external fun custom_bottom_height(): String
external fun custom_second_layer_opacity(): String
external fun aspect_ratio(): String
external fun portrait_layout_option(): String
external fun custom_portrait_top_x(): String
external fun custom_portrait_top_y(): String
external fun custom_portrait_top_width(): String
external fun custom_portrait_top_height(): String
external fun custom_portrait_bottom_x(): String
external fun custom_portrait_bottom_y(): String
external fun custom_portrait_bottom_width(): String
external fun custom_portrait_bottom_height(): String
external fun bg_red(): String
external fun bg_green(): String
external fun bg_blue(): String
external fun render_3d(): String
external fun factor_3d(): String
external fun swap_eyes_3d(): String
external fun render_3d_which_display(): String
external fun cardboard_screen_size(): String
external fun cardboard_x_shift(): String
external fun cardboard_y_shift(): String
external fun filter_mode(): String
external fun pp_shader_name(): String
external fun anaglyph_shader_name(): String
external fun dump_textures(): String
external fun custom_textures(): String
external fun preload_textures(): String
external fun async_custom_loading(): String
external fun disable_right_eye_render(): String
external fun audio_emulation(): String
external fun enable_audio_stretching(): String
external fun enable_realtime_audio(): String
external fun simulate_headphones_plugged(): String
external fun volume(): String
external fun output_type(): String
external fun output_device(): String
external fun input_type(): String
external fun input_device(): String
external fun delay_start_for_lle_modules(): String
external fun use_gdbstub(): String
external fun gdbstub_port(): String
external fun instant_debug_log(): String
external fun enable_rpc_server(): String
external fun toggle_unique_data_console_type(): String
external fun log_filter(): String
external fun log_regex_filter(): String
external fun use_integer_scaling(): String
external fun layouts_to_cycle(): String
external fun camera_inner_flip(): String
external fun camera_outer_left_flip(): String
external fun camera_outer_right_flip(): String
external fun camera_inner_name(): String
external fun camera_inner_config(): String
external fun camera_outer_left_name(): String
external fun camera_outer_left_config(): String
external fun camera_outer_right_name(): String
external fun camera_outer_right_config(): String
external fun last_artic_base_addr(): String
external fun motion_device(): String
external fun touch_device(): String
external fun udp_input_address(): String
external fun udp_input_port(): String
external fun udp_pad_index(): String
external fun record_frame_times(): String
// Android
external fun expand_to_cutout_area(): String
external fun performance_overlay_enable(): String
external fun performance_overlay_show_fps(): String
external fun performance_overlay_show_frame_time(): String
external fun performance_overlay_show_speed(): String
external fun performance_overlay_show_app_ram_usage(): String
external fun performance_overlay_show_available_ram(): String
external fun performance_overlay_show_battery_temp(): String
external fun performance_overlay_background(): String
external fun use_frame_limit(): String
external fun android_hide_images(): String
external fun screen_orientation(): String
external fun performance_overlay_position(): String
}

View file

@ -1,9 +0,0 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model
interface AbstractListSetting<E> : AbstractSetting {
var list: List<E>
}

View file

@ -4,62 +4,52 @@
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class BooleanSetting( enum class BooleanSetting(
override val key: String, override val key: String,
override val section: String, override val section: String,
override val defaultValue: Boolean override val defaultValue: Boolean
) : AbstractBooleanSetting { ) : AbstractBooleanSetting {
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false), EXPAND_TO_CUTOUT_AREA("expand_to_cutout_area", Settings.SECTION_LAYOUT, false),
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true), SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false), ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
DISABLE_SPIRV_OPTIMIZER(SettingKeys.disable_spirv_optimizer(), Settings.SECTION_RENDERER, true), DISABLE_SPIRV_OPTIMIZER("disable_spirv_optimizer", Settings.SECTION_RENDERER, true),
PLUGIN_LOADER(SettingKeys.plugin_loader(), Settings.SECTION_SYSTEM, false), PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER(SettingKeys.allow_plugin_loader(), Settings.SECTION_SYSTEM, true), ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false), SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false), INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false), ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false), CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false), OVERLAY_SHOW_FPS("overlay_show_fps", Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false), OVERLAY_SHOW_FRAMETIME("overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true), OVERLAY_SHOW_SPEED("overlay_show_speed", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false), OVERLAY_SHOW_APP_RAM_USAGE("overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false), OVERLAY_SHOW_AVAILABLE_RAM("overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false), OVERLAY_SHOW_BATTERY_TEMP("overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false), OVERLAY_BACKGROUND("overlay_background", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false), DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false), DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true), REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false), LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false),
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false), NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true),
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false), LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true),
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true), SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false),
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true), DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true),
SHADERS_ACCURATE_MUL(SettingKeys.shaders_accurate_mul(), Settings.SECTION_RENDERER, false), DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false),
DISK_SHADER_CACHE(SettingKeys.use_disk_shader_cache(), Settings.SECTION_RENDERER, true), CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, false),
DUMP_TEXTURES(SettingKeys.dump_textures(), Settings.SECTION_UTILITY, false), ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, true),
CUSTOM_TEXTURES(SettingKeys.custom_textures(), Settings.SECTION_UTILITY, false), PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, false),
ASYNC_CUSTOM_LOADING(SettingKeys.async_custom_loading(), Settings.SECTION_UTILITY, true), ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, true),
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false), ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, false),
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true), CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, true),
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false), HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, true),
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false), SHADER_JIT("use_shader_jit", Settings.SECTION_RENDERER, true),
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true), VSYNC("use_vsync_new", Settings.SECTION_RENDERER, true),
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true), DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, false),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false), DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true), USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false), UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false),
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false), COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false);
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
override var boolean: Boolean = defaultValue override var boolean: Boolean = defaultValue
@ -86,7 +76,6 @@ enum class BooleanSetting(
REQUIRED_ONLINE_LLE_MODULES, REQUIRED_ONLINE_LLE_MODULES,
NEW_3DS, NEW_3DS,
LLE_APPLETS, LLE_APPLETS,
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
VSYNC, VSYNC,
DEBUG_RENDERER, DEBUG_RENDERER,
CPU_JIT, CPU_JIT,
@ -94,10 +83,6 @@ enum class BooleanSetting(
SHADERS_ACCURATE_MUL, SHADERS_ACCURATE_MUL,
USE_ARTIC_BASE_CONTROLLER, USE_ARTIC_BASE_CONTROLLER,
COMPRESS_INSTALLED_CIA_CONTENT, COMPRESS_INSTALLED_CIA_CONTENT,
ASYNC_FS_OPERATIONS,
ANDROID_HIDE_IMAGES,
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
APPLY_REGION_FREE_PATCH
) )
fun from(key: String): BooleanSetting? = fun from(key: String): BooleanSetting? =

View file

@ -1,21 +1,16 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class FloatSetting( enum class FloatSetting(
override val key: String, override val key: String,
override val section: String, override val section: String,
override val defaultValue: Float override val defaultValue: Float
) : AbstractFloatSetting { ) : AbstractFloatSetting {
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f), LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f), EMPTY_SETTING("", "", 0.0f);
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
override var float: Float = defaultValue override var float: Float = defaultValue

View file

@ -1,54 +0,0 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntListSetting(
override val key: String,
override val section: String,
override val defaultValue: List<Int>,
val canBeEmpty: Boolean = true
) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE(SettingKeys.layouts_to_cycle(), Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
private var backingList: List<Int> = defaultValue
private var lastValidList : List<Int> = defaultValue
override var list: List<Int>
get() = backingList
set(value) {
if (!canBeEmpty && value.isEmpty()) {
backingList = lastValidList
} else {
backingList = value
lastValidList = value
}
}
override val valueAsString: String
get() = list.joinToString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue }
}
}

View file

@ -4,59 +4,56 @@
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntSetting( enum class IntSetting(
override val key: String, override val key: String,
override val section: String, override val section: String,
override val defaultValue: Int override val defaultValue: Int
) : AbstractIntSetting { ) : AbstractIntSetting {
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100), FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1), EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0), INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0), CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0), CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2), GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1), RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2), STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0),
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0), STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0), STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
CARDBOARD_SCREEN_SIZE(SettingKeys.cardboard_screen_size(), Settings.SECTION_LAYOUT, 85), CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0), CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0), CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0), SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0), SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0), LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0), LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800), LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480), LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80), LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480), LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640), LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480), LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0), SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0), PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0), SECONDARY_DISPLAY_LAYOUT("secondary_display_layout",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0), PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0), PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800), PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480), PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80), PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480), PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640), PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480), PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0), AUDIO_INPUT_TYPE("input_type", Settings.SECTION_AUDIO, 0),
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100), CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0), TEXTURE_SAMPLING("texture_sampling", Settings.SECTION_RENDERER, 0),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0), DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2), ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200), TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0), PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0), ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue override var int: Int = defaultValue

View file

@ -1,18 +1,16 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class ScaledFloatSetting( enum class ScaledFloatSetting(
override val key: String, override val key: String,
override val section: String, override val section: String,
override val defaultValue: Float, override val defaultValue: Float,
val scale: Int val scale: Int
) : AbstractFloatSetting { ) : AbstractFloatSetting {
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100); AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
override var float: Float = defaultValue override var float: Float = defaultValue
get() = field * scale get() = field * scale

View file

@ -113,7 +113,6 @@ class Settings {
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout" const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay" const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
const val SECTION_STORAGE = "Storage" const val SECTION_STORAGE = "Storage"
const val SECTION_MISC = "Miscellaneous"
const val KEY_BUTTON_A = "button_a" const val KEY_BUTTON_A = "button_a"
const val KEY_BUTTON_B = "button_b" const val KEY_BUTTON_B = "button_b"
@ -136,7 +135,6 @@ class Settings {
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal" const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical" const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal" const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
const val HOTKEY_ENABLE = "hotkey_enable"
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap" const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
@ -204,7 +202,6 @@ class Settings {
R.string.button_zr R.string.button_zr
) )
val hotKeys = listOf( val hotKeys = listOf(
HOTKEY_ENABLE,
HOTKEY_SCREEN_SWAP, HOTKEY_SCREEN_SWAP,
HOTKEY_CYCLE_LAYOUT, HOTKEY_CYCLE_LAYOUT,
HOTKEY_CLOSE_GAME, HOTKEY_CLOSE_GAME,
@ -214,7 +211,6 @@ class Settings {
HOTKEY_TURBO_LIMIT HOTKEY_TURBO_LIMIT
) )
val hotkeyTitles = listOf( val hotkeyTitles = listOf(
R.string.controller_hotkey_enable_button,
R.string.emulation_swap_screens, R.string.emulation_swap_screens,
R.string.emulation_cycle_landscape_layouts, R.string.emulation_cycle_landscape_layouts,
R.string.emulation_close_game, R.string.emulation_close_game,
@ -224,7 +220,6 @@ class Settings {
R.string.turbo_limit_hotkey R.string.turbo_limit_hotkey
) )
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_MATERIAL_YOU = "MaterialYouTheme" const val PREF_MATERIAL_YOU = "MaterialYouTheme"
const val PREF_THEME_MODE = "ThemeMode" const val PREF_THEME_MODE = "ThemeMode"

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