mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-22 11:09:28 -04:00
Compare commits
284 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9d2593c2c | ||
|
|
9e43e451d1 | ||
|
|
0a269688a3 | ||
|
|
3a77813821 | ||
|
|
fbeb53c25f | ||
|
|
1137d530b0 | ||
|
|
04f3a93854 | ||
|
|
28960bf166 | ||
|
|
176cd5cf14 | ||
|
|
735c63174c | ||
|
|
f015a208e5 | ||
|
|
deb130cdb7 | ||
|
|
4687226fa5 | ||
|
|
bd733284a3 | ||
|
|
cd1ef53569 | ||
|
|
7786dae94d | ||
|
|
5ea4bb2511 | ||
|
|
22d7be4e3c | ||
|
|
60e44ff049 | ||
|
|
d70099278a | ||
|
|
1afa24387f | ||
|
|
bfaaaee10d | ||
|
|
2940069fe2 | ||
|
|
a2a20fcc65 | ||
|
|
b4bda9d7f5 | ||
|
|
609ed7caf3 | ||
|
|
9ffb39eab2 | ||
|
|
792cde35ca | ||
|
|
398b13abc2 | ||
|
|
823901a364 | ||
|
|
8a6d597dec | ||
|
|
3dc357a8c9 | ||
|
|
379649dbce | ||
|
|
c03248f158 | ||
|
|
4867bb2e2b | ||
|
|
4e4c7e687b | ||
|
|
56f738eb06 | ||
|
|
b1e537a485 | ||
|
|
59da460177 | ||
|
|
8bdb60a6e1 | ||
|
|
383a28795e | ||
|
|
725544f3b4 | ||
|
|
135f10320a | ||
|
|
0b7114cbf8 | ||
|
|
95d42cb40a | ||
|
|
f0bc64d967 | ||
|
|
ad8526c4cf | ||
|
|
ae7d7dca1f | ||
|
|
4a4b75b0de | ||
|
|
b186b04995 | ||
|
|
ab6896a2ca | ||
|
|
e11f3da493 | ||
|
|
8ffb94b06c | ||
|
|
267887d7a9 | ||
|
|
778ca369cd | ||
|
|
dbe7fd979f | ||
|
|
1c7c7a5f1b | ||
|
|
bf59d26c48 | ||
|
|
652fc02175 | ||
|
|
854e198196 | ||
|
|
5ecd402811 | ||
|
|
0ce2a30d20 | ||
|
|
644a181aff | ||
|
|
422c7865a3 | ||
|
|
ca99574700 | ||
|
|
f902010f04 | ||
|
|
929a51afc6 | ||
|
|
260f08c497 | ||
|
|
921ea178b9 | ||
|
|
b540725090 | ||
|
|
5ddbaeae23 | ||
|
|
b081f800a4 | ||
|
|
76a71d76d4 | ||
|
|
83eef0012e | ||
|
|
ec6a0dd1c8 | ||
|
|
eb498e5ecd | ||
|
|
4fa793b945 | ||
|
|
5d84dfed91 | ||
|
|
eee7f076ee | ||
|
|
996abd1eaf | ||
|
|
91128d6625 | ||
|
|
37b6c91de6 | ||
|
|
b6c54ac8c7 | ||
|
|
b3ee2d8ac5 | ||
|
|
9701a3d874 | ||
|
|
a276623dbb | ||
|
|
2fff086e81 | ||
|
|
5bc58c78ed | ||
|
|
0fe6a8c7df | ||
|
|
d4b5633cf0 | ||
|
|
afbaf8e485 | ||
|
|
52b1e01a6f | ||
|
|
f1cd5f5ff4 | ||
|
|
1edc5de18e | ||
|
|
727377c012 | ||
|
|
4dbe0fd497 | ||
|
|
6d230d28da | ||
|
|
336d871a7f | ||
|
|
e8c75b4107 | ||
|
|
0fc3d692b9 | ||
|
|
5983a23d38 | ||
|
|
599069eb33 | ||
|
|
6eca4afac5 | ||
|
|
b2faa299d5 | ||
|
|
3d69741076 | ||
|
|
d29e15f219 | ||
|
|
c650473fdc | ||
|
|
000530c028 | ||
|
|
df05b5f3db | ||
|
|
06a535f50e | ||
|
|
4cbd75b413 | ||
|
|
60f331b43b | ||
|
|
ba6f8cb744 | ||
|
|
118579adb3 | ||
|
|
23393904e0 | ||
|
|
3066887ff4 | ||
|
|
901f010913 | ||
|
|
5fc9732f05 | ||
|
|
39363cd435 | ||
|
|
60661c3b8b | ||
|
|
be0f096f48 | ||
|
|
6201256e15 | ||
|
|
af188bb7b7 | ||
|
|
0862e5e98a | ||
|
|
49b0bef17d | ||
|
|
f14f095e72 | ||
|
|
7e58ac5bcf | ||
|
|
7220bd2edd | ||
|
|
d4e9daa739 | ||
|
|
9b045bf837 | ||
|
|
7a600e28d2 | ||
|
|
5a07260e1b | ||
|
|
2c8297c34c | ||
|
|
7f9f1e90ca | ||
|
|
04a543290a | ||
|
|
8bcb8a225a | ||
|
|
64cb0b57fb | ||
|
|
7a60160f68 | ||
|
|
dc91e8803e | ||
|
|
c55435b78d | ||
|
|
f721a474e4 | ||
|
|
ab39df3ff0 | ||
|
|
2ff04dccba | ||
|
|
3d5ba09eb1 | ||
|
|
ae9972b6be | ||
|
|
e677f72bda | ||
|
|
4109bb200b | ||
|
|
6ad642a984 | ||
|
|
ccd61d0134 | ||
|
|
d97da17263 | ||
|
|
0ff2aebdf1 | ||
|
|
100b00b3b5 | ||
|
|
9e162705f4 | ||
|
|
b3f82618d7 | ||
|
|
fc6a410dfa | ||
|
|
56a563c239 | ||
|
|
6715959382 | ||
|
|
75cd4ce649 | ||
|
|
463db8ffe4 | ||
|
|
ecaebc54ff | ||
|
|
1febb83942 | ||
|
|
a3db3be4a6 | ||
|
|
909e4b7e1f | ||
|
|
e92272ce31 | ||
|
|
51170ea85d | ||
|
|
9a7cc43d81 | ||
|
|
e351fa56ce | ||
|
|
845fadf49e | ||
|
|
98910fed1c | ||
|
|
d9f28c5b2a | ||
|
|
784fc8cca9 | ||
|
|
a35a619903 | ||
|
|
3a5fa35449 | ||
|
|
0407568006 | ||
|
|
30779d35cd | ||
|
|
32da5ea0ae | ||
|
|
e878174df8 | ||
|
|
7ad6621f91 | ||
|
|
8e1ffc1bdc | ||
|
|
96485a22f8 | ||
|
|
97c9a51015 | ||
|
|
a8ebd0f551 | ||
|
|
fac63ce6b1 | ||
|
|
c71b2dc822 | ||
|
|
e87635095a | ||
|
|
0c624f16a7 | ||
|
|
d1d14cef79 | ||
|
|
7d5da9eaeb | ||
|
|
abc1980418 | ||
|
|
70c9e18eea | ||
|
|
46ca83cc36 | ||
|
|
1e0df67cc4 | ||
|
|
ced1ec0112 | ||
|
|
1b41c78afc | ||
|
|
748b97ac5e | ||
|
|
2207be30a9 | ||
|
|
8d284aeccf | ||
|
|
d49aa070fd | ||
|
|
92cd488754 | ||
|
|
efccedbbd2 | ||
|
|
93e831decb | ||
|
|
d6fadff3ee | ||
|
|
af980b4117 | ||
|
|
f23e296802 | ||
|
|
a0ab331928 | ||
|
|
068d6598bc | ||
|
|
fe2f637467 | ||
|
|
0f9d5f29f3 | ||
|
|
cb09d1e064 | ||
|
|
526d9d4cea | ||
|
|
b477ba09c1 | ||
|
|
6b2ac400eb | ||
|
|
5ac0ef8fde | ||
|
|
3255620934 | ||
|
|
03d62efe13 | ||
|
|
7d19679cc5 | ||
|
|
b3fd0b6c89 | ||
|
|
15bdd27b9c | ||
|
|
d721cbe29b | ||
|
|
17f4c52e56 | ||
|
|
27c3e0e5c3 | ||
|
|
8fac24d2a4 | ||
|
|
fe59958b63 | ||
|
|
13e0fdeac1 | ||
|
|
76db4b08f6 | ||
|
|
5d583a8a41 | ||
|
|
8b72dcb235 | ||
|
|
fcb345e273 | ||
|
|
4c054ff2e7 | ||
|
|
43cecd1692 | ||
|
|
ac0ec5edea | ||
|
|
c55165e19b | ||
|
|
1092295f2a | ||
|
|
9628300ff5 | ||
|
|
4010f4bc1f | ||
|
|
f3fb0b729e | ||
|
|
7bcbf8aba4 | ||
|
|
d9b77cc21e | ||
|
|
d0eaf07a40 | ||
|
|
354f5d698f | ||
|
|
5c6b23c64d | ||
|
|
c43f24e489 | ||
|
|
5d4aef81fe | ||
|
|
6c6dd68780 | ||
|
|
304db9173b | ||
|
|
91abe7f7d0 | ||
|
|
3e27010c7b | ||
|
|
0c478d7614 | ||
|
|
37e688f82d | ||
|
|
f010863ece | ||
|
|
dd65ef4749 | ||
|
|
fc137b0229 | ||
|
|
a9923b6844 | ||
|
|
0a705b7449 | ||
|
|
7c4c77becf | ||
|
|
951b556a2c | ||
|
|
a6688abcf5 | ||
|
|
6d72a6f447 | ||
|
|
f1fa564733 | ||
|
|
ad9ab71301 | ||
|
|
93f54be3f9 | ||
|
|
95a6814752 | ||
|
|
b0fea112e8 | ||
|
|
94b558d3f1 | ||
|
|
6e666a1831 | ||
|
|
b54911a52e | ||
|
|
102f7d24fc | ||
|
|
a5ac24adc5 | ||
|
|
3fdcd6b7dc | ||
|
|
46429f9c02 | ||
|
|
a4c3135bf3 | ||
|
|
d48d51828e | ||
|
|
79d73bbcb9 | ||
|
|
e9846de5be | ||
|
|
671faf8dca | ||
|
|
c7e0364342 | ||
|
|
0805711cba | ||
|
|
54f2221ec6 | ||
|
|
f71844b87f | ||
|
|
6634b8c9d9 | ||
|
|
64516e6420 | ||
|
|
7869f1c618 | ||
|
|
0571187bd3 | ||
|
|
4deb7e63b5 |
591 changed files with 59915 additions and 24609 deletions
|
|
@ -12,6 +12,9 @@ fi
|
|||
|
||||
echo "Tag name is: $TAG_NAME"
|
||||
|
||||
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
|
||||
mkdir -p build
|
||||
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
|
||||
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
|
||||
docker save azahar-room:$TAG_NAME > build/$FILENAME
|
||||
|
||||
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ cmake .. -GNinja \
|
|||
-DCMAKE_SYSTEM_NAME=iOS \
|
||||
-DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
ninja
|
||||
|
||||
ccache -s -v
|
||||
|
|
|
|||
133
.ci/libretro-ci.yml
Normal file
133
.ci/libretro-ci.yml
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
.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 #################################
|
||||
|
||||
13
.ci/libretro-pack.sh
Executable file
13
.ci/libretro-pack.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
# Determine the full revision name.
|
||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||
GITREV="`git show -s --format='%h'`"
|
||||
|
||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
|
||||
fi
|
||||
|
||||
# Create .zip
|
||||
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
if [[ "$TARGET" == "appimage"* ]]; then
|
||||
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
|
||||
# Compile the AppImage we distribute with Clang.
|
||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
|
|
@ -25,9 +25,8 @@ cmake .. -G Ninja \
|
|||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DENABLE_ROOM_STANDALONE=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
strip -s bin/Release/*
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
ARTIFACTS_LIST=($ARTIFACTS)
|
||||
|
||||
BUNDLE_DIR=build/bundle
|
||||
mkdir build
|
||||
BUILD_DIR=build
|
||||
UNIVERSAL_DIR=$BUILD_DIR/universal
|
||||
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
|
||||
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
|
||||
|
||||
# Set up the base artifact to combine into.
|
||||
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
|
||||
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
|
||||
mv $BASE_ARTIFACT $BUNDLE_DIR
|
||||
# Set up the base bundle to combine into.
|
||||
mkdir $UNIVERSAL_DIR
|
||||
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
|
||||
|
||||
# Executable binary paths that need to be combined.
|
||||
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
|
||||
|
|
@ -19,21 +20,18 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
|
|||
unset IFS
|
||||
|
||||
# Combine all of the executable binaries and dylibs.
|
||||
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
|
||||
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
|
||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
|
||||
done
|
||||
|
||||
for BIN_PATH in "${BIN_PATHS[@]}"; do
|
||||
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
|
||||
done
|
||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||
|
||||
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
|
||||
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
|
||||
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
|
||||
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
|
||||
if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
|
||||
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
|
||||
fi
|
||||
done
|
||||
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
|
||||
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
|
||||
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove leftover libs so that they aren't distributed
|
||||
|
|
|
|||
14
.ci/macos.sh
14
.ci/macos.sh
|
|
@ -4,23 +4,19 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
|||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||
fi
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -GNinja \
|
||||
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
|
||||
cmake ../.. -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
|
||||
-DENABLE_ROOM_STANDALONE=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
ninja bundle
|
||||
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
|
||||
|
||||
ccache -s -v
|
||||
|
||||
CURRENT_ARCH=`arch`
|
||||
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
||||
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
|
||||
ctest -VV -C Release
|
||||
fi
|
||||
|
|
|
|||
25
.ci/mxe.sh
Executable file
25
.ci/mxe.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
|
||||
# It works fine when using the image locally.
|
||||
export PATH="/mxe/usr/bin:${PATH}"
|
||||
|
||||
mkdir build && cd build
|
||||
|
||||
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
|
||||
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
|
||||
fi
|
||||
|
||||
x86_64-w64-mingw32.shared-cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
-DUSE_SYSTEM_BOOST=ON \
|
||||
-DUSE_SYSTEM_CRYPTOPP=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
|
||||
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
|
||||
make bundle
|
||||
|
||||
ccache -s -v
|
||||
35
.ci/pack.sh
35
.ci/pack.sh
|
|
@ -3,20 +3,21 @@
|
|||
# Determine the full revision name.
|
||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||
GITREV="`git show -s --format='%h'`"
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
||||
|
||||
# Determine the name of the release being built.
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
||||
REV_NAME="azahar-$GITHUB_REF_NAME-$OS-$TARGET"
|
||||
else
|
||||
RELEASE_NAME=azahar-head
|
||||
fi
|
||||
|
||||
# Archive and upload the artifacts.
|
||||
mkdir -p artifacts
|
||||
|
||||
function pack_artifacts() {
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
||||
|
||||
# Determine the name of the release being built.
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
|
||||
else
|
||||
RELEASE_NAME=azahar-head
|
||||
fi
|
||||
|
||||
ARTIFACTS_PATH="$1"
|
||||
|
||||
# Set up root directory for archive.
|
||||
|
|
@ -35,10 +36,10 @@ function pack_artifacts() {
|
|||
fi
|
||||
|
||||
# Create .zip/.tar.gz
|
||||
if [ "$OS" = "windows" ]; then
|
||||
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
|
||||
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
|
||||
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||
else
|
||||
|
|
@ -56,11 +57,23 @@ if [ -n "$UNPACKED" ]; then
|
|||
FILENAME=$(basename "$ARTIFACT")
|
||||
EXTENSION="${FILENAME##*.}"
|
||||
|
||||
# TODO: Deduplicate
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
|
||||
|
||||
# Determine the name of the release being built.
|
||||
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
||||
RELEASE_NAME=azahar-$GITHUB_REF_NAME
|
||||
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
|
||||
else
|
||||
RELEASE_NAME=azahar-head
|
||||
fi
|
||||
|
||||
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
||||
done
|
||||
elif [ -n "$PACK_INDIVIDUALLY" ]; then
|
||||
# Pack and upload the artifacts one-by-one.
|
||||
for ARTIFACT in build/bundle/*; do
|
||||
TARGET=$(basename "$ARTIFACT")
|
||||
pack_artifacts "$ARTIFACT"
|
||||
done
|
||||
else
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ gcc -v
|
|||
tx --version
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
||||
cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
|
||||
make translation
|
||||
cd ..
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ cmake .. -G Ninja \
|
|||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_DISCORD_RPC=ON \
|
||||
"${EXTRA_CMAKE_FLAGS[@]}"
|
||||
ninja
|
||||
ninja bundle
|
||||
|
|
|
|||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
- [ ] 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.
|
||||
-->
|
||||
|
||||

|
||||
---
|
||||
BIN
.github/ignore_unless_human.png
vendored
Normal file
BIN
.github/ignore_unless_human.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
333
.github/workflows/build.yml
vendored
333
.github/workflows/build.yml
vendored
|
|
@ -7,28 +7,112 @@ on:
|
|||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
source:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: ./
|
||||
format: spdx-json
|
||||
output-file: artifacts/source.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.tar.xz
|
||||
sbom-path: artifacts/source.spdx.json
|
||||
|
||||
linux:
|
||||
linux-x86_64:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["appimage", "appimage-wayland", "fresh"]
|
||||
target: ["appimage", "appimage-wayland", "gcc-nopch"]
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ matrix.target }}-
|
||||
- name: Build
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: ./.ci/linux.sh
|
||||
- name: Move AppImage to artifacts directory
|
||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
mv build/bundle/*.AppImage artifacts/
|
||||
- name: Rename AppImage
|
||||
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||
- name: Generate SBOM
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ github.job }}-${{ matrix.target }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.AppImage
|
||||
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
|
||||
|
||||
linux-arm64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["clang", "gcc-nopch"]
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
|
|
@ -39,114 +123,94 @@ jobs:
|
|||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
${{ github.job }}-${{ matrix.target }}-
|
||||
- name: Build
|
||||
run: ./.ci/linux.sh
|
||||
- name: Move AppImage to artifacts directory
|
||||
if: ${{ contains(matrix.target, 'appimage') }}
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
mv build/bundle/*.AppImage artifacts/
|
||||
- name: Rename AppImage
|
||||
if: ${{ matrix.target == 'appimage-wayland' }}
|
||||
run: |
|
||||
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
|
||||
- name: Upload
|
||||
if: ${{ contains(matrix.target, 'appimage') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
|
||||
macos:
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
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
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
${{ runner.os }}-
|
||||
- name: Install tools
|
||||
run: brew install ccache ninja spirv-tools
|
||||
- name: Build
|
||||
run: ./.ci/macos.sh
|
||||
- name: Prepare outputs for caching
|
||||
run: cp -R build/bundle $OS-$TARGET
|
||||
- name: Cache outputs for universal build
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
|
||||
macos-universal:
|
||||
runs-on: macos-26
|
||||
needs: macos
|
||||
env:
|
||||
OS: macos
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download x86_64 build from cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-x86_64
|
||||
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Download ARM64 build from cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-arm64
|
||||
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Build (x86_64)
|
||||
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
|
||||
- name: Build (arm64)
|
||||
run: BUILD_ARCH=arm64 ./.ci/macos.sh
|
||||
- name: Create universal app
|
||||
run: ./.ci/macos-universal.sh
|
||||
env:
|
||||
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
|
||||
- name: Prepare for packing
|
||||
run: |
|
||||
mkdir build/bundle
|
||||
cp -r build/x86_64/bundle build/bundle/x86_64
|
||||
cp -r build/arm64/bundle build/bundle/arm64
|
||||
cp -r build/universal/bundle build/bundle/universal
|
||||
- name: Pack
|
||||
env:
|
||||
PACK_INDIVIDUALLY: 1
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/macos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
sbom-path: artifacts/macos.spdx.json
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["msvc", "msys2"]
|
||||
include:
|
||||
- target: msvc
|
||||
os: windows-latest
|
||||
- target: msys2
|
||||
os: windows-latest
|
||||
- target: mxe
|
||||
os: ubuntu-latest
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
||||
|
|
@ -154,14 +218,16 @@ jobs:
|
|||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
|
|
@ -169,7 +235,7 @@ jobs:
|
|||
${{ runner.os }}-${{ matrix.target }}-
|
||||
- name: Set up MSVC
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
uses: azahar-emu/msvc-dev-cmd@v1
|
||||
- name: Install extra tools (MSVC)
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
run: choco install ccache ninja ptime wget
|
||||
|
|
@ -190,34 +256,62 @@ jobs:
|
|||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Install extra tools (MSYS2)
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
uses: crazy-max/ghaction-chocolatey@v3
|
||||
uses: crazy-max/ghaction-chocolatey@v4
|
||||
with:
|
||||
args: install ptime wget
|
||||
- name: Install NSIS
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
|
||||
ptime D:/a/_temp/nsis-setup.exe /S
|
||||
shell: pwsh
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
- name: Build (Native)
|
||||
if: ${{ matrix.target != 'mxe' }}
|
||||
run: ./.ci/windows.sh
|
||||
- name: Generate installer
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
- name: Build (MXE)
|
||||
if: ${{ matrix.target == 'mxe' }}
|
||||
run: ./.ci/mxe.sh
|
||||
- name: Generate installer (Native)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
|
||||
run: |
|
||||
cd src\installer
|
||||
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir ..\..\artifacts 2> NUL
|
||||
move /y *.exe ..\..\artifacts\
|
||||
shell: cmd
|
||||
- name: Generate installer (MXE)
|
||||
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
|
||||
run: |
|
||||
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
|
||||
cd src/installer
|
||||
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
|
||||
mkdir -p ../../artifacts
|
||||
mv ./*.exe ../../artifacts/
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/*.zip
|
||||
artifacts/*.exe
|
||||
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
|
||||
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -229,17 +323,18 @@ jobs:
|
|||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
|
||||
OS: android
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
|
@ -277,23 +372,48 @@ jobs:
|
|||
working-directory: src/android/app
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: src/android
|
||||
format: spdx-json
|
||||
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
src/android/app/artifacts/*.apk
|
||||
src/android/app/artifacts/*.aab
|
||||
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64
|
||||
os: ubuntu-24.04
|
||||
- target: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: docker:dind
|
||||
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
|
||||
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: apk add bash
|
||||
- name: Fix git ownership
|
||||
run: git config --global --add safe.directory .
|
||||
- name: Build Docker image
|
||||
|
|
@ -302,8 +422,23 @@ jobs:
|
|||
run: |
|
||||
mkdir -p artifacts
|
||||
mv build/*.dockerimage artifacts/
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
name: docker
|
||||
path: artifacts/
|
||||
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
|
||||
|
|
|
|||
51
.github/workflows/first_time_contributor_detect.yml
vendored
Normal file
51
.github/workflows/first_time_contributor_detect.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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.',
|
||||
});
|
||||
79
.github/workflows/first_time_contributor_reopen.yml
vendored
Normal file
79
.github/workflows/first_time_contributor_reopen.yml
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
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.',
|
||||
});
|
||||
}
|
||||
14
.github/workflows/format.yml
vendored
14
.github/workflows/format.yml
vendored
|
|
@ -13,10 +13,22 @@ jobs:
|
|||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
env:
|
||||
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
||||
run: ./.ci/clang-format.sh
|
||||
|
||||
ktlint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
run: ./tools/check-kotlin-formatting.sh
|
||||
304
.github/workflows/libretro.yml
vendored
Normal file
304
.github/workflows/libretro.yml
vendored
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
name: citra-libretro
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CORE_ARGS: -DENABLE_LIBRETRO=ON
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
libretro-android:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: android
|
||||
TARGET: arm64-v8a
|
||||
API_LEVEL: 21
|
||||
ANDROID_NDK_VERSION: 26.2.11394342
|
||||
ANDROID_ABI: arm64-v8a
|
||||
BUILD_DIR: build/android-arm64-v8a
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set tag name
|
||||
run: |
|
||||
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
||||
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
fi
|
||||
echo $GIT_TAG_NAME
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- name: Update Android SDK CMake version
|
||||
run: |
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
|
||||
- name: Build
|
||||
run: |
|
||||
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
|
||||
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-android.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-android.spdx.json
|
||||
|
||||
libretro-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
OS: linux
|
||||
TARGET: x86_64
|
||||
BUILD_DIR: build/linux-x86_64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y llvm
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
|
||||
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-linux.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-linux.spdx.json
|
||||
|
||||
libretro-windows:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OS: windows
|
||||
TARGET: x86_64
|
||||
BUILD_DIR: build/windows-x86_64
|
||||
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
|
||||
CMAKE: x86_64-w64-mingw32.static-cmake
|
||||
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build in cross-container
|
||||
run: |
|
||||
docker pull $IMAGE
|
||||
docker run --rm --user root \
|
||||
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
|
||||
-w "${GITHUB_WORKSPACE}" \
|
||||
$IMAGE \
|
||||
bash -lc "\
|
||||
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
|
||||
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
|
||||
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-windows.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-windows.spdx.json
|
||||
libretro-macos:
|
||||
runs-on: macos-26
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
env:
|
||||
OS: macos
|
||||
TARGET: ${{ matrix.target }}
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
BUILD_DIR: build/osx-${{ matrix.target }}
|
||||
EXTRA_PATH: bin/Release
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install tools
|
||||
run: brew install spirv-tools
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
|
||||
|
||||
libretro-ios:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: ios
|
||||
TARGET: arm64
|
||||
BUILD_DIR: build/ios-arm64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-ios.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-ios.spdx.json
|
||||
|
||||
libretro-tvos:
|
||||
runs-on: macos-26
|
||||
env:
|
||||
OS: tvos
|
||||
TARGET: arm64
|
||||
BUILD_DIR: build/tvos-arm64
|
||||
EXTRA_PATH: bin/Release
|
||||
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
|
||||
cmake --build $BUILD_DIR --target azahar_libretro --config Release
|
||||
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
|
||||
- name: Pack
|
||||
run: ./.ci/libretro-pack.sh
|
||||
- name: Generate SBOM
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
path: build/
|
||||
format: spdx-json
|
||||
output-file: libretro-tvos.spdx.json
|
||||
upload-artifact: false
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: |
|
||||
./*.zip
|
||||
./*.spdx.json
|
||||
- name: Attest artifacts
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
./*.zip
|
||||
sbom-path: libretro-tvos.spdx.json
|
||||
2
.github/workflows/license-header.yml
vendored
2
.github/workflows/license-header.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch master branch
|
||||
|
|
|
|||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 90
|
||||
|
|
|
|||
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
|
|
@ -7,10 +7,10 @@ on:
|
|||
jobs:
|
||||
transifex:
|
||||
runs-on: ubuntu-latest
|
||||
container: opensauce04/azahar-build-environment:transifex
|
||||
container: opensauce04/azahar-build-environment:latest
|
||||
if: ${{ github.repository == 'azahar-emu/azahar' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
|
|
|||
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -17,7 +17,8 @@ src/common/scm_rev.cpp
|
|||
*.swp
|
||||
*.kdev4
|
||||
.markdown-preview.html
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
|
@ -56,3 +57,10 @@ repo/
|
|||
.ccache/
|
||||
node_modules/
|
||||
VULKAN_SDK/
|
||||
|
||||
# Version info files
|
||||
GIT-COMMIT
|
||||
GIT-TAG
|
||||
|
||||
# verify-release.sh downloads
|
||||
verify/
|
||||
17
.gitmodules
vendored
17
.gitmodules
vendored
|
|
@ -55,18 +55,12 @@
|
|||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "cryptopp-cmake"]
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
[submodule "cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
[submodule "dds-ktx"]
|
||||
path = externals/dds-ktx
|
||||
url = https://github.com/septag/dds-ktx
|
||||
[submodule "openal-soft"]
|
||||
path = externals/openal-soft
|
||||
url = https://github.com/kcat/openal-soft
|
||||
url = https://github.com/azahar-emu/openal-soft
|
||||
[submodule "glslang"]
|
||||
path = externals/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
|
|
@ -103,3 +97,12 @@
|
|||
[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
|
||||
|
|
|
|||
20
AI-POLICY.md
Normal file
20
AI-POLICY.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Azahar Emulator AI Use Policy
|
||||
|
||||
The following document outlines the acceptable and unacceptable uses of AI within the Azahar codebase.
|
||||
|
||||
It describes whether or not submissions which were exposed to large language models (LLMs) such as ChatGPT, Claude, DeepSeek, and similar models would be capable of being merged in a pull request or otherwise utilized.
|
||||
|
||||
- ✅ Use of AI to help developers discover or understand problems in the codebase is acceptable **under the condition that any discovered issue is independently verified by a human**.
|
||||
- ✅ Use of AI to write code snippets of a sufficiently small size that they aren't reasonably copyrightable **with disclosure in the PR description** is acceptable.
|
||||
- This will be handled on a case-by-case basis and is up to the interpretation of the maintainer, but generic algorithm snippets up to a maximum of approximately 5 lines of code are acceptable.
|
||||
- ❌ Use of AI to write code for submission without disclosure is prohibited.
|
||||
- ❌ Use of AI to write the entirety/ a significant portion of a contribution is prohibited.
|
||||
- ❌ Use of AI to write snippets of code which are of a size such that they could reasonably be copyrightable is prohibited.
|
||||
- ❌ Use of AI to rewrite incompatibly-licensed code for submission to Azahar is prohibited.
|
||||
- ❌ Use of AI to autonomously submit pull requests or issues is prohibited.
|
||||
|
||||
Pull requests which violate these rules will be closed. Previously accepted submissions which are found to violate these rules will be retroactively removed from the codebase.
|
||||
|
||||
This document may be updated in the future if further clarification is required.
|
||||
|
||||
This policy is effective for code submitted on or after the 20th of March 2026.
|
||||
|
|
@ -13,24 +13,39 @@ cmake_policy(SET CMP0063 NEW)
|
|||
cmake_policy(SET CMP0127 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
|
||||
|
||||
# Prefer building bundled dependencies as static instead of shared
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
include(FindPkgConfig)
|
||||
|
||||
project(citra LANGUAGES C CXX ASM)
|
||||
# must be invoked after project() command when using CMAKE_TOOLCHAIN_FILE
|
||||
include(FindPkgConfig)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
|
||||
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
|
||||
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
|
||||
endif()
|
||||
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
add_link_options(-z wxneeded)
|
||||
endif()
|
||||
|
||||
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
|
||||
|
||||
# Some submodules like to pick their own default build type if not specified.
|
||||
# Make sure we default to Release build type always, unless the generator has custom types.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (APPLE AND NOT ENABLE_LIBRETRO)
|
||||
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
|
|
@ -90,13 +105,23 @@ else()
|
|||
set(DEFAULT_ENABLE_OPENGL ON)
|
||||
endif()
|
||||
|
||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||
set(_USER_SET_OPTIONS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
if(DEFINED ${_opt})
|
||||
list(APPEND _USER_SET_OPTIONS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
option(ENABLE_SDL2 "Enable using SDL2" ON)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF)
|
||||
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
||||
|
||||
# Set bundled qt as dependent options.
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON)
|
||||
option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
|
||||
|
|
@ -105,6 +130,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
|
|||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||
|
|
@ -113,9 +139,10 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
|||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
|
||||
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||
# NetBSD doesn't support Vulkan yet, remove this check when it does.
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF)
|
||||
|
||||
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
|
||||
|
||||
|
|
@ -123,6 +150,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
|
|||
|
||||
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
|
||||
|
||||
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON)
|
||||
|
||||
# Compile options
|
||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
||||
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
||||
|
|
@ -130,6 +159,31 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
|
|||
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
|
||||
|
||||
# Handle incompatible options for libretro builds
|
||||
if(ENABLE_LIBRETRO)
|
||||
# Check for explicitly-set conflicting options
|
||||
set(_CONFLICTS "")
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
list(FIND _USER_SET_OPTIONS ${_opt} _idx)
|
||||
if(NOT _idx EQUAL -1 AND ${_opt})
|
||||
list(APPEND _CONFLICTS ${_opt})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(_CONFLICTS)
|
||||
string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}")
|
||||
message(FATAL_ERROR
|
||||
"ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n"
|
||||
"These options were explicitly enabled but are not supported for libretro builds.\n"
|
||||
"Remove these options or set them to OFF.")
|
||||
endif()
|
||||
|
||||
# Force disable incompatible options (handles defaulted-on options)
|
||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||
set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Pass the following values to C++ land
|
||||
if (ENABLE_QT)
|
||||
add_definitions(-DENABLE_QT)
|
||||
|
|
@ -143,9 +197,6 @@ endif()
|
|||
if (ENABLE_SDL2)
|
||||
add_definitions(-DENABLE_SDL2)
|
||||
endif()
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_definitions(-DENABLE_SDL2_FRONTEND)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
|
||||
message(STATUS "SSE4.2 enabled for x86_64")
|
||||
|
|
@ -223,7 +274,7 @@ function(check_submodules_present)
|
|||
foreach(module ${gitmodules})
|
||||
string(REGEX REPLACE "path *= *" "" module ${module})
|
||||
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
||||
message(SEND_ERROR "Git submodule ${module} not found."
|
||||
message(SEND_ERROR "Git submodule ${module} not found.\n"
|
||||
"Please run: git submodule update --init --recursive")
|
||||
endif()
|
||||
endforeach()
|
||||
|
|
@ -300,6 +351,9 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
|
|||
# set up output paths for executable binaries
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
|
@ -310,7 +364,7 @@ find_package(Threads REQUIRED)
|
|||
|
||||
if (ENABLE_QT)
|
||||
if (NOT USE_SYSTEM_QT)
|
||||
download_qt(6.9.2)
|
||||
download_qt(6.9.3)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
|
|
@ -357,13 +411,21 @@ if (APPLE)
|
|||
endif()
|
||||
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
|
||||
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
if (NOT USE_SYSTEM_MOLTENVK)
|
||||
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
|
||||
if (USE_SYSTEM_MOLTENVK)
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
else()
|
||||
download_moltenvk()
|
||||
if (IOS)
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
|
||||
else()
|
||||
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
|
||||
endif()
|
||||
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
|
||||
endif()
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
|
||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
|
||||
endif()
|
||||
|
|
@ -513,8 +575,6 @@ if (NOT ANDROID AND NOT IOS)
|
|||
include(BundleTarget)
|
||||
if (ENABLE_QT)
|
||||
qt_bundle_target(citra_meta)
|
||||
elseif (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra_meta)
|
||||
endif()
|
||||
if (ENABLE_ROOM_STANDALONE)
|
||||
bundle_target(citra_room_standalone)
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
# 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})
|
||||
|
|
@ -198,6 +198,10 @@ if (BUNDLE_TARGET_EXECUTE)
|
|||
|
||||
# On Linux, always bundle an AppImage.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (IS_MINGW)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (IN_PLACE)
|
||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||
endif()
|
||||
|
|
@ -273,15 +277,23 @@ else()
|
|||
|
||||
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
if (MINGW)
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
# The target here is arbitrary
|
||||
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
|
||||
POST_BUILD)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
POST_BUILD)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
|
@ -293,6 +305,11 @@ else()
|
|||
create_base_bundle_target()
|
||||
endif()
|
||||
|
||||
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
|
||||
# We don't really need to "bundle" MXE builds, so don't do anything
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||
if (bundle_qt AND APPLE)
|
||||
# For Qt targets on Apple, expect an app bundle.
|
||||
|
|
@ -331,6 +348,7 @@ else()
|
|||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||
"-DBUNDLE_QT=${bundle_qt}"
|
||||
"-DIN_PLACE=${in_place}"
|
||||
"-DIS_MINGW=${MINGW}"
|
||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
|
|
|
|||
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
11
CMakeModules/DisablePaxMprotect.cmake
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
function(disable_pax_mprotect target)
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
|
||||
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
|
@ -171,27 +171,16 @@ function(download_qt target)
|
|||
endfunction()
|
||||
|
||||
function(download_moltenvk)
|
||||
if (IOS)
|
||||
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
|
||||
else()
|
||||
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
|
||||
endif()
|
||||
|
||||
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
|
||||
if (NOT EXISTS ${MOLTENVK_DIR})
|
||||
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||
if (NOT EXISTS ${MOLTENVK_TAR})
|
||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
|
||||
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar
|
||||
${MOLTENVK_TAR} SHOW_PROGRESS)
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||
endif()
|
||||
|
||||
# Add the MoltenVK library path to the prefix so find_library can locate it.
|
||||
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(get_external_prefix lib_name prefix_var)
|
||||
|
|
|
|||
|
|
@ -1,49 +1,52 @@
|
|||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
macro(generate_build_info)
|
||||
find_package(Git QUIET)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
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}")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
|
||||
|
||||
# only use Git to check revision info when source is obtained via Git
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
|
||||
# unified source archive
|
||||
file(READ "${SRC_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
else()
|
||||
# self-packed archive?
|
||||
set(GIT_REV "UNKNOWN")
|
||||
set(GIT_DESC "UNKNOWN")
|
||||
set(GIT_BRANCH "UNKNOWN")
|
||||
endif()
|
||||
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
|
||||
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
|
||||
string(STRIP "${GIT_REV_RAW}" GIT_REV)
|
||||
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
|
||||
set(GIT_BRANCH "HEAD")
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
|
||||
# Find the package here with the known path so that the GetGit commands can find it as well
|
||||
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
|
||||
|
||||
# Set build version
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
|
||||
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
|
||||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||
# only use Git to check revision info when source is obtained via Git
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
else()
|
||||
# self-packed archive?
|
||||
set(GIT_REV "UNKNOWN")
|
||||
set(GIT_DESC "UNKNOWN")
|
||||
set(GIT_BRANCH "UNKNOWN")
|
||||
endif()
|
||||
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()
|
||||
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
|
||||
|
||||
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(BUILD_FULLNAME "${BUILD_VERSION}")
|
||||
endif()
|
||||
# Set build version
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
|
||||
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
|
||||
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
|
||||
set(GIT_TAG $ENV{GITHUB_REF_NAME})
|
||||
endif()
|
||||
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
|
||||
string(STRIP ${GIT_TAG} GIT_TAG)
|
||||
endif()
|
||||
|
||||
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(BUILD_FULLNAME "${BUILD_VERSION}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
|
||||
|
||||
include(GenerateBuildInfo)
|
||||
generate_build_info()
|
||||
|
||||
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
||||
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
|
||||
set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||
|
|
@ -10,6 +12,10 @@ set(HASH_FILES
|
|||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
||||
|
|
@ -18,6 +24,7 @@ set(HASH_FILES
|
|||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
|
||||
"${VIDEO_CORE}/shader/generator/profile.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
||||
|
|
@ -41,4 +48,4 @@ foreach (F IN LISTS HASH_FILES)
|
|||
set(COMBINED "${COMBINED}${TMP}")
|
||||
endforeach()
|
||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
|
|
|
|||
283
CMakeModules/GenerateSettingKeys.cmake
Normal file
283
CMakeModules/GenerateSettingKeys.cmake
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
## This file should be the *only place* where setting keys exist as strings.
|
||||
# All references to setting strings should be derived from the
|
||||
# `setting_keys.h` and `jni_setting_keys.cpp` files generated here.
|
||||
|
||||
# !!! Changes made here should be mirrored to SettingKeys.kt if used on Android
|
||||
|
||||
# Shared setting keys (multi-platform)
|
||||
foreach(KEY IN ITEMS
|
||||
"use_artic_base_controller"
|
||||
"enable_gamemode"
|
||||
"use_cpu_jit"
|
||||
"cpu_clock_percentage"
|
||||
"is_new_3ds"
|
||||
"lle_applets"
|
||||
"deterministic_async_operations"
|
||||
"enable_required_online_lle_modules"
|
||||
"use_virtual_sd"
|
||||
"use_custom_storage"
|
||||
"compress_cia_installs"
|
||||
"async_fs_operations"
|
||||
"region_value"
|
||||
"init_clock"
|
||||
"init_time"
|
||||
"init_time_offset"
|
||||
"init_ticks_type"
|
||||
"init_ticks_override"
|
||||
"plugin_loader"
|
||||
"allow_plugin_loader"
|
||||
"steps_per_hour"
|
||||
"apply_region_free_patch"
|
||||
"graphics_api"
|
||||
"physical_device"
|
||||
"use_gles"
|
||||
"renderer_debug"
|
||||
"dump_command_buffers"
|
||||
"spirv_shader_gen"
|
||||
"disable_spirv_optimizer"
|
||||
"async_shader_compilation"
|
||||
"async_presentation"
|
||||
"use_hw_shader"
|
||||
"use_disk_shader_cache"
|
||||
"shaders_accurate_mul"
|
||||
"use_vsync"
|
||||
"use_display_refresh_rate_detection"
|
||||
"use_shader_jit"
|
||||
"resolution_factor"
|
||||
"frame_limit"
|
||||
"turbo_limit"
|
||||
"texture_filter"
|
||||
"texture_sampling"
|
||||
"delay_game_render_thread_us"
|
||||
"simulate_3ds_gpu_timings"
|
||||
"layout_option"
|
||||
"swap_screen"
|
||||
"upright_screen"
|
||||
"secondary_display_layout"
|
||||
"large_screen_proportion"
|
||||
"screen_gap"
|
||||
"small_screen_position"
|
||||
"custom_top_x"
|
||||
"custom_top_y"
|
||||
"custom_top_width"
|
||||
"custom_top_height"
|
||||
"custom_bottom_x"
|
||||
"custom_bottom_y"
|
||||
"custom_bottom_width"
|
||||
"custom_bottom_height"
|
||||
"custom_second_layer_opacity"
|
||||
"aspect_ratio"
|
||||
"screen_top_stretch"
|
||||
"screen_top_leftright_padding"
|
||||
"screen_top_topbottom_padding"
|
||||
"screen_bottom_stretch"
|
||||
"screen_bottom_leftright_padding"
|
||||
"screen_bottom_topbottom_padding"
|
||||
"portrait_layout_option"
|
||||
"custom_portrait_top_x"
|
||||
"custom_portrait_top_y"
|
||||
"custom_portrait_top_width"
|
||||
"custom_portrait_top_height"
|
||||
"custom_portrait_bottom_x"
|
||||
"custom_portrait_bottom_y"
|
||||
"custom_portrait_bottom_width"
|
||||
"custom_portrait_bottom_height"
|
||||
"bg_red"
|
||||
"bg_green"
|
||||
"bg_blue"
|
||||
"render_3d"
|
||||
"factor_3d"
|
||||
"swap_eyes_3d"
|
||||
"render_3d_which_display"
|
||||
"mono_render_option"
|
||||
"cardboard_screen_size"
|
||||
"cardboard_x_shift"
|
||||
"cardboard_y_shift"
|
||||
"filter_mode"
|
||||
"pp_shader_name"
|
||||
"anaglyph_shader_name"
|
||||
"dump_textures"
|
||||
"custom_textures"
|
||||
"preload_textures"
|
||||
"async_custom_loading"
|
||||
"disable_right_eye_render"
|
||||
"audio_emulation"
|
||||
"enable_audio_stretching"
|
||||
"enable_realtime_audio"
|
||||
"volume"
|
||||
"output_type"
|
||||
"output_device"
|
||||
"input_type"
|
||||
"input_device"
|
||||
"simulate_headphones_plugged"
|
||||
"delay_start_for_lle_modules"
|
||||
"use_gdbstub"
|
||||
"gdbstub_port"
|
||||
"instant_debug_log"
|
||||
"enable_rpc_server"
|
||||
"log_filter"
|
||||
"log_regex_filter"
|
||||
"toggle_unique_data_console_type"
|
||||
"break_on_unmapped_memory_access"
|
||||
"use_integer_scaling"
|
||||
"layouts_to_cycle"
|
||||
"camera_inner_flip"
|
||||
"camera_outer_left_flip"
|
||||
"camera_outer_right_flip"
|
||||
"camera_inner_name"
|
||||
"camera_inner_config"
|
||||
"camera_outer_left_name"
|
||||
"camera_outer_left_config"
|
||||
"camera_outer_right_name"
|
||||
"camera_outer_right_config"
|
||||
"video_encoder"
|
||||
"video_encoder_options"
|
||||
"video_bitrate"
|
||||
"audio_encoder"
|
||||
"audio_encoder_options"
|
||||
"audio_bitrate"
|
||||
"last_artic_base_addr"
|
||||
"motion_device"
|
||||
"touch_device"
|
||||
"udp_input_address"
|
||||
"udp_input_port"
|
||||
"udp_pad_index"
|
||||
"record_frame_times"
|
||||
"language" # FIXME: DUPLICATE KEY (libretro equivalent: language_value)
|
||||
"web_api_url"
|
||||
"citra_username"
|
||||
"citra_token"
|
||||
)
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
if (ANDROID)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
|
||||
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Qt exclusive setting keys
|
||||
# Note: A lot of these are very generic because our Qt settings are currently put under groups:
|
||||
# E.g. UILayout\geometry
|
||||
# TODO: We should probably get rid of these groups and use complete keys at some point. -OS
|
||||
# FIXME: Some of these settings don't use the standard snake_case. When we can migrate, address that. -OS
|
||||
if (ENABLE_QT)
|
||||
foreach(KEY IN ITEMS
|
||||
"nickname"
|
||||
"ip"
|
||||
"port"
|
||||
"room_nickname"
|
||||
"room_name"
|
||||
"room_port"
|
||||
"host_type"
|
||||
"max_player"
|
||||
"room_description"
|
||||
"multiplayer_filter_text"
|
||||
"multiplayer_filter_games_owned"
|
||||
"multiplayer_filter_hide_empty"
|
||||
"multiplayer_filter_hide_full"
|
||||
"username_ban_list"
|
||||
"username"
|
||||
"ip_ban_list"
|
||||
"romsPath"
|
||||
"symbolsPath"
|
||||
"movieRecordPath"
|
||||
"moviePlaybackPath"
|
||||
"videoDumpingPath"
|
||||
"gameListRootDir"
|
||||
"gameListDeepScan"
|
||||
"path"
|
||||
"deep_scan"
|
||||
"expanded"
|
||||
"recentFiles"
|
||||
"output_format"
|
||||
"format_options"
|
||||
"theme"
|
||||
"program_id"
|
||||
"geometry"
|
||||
"state"
|
||||
"geometryRenderWindow"
|
||||
"gameListHeaderState"
|
||||
"microProfileDialogGeometry"
|
||||
"name"
|
||||
"bind"
|
||||
"profile"
|
||||
"use_touchpad"
|
||||
"controller_touch_device"
|
||||
"use_touch_from_button"
|
||||
"touch_from_button_map"
|
||||
"touch_from_button_maps" # Why are these two so similar? Basically typo bait
|
||||
"nand_directory"
|
||||
"sdmc_directory"
|
||||
"game_id"
|
||||
"KeySeq"
|
||||
"gamedirs"
|
||||
"libvorbis"
|
||||
"Context"
|
||||
"favorites"
|
||||
)
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Android exclusive setting keys (standalone app only, not Android libretro)
|
||||
if (ANDROID)
|
||||
foreach(KEY IN ITEMS
|
||||
"expand_to_cutout_area"
|
||||
"performance_overlay_enable"
|
||||
"performance_overlay_show_fps"
|
||||
"performance_overlay_show_frame_time"
|
||||
"performance_overlay_show_speed"
|
||||
"performance_overlay_show_app_ram_usage"
|
||||
"performance_overlay_show_available_ram"
|
||||
"performance_overlay_show_battery_temp"
|
||||
"performance_overlay_background"
|
||||
"use_frame_limit" # FIXME: DUPLICATE KEY (shared equivalent: frame_limit)
|
||||
"android_hide_images"
|
||||
"screen_orientation"
|
||||
"performance_overlay_position"
|
||||
"enable_secondary_display"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
|
||||
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Libretro exclusive setting keys
|
||||
if (ENABLE_LIBRETRO)
|
||||
foreach(KEY IN ITEMS
|
||||
"language_value"
|
||||
"swap_screen_mode"
|
||||
"use_libretro_save_path"
|
||||
"analog_function"
|
||||
"analog_deadzone"
|
||||
"enable_mouse_touchscreen"
|
||||
"enable_touch_touchscreen"
|
||||
"enable_touch_pointer_timeout"
|
||||
"enable_motion"
|
||||
"motion_sensitivity"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Trim trailing comma and newline from SETTING_KEY_LIST
|
||||
string(LENGTH "${SETTING_KEY_LIST}" SETTING_KEY_LIST_LENGTH)
|
||||
math(EXPR SETTING_KEY_LIST_NEW_LENGTH "${SETTING_KEY_LIST_LENGTH} - 1")
|
||||
string(SUBSTRING "${SETTING_KEY_LIST}" 0 ${SETTING_KEY_LIST_NEW_LENGTH} SETTING_KEY_LIST)
|
||||
|
||||
# Configure files
|
||||
configure_file("common/setting_keys.h.in" "common/setting_keys.h" @ONLY)
|
||||
if (ENABLE_QT)
|
||||
configure_file("citra_qt/setting_qkeys.h.in" "citra_qt/setting_qkeys.h" @ONLY)
|
||||
endif()
|
||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
||||
configure_file("android/app/src/main/jni/jni_setting_keys.cpp.in" "android/app/src/main/jni/jni_setting_keys.cpp" @ONLY)
|
||||
endif()
|
||||
|
|
@ -1 +0,0 @@
|
|||
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**
|
||||
|
|
@ -1,6 +1,8 @@
|
|||

|
||||
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
|
|
|
|||
1
dist/apple/Info.plist.in
vendored
1
dist/apple/Info.plist.in
vendored
|
|
@ -35,6 +35,7 @@
|
|||
<string>cci</string>
|
||||
<string>cxi</string>
|
||||
<string>cia</string>
|
||||
<string>3ds</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Nintendo 3DS File</string>
|
||||
|
|
|
|||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
|
||||
Subproject commit d9f1126e42b606d02ecc89b10cb9a336a3b2f5a3
|
||||
2
dist/languages/.tx/config
vendored
2
dist/languages/.tx/config
vendored
|
|
@ -12,4 +12,4 @@ lang_map = ca@valencia:ca_ES_valencia
|
|||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW
|
||||
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
|
||||
|
|
|
|||
1605
dist/languages/ca_ES_valencia.ts
vendored
1605
dist/languages/ca_ES_valencia.ts
vendored
File diff suppressed because it is too large
Load diff
1464
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
1464
dist/languages/da_DK.ts → dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load diff
1630
dist/languages/de.ts
vendored
1630
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
2489
dist/languages/el.ts
vendored
2489
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load diff
1505
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
1505
dist/languages/es_ES.ts → dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load diff
7918
dist/languages/es_419.ts
vendored
Normal file
7918
dist/languages/es_419.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
1470
dist/languages/fi.ts
vendored
1470
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load diff
1606
dist/languages/fr.ts
vendored
1606
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
1470
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
1470
dist/languages/hu_HU.ts → dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load diff
1472
dist/languages/id.ts
vendored
1472
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load diff
1559
dist/languages/it.ts
vendored
1559
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
1488
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
1488
dist/languages/ja_JP.ts → dist/languages/ja.ts
vendored
File diff suppressed because it is too large
Load diff
1478
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
1478
dist/languages/ko_KR.ts → dist/languages/ko.ts
vendored
File diff suppressed because it is too large
Load diff
1472
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
1472
dist/languages/lt_LT.ts → dist/languages/lt.ts
vendored
File diff suppressed because it is too large
Load diff
1898
dist/languages/nb.ts
vendored
1898
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
1474
dist/languages/nl.ts
vendored
1474
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
1493
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
1493
dist/languages/pl_PL.ts → dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load diff
1515
dist/languages/pt_BR.ts
vendored
1515
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
1476
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
1476
dist/languages/ro_RO.ts → dist/languages/ro.ts
vendored
File diff suppressed because it is too large
Load diff
1476
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
1476
dist/languages/ru_RU.ts → dist/languages/ru.ts
vendored
File diff suppressed because it is too large
Load diff
1491
dist/languages/sv.ts
vendored
1491
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load diff
1480
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
1480
dist/languages/tr_TR.ts → dist/languages/tr.ts
vendored
File diff suppressed because it is too large
Load diff
1474
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
1474
dist/languages/vi_VN.ts → dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load diff
1486
dist/languages/zh_CN.ts
vendored
1486
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
1472
dist/languages/zh_TW.ts
vendored
1472
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
1
dist/org.azahar_emu.Azahar.xml
vendored
1
dist/org.azahar_emu.Azahar.xml
vendored
|
|
@ -16,6 +16,7 @@
|
|||
<expanded-acronym>CTR Cart Image</expanded-acronym>
|
||||
<icon name="azahar"/>
|
||||
<glob pattern="*.cci"/>
|
||||
<glob pattern="*.3ds"/>
|
||||
<magic><match value="NCSD" type="string" offset="256"/></magic>
|
||||
</mime-type>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,23 +4,19 @@
|
|||
# --- Builder ----------------
|
||||
FROM opensauce04/azahar-build-environment:latest AS builder
|
||||
|
||||
COPY . /var/azahar-src
|
||||
RUN mkdir /var/azahar-src/
|
||||
COPY ./ /var/azahar-src/
|
||||
|
||||
RUN mkdir builddir && cd builddir && \
|
||||
cmake /var/azahar-src -G Ninja \
|
||||
RUN mkdir ./builddir/
|
||||
WORKDIR ./builddir/
|
||||
RUN cmake /var/azahar-src -G Ninja \
|
||||
-DENABLE_QT=OFF \
|
||||
-DENABLE_GDBSTUB=OFF \
|
||||
-DENABLE_TESTS=OFF \
|
||||
-DENABLE_ROOM=ON \
|
||||
-DENABLE_ROOM_STANDALONE=ON \
|
||||
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
|
||||
-DENABLE_VULKAN=OFF \
|
||||
-DENABLE_SDL2=OFF \
|
||||
-DENABLE_LIBUSB=OFF \
|
||||
-DENABLE_CUBEB=OFF \
|
||||
-DENABLE_OPENAL=OFF && \
|
||||
ninja && \
|
||||
mv bin/Release/azahar-room /usr/local/bin/ && \
|
||||
cd .. && rm -rf builddir
|
||||
-DENABLE_ROOM_STANDALONE=ON
|
||||
RUN ninja
|
||||
RUN mv ./bin/Release/azahar-room /usr/local/bin/
|
||||
|
||||
# --- Final ------------------
|
||||
FROM debian:trixie AS final
|
||||
|
|
|
|||
98
externals/CMakeLists.txt
vendored
98
externals/CMakeLists.txt
vendored
|
|
@ -50,15 +50,18 @@ else()
|
|||
endif()
|
||||
|
||||
# Catch2
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
if (ENABLE_TESTS)
|
||||
add_library(catch2 INTERFACE)
|
||||
if(USE_SYSTEM_CATCH2)
|
||||
find_package(Catch2 3.0.0 REQUIRED)
|
||||
else()
|
||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
include(Catch)
|
||||
endif()
|
||||
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||
|
||||
# Crypto++
|
||||
if(USE_SYSTEM_CRYPTOPP)
|
||||
|
|
@ -66,17 +69,9 @@ if(USE_SYSTEM_CRYPTOPP)
|
|||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
|
||||
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
||||
add_subdirectory(cryptopp-cmake)
|
||||
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# dds-ktx
|
||||
|
|
@ -109,7 +104,13 @@ endif()
|
|||
|
||||
# Oaknut
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||
if(USE_SYSTEM_OAKNUT)
|
||||
find_package(oaknut REQUIRED)
|
||||
add_library(oaknut INTERFACE)
|
||||
target_link_libraries(oaknut INTERFACE merry::oaknut)
|
||||
else()
|
||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Dynarmic
|
||||
|
|
@ -133,7 +134,7 @@ endif()
|
|||
|
||||
# getopt
|
||||
if (MSVC)
|
||||
add_subdirectory(getopt)
|
||||
add_subdirectory(getopt EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# inih
|
||||
|
|
@ -142,7 +143,7 @@ if(USE_SYSTEM_INIH)
|
|||
add_library(inih INTERFACE)
|
||||
target_link_libraries(inih INTERFACE inih::inih inih::inir)
|
||||
else()
|
||||
add_subdirectory(inih)
|
||||
add_subdirectory(inih EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# MicroProfile
|
||||
|
|
@ -165,7 +166,7 @@ if (NOT MSVC)
|
|||
endif()
|
||||
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives)
|
||||
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
|
||||
|
||||
# faad2
|
||||
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||
|
|
@ -204,12 +205,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
|
|||
|
||||
# SDL2
|
||||
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
|
||||
add_subdirectory(sdl2)
|
||||
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# libusb
|
||||
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
||||
add_subdirectory(libusb)
|
||||
add_subdirectory(libusb EXCLUDE_FROM_ALL)
|
||||
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
|
||||
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
|
||||
endif()
|
||||
|
|
@ -253,7 +254,7 @@ if(USE_SYSTEM_ENET)
|
|||
add_library(enet INTERFACE)
|
||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||
else()
|
||||
add_subdirectory(enet)
|
||||
add_subdirectory(enet EXCLUDE_FROM_ALL)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
endif()
|
||||
|
||||
|
|
@ -275,7 +276,7 @@ if (ENABLE_CUBEB)
|
|||
endif()
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
|
||||
include(TestBigEndian)
|
||||
test_big_endian(RAPIDJSON_BIG_ENDIAN)
|
||||
|
|
@ -292,6 +293,15 @@ if (USE_DISCORD_PRESENCE)
|
|||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
# LibRetro
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_library(libretro INTERFACE)
|
||||
target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include)
|
||||
if (ANDROID)
|
||||
add_subdirectory(libretro-common EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# JSON
|
||||
add_library(json-headers INTERFACE)
|
||||
if (USE_SYSTEM_JSON)
|
||||
|
|
@ -309,12 +319,8 @@ endif()
|
|||
# OpenSSL
|
||||
if (USE_SYSTEM_OPENSSL)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
else()
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
|
|
@ -356,7 +362,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
|||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_subdirectory(gamemode)
|
||||
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
|
|
@ -379,13 +385,13 @@ if(USE_SYSTEM_LODEPNG)
|
|||
find_package(lodepng REQUIRED)
|
||||
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
|
||||
else()
|
||||
add_subdirectory(lodepng)
|
||||
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
||||
if(ANDROID)
|
||||
# libyuv
|
||||
add_subdirectory(libyuv)
|
||||
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
|
||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||
endif()
|
||||
|
||||
|
|
@ -396,6 +402,9 @@ if (ENABLE_OPENAL)
|
|||
find_package(OpenAL REQUIRED)
|
||||
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
|
||||
else()
|
||||
if (BSD STREQUAL "OpenBSD")
|
||||
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
|
||||
endif()
|
||||
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
||||
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
||||
set(ALSOFT_INSTALL OFF CACHE BOOL "")
|
||||
|
|
@ -411,7 +420,7 @@ endif()
|
|||
# OpenGL dependencies
|
||||
if (ENABLE_OPENGL)
|
||||
# Glad
|
||||
add_subdirectory(glad)
|
||||
add_subdirectory(glad EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# Vulkan dependencies
|
||||
|
|
@ -451,7 +460,7 @@ if (ENABLE_VULKAN)
|
|||
set(ENABLE_CTEST OFF CACHE BOOL "")
|
||||
set(ENABLE_HLSL OFF CACHE BOOL "")
|
||||
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
||||
add_subdirectory(glslang)
|
||||
add_subdirectory(glslang EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# sirit
|
||||
|
|
@ -482,11 +491,24 @@ if (ENABLE_VULKAN)
|
|||
else()
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
target_disable_warnings(vulkan-headers)
|
||||
if (BSD STREQUAL "NetBSD")
|
||||
# There may be a better way to do this with
|
||||
# find_package(X11), but I couldn't get
|
||||
# CMake to do it, so we're depending on
|
||||
# the x11-links package and assuming the
|
||||
# prefix location. -OS
|
||||
target_include_directories(vulkan-headers INTERFACE
|
||||
/usr/pkg/share/x11-links/include)
|
||||
elseif (BSD STREQUAL "OpenBSD")
|
||||
# This is fine to hardcode because it'll never change
|
||||
target_include_directories(vulkan-headers INTERFACE
|
||||
/usr/X11R6/include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# adrenotools
|
||||
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(libadrenotools)
|
||||
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
@ -502,4 +524,4 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|armv8")
|
|||
else()
|
||||
target_compile_definitions(xxhash PRIVATE XXH_VECTOR=XXH_SCALAR)
|
||||
message(STATUS "Disabling SIMD for xxHash")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
2
externals/boost
vendored
2
externals/boost
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61
|
||||
|
|
@ -14,6 +14,7 @@ option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of
|
|||
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
||||
|
|
@ -40,6 +41,7 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
|
|||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
|
|
@ -66,6 +68,7 @@ set(LIB_VAR_LIST
|
|||
DYNARMIC
|
||||
FMT
|
||||
XBYAK
|
||||
OAKNUT
|
||||
INIH
|
||||
FFMPEG_HEADERS
|
||||
GLSLANG
|
||||
|
|
|
|||
2
externals/cryptopp
vendored
2
externals/cryptopp
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e
|
||||
Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab
|
||||
1
externals/cryptopp-cmake
vendored
1
externals/cryptopp-cmake
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c
|
||||
2
externals/discord-rpc
vendored
2
externals/discord-rpc
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a
|
||||
Subproject commit 049826c3f42875c2f9599552f425e76435789cc7
|
||||
1
externals/dllwalker
vendored
Submodule
1
externals/dllwalker
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e
|
||||
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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
externals/libretro-common/libretro-common
vendored
Submodule
1
externals/libretro-common/libretro-common
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d
|
||||
2
externals/openal-soft
vendored
2
externals/openal-soft
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727
|
||||
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906
|
||||
2
externals/sdl2/SDL
vendored
2
externals/sdl2/SDL
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 2359383fc187386204c3bb22de89655a494cd128
|
||||
Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# Enable modules to include each other's files
|
||||
include_directories(.)
|
||||
|
||||
include(GenerateSettingKeys)
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
|
@ -110,10 +112,14 @@ else()
|
|||
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
||||
-Wno-unused-command-line-argument
|
||||
# Build fortification options
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-protector-strong
|
||||
-fstack-clash-protection
|
||||
)
|
||||
if (NOT ENABLE_LIBRETRO)
|
||||
add_compile_options(
|
||||
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||
-fstack-clash-protection
|
||||
)
|
||||
endif()
|
||||
|
||||
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
|
||||
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
|
||||
|
|
@ -177,6 +183,9 @@ endif()
|
|||
if(ENABLE_DEVELOPER_OPTIONS)
|
||||
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
|
||||
endif()
|
||||
if(ENABLE_BUILTIN_KEYBLOB)
|
||||
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
|
||||
endif()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
|
|
@ -189,18 +198,19 @@ if (ENABLE_TESTS)
|
|||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
add_subdirectory(citra_sdl)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(citra_qt)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
|
||||
if (ENABLE_QT) # Or any other hypothetical future frontends
|
||||
add_subdirectory(citra_cli)
|
||||
add_subdirectory(citra_meta)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBRETRO)
|
||||
add_subdirectory(citra_libretro)
|
||||
endif()
|
||||
|
||||
if (ENABLE_ROOM)
|
||||
add_subdirectory(citra_room)
|
||||
endif()
|
||||
|
|
@ -209,7 +219,7 @@ if (ENABLE_ROOM_STANDALONE)
|
|||
add_subdirectory(citra_room_standalone)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
if (ANDROID AND NOT ENABLE_LIBRETRO)
|
||||
add_subdirectory(android/app/src/main/jni)
|
||||
target_include_directories(citra-android PRIVATE android/app/src/main)
|
||||
endif()
|
||||
|
|
|
|||
20
src/android/.editorconfig
Normal file
20
src/android/.editorconfig
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
ktlint_code_style = android_studio
|
||||
|
||||
# Disable wildcard imports
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||
|
||||
# Style configuration
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
ktlint_standard_comment-wrapping = disabled
|
||||
ktlint_standard_no-unused-imports = enabled
|
||||
# ^- Reportedly finicky, use for now but maybe delete later if there are issues.
|
||||
# See https://github.com/ktlint/ktlint/issues/3038
|
||||
ktlint_standard_package-name = disabled
|
||||
max_line_length = 100
|
||||
3
src/android/.gitignore
vendored
3
src/android/.gitignore
vendored
|
|
@ -34,7 +34,8 @@ captures/
|
|||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following line if you do not want to check your keystore files in.
|
||||
|
|
|
|||
14
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
14
src/android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
|
|
@ -12,6 +12,7 @@ plugins {
|
|||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "2.0.20"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -24,12 +25,11 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
|
|||
|
||||
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.citra.citra_emu"
|
||||
|
||||
compileSdkVersion = "android-35"
|
||||
ndkVersion = "27.1.12297006"
|
||||
ndkVersion = "27.3.13750724"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
|
|
@ -63,7 +63,7 @@ android {
|
|||
defaultConfig {
|
||||
// The application ID refers to Lime3DS to allow for
|
||||
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
|
||||
applicationId = "io.github.lime3ds.android"
|
||||
applicationId = "org.azahar_emu.azahar"
|
||||
minSdk = 29
|
||||
targetSdk = 35
|
||||
versionCode = autoVersion
|
||||
|
|
@ -80,7 +80,9 @@ android {
|
|||
"-DENABLE_QT=0", // Don't use QT
|
||||
"-DENABLE_SDL2=0", // Don't use SDL
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page
|
||||
// sizes
|
||||
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +127,8 @@ android {
|
|||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
|
||||
isShrinkResources = true
|
||||
// TODO: ^- Does this actually do anything when isDebuggable is enabled? -OS
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
proguardFiles(
|
||||
|
|
@ -136,8 +139,10 @@ android {
|
|||
}
|
||||
|
||||
// Same as above, but with isDebuggable disabled.
|
||||
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS) without constantly tripping over years-old and seemingly harmless memory bugs.
|
||||
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
|
||||
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS)
|
||||
// without constantly tripping over years-old and seemingly harmless memory bugs.
|
||||
// We should fix those bugs eventually, but for now this exists as a workaround to
|
||||
// allow other work to be done on these devices.
|
||||
register("relWithDebInfoLite") {
|
||||
initWith(getByName("relWithDebInfo"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
|
@ -147,7 +152,7 @@ android {
|
|||
}
|
||||
lint {
|
||||
checkReleaseBuilds = false // Ditto
|
||||
// The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||
// ^- The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +178,7 @@ android {
|
|||
register("googlePlay") {
|
||||
dimension = "version"
|
||||
versionNameSuffix = "-googleplay"
|
||||
applicationId = "io.github.lime3ds.android"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +220,9 @@ dependencies {
|
|||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
||||
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip")
|
||||
src(
|
||||
"https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip"
|
||||
)
|
||||
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
|
||||
onlyIfModified(true)
|
||||
}
|
||||
|
|
@ -234,6 +242,11 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
|
|||
|
||||
tasks.named("preBuild") {
|
||||
dependsOn(unzipVulkanValidationLayers)
|
||||
dependsOn("ktlintCheck")
|
||||
}
|
||||
|
||||
ktlint {
|
||||
version = "1.8.0"
|
||||
}
|
||||
|
||||
fun getGitVersion(): String {
|
||||
|
|
@ -265,7 +278,7 @@ fun getGitHash(): String =
|
|||
fun getBranch(): String =
|
||||
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
|
||||
|
||||
fun runGitCommand(command: ProcessBuilder) : String? {
|
||||
fun runGitCommand(command: ProcessBuilder): String? {
|
||||
try {
|
||||
command.directory(project.rootDir)
|
||||
val process = command.start()
|
||||
|
|
@ -291,7 +304,7 @@ android.applicationVariants.configureEach {
|
|||
val variant = this
|
||||
val capitalizedName = variant.name.capitalizeUS()
|
||||
|
||||
val copyTask = tasks.register("copyBundle${capitalizedName}") {
|
||||
val copyTask = tasks.register("copyBundle$capitalizedName") {
|
||||
doLast {
|
||||
project.copy {
|
||||
from(variant.outputs.first().outputFile.parentFile)
|
||||
|
|
@ -305,5 +318,5 @@ android.applicationVariants.configureEach {
|
|||
}
|
||||
}
|
||||
}
|
||||
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
|
||||
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import android.content.Context
|
|||
import android.os.Build
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DocumentsTree
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.GraphicsUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.MemoryUtil
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
|
||||
class CitraApplication : Application() {
|
||||
private fun createNotificationChannel() {
|
||||
|
|
@ -69,6 +69,7 @@ class CitraApplication : Application() {
|
|||
Log.info("SoC Model - ${Build.SOC_MODEL}")
|
||||
}
|
||||
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
|
||||
Log.info("OpenGL ES Renderer - ${GraphicsUtil.openGLRendererString}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -19,19 +19,22 @@ import android.view.Surface
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GraphicsUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RemovableStorageHelper
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Class which contains methods that interact
|
||||
|
|
@ -41,7 +44,7 @@ object NativeLibrary {
|
|||
/**
|
||||
* Default touchscreen device
|
||||
*/
|
||||
const val TouchScreenDevice = "Touchscreen"
|
||||
const val TOUCHSCREEN_DEVICE = "Touchscreen"
|
||||
|
||||
@JvmField
|
||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||
|
|
@ -132,7 +135,24 @@ object NativeLibrary {
|
|||
* If not set, it auto-detects a location
|
||||
*/
|
||||
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.
|
||||
external fun createConfigFile()
|
||||
|
|
@ -230,6 +250,15 @@ object NativeLibrary {
|
|||
external fun playTimeManagerGetPlayTime(titleId: Long): Long
|
||||
external fun playTimeManagerGetCurrentTitleId(): Long
|
||||
|
||||
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
|
||||
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
|
||||
uninstallTitle(titleId, mediaType.value)
|
||||
|
||||
external fun nativeFileExists(path: String): Boolean
|
||||
|
||||
external fun deleteOpenGLShaderCache(titleId: Long)
|
||||
external fun deleteVulkanShaderCache(titleId: Long)
|
||||
|
||||
private var coreErrorAlertResult = false
|
||||
private val coreErrorAlertLock = Object()
|
||||
|
||||
|
|
@ -286,6 +315,12 @@ object NativeLibrary {
|
|||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorCoreExceptionRaised -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
canContinue = false
|
||||
}
|
||||
|
||||
CoreError.ErrorUnknown -> {
|
||||
title = emulationActivity.getString(R.string.fatal_error)
|
||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||
|
|
@ -310,11 +345,12 @@ object NativeLibrary {
|
|||
return coreErrorAlertResult
|
||||
}
|
||||
|
||||
@get:Keep
|
||||
@get:JvmStatic
|
||||
val isPortraitMode: Boolean
|
||||
get() = CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isPortraitMode(): Boolean = (
|
||||
CitraApplication.appContext.resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
|
|
@ -408,7 +444,7 @@ object NativeLibrary {
|
|||
return
|
||||
}
|
||||
|
||||
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
||||
if (resultCode == CoreError.ShutdownRequested.value) {
|
||||
emulationActivity.finish()
|
||||
return
|
||||
}
|
||||
|
|
@ -427,24 +463,58 @@ object NativeLibrary {
|
|||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
|
||||
var captionId = R.string.loader_error_invalid_format
|
||||
val result = requireArguments().getInt(RESULT_CODE)
|
||||
if (result == ErrorLoader_ErrorEncrypted) {
|
||||
captionId = R.string.loader_error_encrypted
|
||||
}
|
||||
if (result == ErrorArticDisconnected) {
|
||||
captionId = R.string.artic_base
|
||||
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
|
||||
val title: String
|
||||
val message: String
|
||||
when (coreError) {
|
||||
CoreError.ErrorGetLoader,
|
||||
CoreError.ErrorLoaderErrorInvalidFormat,
|
||||
CoreError.ErrorSystemMode -> {
|
||||
title = getString(R.string.loader_error_invalid_format)
|
||||
message = getString(R.string.loader_error_invalid_format_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoaderErrorEncrypted -> {
|
||||
title = getString(R.string.loader_error_encrypted)
|
||||
message = getString(R.string.loader_error_encrypted_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorArticDisconnected -> {
|
||||
title = getString(R.string.artic_base)
|
||||
message = getString(R.string.artic_server_comm_error)
|
||||
}
|
||||
|
||||
CoreError.ErrorN3DSApplication -> {
|
||||
title = getString(R.string.loader_error_invalid_system_mode)
|
||||
message = getString(R.string.loader_error_invalid_system_mode_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoaderErrorPatches -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_applying_patches_description)
|
||||
}
|
||||
|
||||
CoreError.ErrorLoaderErrorPatchesInvalidTitle -> {
|
||||
title = getString(R.string.loader_error_applying_patches)
|
||||
message = getString(R.string.loader_error_patch_wrong_application)
|
||||
}
|
||||
|
||||
else -> {
|
||||
title = getString(R.string.loader_error_generic_title)
|
||||
message = getString(
|
||||
R.string.loader_error_generic,
|
||||
getString(coreError.stringRes),
|
||||
coreError.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(captionId)
|
||||
.setTitle(title)
|
||||
.setMessage(
|
||||
Html.fromHtml(
|
||||
if (result == ErrorArticDisconnected)
|
||||
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
|
||||
else
|
||||
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
message,
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
|
|
@ -465,20 +535,6 @@ object NativeLibrary {
|
|||
|
||||
const val RESULT_CODE = "resultcode"
|
||||
|
||||
const val Success = 0
|
||||
const val ErrorNotInitialized = 1
|
||||
const val ErrorGetLoader = 2
|
||||
const val ErrorSystemMode = 3
|
||||
const val ErrorLoader = 4
|
||||
const val ErrorLoader_ErrorEncrypted = 5
|
||||
const val ErrorLoader_ErrorInvalidFormat = 6
|
||||
const val ErrorLoader_ErrorGBATitle = 7
|
||||
const val ErrorSystemFiles = 8
|
||||
const val ErrorSavestate = 9
|
||||
const val ErrorArticDisconnected = 10
|
||||
const val ShutdownRequested = 11
|
||||
const val ErrorUnknown = 12
|
||||
|
||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putInt(RESULT_CODE, resultCode)
|
||||
|
|
@ -589,7 +645,7 @@ object NativeLibrary {
|
|||
fun loadStateIfAvailable(slot: Int): Boolean {
|
||||
var available = false
|
||||
getSavestateInfo()?.forEach {
|
||||
if (it.slot == slot){
|
||||
if (it.slot == slot) {
|
||||
available = true
|
||||
return@forEach
|
||||
}
|
||||
|
|
@ -627,19 +683,17 @@ object NativeLibrary {
|
|||
// Compression / Decompression
|
||||
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
|
||||
CompressStatus.fromValue(
|
||||
compressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
|
||||
CompressStatus.fromValue(
|
||||
decompressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
|
||||
|
||||
|
|
@ -673,84 +727,100 @@ object NativeLibrary {
|
|||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun openContentUri(path: String, openMode: String): Int =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openMode)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getFilesName(path: String): Array<String?> =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFilesName(path)
|
||||
} else {
|
||||
FileUtil.getFilesName(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getUserDirectory(uriOverride: Uri? = null): String {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
|
||||
val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
|
||||
val dirSep = "/"
|
||||
val udUri = uriOverride ?:
|
||||
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||
val udPathSegment = udUri.lastPathSegment!!
|
||||
val udVirtualPath = udPathSegment.substringAfter(":")
|
||||
|
||||
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
|
||||
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
|
||||
return primaryStoragePath + dirSep + udVirtualPath + dirSep
|
||||
} else { // User directory probably located on a removable storage device
|
||||
val storageIdString = udPathSegment.substringBefore(":")
|
||||
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
|
||||
|
||||
if (udRemovablePath == null) {
|
||||
android.util.Log.e("NativeLibrary",
|
||||
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
|
||||
)
|
||||
return ""
|
||||
}
|
||||
return udRemovablePath + dirSep + udVirtualPath + dirSep
|
||||
}
|
||||
|
||||
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openMode)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String): Long =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFilesName(path)
|
||||
} else {
|
||||
FileUtil.getFilesName(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getNativePath(uri: Uri): String {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
|
||||
val dirSep = "/"
|
||||
|
||||
val uriString = uri.toString()
|
||||
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
|
||||
return uriString
|
||||
}
|
||||
|
||||
if (uri.scheme == "file") {
|
||||
return uri.path!!
|
||||
}
|
||||
|
||||
val pathSegment = uri.lastPathSegment ?: return ""
|
||||
val virtualPath = pathSegment.substringAfter(":")
|
||||
|
||||
if (pathSegment.startsWith("primary:")) { // User directory is located in primary storage
|
||||
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
|
||||
return primaryStoragePath + dirSep + virtualPath
|
||||
} else { // User directory probably located on a removable storage device
|
||||
val storageIdString = pathSegment.substringBefore(":")
|
||||
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
|
||||
CitraApplication.appContext,
|
||||
storageIdString
|
||||
)
|
||||
|
||||
if (removablePath == null) {
|
||||
android.util.Log.e(
|
||||
"NativeLibrary",
|
||||
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
|
||||
)
|
||||
return ""
|
||||
}
|
||||
return removablePath + dirSep + virtualPath
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getUserDirectory(): String {
|
||||
val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
|
||||
return getNativePath(userDirectoryUri)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getBuildFlavor(): String = BuildConfig.FLAVOR
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun fileExists(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
}
|
||||
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
}
|
||||
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
|
|
@ -758,19 +828,18 @@ object NativeLibrary {
|
|||
sourcePath: String,
|
||||
destinationParentPath: String,
|
||||
destinationFilename: String
|
||||
): Boolean =
|
||||
if (FileUtil.isNativePath(sourcePath) &&
|
||||
FileUtil.isNativePath(destinationParentPath)
|
||||
) {
|
||||
CitraApplication.documentsTree
|
||||
.copyFile(sourcePath, destinationParentPath, destinationFilename)
|
||||
} else {
|
||||
FileUtil.copyFile(
|
||||
Uri.parse(sourcePath),
|
||||
Uri.parse(destinationParentPath),
|
||||
destinationFilename
|
||||
)
|
||||
}
|
||||
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
|
||||
FileUtil.isNativePath(destinationParentPath)
|
||||
) {
|
||||
CitraApplication.documentsTree
|
||||
.copyFile(sourcePath, destinationParentPath, destinationFilename)
|
||||
} else {
|
||||
FileUtil.copyFile(
|
||||
Uri.parse(sourcePath),
|
||||
Uri.parse(destinationParentPath),
|
||||
destinationFilename
|
||||
)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
|
|
@ -805,19 +874,35 @@ object NativeLibrary {
|
|||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun deleteDocument(path: String): Boolean =
|
||||
if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.deleteDocument(path)
|
||||
} else {
|
||||
FileUtil.deleteDocument(path)
|
||||
}
|
||||
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
|
||||
CitraApplication.documentsTree.deleteDocument(path)
|
||||
} else {
|
||||
FileUtil.deleteDocument(path)
|
||||
}
|
||||
|
||||
enum class CoreError {
|
||||
ErrorSystemFiles,
|
||||
ErrorSavestate,
|
||||
ErrorArticDisconnected,
|
||||
ErrorN3DSApplication,
|
||||
ErrorUnknown
|
||||
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
|
||||
Success(0, R.string.core_error_success),
|
||||
ErrorNotInitialized(1, R.string.core_error_not_initialized),
|
||||
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||
ErrorLoader(4, R.string.core_error_loader),
|
||||
ErrorLoaderErrorEncrypted(5, R.string.core_error_loader_encrypted),
|
||||
ErrorLoaderErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
|
||||
ErrorLoaderErrorGBATitle(7, R.string.core_error_loader_gba_title),
|
||||
ErrorLoaderErrorPatches(8, R.string.core_error_loader_error_patches),
|
||||
ErrorLoaderErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
|
||||
ErrorSystemFiles(10, R.string.core_error_system_files),
|
||||
ErrorSavestate(11, R.string.core_error_savestate),
|
||||
ErrorArticDisconnected(12, R.string.core_error_artic_disconnected),
|
||||
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
|
||||
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
|
||||
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
|
||||
ShutdownRequested(16, R.string.core_error_shutdown_requested),
|
||||
ErrorUnknown(17, R.string.core_error_unknown);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
|
||||
}
|
||||
}
|
||||
|
||||
enum class InstallStatus {
|
||||
|
|
@ -868,7 +953,11 @@ object NativeLibrary {
|
|||
const val MESSAGE = "message"
|
||||
const val CAN_CONTINUE = "canContinue"
|
||||
|
||||
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
|
||||
fun newInstance(
|
||||
title: String,
|
||||
message: String,
|
||||
canContinue: Boolean
|
||||
): CoreErrorDialogFragment {
|
||||
val frag = CoreErrorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString(TITLE, title)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import android.Manifest.permission
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
|
|
@ -21,6 +21,7 @@ import android.widget.Toast
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
|
@ -30,7 +31,7 @@ import androidx.preference.PreferenceManager
|
|||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
|
||||
import org.citra.citra_emu.camera.StillImageCameraHelper.onFilePickerResult
|
||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||
|
|
@ -43,10 +44,11 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
|||
import org.citra.citra_emu.fragments.EmulationFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.ControllerMappingHelper
|
||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RefreshRateUtil
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
|
|
@ -62,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
private lateinit var binding: ActivityEmulationBinding
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
private lateinit var hotkeyUtility: HotkeyUtility
|
||||
private lateinit var secondaryDisplay: SecondaryDisplay
|
||||
lateinit var secondaryDisplayManager: SecondaryDisplay
|
||||
|
||||
private val onShutdown = Runnable {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
|
|
@ -79,7 +81,9 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
|
||||
}
|
||||
|
||||
private var isRotationBlocked: Boolean = true
|
||||
private var isEmulationRunning: Boolean = false
|
||||
private var isEmulationReady: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
|
@ -88,12 +92,20 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
ThemeUtil.setTheme(this)
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
|
||||
// Block orientation until emulation is ready to prevent unneccesary
|
||||
// surface recreation until the renderer is ready.
|
||||
isRotationBlocked = true
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
|
||||
secondaryDisplayManager = SecondaryDisplay(this)
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
|
@ -118,8 +130,6 @@ class EmulationActivity : AppCompatActivity() {
|
|||
isEmulationRunning = true
|
||||
instance = this
|
||||
|
||||
applyOrientationSettings() // Check for orientation settings at startup
|
||||
|
||||
val game = try {
|
||||
intent.extras?.let { extras ->
|
||||
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||
|
|
@ -135,39 +145,76 @@ class EmulationActivity : AppCompatActivity() {
|
|||
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
|
||||
NativeLibrary.stopEmulation()
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
|
||||
isEmulationReady = false
|
||||
isRotationBlocked = true
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
|
||||
val game = intent.extras?.let { extras ->
|
||||
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||
}
|
||||
if (game != null) {
|
||||
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||
}
|
||||
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
||||
}
|
||||
|
||||
// On some devices, the system bars will not disappear on first boot or after some
|
||||
// rotations. Here we set full screen immersive repeatedly in onResume and in
|
||||
// onWindowFocusChanged to prevent the unwanted status bar state.
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
enableFullscreenImmersive()
|
||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
||||
if (isEmulationReady) {
|
||||
// If emulation is ready then unblock rotation
|
||||
isRotationBlocked = false
|
||||
applyOrientationSettings()
|
||||
emulationViewModel.setEmulationStarted(true)
|
||||
} else {
|
||||
if (!isRotationBlocked) {
|
||||
applyOrientationSettings()
|
||||
}
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
enableFullscreenImmersive()
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
}
|
||||
|
||||
public override fun onRestart() {
|
||||
super.onRestart()
|
||||
secondaryDisplay.updateDisplay()
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
NativeLibrary.reloadCameraDevices()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean("isEmulationRunning", isEmulationRunning)
|
||||
outState.putBoolean("isEmulationReady", isEmulationReady)
|
||||
outState.putBoolean("isRotationBlocked", isRotationBlocked)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
|
||||
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
|
||||
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -175,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
NativeLibrary.playTimeManagerStop()
|
||||
isEmulationRunning = false
|
||||
instance = null
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
secondaryDisplayManager.releaseVD()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
@ -221,6 +268,11 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
fun onEmulationStarted() {
|
||||
emulationViewModel.setEmulationStarted(true)
|
||||
isEmulationReady = true
|
||||
if (isRotationBlocked) {
|
||||
isRotationBlocked = false
|
||||
applyOrientationSettings()
|
||||
}
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(R.string.emulation_menu_help),
|
||||
|
|
@ -267,41 +319,36 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
val button =
|
||||
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||
val action: Int = when (event.action) {
|
||||
when (event.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
hotkeyUtility.handleHotkey(button)
|
||||
|
||||
// On some devices, the back gesture / button press is not intercepted by androidx
|
||||
// and fails to open the emulation menu. So we're stuck running deprecated code to
|
||||
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// If the hotkey is pressed, we don't want to open the drawer
|
||||
if (!hotkeyUtility.HotkeyIsPressed) {
|
||||
if (!hotkeyUtility.hotkeyIsPressed) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
return hotkeyUtility.handleKeyPress(event)
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> {
|
||||
hotkeyUtility.HotkeyIsPressed = false
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
return hotkeyUtility.handleKeyRelease(event)
|
||||
}
|
||||
|
||||
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) {
|
||||
val success = NativeLibrary.loadAmiibo(selectedFile)
|
||||
if (!success) {
|
||||
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.amiibo_load_error,
|
||||
R.string.amiibo_load_error_message
|
||||
|
|
@ -313,7 +360,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
|
||||
if (!NativeLibrary.isRunning() ||
|
||||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
|
||||
emulationFragment.isDrawerOpen()) {
|
||||
emulationFragment.isDrawerOpen()
|
||||
) {
|
||||
return super.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
|
|
@ -342,16 +390,19 @@ class EmulationActivity : AppCompatActivity() {
|
|||
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
|
||||
val guestOrientation =
|
||||
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
|
||||
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
|
||||
val inverted = preferences.getBoolean(
|
||||
InputBindingSetting.getInputAxisInvertedKey(axis),
|
||||
false
|
||||
)
|
||||
if (nextMapping == -1 || guestOrientation == -1) {
|
||||
// Axis is unmapped
|
||||
continue
|
||||
}
|
||||
if (value > 0f && value < 0.1f || value < 0f && value > -0.1f) {
|
||||
if ((value > 0f && value < 0.1f) || (value < 0f && value > -0.1f)) {
|
||||
// Skip joystick wobble
|
||||
value = 0f
|
||||
}
|
||||
if (inverted) value = -value;
|
||||
if (inverted) value = -value
|
||||
|
||||
when (nextMapping) {
|
||||
NativeLibrary.ButtonType.STICK_LEFT -> {
|
||||
|
|
@ -405,7 +456,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
// Triggers L/R and ZL/ZR
|
||||
if (isTriggerPressedLMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.TRIGGER_L,
|
||||
if (isTriggerPressedL) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
|
|
@ -416,7 +467,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
if (isTriggerPressedRMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.TRIGGER_R,
|
||||
if (isTriggerPressedR) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
|
|
@ -427,7 +478,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
if (isTriggerPressedZLMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.BUTTON_ZL,
|
||||
if (isTriggerPressedZL) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
|
|
@ -438,7 +489,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
if (isTriggerPressedZRMapped) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.BUTTON_ZR,
|
||||
if (isTriggerPressedZR) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
|
|
@ -451,72 +502,72 @@ class EmulationActivity : AppCompatActivity() {
|
|||
// Work-around to allow D-pad axis to be bound to emulated buttons
|
||||
if (axisValuesDPad[0] == 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[0] < 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[0] > 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] == 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] < 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
}
|
||||
if (axisValuesDPad[1] > 0f) {
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_UP,
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
)
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.TOUCHSCREEN_DEVICE,
|
||||
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
)
|
||||
|
|
@ -524,13 +575,21 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return true
|
||||
}
|
||||
|
||||
val openFileLauncher =
|
||||
val openAmiiboFileLauncher =
|
||||
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
||||
if (result == null) return@registerForActivityResult
|
||||
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
||||
result, applicationContext, listOf<String>("bin")
|
||||
result,
|
||||
applicationContext,
|
||||
listOf<String>("bin")
|
||||
) ?: return@registerForActivityResult
|
||||
onAmiiboSelected(selectedFiles[0])
|
||||
if (BuildUtil.isGooglePlayBuild) {
|
||||
onAmiiboSelected(selectedFiles[0])
|
||||
} else {
|
||||
val fileUri = selectedFiles[0].toUri()
|
||||
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
|
||||
onAmiiboSelected(nativePath)
|
||||
}
|
||||
}
|
||||
|
||||
val openImageLauncher =
|
||||
|
|
@ -539,14 +598,12 @@ class EmulationActivity : AppCompatActivity() {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
OnFilePickerResult(result.toString())
|
||||
onFilePickerResult(result.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: EmulationActivity? = null
|
||||
|
||||
fun isRunning(): Boolean {
|
||||
return instance?.isEmulationRunning ?: false
|
||||
}
|
||||
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.CardDriverOptionBinding
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.GpuDriverMetadata
|
||||
import org.citra.citra_emu.viewmodel.DriverViewModel
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
|
||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
|
||||
|
|
@ -105,15 +105,11 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
|||
override fun areItemsTheSame(
|
||||
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||
newItem: Pair<Uri, GpuDriverMetadata>
|
||||
): Boolean {
|
||||
return oldItem.first == newItem.first
|
||||
}
|
||||
): Boolean = oldItem.first == newItem.first
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||
newItem: Pair<Uri, GpuDriverMetadata>
|
||||
): Boolean {
|
||||
return oldItem.second == newItem.second
|
||||
}
|
||||
): Boolean = oldItem.second == newItem.second
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,25 @@
|
|||
|
||||
package org.citra.citra_emu.adapters
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.TextView
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.Bitmap
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
|
|
@ -29,23 +30,23 @@ import androidx.core.graphics.scale
|
|||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.widget.PopupMenu
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.citra.citra_emu.HomeNavigationDirections
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.HomeNavigationDirections
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
|
||||
|
|
@ -54,18 +55,22 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
|
|||
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
|
||||
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GameIconUtils
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
||||
class GameAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
private val inflater: LayoutInflater,
|
||||
private val openImageLauncher: ActivityResultLauncher<String>?,
|
||||
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
|
||||
) :
|
||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
private val onRequestCompressOrDecompress: (
|
||||
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
|
||||
)? = null
|
||||
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||
View.OnClickListener,
|
||||
View.OnLongClickListener {
|
||||
private var lastClickTime = 0L
|
||||
private var imagePath: String? = null
|
||||
private var dialogShortcutBinding: DialogShortcutBinding? = null
|
||||
|
|
@ -136,7 +141,7 @@ class GameAdapter(
|
|||
val holder = view.tag as GameViewHolder
|
||||
gameExists(holder)
|
||||
|
||||
if (holder.game.titleId == 0L) {
|
||||
if (!holder.game.valid) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.properties)
|
||||
.setMessage(R.string.properties_not_loaded)
|
||||
|
|
@ -153,12 +158,21 @@ class GameAdapter(
|
|||
if (holder.game.isInstalled) {
|
||||
return true
|
||||
}
|
||||
|
||||
val gameExists = DocumentFile.fromSingleUri(
|
||||
CitraApplication.appContext,
|
||||
Uri.parse(holder.game.path)
|
||||
)?.exists() == true
|
||||
val path = holder.game.path
|
||||
val pathUri = path.toUri()
|
||||
var gameExists: Boolean
|
||||
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
|
||||
gameExists =
|
||||
DocumentFile.fromSingleUri(
|
||||
CitraApplication.appContext,
|
||||
pathUri
|
||||
)?.exists() == true
|
||||
} else {
|
||||
val nativePath = NativeLibrary.getNativePath(pathUri)
|
||||
gameExists = NativeLibrary.nativeFileExists(nativePath)
|
||||
}
|
||||
return if (!gameExists) {
|
||||
Log.error("[GameAdapter] ROM file does not exist: $path")
|
||||
Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.loader_error_file_not_found,
|
||||
|
|
@ -200,15 +214,18 @@ class GameAdapter(
|
|||
binding.textGameTitle.text = game.title
|
||||
binding.textCompany.text = game.company
|
||||
binding.textGameRegion.text = game.regions
|
||||
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.imageCartridge.visibility =
|
||||
if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
|
||||
val backgroundColorId =
|
||||
if (
|
||||
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
|
||||
isValidGame(
|
||||
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
|
||||
)
|
||||
) {
|
||||
R.attr.colorSurface
|
||||
} else {
|
||||
|
|
@ -248,16 +265,45 @@ class GameAdapter(
|
|||
val extraDir: String
|
||||
)
|
||||
private fun getGameDirectories(game: Game): GameDirectories {
|
||||
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
||||
val basePath =
|
||||
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
|
||||
return GameDirectories(
|
||||
gameDir = game.path.substringBeforeLast("/"),
|
||||
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
|
||||
saveDir =
|
||||
basePath +
|
||||
"/title/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(
|
||||
0,
|
||||
8
|
||||
)}/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/data/00000001",
|
||||
modsDir = "load/mods/${String.format("%016X", game.titleId)}",
|
||||
texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
|
||||
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
|
||||
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
||||
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
|
||||
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
|
||||
appDir = game.path.substringBeforeLast("/").split("/").filter {
|
||||
it.isNotEmpty()
|
||||
}.joinToString("/"),
|
||||
dlcDir =
|
||||
basePath +
|
||||
"/title/0004008c/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/content",
|
||||
updatesDir =
|
||||
basePath +
|
||||
"/title/0004000e/${String.format(
|
||||
"%016x",
|
||||
game.titleId
|
||||
).lowercase().substring(8)}/content",
|
||||
extraDir =
|
||||
basePath +
|
||||
"/extdata/00000000/${String.format(
|
||||
"%016X",
|
||||
game.titleId
|
||||
).substring(8, 14).padStart(8, '0')}"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -287,13 +333,36 @@ class GameAdapter(
|
|||
.setType("*/*")
|
||||
|
||||
val uri = when (menuItem.itemId) {
|
||||
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
|
||||
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
|
||||
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
|
||||
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
|
||||
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
|
||||
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
|
||||
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
|
||||
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.appDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.saveDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.updatesDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.dlcDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.extraDir
|
||||
)
|
||||
|
||||
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.texturesDir,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
|
||||
dirs.modsDir,
|
||||
true
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +376,11 @@ class GameAdapter(
|
|||
popup.show()
|
||||
}
|
||||
|
||||
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
|
||||
private fun showUninstallContextMenu(
|
||||
view: View,
|
||||
game: Game,
|
||||
bottomSheetDialog: BottomSheetDialog
|
||||
) {
|
||||
val dirs = getGameDirectories(game)
|
||||
val popup = PopupMenu(view.context, view).apply {
|
||||
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
|
||||
|
|
@ -323,21 +396,45 @@ class GameAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
val titleId = game.titleId
|
||||
val dlcTitleId = titleId or 0x8C00000000L
|
||||
val updateTitleId = titleId or 0xE00000000L
|
||||
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
val uninstallAction: () -> Unit = {
|
||||
when (menuItem.itemId) {
|
||||
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
|
||||
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
|
||||
.toString())
|
||||
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
|
||||
.toString())
|
||||
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
|
||||
titleId,
|
||||
game.mediaType
|
||||
)
|
||||
|
||||
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(
|
||||
dlcTitleId,
|
||||
Game.MediaType.SDMC
|
||||
)
|
||||
|
||||
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(
|
||||
updateTitleId,
|
||||
Game.MediaType.SDMC
|
||||
)
|
||||
}
|
||||
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
|
||||
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
|
||||
if (menuItem.itemId in
|
||||
listOf(
|
||||
R.id.game_context_uninstall,
|
||||
R.id.game_context_uninstall_dlc,
|
||||
R.id.game_context_uninstall_updates
|
||||
)
|
||||
) {
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.uninstalling,
|
||||
false,
|
||||
uninstallAction
|
||||
)
|
||||
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||
true
|
||||
} else {
|
||||
|
|
@ -348,7 +445,12 @@ class GameAdapter(
|
|||
popup.show()
|
||||
}
|
||||
|
||||
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
|
||||
private fun showAboutGameDialog(
|
||||
context: Context,
|
||||
game: Game,
|
||||
holder: GameViewHolder,
|
||||
view: View
|
||||
) {
|
||||
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
|
||||
|
||||
val bottomSheetDialog = BottomSheetDialog(context)
|
||||
|
|
@ -360,12 +462,22 @@ class GameAdapter(
|
|||
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text =
|
||||
context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text =
|
||||
context.getString(R.string.game_context_file) + " " + game.filename
|
||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text =
|
||||
context.getString(R.string.game_context_type) + " " + game.fileType
|
||||
|
||||
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
|
||||
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
|
||||
val insertButton = bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.insert_cartridge_button
|
||||
)
|
||||
insertButton.text =
|
||||
if (inserted) {
|
||||
context.getString(R.string.game_context_eject)
|
||||
} else {
|
||||
context.getString(R.string.game_context_insert)
|
||||
}
|
||||
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
|
||||
insertButton.setOnClickListener {
|
||||
if (inserted) {
|
||||
|
|
@ -406,7 +518,7 @@ class GameAdapter(
|
|||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
// Default to false for zoomed in shortcut icons
|
||||
preferences.edit() {
|
||||
preferences.edit {
|
||||
putBoolean(
|
||||
"shouldStretchIcon",
|
||||
false
|
||||
|
|
@ -438,10 +550,15 @@ class GameAdapter(
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
|
||||
if (shortcutName.isEmpty()) {
|
||||
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.shortcut_name_empty,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
return@setPositiveButton
|
||||
}
|
||||
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
|
||||
val iconBitmap =
|
||||
(dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
|
||||
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
|
@ -449,9 +566,11 @@ class GameAdapter(
|
|||
val shortcut = ShortcutInfo.Builder(context, shortcutName)
|
||||
.setShortLabel(shortcutName)
|
||||
.setIcon(icon)
|
||||
.setIntent(game.launchIntent.apply {
|
||||
putExtra("launchedFromShortcut", true)
|
||||
})
|
||||
.setIntent(
|
||||
game.launchIntent.apply {
|
||||
putExtra("launchedFromShortcut", true)
|
||||
}
|
||||
)
|
||||
.build()
|
||||
|
||||
shortcutManager?.requestPinShortcut(shortcut, null)
|
||||
|
|
@ -472,7 +591,9 @@ class GameAdapter(
|
|||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.compress_decompress
|
||||
)
|
||||
if (game.isInstalled) {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
Toast.makeText(
|
||||
|
|
@ -485,22 +606,90 @@ class GameAdapter(
|
|||
} else {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
val shouldCompress = !game.isCompressed
|
||||
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
|
||||
val recommendedExt = NativeLibrary.getRecommendedExtension(
|
||||
holder.game.path,
|
||||
shouldCompress
|
||||
)
|
||||
val baseName = holder.game.filename.substringBeforeLast('.')
|
||||
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
|
||||
onRequestCompressOrDecompress?.invoke(
|
||||
holder.game.path,
|
||||
"$baseName.$recommendedExt",
|
||||
shouldCompress
|
||||
)
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
}
|
||||
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
||||
compressDecompressButton.text =
|
||||
context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
|
||||
showOpenContextMenu(it, game)
|
||||
}
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener {
|
||||
bottomSheetView.findViewById<MaterialButton>(
|
||||
R.id.menu_button_uninstall
|
||||
).setOnClickListener {
|
||||
showUninstallContextMenu(it, game, bottomSheetDialog)
|
||||
}
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
|
||||
val options =
|
||||
arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
|
||||
var selectedIndex = -1
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_cache_select_backend)
|
||||
.setSingleChoiceItems(options, -1) { dialog, which ->
|
||||
selectedIndex = which
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val progToast = Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.deleting_shader_cache,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
progToast.show()
|
||||
|
||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
when (selectedIndex) {
|
||||
0 -> {
|
||||
NativeLibrary.deleteVulkanShaderCache(game.titleId)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
|
||||
}
|
||||
}
|
||||
|
||||
activity.runOnUiThread {
|
||||
progToast.cancel()
|
||||
Toast.makeText(
|
||||
CitraApplication.appContext,
|
||||
R.string.shader_cache_deleted,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
|
||||
|
||||
positiveButton.isEnabled = false
|
||||
|
||||
val listView = dialog.listView
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
selectedIndex = position
|
||||
positiveButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val bottomSheetBehavior = bottomSheetDialog.getBehavior()
|
||||
bottomSheetBehavior.skipCollapsed = true
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
|
@ -549,18 +738,16 @@ class GameAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isValidGame(extension: String): Boolean {
|
||||
return Game.badExtensions.stream()
|
||||
.noneMatch { extension == it.lowercase() }
|
||||
}
|
||||
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
|
||||
.noneMatch { extension == it.lowercase() }
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.titleId == newItem.titleId
|
||||
// The title is taken into account to support 3DSX, which all have the titleID 0.
|
||||
// This only works now because we always return the English title, adjust if that changes.
|
||||
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -9,27 +9,24 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.CardHomeOptionBinding
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.model.HomeSetting
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
||||
class HomeSettingAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
private val viewLifecycle: LifecycleOwner,
|
||||
var options: List<HomeSetting>
|
||||
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
|
||||
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
|
||||
View.OnClickListener {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
||||
val binding =
|
||||
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
|
@ -37,9 +34,7 @@ class HomeSettingAdapter(
|
|||
return HomeOptionViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return options.size
|
||||
}
|
||||
override fun getItemCount(): Int = options.size
|
||||
|
||||
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
|
||||
holder.bind(options[position])
|
||||
|
|
|
|||
|
|
@ -8,18 +8,17 @@ import android.content.res.ColorStateList
|
|||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.databinding.PageSetupBinding
|
||||
import org.citra.citra_emu.model.ButtonState
|
||||
import org.citra.citra_emu.model.PageState
|
||||
import org.citra.citra_emu.model.SetupCallback
|
||||
import org.citra.citra_emu.model.SetupPage
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.ViewUtils
|
||||
|
||||
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
|
||||
|
|
@ -35,7 +34,8 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
|||
holder.bind(pages[position])
|
||||
|
||||
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
||||
RecyclerView.ViewHolder(binding.root), SetupCallback {
|
||||
RecyclerView.ViewHolder(binding.root),
|
||||
SetupCallback {
|
||||
lateinit var page: SetupPage
|
||||
|
||||
init {
|
||||
|
|
@ -49,7 +49,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
|||
onStepCompleted(0, pageFullyCompleted = true)
|
||||
}
|
||||
|
||||
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
|
||||
if (page.pageButtons != null &&
|
||||
page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE
|
||||
) {
|
||||
for (pageButton in page.pageButtons) {
|
||||
val pageButtonView = LayoutInflater.from(activity)
|
||||
.inflate(
|
||||
|
|
@ -108,9 +110,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
|
|||
.alpha(0.38f)
|
||||
.setDuration(200)
|
||||
.start()
|
||||
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
||||
button.setTextColor(
|
||||
button.context.getColor(
|
||||
com.google.android.material.R.color.material_on_surface_disabled
|
||||
)
|
||||
)
|
||||
button.iconTint =
|
||||
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
|
||||
ColorStateList.valueOf(
|
||||
button.context.getColor(
|
||||
com.google.android.material.R.color.material_on_surface_disabled
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.applets
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import java.io.Serializable
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object MiiSelector {
|
||||
lateinit var data: MiiSelectorData
|
||||
val finishLock = Object()
|
||||
|
||||
private fun ExecuteImpl(config: MiiSelectorConfig) {
|
||||
private fun executeImpl(config: MiiSelectorConfig) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
data = MiiSelectorData(0, 0)
|
||||
val fragment = MiiSelectorDialogFragment.newInstance(config)
|
||||
|
|
@ -22,8 +22,8 @@ object MiiSelector {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Execute(config: MiiSelectorConfig): MiiSelectorData {
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
|
||||
fun execute(config: MiiSelectorConfig): MiiSelectorData {
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
|
||||
synchronized(finishLock) {
|
||||
try {
|
||||
finishLock.wait()
|
||||
|
|
@ -43,5 +43,5 @@ object MiiSelector {
|
|||
lateinit var miiNames: Array<String>
|
||||
}
|
||||
|
||||
class MiiSelectorData (var returnCode: Long, var index: Int)
|
||||
class MiiSelectorData(var returnCode: Long, var index: Int)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -7,27 +7,27 @@ package org.citra.citra_emu.applets
|
|||
import android.text.InputFilter
|
||||
import android.text.Spanned
|
||||
import androidx.annotation.Keep
|
||||
import java.io.Serializable
|
||||
import org.citra.citra_emu.CitraApplication.Companion.appContext
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.fragments.KeyboardDialogFragment
|
||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object SoftwareKeyboard {
|
||||
lateinit var data: KeyboardData
|
||||
val finishLock = Object()
|
||||
|
||||
private fun ExecuteImpl(config: KeyboardConfig) {
|
||||
private fun executeImpl(config: KeyboardConfig) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
data = KeyboardData(0, "")
|
||||
KeyboardDialogFragment.newInstance(config)
|
||||
.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun HandleValidationError(config: KeyboardConfig, error: ValidationError) {
|
||||
fun handleValidationError(config: KeyboardConfig, error: ValidationError) {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
|
||||
val message: String = when (error) {
|
||||
ValidationError.FixedLengthRequired -> emulationActivity.getString(
|
||||
|
|
@ -54,12 +54,12 @@ object SoftwareKeyboard {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Execute(config: KeyboardConfig): KeyboardData {
|
||||
if (config.buttonConfig == ButtonConfig.None) {
|
||||
fun execute(config: KeyboardConfig): KeyboardData {
|
||||
if (config.buttonConfig == ButtonConfig.NONE) {
|
||||
Log.error("Unexpected button config None")
|
||||
return KeyboardData(0, "")
|
||||
}
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
|
||||
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
|
||||
synchronized(finishLock) {
|
||||
try {
|
||||
finishLock.wait()
|
||||
|
|
@ -69,8 +69,9 @@ object SoftwareKeyboard {
|
|||
return data
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun ShowError(error: String) {
|
||||
fun showError(error: String) {
|
||||
NativeLibrary.displayAlertMsg(
|
||||
appContext.resources.getString(R.string.software_keyboard),
|
||||
error,
|
||||
|
|
@ -78,20 +79,23 @@ object SoftwareKeyboard {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
private external fun ValidateFilters(text: String): ValidationError
|
||||
|
||||
@Suppress("FunctionName")
|
||||
external fun ValidateInput(text: String): ValidationError
|
||||
|
||||
/// Corresponds to Frontend::ButtonConfig
|
||||
// / Corresponds to Frontend::ButtonConfig
|
||||
interface ButtonConfig {
|
||||
companion object {
|
||||
const val Single = 0 /// Ok button
|
||||
const val Dual = 1 /// Cancel | Ok buttons
|
||||
const val Triple = 2 /// Cancel | I Forgot | Ok buttons
|
||||
const val None = 3 /// No button (returned by swkbdInputText in special cases)
|
||||
const val SINGLE = 0 // / Ok button
|
||||
const val DUAL = 1 // / Cancel | Ok buttons
|
||||
const val TRIPLE = 2 // / Cancel | I Forgot | Ok buttons
|
||||
const val NONE = 3 // / No button (returned by swkbdInputText in special cases)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to Frontend::ValidationError
|
||||
// / Corresponds to Frontend::ValidationError
|
||||
enum class ValidationError {
|
||||
None,
|
||||
|
||||
|
|
@ -128,7 +132,7 @@ object SoftwareKeyboard {
|
|||
lateinit var buttonText: Array<String>
|
||||
}
|
||||
|
||||
/// Corresponds to Frontend::KeyboardData
|
||||
// / Corresponds to Frontend::KeyboardData
|
||||
class KeyboardData(var button: Int, var text: String)
|
||||
class Filter : InputFilter {
|
||||
override fun filter(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -21,9 +21,10 @@ object StillImageCameraHelper {
|
|||
private var filePickerPath: String? = null
|
||||
|
||||
// Opens file picker for camera.
|
||||
@Suppress("unused")
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun OpenFilePicker(): String? {
|
||||
fun openFilePicker(): String? {
|
||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||
|
||||
// At this point, we are assuming that we already have permissions as they are
|
||||
|
|
@ -44,19 +45,21 @@ object StillImageCameraHelper {
|
|||
|
||||
// Called from EmulationActivity.
|
||||
@JvmStatic
|
||||
fun OnFilePickerResult(result: String) {
|
||||
fun onFilePickerResult(result: String) {
|
||||
filePickerPath = result
|
||||
synchronized(filePickerLock) { filePickerLock.notifyAll() }
|
||||
}
|
||||
|
||||
// Blocking call. Load image from file and crop/resize it to fit in width x height.
|
||||
@Suppress("unused")
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
|
||||
fun loadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
|
||||
val context = CitraApplication.appContext
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(uri)
|
||||
.size(width, height)
|
||||
.allowHardware(false)
|
||||
.build()
|
||||
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
|
||||
width,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -9,11 +9,10 @@ import android.content.Intent
|
|||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
|
||||
override fun createIntent(context: Context, input: Boolean?): Intent {
|
||||
return Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
override fun createIntent(context: Context, input: Boolean?): Intent =
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.setType("application/octet-stream")
|
||||
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
package org.citra.citra_emu.display
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntListSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
|
|
@ -19,7 +19,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
|
|||
class ScreenAdjustmentUtil(
|
||||
private val context: Context,
|
||||
private val windowManager: WindowManager,
|
||||
private val settings: Settings,
|
||||
private val settings: Settings
|
||||
) {
|
||||
fun swapScreen() {
|
||||
val isEnabled = !EmulationMenuSettings.swapScreens
|
||||
|
|
@ -31,11 +31,20 @@ class ScreenAdjustmentUtil(
|
|||
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
|
||||
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (NativeLibrary.isPortraitMode) {
|
||||
if (NativeLibrary.isPortraitMode()) {
|
||||
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
|
||||
val pos = portraitValues.indexOf(currentLayout)
|
||||
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
|
||||
|
|
@ -52,14 +61,32 @@ class ScreenAdjustmentUtil(
|
|||
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun changeScreenOrientation(layoutOption: Int) {
|
||||
IntSetting.SCREEN_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun changeSecondaryOrientation(layoutOption: Int) {
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
|
||||
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
|
||||
fun enableSecondaryDisplay(layoutOption: Int) {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = true
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
changeSecondaryOrientation(layoutOption)
|
||||
}
|
||||
|
||||
fun disableSecondaryDisplay() {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
fun changeActivityOrientation(orientationOption: Int) {
|
||||
|
|
@ -74,7 +101,6 @@ class ScreenAdjustmentUtil(
|
|||
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
|
||||
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
|
||||
NativeLibrary.reloadSettings()
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
|
||||
|
||||
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,8 @@ enum class ScreenLayout(val int: Int) {
|
|||
HYBRID_SCREEN(4),
|
||||
CUSTOM_LAYOUT(5);
|
||||
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): ScreenLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
||||
}
|
||||
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
|
|||
BELOW(7);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): SmallScreenPosition {
|
||||
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
||||
}
|
||||
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,23 +40,27 @@ enum class PortraitScreenLayout(val int: Int) {
|
|||
ORIGINAL(2);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): PortraitScreenLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
||||
}
|
||||
fun from(int: Int): PortraitScreenLayout =
|
||||
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
||||
}
|
||||
}
|
||||
|
||||
enum class SecondaryDisplayLayout(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
// NONE is no longer selectable in the interface, having been replaced with
|
||||
// the boolean ENABLE_SECONDARY_DISPLAY setting, but is left here for backwards compatibility
|
||||
NONE(0),
|
||||
TOP_SCREEN(1),
|
||||
BOTTOM_SCREEN(2),
|
||||
SIDE_BY_SIDE(3);
|
||||
SIDE_BY_SIDE(3),
|
||||
REVERSE_PRIMARY(4),
|
||||
ORIGINAL(5),
|
||||
HYBRID(6),
|
||||
LARGE_SCREEN(7)
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): SecondaryDisplayLayout {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,26 +73,22 @@ enum class StereoWhichDisplay(val int: Int) {
|
|||
SECONDARY_ONLY(3);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoWhichDisplay {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
enum class StereoMode(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
// These must match what is defined in src/common/settings.h
|
||||
|
||||
OFF(0),
|
||||
SIDE_BY_SIDE(1),
|
||||
SIDE_BY_SIDE_FULL(2),
|
||||
ANAGLYPH(3),
|
||||
INTERLACED(4),
|
||||
REVERSE_INTERLACED (5),
|
||||
CARDBOARD_VR (6);
|
||||
REVERSE_INTERLACED(5),
|
||||
CARDBOARD_VR(6);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoMode {
|
||||
return entries.firstOrNull { it.int == int } ?: OFF
|
||||
}
|
||||
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,24 +6,29 @@ package org.citra.citra_emu.display
|
|||
|
||||
import android.app.Presentation
|
||||
import android.content.Context
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
||||
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
||||
private var pres: SecondaryDisplayPresentation? = null
|
||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
private val vd: VirtualDisplay
|
||||
var preferredDisplayId = -1
|
||||
var currentDisplayId = -1
|
||||
|
||||
val availableDisplays: List<Display>
|
||||
get() = getSecondaryDisplays()
|
||||
|
||||
init {
|
||||
vd = displayManager.createVirtualDisplay(
|
||||
|
|
@ -38,48 +43,98 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||
}
|
||||
|
||||
fun updateSurface() {
|
||||
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
|
||||
val surface = pres?.getSurfaceHolder()?.surface
|
||||
if (surface != null && surface.isValid) {
|
||||
NativeLibrary.secondarySurfaceChanged(surface)
|
||||
} else {
|
||||
Log.warning("SecondaryDisplay Attempted to update null or invalid surface")
|
||||
}
|
||||
}
|
||||
|
||||
fun destroySurface() {
|
||||
NativeLibrary.secondarySurfaceDestroyed()
|
||||
}
|
||||
|
||||
private fun getExternalDisplay(context: Context): Display? {
|
||||
private fun getSecondaryDisplays(): List<Display> {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val currentDisplayId = context.display.displayId
|
||||
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.display.displayId
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
|
||||
.defaultDisplay.displayId
|
||||
}
|
||||
val displays = dm.displays
|
||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
|
||||
return displays.firstOrNull {
|
||||
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
return displays.filter {
|
||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
||||
val isNotDefaultOrPresentable =
|
||||
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
|
||||
|
||||
isNotDefaultOrPresentable &&
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDisplay() {
|
||||
// decide if we are going to the external display or the internal one
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||
display = vd.display
|
||||
// return early if the parent context is dead or dying
|
||||
if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
|
||||
return
|
||||
}
|
||||
|
||||
val displayToUse = if (availableDisplays.isEmpty() ||
|
||||
// Theoretically, the NONE option is no longer selectable, but
|
||||
// I am leaving this in for backwards compatibility
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
|
||||
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
|
||||
) {
|
||||
currentDisplayId = -1
|
||||
vd.display
|
||||
} else if (preferredDisplayId >= 0 &&
|
||||
availableDisplays.any { it.displayId == preferredDisplayId }
|
||||
) {
|
||||
currentDisplayId = preferredDisplayId
|
||||
availableDisplays.first { it.displayId == preferredDisplayId }
|
||||
} else {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val default = dm.displays.first { it.displayId == Display.DEFAULT_DISPLAY }
|
||||
// prioritize displays that have a different name from the default display, as
|
||||
// some devices such as the Odin 2 create a permanent virtual display with the same
|
||||
// name as the default display that should be skipped in most cases
|
||||
currentDisplayId = availableDisplays.firstOrNull {
|
||||
it.name != default.name && !it.name.contains("Built", true)
|
||||
}?.displayId
|
||||
?: availableDisplays[0].displayId
|
||||
availableDisplays.first { it.displayId == currentDisplayId }
|
||||
}
|
||||
|
||||
// if our presentation is already on the right display, ignore
|
||||
if (pres?.display == display) return
|
||||
if (pres?.display == displayToUse) return
|
||||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
||||
pres?.show()
|
||||
|
||||
try {
|
||||
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
|
||||
pres?.show()
|
||||
}
|
||||
// catch BadTokenException and InvalidDisplayException,
|
||||
// the display became invalid asynchronously, so we can assign to null
|
||||
// until onDisplayAdded/Removed/Changed is called and logic retriggered
|
||||
catch (_: WindowManager.BadTokenException) {
|
||||
pres = null
|
||||
} catch (_: WindowManager.InvalidDisplayException) {
|
||||
pres = null
|
||||
}
|
||||
}
|
||||
|
||||
fun releasePresentation() {
|
||||
pres?.dismiss()
|
||||
try {
|
||||
pres?.dismiss()
|
||||
} catch (_: Exception) { }
|
||||
pres = null
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||
}
|
||||
}
|
||||
class SecondaryDisplayPresentation(
|
||||
context: Context, display: Display, val parent: SecondaryDisplay
|
||||
context: Context,
|
||||
display: Display,
|
||||
val parent: SecondaryDisplay
|
||||
) : Presentation(context, display) {
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private var touchscreenPointerId = -1
|
||||
|
|
@ -118,16 +175,21 @@ class SecondaryDisplayPresentation(
|
|||
surfaceView = SurfaceView(context)
|
||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
|
||||
Log.debug("SecondaryDisplay Surface created")
|
||||
}
|
||||
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder, format: Int, width: Int, height: Int
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
Log.debug("SecondaryDisplay Surface changed: ${width}x$height")
|
||||
parent.updateSurface()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
Log.debug("SecondaryDisplay Surface destroyed")
|
||||
parent.destroySurface()
|
||||
}
|
||||
})
|
||||
|
|
@ -172,7 +234,5 @@ class SecondaryDisplayPresentation(
|
|||
}
|
||||
|
||||
// Publicly accessible method to get the SurfaceHolder
|
||||
fun getSurfaceHolder(): SurfaceHolder {
|
||||
return surfaceView.holder
|
||||
}
|
||||
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
|
|||
private var selectedCheatPosition = -1
|
||||
|
||||
fun initialize(titleId_: Long) {
|
||||
titleId = titleId_;
|
||||
titleId = titleId_
|
||||
load()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -170,24 +170,23 @@ class CheatDetailsFragment : Fragment() {
|
|||
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View?, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View?, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
|
||||
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarCheatDetails.layoutParams = mlpAppBar
|
||||
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarCheatDetails.layoutParams = mlpAppBar
|
||||
|
||||
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
|
||||
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ class CheatListFragment : Fragment() {
|
|||
left = leftInsets,
|
||||
right = rightInsets,
|
||||
bottom = barInsets.bottom +
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
|
||||
)
|
||||
|
||||
val mlpFab = binding.fab.layoutParams as MarginLayoutParams
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -41,7 +41,8 @@ class CheatsAdapter(
|
|||
}
|
||||
|
||||
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
|
||||
RecyclerView.ViewHolder(binding.root), View.OnClickListener,
|
||||
RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener,
|
||||
CompoundButton.OnCheckedChangeListener {
|
||||
private lateinit var viewModel: CheatsViewModel
|
||||
private lateinit var cheat: Cheat
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -32,7 +32,9 @@ import org.citra.citra_emu.ui.TwoPaneOnBackPressedCallback
|
|||
import org.citra.citra_emu.ui.main.MainActivity
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
|
||||
class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
||||
class CheatsFragment :
|
||||
Fragment(),
|
||||
SlidingPaneLayout.PanelSlideListener {
|
||||
private var cheatListLastFocus: View? = null
|
||||
private var cheatDetailsLastFocus: View? = null
|
||||
|
||||
|
|
@ -157,12 +159,15 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
|||
}
|
||||
|
||||
private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
|
||||
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value!!
|
||||
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value
|
||||
if (!cheatSelected && binding.slidingPaneLayout.isOpen) {
|
||||
binding.slidingPaneLayout.close()
|
||||
}
|
||||
binding.slidingPaneLayout.lockMode =
|
||||
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
binding.slidingPaneLayout.lockMode = if (cheatSelected) {
|
||||
SlidingPaneLayout.LOCK_MODE_UNLOCKED
|
||||
} else {
|
||||
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
fun onListViewFocusChange(hasFocus: Boolean) {
|
||||
|
|
@ -203,7 +208,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
|||
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
|
||||
// Set keyboard insets if the system supports smooth keyboard animations
|
||||
val mlpDetails = binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
|
||||
val mlpDetails = binding.cheatDetailsContainer.layoutParams
|
||||
as ViewGroup.MarginLayoutParams
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
if (keyboardInsets.bottom > 0) {
|
||||
mlpDetails.bottomMargin = keyboardInsets.bottom
|
||||
|
|
@ -231,14 +237,16 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
|
|||
runningAnimations: List<WindowInsetsAnimationCompat>
|
||||
): WindowInsetsCompat {
|
||||
val mlpDetails =
|
||||
binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
|
||||
binding.cheatDetailsContainer.layoutParams
|
||||
as ViewGroup.MarginLayoutParams
|
||||
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
||||
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
|
||||
binding.cheatDetailsContainer.layoutParams = mlpDetails
|
||||
return insets
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ enum class Hotkey(val button: Int) {
|
|||
PAUSE_OR_RESUME(10004),
|
||||
QUICKSAVE(10005),
|
||||
QUICKLOAD(10006),
|
||||
TURBO_LIMIT(10007);
|
||||
TURBO_LIMIT(10007),
|
||||
ENABLE(10008)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue