mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 10:43:39 -04:00
Compare commits
42 commits
master
...
2123.4-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e8cb3d97a | ||
|
|
d25eacdf83 | ||
|
|
e3b04fe22e | ||
|
|
7a436ca7e8 | ||
|
|
a63d6a3834 | ||
|
|
d246f5b436 | ||
|
|
9a926e44c1 | ||
|
|
f906242a23 | ||
|
|
33e7ed5c5c | ||
|
|
f911af4197 | ||
|
|
37dd01fd51 | ||
|
|
ba5215242f | ||
|
|
8e4d4efce4 | ||
|
|
14dccb3c47 | ||
|
|
5431705e79 | ||
|
|
633ae42fbb | ||
|
|
0ed6b7558e | ||
|
|
bf1a95f438 | ||
|
|
01dc2bb776 | ||
|
|
4ac3cab012 | ||
|
|
897447e9bd | ||
|
|
724576cc61 | ||
|
|
3e1b86548a | ||
|
|
246e06d1a4 | ||
|
|
a607e3dd22 | ||
|
|
a65114eabf | ||
|
|
6ac0733002 | ||
|
|
8519e92eae | ||
|
|
7f2ac35870 | ||
|
|
1e2dd5ea78 | ||
|
|
beba099fed | ||
|
|
c888c40b3e | ||
|
|
57995cd89c | ||
|
|
29a77b342b | ||
|
|
3ef5bc0bfe | ||
|
|
d94657a44d | ||
|
|
ee58988897 | ||
|
|
ec7f00c9a4 | ||
|
|
164b9329c7 | ||
|
|
2292f3ab1b | ||
|
|
3ab6a304cd | ||
|
|
2e3d926dd5 |
60 changed files with 1140 additions and 240 deletions
|
|
@ -9,8 +9,14 @@ fi
|
|||
|
||||
cd src/android
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease
|
||||
./gradlew bundleRelease
|
||||
|
||||
if [[ "$TARGET" == "googleplay" ]]; then
|
||||
./gradlew assembleGooglePlayRelease
|
||||
./gradlew bundleGooglePlayRelease
|
||||
else
|
||||
./gradlew assembleVanillaRelease
|
||||
./gradlew bundleVanillaRelease
|
||||
fi
|
||||
|
||||
ccache -s -v
|
||||
|
||||
|
|
|
|||
12
.ci/linux.sh
12
.ci/linux.sh
|
|
@ -1,14 +1,16 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
if [ "$TARGET" = "appimage" ]; then
|
||||
if [[ "$TARGET" == "appimage"* ]]; then
|
||||
# Compile the AppImage we distribute with Clang.
|
||||
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
-DCMAKE_LINKER=/etc/bin/ld.lld
|
||||
-DENABLE_ROOM_STANDALONE=OFF)
|
||||
# Bundle required QT wayland libraries
|
||||
export EXTRA_QT_PLUGINS="waylandcompositor"
|
||||
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
|
||||
if [ "$TARGET" = "appimage-wayland" ]; then
|
||||
# Bundle required QT wayland libraries
|
||||
export EXTRA_QT_PLUGINS="waylandcompositor"
|
||||
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
|
||||
fi
|
||||
else
|
||||
# For the linux-fresh verification target, verify compilation without PCH as well.
|
||||
export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF)
|
||||
|
|
@ -30,7 +32,7 @@ cmake .. -G Ninja \
|
|||
ninja
|
||||
strip -s bin/Release/*
|
||||
|
||||
if [ "$TARGET" = "appimage" ]; then
|
||||
if [[ "$TARGET" == "appimage"* ]]; then
|
||||
ninja bundle
|
||||
# TODO: Our AppImage environment currently uses an older ccache version without the verbose flag.
|
||||
ccache -s
|
||||
|
|
|
|||
35
.github/workflows/build.yml
vendored
35
.github/workflows/build.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["appimage", "fresh"]
|
||||
target: ["appimage", "appimage-wayland", "fresh"]
|
||||
container:
|
||||
image: opensauce04/azahar-build-environment:latest
|
||||
options: -u 1001
|
||||
|
|
@ -51,18 +51,22 @@ jobs:
|
|||
- name: Build
|
||||
run: ./.ci/linux.sh
|
||||
- name: Move AppImage to artifacts directory
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
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: ${{ matrix.target == 'appimage' }}
|
||||
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-13') || 'macos-14' }}
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-15' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -103,7 +107,7 @@ jobs:
|
|||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
macos-universal:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
needs: macos
|
||||
env:
|
||||
OS: macos
|
||||
|
|
@ -212,51 +216,64 @@ jobs:
|
|||
path: artifacts/
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["vanilla", "googleplay"]
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: content
|
||||
CCACHE_SLOPPINESS: time_macros
|
||||
OS: android
|
||||
TARGET: universal
|
||||
TARGET: ${{ matrix.target }}
|
||||
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-android-${{ github.sha }}
|
||||
key: ${{ runner.os }}-${{ env.OS }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-
|
||||
${{ runner.os }}-${{ env.OS }}-${{ matrix.target }}-
|
||||
- name: Set tag name
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
||||
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
fi
|
||||
echo $GIT_TAG_NAME
|
||||
- name: Deps
|
||||
- name: Install tools
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install ccache apksigner -y
|
||||
- name: Update Android SDK CMake version
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: |
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
|
||||
- name: Build
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
||||
env:
|
||||
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
|
||||
- name: Pack
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
run: ../../../.ci/pack.sh
|
||||
working-directory: src/android/app
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Upload
|
||||
if: ${{ env.SHOULD_RUN == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,15 @@ if (APPLE)
|
|||
else()
|
||||
# Minimum macOS 13
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
|
||||
|
||||
# Catch compiler issue on AppleClang versions below 15.0
|
||||
# TODO: Remove this check when we drop macOS 13 Ventura
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND
|
||||
CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15.0)
|
||||
message(FATAL_ERROR "AppleClang 15.0 or later is required due to a compiler bug in earlier versions.\n"
|
||||
"Current version: ${CMAKE_CXX_COMPILER_VERSION}\n"
|
||||
"After updating, delete 'CMakeCache.txt' in the build directory.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
@ -299,7 +308,7 @@ find_package(Threads REQUIRED)
|
|||
|
||||
if (ENABLE_QT)
|
||||
if (NOT USE_SYSTEM_QT)
|
||||
download_qt(6.7.2)
|
||||
download_qt(6.9.2)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
|
|
|
|||
|
|
@ -130,25 +130,10 @@ if (BUNDLE_TARGET_EXECUTE)
|
|||
--icon-file "${CMAKE_BINARY_DIR}/dist/org.azahar_emu.Azahar.svg"
|
||||
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
||||
--appdir "${appdir_path}"
|
||||
RESULT_VARIABLE linuxdeploy_appdir_result)
|
||||
RESULT_VARIABLE linuxdeploy_appdir_result
|
||||
ERROR_VARIABLE linuxdeploy_appdir_error)
|
||||
if (NOT linuxdeploy_appdir_result EQUAL "0")
|
||||
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
|
||||
endif()
|
||||
|
||||
if (enable_qt)
|
||||
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
|
||||
file(READ "${qt_hook_file}" qt_hook_contents)
|
||||
# Add Cinnamon to list of DEs for GTK3 theming.
|
||||
string(REPLACE
|
||||
"*XFCE*"
|
||||
"*X-Cinnamon*|*XFCE*"
|
||||
qt_hook_contents "${qt_hook_contents}")
|
||||
# Wayland backend crashes due to changed schemas in Gnome 40.
|
||||
string(REPLACE
|
||||
"export QT_QPA_PLATFORMTHEME=gtk3"
|
||||
"export QT_QPA_PLATFORMTHEME=gtk3; export GDK_BACKEND=x11"
|
||||
qt_hook_contents "${qt_hook_contents}")
|
||||
file(WRITE "${qt_hook_file}" "${qt_hook_contents}")
|
||||
message(FATAL_ERROR "linuxdeploy failed to create AppDir w/ exit code ${linuxdeploy_appdir_result}:\n${linuxdeploy_appdir_error}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Creating AppImage for executable ${executable_path}")
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
|
|||
set(arch_path "mingw_64")
|
||||
elseif (MSVC)
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
set(arch_path "msvc2019_arm64")
|
||||
set(arch_path "msvc2022_arm64")
|
||||
elseif ("x86_64" IN_LIST ARCHITECTURE)
|
||||
set(arch_path "msvc2019_64")
|
||||
set(arch_path "msvc2022_64")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
||||
endif()
|
||||
|
|
@ -30,12 +30,13 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
|
|||
|
||||
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
|
||||
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
||||
set(host_arch_path "msvc2019_64")
|
||||
set(host_arch_path "msvc2022_64")
|
||||
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
|
||||
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
|
||||
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
|
||||
# TODO: ^ Is this still true with msvc2022?
|
||||
# set(host_arch_path "msvc2019_arm64")
|
||||
set(host_arch_path "msvc2019_64")
|
||||
set(host_arch_path "msvc2022_64")
|
||||
endif()
|
||||
set(host_arch "win64_${host_arch_path}")
|
||||
else()
|
||||
|
|
@ -105,7 +106,7 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
|
|||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
||||
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.18")
|
||||
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0")
|
||||
if (WIN32)
|
||||
set(aqt_path "${base_path}/aqt.exe")
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ Download the latest release from [Releases](https://github.com/azahar-emu/azahar
|
|||
If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2.
|
||||
|
||||
---
|
||||
|
||||
### MacOS
|
||||
|
||||
To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
||||
|
|
@ -55,6 +54,11 @@ The recommended format for using Azahar on Linux is the Flatpak available on Fla
|
|||
|
||||
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
|
||||
|
||||
If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)).
|
||||
|
||||
Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended.
|
||||
|
||||
If you are using the Flatpak and run into issues with Wayland, you can disable Wayland support for the Azahar Flatpak using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal).
|
||||
|
||||
# Build instructions
|
||||
|
||||
|
|
|
|||
3
dist/apple/Info.plist.in
vendored
3
dist/apple/Info.plist.in
vendored
|
|
@ -75,6 +75,9 @@
|
|||
<true/>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
<key>UIDesignRequiresCompatibility</key>
|
||||
<true/> <!-- Remove when Qt Liquid Glass issues are fixed upstream:
|
||||
https://bugreports.qt.io/browse/QTBUG-138942 -->
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
|
|
|||
2
dist/compatibility_list
vendored
2
dist/compatibility_list
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 4f39041699412873d0afcec89a9313148a192647
|
||||
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
|
||||
54
dist/languages/ca_ES_valencia.ts
vendored
54
dist/languages/ca_ES_valencia.ts
vendored
|
|
@ -27,7 +27,7 @@
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/aboutdialog.ui" line="30"/>
|
||||
<source><html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><html><head/><body><p><img src=":/icons/default/256x256/azahar.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/aboutdialog.ui" line="60"/>
|
||||
|
|
@ -725,7 +725,7 @@ Desitja ignorar l'error i continuar?</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="107"/>
|
||||
<source>Show log output in console</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Mostrar l'eixida del registre en la consola</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_debug.ui" line="114"/>
|
||||
|
|
@ -2275,7 +2275,7 @@ Desitja ignorar l'error i continuar?</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="102"/>
|
||||
<source><a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><a href='https://web.archive.org/web/20240301211230/https://citra-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Més Informació</span></a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_motion_touch.cpp" line="209"/>
|
||||
|
|
@ -2494,12 +2494,12 @@ Desitja ignorar l'error i continuar?</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="187"/>
|
||||
<source>Compress installed CIA content</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Comprimir el contingut de CIAs instal·lats</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_storage.ui" line="190"/>
|
||||
<source>Compresses the content of CIA files when installed to the emulated SD card. Only affects CIA content which is installed while the setting is enabled.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_storage.cpp" line="26"/>
|
||||
|
|
@ -4431,7 +4431,7 @@ Vols reinstal·lar els arxius de totes maneres?</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
||||
<source>3DS Installation File (*.cia *.zcia)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Fitxers d'Instalació de 3DS (*.cia *.zcia)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2250"/>
|
||||
|
|
@ -4510,24 +4510,24 @@ Vols reinstal·lar els arxius de totes maneres?</translation>
|
|||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3107"/>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3113"/>
|
||||
<source>Error compressing file</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Error al comprimir el fitxer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
||||
<source>File compress operation failed, check log for details.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Operació de compressió fallida, mira el registre per a més detalls.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
||||
<source>Error decompressing file</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Error de descompressió del fitxer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||
<source>File decompress operation failed, check log for details.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Operació de descompressió fallida, mira el registre per a més detalls.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
||||
|
|
@ -4663,62 +4663,62 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3063"/>
|
||||
<source>Load 3DS ROM File</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Carregar ROM de 3DS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3064"/>
|
||||
<source>3DS ROM Files (*.cia *cci *3dsx *cxi)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Fitxers ROM 3DS (*.cia *cci *3dsx *cxi)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3108"/>
|
||||
<source>The selected file is not a compatible 3DS ROM format. Make sure you have chosen the right file, and that it is not encrypted.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El fitxer seleccionat no és un ROM de 3DS compatible. Assegura't que has triat el fitxer correcte i que no estiga xifrat.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3114"/>
|
||||
<source>The selected file is already compressed.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El fitxer seleccionat ja està comprimit.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3119"/>
|
||||
<source>3DS Compressed ROM File (*.%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Fitxer ROM 3DS comprimit (*.%1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3127"/>
|
||||
<source>Save 3DS Compressed ROM File</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Desar fitxer 3DS comprimit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3152"/>
|
||||
<source>Load 3DS Compressed ROM File</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Carregar fitxer 3DS comprimit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3153"/>
|
||||
<source>3DS Compressed ROM Files (*.zcia *zcci *z3dsx *zcxi)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Fitxer ROM 3DS comprimit (*.zcia *zcci *z3dsx *zcxi)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3182"/>
|
||||
<source>The selected file is not a compatible compressed 3DS ROM format. Make sure you have chosen the right file.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El fitxer seleccionat no és un format de ROM 3DS comprimit compatible. Assegura't d'haver triat l'arxiu correcte.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3189"/>
|
||||
<source>The selected file is already decompressed.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El fitxer seleccionat ja està descomprimit.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3194"/>
|
||||
<source>3DS ROM File (*.%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Fitxer ROM 3DS (*.%1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3202"/>
|
||||
<source>Save 3DS ROM File</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Desar fitxer ROM 3DS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3227"/>
|
||||
|
|
@ -4818,7 +4818,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3431"/>
|
||||
<source>Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3440"/>
|
||||
|
|
@ -4839,7 +4839,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3600"/>
|
||||
<source>%1 is missing. Please <a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>dump your system archives</a>.<br/>Continuing emulation may result in crashes and bugs.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Falta %1 . Per favor,<a href='https://web.archive.org/web/20240304201103/https://citra-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>bolca els teus arxius de sistema</a>.<br/>Continuar l'emulació pot resultar en penges i errors.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3608"/>
|
||||
|
|
@ -4869,7 +4869,7 @@ Per a veure una guia sobre com instal·lar FFmpeg, polsa Ajuda.</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3627"/>
|
||||
<source>A fatal error occurred. <a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Check the log</a> for details.<br/>Continuing emulation may result in crashes and bugs.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Error fatal.<a href='https://web.archive.org/web/20240228001712/https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Mira el log</a>per a més detalls.<br/>Continuar l'emulació pot resultar en penges i errors.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3632"/>
|
||||
|
|
@ -6387,12 +6387,12 @@ Missatge de depuració:</translation>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/main.ui" line="466"/>
|
||||
<source>Compress ROM File...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Comprimir fitxer ROM...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/main.ui" line="471"/>
|
||||
<source>Decompress ROM File...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Descomprimir fitxer ROM...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/main.ui" line="479"/>
|
||||
|
|
|
|||
4
dist/languages/es_ES.ts
vendored
4
dist/languages/es_ES.ts
vendored
|
|
@ -4517,7 +4517,7 @@ Reinstall the files anyway?</source>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2339"/>
|
||||
<source>File compress operation failed, check log for details.</source>
|
||||
<translation>Operación de comprensión fallida, mira el registro para más detalles.</translation>
|
||||
<translation>Operación de compresión fallida, mira el registro para más detalles.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2341"/>
|
||||
|
|
@ -4529,7 +4529,7 @@ Reinstall the files anyway?</source>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||
<source>File decompress operation failed, check log for details.</source>
|
||||
<translation>Operación de descomprensión fallida, mira el registro para más detalles.</translation>
|
||||
<translation>Operación de descompresión fallida, mira el registro para más detalles.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2364"/>
|
||||
|
|
|
|||
15
dist/languages/ru_RU.ts
vendored
15
dist/languages/ru_RU.ts
vendored
|
|
@ -3894,7 +3894,7 @@ Drag points to change position, or double-click table cells to edit values.</sou
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/configuration/configure_web.ui" line="26"/>
|
||||
<source>Show current application in your Discord status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Показывать текущее приложение в статусе Discord</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
|
@ -4027,7 +4027,7 @@ Drag points to change position, or double-click table cells to edit values.</sou
|
|||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
||||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="85"/>
|
||||
<source>Azahar</source>
|
||||
<translation type="unfinished">Azahar</translation>
|
||||
<translation>Azahar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/dumping/dumping_dialog.cpp" line="25"/>
|
||||
|
|
@ -4170,7 +4170,7 @@ Please check your FFmpeg installation used for compilation.</source>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="1299"/>
|
||||
<source>GBA Virtual Console is not supported by Azahar.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Azahar не поддерживает GBA Virtual Console.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="1304"/>
|
||||
|
|
@ -4315,7 +4315,7 @@ Please check your FFmpeg installation used for compilation.</source>
|
|||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3682"/>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3775"/>
|
||||
<source>Azahar</source>
|
||||
<translation type="unfinished">Azahar</translation>
|
||||
<translation>Azahar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2066"/>
|
||||
|
|
@ -4523,7 +4523,7 @@ Reinstall the files anyway?</source>
|
|||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3181"/>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="3188"/>
|
||||
<source>Error decompressing file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Ошибра при разжатии файла</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/citra_qt.cpp" line="2342"/>
|
||||
|
|
@ -5036,7 +5036,7 @@ Would you like to download it?</source>
|
|||
<message>
|
||||
<location filename="../../src/citra_qt/game_list.cpp" line="366"/>
|
||||
<source>Don't show again</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Не показывать снова</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/game_list.cpp" line="564"/>
|
||||
|
|
@ -7383,7 +7383,8 @@ They may have left the room.</source>
|
|||
<source>Azahar has detected user data for Citra.
|
||||
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Azahar обнаружил файлы пользователя Citra.
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/citra_qt/user_data_migration.cpp" line="77"/>
|
||||
|
|
|
|||
67
externals/CMakeLists.txt
vendored
67
externals/CMakeLists.txt
vendored
|
|
@ -1,24 +1,37 @@
|
|||
# Definitions for all external bundled libraries
|
||||
|
||||
# Suppress warnings from external libraries
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
if (MSVC)
|
||||
add_compile_options(/W0)
|
||||
else()
|
||||
add_compile_options(-w)
|
||||
endif()
|
||||
|
||||
function(target_disable_warnings target)
|
||||
if (MSVC)
|
||||
target_compile_options(${target} INTERFACE /W0)
|
||||
else()
|
||||
target_compile_options(${target} INTERFACE -w)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
||||
include(DownloadExternals)
|
||||
include(ExternalProject)
|
||||
|
||||
# Boost
|
||||
if (NOT USE_SYSTEM_BOOST)
|
||||
if (USE_SYSTEM_BOOST)
|
||||
unset(BOOST_ROOT CACHE)
|
||||
unset(Boost_INCLUDE_DIR CACHE)
|
||||
set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE)
|
||||
else()
|
||||
message(STATUS "Including vendored Boost library")
|
||||
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
|
||||
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
|
||||
add_library(boost INTERFACE)
|
||||
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
|
||||
target_include_directories(boost INTERFACE ${Boost_INCLUDE_DIR})
|
||||
target_disable_warnings(boost)
|
||||
|
||||
# Boost::serialization
|
||||
file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
|
||||
|
|
@ -33,11 +46,7 @@ if (NOT USE_SYSTEM_BOOST)
|
|||
${CMAKE_SOURCE_DIR}/externals/boost/libs/iostreams/src/mapped_file.cpp
|
||||
)
|
||||
target_link_libraries(boost_iostreams PUBLIC boost)
|
||||
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
||||
else()
|
||||
unset(BOOST_ROOT CACHE)
|
||||
unset(Boost_INCLUDE_DIR CACHE)
|
||||
set(Boost_NO_SYSTEM_PATHS OFF CACHE BOOL "" FORCE)
|
||||
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
||||
endif()
|
||||
|
||||
# Catch2
|
||||
|
|
@ -73,6 +82,7 @@ endif()
|
|||
# dds-ktx
|
||||
add_library(dds-ktx INTERFACE)
|
||||
target_include_directories(dds-ktx INTERFACE ./dds-ktx)
|
||||
target_disable_warnings(dds-ktx)
|
||||
|
||||
# fmt and Xbyak need to be added before dynarmic
|
||||
# libfmt
|
||||
|
|
@ -137,7 +147,8 @@ endif()
|
|||
|
||||
# MicroProfile
|
||||
add_library(microprofile INTERFACE)
|
||||
target_include_directories(microprofile SYSTEM INTERFACE ./microprofile)
|
||||
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
target_disable_warnings(microprofile)
|
||||
if (ENABLE_MICROPROFILE)
|
||||
target_compile_definitions(microprofile INTERFACE MICROPROFILE_ENABLED=1)
|
||||
else()
|
||||
|
|
@ -146,10 +157,11 @@ endif()
|
|||
|
||||
# Nihstro
|
||||
add_library(nihstro-headers INTERFACE)
|
||||
target_include_directories(nihstro-headers SYSTEM INTERFACE ./nihstro/include)
|
||||
if (MSVC)
|
||||
# TODO: For some reason MSVC still applies this warning even with /W0 for externals.
|
||||
target_compile_options(nihstro-headers INTERFACE /wd4715)
|
||||
target_include_directories(nihstro-headers INTERFACE ./nihstro/include)
|
||||
target_disable_warnings(nihstro-headers)
|
||||
if (NOT MSVC)
|
||||
# TODO: For some reason MSYS2 still applied this warnin even with -w
|
||||
target_compile_options(nihstro-headers INTERFACE -Wno-invalid-specialization)
|
||||
endif()
|
||||
|
||||
# Open Source Archives
|
||||
|
|
@ -173,7 +185,8 @@ if (USE_SYSTEM_FFMPEG_HEADERS)
|
|||
endif()
|
||||
if (NOT FOUND_FFMPEG_HEADERS)
|
||||
message(STATUS "Using bundled ffmpeg headers.")
|
||||
target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/ffmpeg/include)
|
||||
target_include_directories(library-headers INTERFACE ./library-headers/ffmpeg/include)
|
||||
target_disable_warnings(library-headers)
|
||||
endif()
|
||||
|
||||
# SoundTouch
|
||||
|
|
@ -294,7 +307,8 @@ if (USE_SYSTEM_JSON)
|
|||
# Citra uses "#include <json.hpp>" so we have to add this manually
|
||||
target_include_directories(json-headers SYSTEM INTERFACE "${NLOHMANN_PREFIX}/nlohmann")
|
||||
else()
|
||||
target_include_directories(json-headers SYSTEM INTERFACE ./json)
|
||||
target_include_directories(json-headers INTERFACE ./json)
|
||||
target_disable_warnings(json-headers)
|
||||
endif()
|
||||
|
||||
# OpenSSL
|
||||
|
|
@ -310,7 +324,8 @@ if (NOT OPENSSL_FOUND)
|
|||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||
target_include_directories(ssl SYSTEM INTERFACE ./libressl/include)
|
||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||
target_disable_warnings(ssl)
|
||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||
get_directory_property(OPENSSL_LIBRARIES
|
||||
DIRECTORY libressl
|
||||
|
|
@ -327,17 +342,20 @@ if(USE_SYSTEM_CPP_HTTPLIB)
|
|||
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
||||
if(HTTP_LIBS)
|
||||
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_disable_warnings(httplib)
|
||||
else()
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
endif()
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_disable_warnings(httplib)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_disable_warnings(httplib)
|
||||
endif()
|
||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
|
@ -354,7 +372,8 @@ if (ENABLE_WEB_SERVICE)
|
|||
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
||||
else()
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_include_directories(cpp-jwt SYSTEM INTERFACE ./cpp-jwt/include)
|
||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||
target_disable_warnings(cpp-jwt)
|
||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -453,7 +472,8 @@ if (ENABLE_VULKAN)
|
|||
endif()
|
||||
else()
|
||||
add_library(vma INTERFACE)
|
||||
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
|
||||
target_include_directories(vma INTERFACE ./vma/include)
|
||||
target_disable_warnings(vma)
|
||||
endif()
|
||||
|
||||
# vulkan-headers
|
||||
|
|
@ -465,7 +485,8 @@ if (ENABLE_VULKAN)
|
|||
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
|
||||
endif()
|
||||
else()
|
||||
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
target_disable_warnings(vulkan-headers)
|
||||
endif()
|
||||
|
||||
# adrenotools
|
||||
|
|
|
|||
2
externals/boost
vendored
2
externals/boost
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 3c27c785ad0f8a742af02e620dc225673f3a12d8
|
||||
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
|
||||
2
externals/fmt
vendored
2
externals/fmt
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a
|
||||
Subproject commit e424e3f2e607da02742f73db84873b8084fc714c
|
||||
|
|
@ -125,7 +125,7 @@ android {
|
|||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isShrinkResources = true
|
||||
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
proguardFiles(
|
||||
|
|
@ -135,6 +135,22 @@ android {
|
|||
isDefault = true
|
||||
}
|
||||
|
||||
// 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.
|
||||
register("relWithDebInfoLite") {
|
||||
initWith(getByName("relWithDebInfo"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isDebuggable = false
|
||||
installation {
|
||||
enableBaselineProfile = false // Disabled by default when isDebuggable is true
|
||||
}
|
||||
lint {
|
||||
checkReleaseBuilds = false // Ditto
|
||||
// The name of this property is misleading, this doesn't actually disable linting for the `release` build.
|
||||
}
|
||||
}
|
||||
|
||||
// Signed by debug key disallowing distribution on Play Store.
|
||||
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
||||
debug {
|
||||
|
|
@ -148,6 +164,18 @@ android {
|
|||
|
||||
flavorDimensions.add("version")
|
||||
|
||||
productFlavors {
|
||||
register("vanilla") {
|
||||
isDefault = true
|
||||
dimension = "version"
|
||||
versionNameSuffix = "-vanilla"
|
||||
}
|
||||
register("googlePlay") {
|
||||
dimension = "version"
|
||||
versionNameSuffix = "-googleplay"
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.25.0+"
|
||||
|
|
@ -186,7 +214,7 @@ dependencies {
|
|||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
||||
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.304.1/android-binaries-1.4.304.1.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)
|
||||
}
|
||||
|
|
|
|||
8
src/android/app/src/googlePlay/AndroidManifest.xml
Normal file
8
src/android/app/src/googlePlay/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- These permissions aren't allowed by Google. We asked, and they declined. -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
|
||||
|
||||
</manifest>
|
||||
|
|
@ -29,6 +29,8 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name="org.citra.citra_emu.CitraApplication"
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ package org.citra.citra_emu
|
|||
import android.Manifest.permission
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Surface
|
||||
|
|
@ -18,11 +20,14 @@ import android.view.View
|
|||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
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 org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RemovableStorageHelper
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
|
||||
|
|
@ -96,6 +101,24 @@ object NativeLibrary {
|
|||
*/
|
||||
external fun onTouchMoved(xAxis: Float, yAxis: Float)
|
||||
|
||||
/**
|
||||
* Handles touch events on the secondary display.
|
||||
*
|
||||
* @param xAxis The value of the x-axis.
|
||||
* @param yAxis The value of the y-axis.
|
||||
* @param pressed To identify if the touch held down or released.
|
||||
* @return true if the pointer is within the touchscreen
|
||||
*/
|
||||
external fun onSecondaryTouchEvent(xAxis: Float, yAxis: Float, pressed: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Handles touch movement on the secondary display.
|
||||
*
|
||||
* @param xAxis The value of the instantaneous x-axis.
|
||||
* @param yAxis The value of the instantaneous y-axis.
|
||||
*/
|
||||
external fun onSecondaryTouchMoved(xAxis: Float, yAxis: Float)
|
||||
|
||||
external fun reloadSettings()
|
||||
|
||||
external fun getTitleId(filename: String): Long
|
||||
|
|
@ -611,6 +634,36 @@ object NativeLibrary {
|
|||
FileUtil.getFilesName(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getUserDirectory(uriOverride: Uri? = null): String {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String): Long =
|
||||
|
|
@ -620,6 +673,10 @@ object NativeLibrary {
|
|||
FileUtil.getFileSize(path)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getBuildFlavor(): String = BuildConfig.FLAVOR
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun fileExists(path: String): Boolean =
|
||||
|
|
@ -671,6 +728,24 @@ object NativeLibrary {
|
|||
FileUtil.renameFile(path, destinationFilename)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun updateDocumentLocation(sourcePath: String, destinationPath: String): Boolean =
|
||||
CitraApplication.documentsTree.updateDocumentLocation(sourcePath, destinationPath)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun moveFile(filename: String, sourceDirPath: String, destinationDirPath: String): Boolean =
|
||||
if (FileUtil.isNativePath(sourceDirPath)) {
|
||||
try {
|
||||
CitraApplication.documentsTree.moveFile(filename, sourceDirPath, destinationDirPath)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
FileUtil.moveFile(filename, sourceDirPath, destinationDirPath)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun deleteDocument(path: String): Boolean =
|
||||
|
|
|
|||
|
|
@ -60,7 +60,15 @@ class EmulationActivity : AppCompatActivity() {
|
|||
private lateinit var binding: ActivityEmulationBinding
|
||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||
private lateinit var hotkeyUtility: HotkeyUtility
|
||||
private lateinit var secondaryDisplay: SecondaryDisplay;
|
||||
private lateinit var secondaryDisplay: SecondaryDisplay
|
||||
|
||||
private val onShutdown = Runnable {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
finishAffinity()
|
||||
} else {
|
||||
this.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private val emulationFragment: EmulationFragment
|
||||
get() {
|
||||
|
|
@ -77,8 +85,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
ThemeUtil.setTheme(this)
|
||||
settingsViewModel.settings.loadSettings()
|
||||
super.onCreate(savedInstanceState)
|
||||
secondaryDisplay = SecondaryDisplay(this);
|
||||
secondaryDisplay.updateDisplay();
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||
|
|
@ -101,13 +109,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
windowManager.defaultDisplay.rotation
|
||||
)
|
||||
|
||||
EmulationLifecycleUtil.addShutdownHook(hook = {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
finishAffinity()
|
||||
} else {
|
||||
this.finish()
|
||||
}
|
||||
})
|
||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||
|
||||
isEmulationRunning = true
|
||||
instance = this
|
||||
|
|
@ -165,12 +167,12 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationLifecycleUtil.clear()
|
||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
isEmulationRunning = false
|
||||
instance = null
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD();
|
||||
secondaryDisplay.releaseVD()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,29 +11,30 @@ import android.hardware.display.DisplayManager
|
|||
import android.hardware.display.VirtualDisplay
|
||||
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 org.citra.citra_emu.NativeLibrary
|
||||
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
|
||||
|
||||
class SecondaryDisplay(val context: Context) {
|
||||
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
|
||||
|
||||
init {
|
||||
val st = SurfaceTexture(0)
|
||||
st.setDefaultBufferSize(1920, 1080)
|
||||
val vdSurface = Surface(st)
|
||||
vd = displayManager.createVirtualDisplay(
|
||||
"HiddenDisplay",
|
||||
1920,
|
||||
1080,
|
||||
320,
|
||||
vdSurface,
|
||||
null,
|
||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
|
||||
)
|
||||
displayManager.registerDisplayListener(this, null)
|
||||
}
|
||||
|
||||
fun updateSurface() {
|
||||
|
|
@ -44,16 +45,23 @@ class SecondaryDisplay(val context: Context) {
|
|||
NativeLibrary.secondarySurfaceDestroyed()
|
||||
}
|
||||
|
||||
private fun getExternalDisplay(context: Context): Display? {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY
|
||||
val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" }
|
||||
}
|
||||
|
||||
fun updateDisplay() {
|
||||
// decide if we are going to the external display or the internal one
|
||||
var display = getCustomerDisplay()
|
||||
var display = getExternalDisplay(context)
|
||||
if (display == null ||
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
|
||||
display = vd.display
|
||||
}
|
||||
|
||||
// if our presentation is already on the right display, ignore
|
||||
if (pres?.display == display) return;
|
||||
if (pres?.display == display) return
|
||||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
|
|
@ -61,29 +69,41 @@ class SecondaryDisplay(val context: Context) {
|
|||
pres?.show()
|
||||
}
|
||||
|
||||
private fun getCustomerDisplay(): Display? {
|
||||
val displays = displayManager.displays
|
||||
// code taken from MelonDS dual screen - should fix odin 2 detection bug
|
||||
return displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
||||
.firstOrNull { it.displayId != Display.DEFAULT_DISPLAY && it.name != "Built-in Screen" && it.name != "HiddenDisplay"}
|
||||
}
|
||||
|
||||
fun releasePresentation() {
|
||||
pres?.dismiss()
|
||||
pres = null
|
||||
}
|
||||
|
||||
fun releaseVD() {
|
||||
displayManager.unregisterDisplayListener(this)
|
||||
vd.release()
|
||||
}
|
||||
|
||||
override fun onDisplayAdded(displayId: Int) {
|
||||
updateDisplay()
|
||||
}
|
||||
|
||||
override fun onDisplayRemoved(displayId: Int) {
|
||||
updateDisplay()
|
||||
}
|
||||
override fun onDisplayChanged(displayId: Int) {
|
||||
updateDisplay()
|
||||
}
|
||||
}
|
||||
class SecondaryDisplayPresentation(
|
||||
context: Context, display: Display, val parent: SecondaryDisplay
|
||||
) : Presentation(context, display) {
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private var touchscreenPointerId = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window?.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
)
|
||||
|
||||
// Initialize SurfaceView
|
||||
surfaceView = SurfaceView(context)
|
||||
|
|
@ -103,6 +123,42 @@ class SecondaryDisplayPresentation(
|
|||
}
|
||||
})
|
||||
|
||||
this.surfaceView.setOnTouchListener { _, event ->
|
||||
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (touchscreenPointerId == -1) {
|
||||
touchscreenPointerId = pointerId
|
||||
NativeLibrary.onSecondaryTouchEvent(
|
||||
event.getX(pointerIndex),
|
||||
event.getY(pointerIndex),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val index = event.findPointerIndex(touchscreenPointerId)
|
||||
if (index != -1) {
|
||||
NativeLibrary.onSecondaryTouchMoved(
|
||||
event.getX(index),
|
||||
event.getY(index)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
if (pointerId == touchscreenPointerId) {
|
||||
NativeLibrary.onSecondaryTouchEvent(0f, 0f, false)
|
||||
touchscreenPointerId = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
setContentView(surfaceView) // Set SurfaceView as content
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright Citra Emulator Project / Lime3DS Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
|
|||
import org.citra.citra_emu.features.settings.model.FloatSetting
|
||||
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting?,
|
||||
|
|
@ -27,7 +26,8 @@ class SliderSetting(
|
|||
val selectedFloat: Float
|
||||
get() {
|
||||
val setting = setting ?: return defaultValue!!.toFloat()
|
||||
return when (setting) {
|
||||
|
||||
val ret = when (setting) {
|
||||
is AbstractIntSetting -> setting.int.toFloat()
|
||||
is FloatSetting -> setting.float
|
||||
is ScaledFloatSetting -> setting.float
|
||||
|
|
@ -36,8 +36,8 @@ class SliderSetting(
|
|||
-1f
|
||||
}
|
||||
}
|
||||
return ret.coerceIn(min.toFloat(), max.toFloat())
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to the backing int. If that int was previously null,
|
||||
* initializes a new one and returns it, so it can be added to the Hashmap.
|
||||
|
|
|
|||
|
|
@ -1,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.
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class CitraDirectoryDialogFragment : DialogFragment() {
|
|||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
||||
if (!PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||
(requireActivity() as MainActivity)?.openCitraDirectory?.launch(null)
|
||||
PermissionsHandler.compatibleSelectDirectory((requireActivity() as MainActivity).openCitraDirectory)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
private val onPause = Runnable{ togglePause() }
|
||||
private val onShutdown = Runnable{ emulationState.stop() }
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is EmulationActivity) {
|
||||
|
|
@ -156,8 +159,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
emulationState = EmulationState(game.path)
|
||||
emulationActivity = requireActivity() as EmulationActivity
|
||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
||||
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
|
||||
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
|
||||
EmulationLifecycleUtil.addPauseResumeHook(onPause)
|
||||
EmulationLifecycleUtil.addShutdownHook(onShutdown)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -507,6 +510,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationLifecycleUtil.removeHook(onPause)
|
||||
EmulationLifecycleUtil.removeHook(onShutdown)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupCitraDirectoriesThenStartEmulation() {
|
||||
val directoryInitializationState = DirectoryInitialization.start()
|
||||
if (directoryInitializationState ===
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
// 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.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.ui.main.MainActivity
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
|
||||
class GrantMissingFilesystemPermissionFragment : DialogFragment() {
|
||||
private lateinit var mainActivity: MainActivity
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
isCancelable = false
|
||||
|
||||
val requestPermissionFunction =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
{
|
||||
manageExternalStoragePermissionLauncher.launch(
|
||||
Intent(
|
||||
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||
Uri.fromParts("package", mainActivity.packageName, null)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
{ permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.filesystem_permission_warning)
|
||||
.setMessage(R.string.filesystem_permission_lost)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
requestPermissionFunction()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private val manageExternalStoragePermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
}
|
||||
|
||||
private val permissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (isGranted) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "GrantMissingFilesystemPermissionFragment"
|
||||
|
||||
fun newInstance(): GrantMissingFilesystemPermissionFragment {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
return GrantMissingFilesystemPermissionFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.select_citra_user_folder,
|
||||
R.string.select_citra_user_folder_home_description,
|
||||
R.drawable.ic_home,
|
||||
{ mainActivity?.openCitraDirectory?.launch(null) },
|
||||
{ PermissionsHandler.compatibleSelectDirectory(mainActivity.openCitraDirectory) },
|
||||
details = homeViewModel.userDir
|
||||
),
|
||||
HomeSetting(
|
||||
|
|
|
|||
|
|
@ -13,21 +13,25 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.ui.main.MainActivity
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
|
||||
class SelectUserDirectoryDialogFragment : DialogFragment() {
|
||||
class SelectUserDirectoryDialogFragment(titleOverride: Int? = null, descriptionOverride: Int? = null) : DialogFragment() {
|
||||
private lateinit var mainActivity: MainActivity
|
||||
|
||||
private val title = titleOverride ?: R.string.select_citra_user_folder
|
||||
private val description = descriptionOverride ?: R.string.selecting_user_directory_without_write_permissions
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
isCancelable = false
|
||||
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.select_citra_user_folder)
|
||||
.setMessage(R.string.selecting_user_directory_without_write_permissions)
|
||||
.setTitle(title)
|
||||
.setMessage(description)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
mainActivity?.openCitraDirectoryLostPermission?.launch(null)
|
||||
PermissionsHandler.compatibleSelectDirectory(mainActivity.openCitraDirectoryLostPermission)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
|
@ -35,9 +39,10 @@ class SelectUserDirectoryDialogFragment : DialogFragment() {
|
|||
companion object {
|
||||
const val TAG = "SelectUserDirectoryDialogFragment"
|
||||
|
||||
fun newInstance(activity: FragmentActivity): SelectUserDirectoryDialogFragment {
|
||||
fun newInstance(activity: FragmentActivity, titleOverride: Int? = null, descriptionOverride: Int? = null):
|
||||
SelectUserDirectoryDialogFragment {
|
||||
ViewModelProvider(activity)[HomeViewModel::class.java].setPickingUserDir(true)
|
||||
return SelectUserDirectoryDialogFragment()
|
||||
return SelectUserDirectoryDialogFragment(titleOverride, descriptionOverride)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ import android.content.pm.PackageManager
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -30,7 +32,9 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.adapters.SetupAdapter
|
||||
import org.citra.citra_emu.databinding.FragmentSetupBinding
|
||||
|
|
@ -41,6 +45,7 @@ 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.ui.main.MainActivity
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import org.citra.citra_emu.utils.CitraDirectoryHelper
|
||||
import org.citra.citra_emu.utils.GameHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
|
|
@ -142,7 +147,57 @@ class SetupFragment : Fragment() {
|
|||
false,
|
||||
0,
|
||||
pageButtons = mutableListOf<PageButton>().apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_folder,
|
||||
R.string.filesystem_permission,
|
||||
R.string.filesystem_permission_description,
|
||||
buttonAction = {
|
||||
pageButtonCallback = it
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
manageExternalStoragePermissionLauncher.launch(
|
||||
Intent(
|
||||
android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||
Uri.fromParts(
|
||||
"package",
|
||||
requireActivity().packageName,
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
},
|
||||
buttonState = {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
ButtonState.BUTTON_ACTION_COMPLETE
|
||||
} else {
|
||||
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||
}
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ButtonState.BUTTON_ACTION_COMPLETE
|
||||
} else {
|
||||
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||
}
|
||||
}
|
||||
},
|
||||
isUnskippable = true,
|
||||
hasWarning = true,
|
||||
R.string.filesystem_permission_warning,
|
||||
R.string.filesystem_permission_warning_description,
|
||||
)
|
||||
)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_notification,
|
||||
|
|
@ -214,18 +269,36 @@ class SetupFragment : Fragment() {
|
|||
)
|
||||
},
|
||||
) {
|
||||
if (
|
||||
var permissionsComplete =
|
||||
// Microphone
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
) == PackageManager.PERMISSION_GRANTED &&
|
||||
// Camera
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.CAMERA
|
||||
) == PackageManager.PERMISSION_GRANTED &&
|
||||
// Notifications
|
||||
NotificationManagerCompat.from(requireContext())
|
||||
.areNotificationsEnabled()
|
||||
) {
|
||||
// External Storage
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
permissionsComplete =
|
||||
(permissionsComplete && Environment.isExternalStorageManager())
|
||||
} else {
|
||||
permissionsComplete =
|
||||
(permissionsComplete && ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED)
|
||||
}
|
||||
}
|
||||
|
||||
if (permissionsComplete) {
|
||||
PageState.PAGE_STEPS_COMPLETE
|
||||
} else {
|
||||
PageState.PAGE_STEPS_INCOMPLETE
|
||||
|
|
@ -249,7 +322,7 @@ class SetupFragment : Fragment() {
|
|||
R.string.select_citra_user_folder_description,
|
||||
buttonAction = {
|
||||
pageButtonCallback = it
|
||||
openCitraDirectory.launch(null)
|
||||
PermissionsHandler.compatibleSelectDirectory(openCitraDirectory)
|
||||
},
|
||||
buttonState = {
|
||||
if (PermissionsHandler.hasWriteAccess(requireContext())) {
|
||||
|
|
@ -452,6 +525,19 @@ class SetupFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun showPermissionDeniedSnackbar() {
|
||||
Snackbar.make(binding.root, R.string.permission_denied, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(binding.buttonNext)
|
||||
.setAction(R.string.grid_menu_core_settings) {
|
||||
val intent =
|
||||
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private val permissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (isGranted) {
|
||||
|
|
@ -459,16 +545,20 @@ class SetupFragment : Fragment() {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
Snackbar.make(binding.root, R.string.permission_denied, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(binding.buttonNext)
|
||||
.setAction(R.string.grid_menu_core_settings) {
|
||||
val intent =
|
||||
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
val uri = Uri.fromParts("package", requireActivity().packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}
|
||||
.show()
|
||||
showPermissionDeniedSnackbar()
|
||||
}
|
||||
|
||||
// We can't use permissionLauncher because MANAGE_EXTERNAL_STORAGE is a special snowflake
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private val manageExternalStoragePermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
checkForButtonState.invoke()
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
showPermissionDeniedSnackbar()
|
||||
}
|
||||
|
||||
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
|
||||
|
|
@ -478,6 +568,15 @@ class SetupFragment : Fragment() {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||
SelectUserDirectoryDialogFragment.newInstance(
|
||||
mainActivity,
|
||||
R.string.invalid_selection,
|
||||
R.string.invalid_user_directory
|
||||
).show(mainActivity.supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,8 +153,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
if (isActionMove) {
|
||||
NativeLibrary.onTouchMoved(xPosition.toFloat(), yPosition.toFloat())
|
||||
continue
|
||||
}
|
||||
else if (isActionUp) {
|
||||
} else if (isActionUp) {
|
||||
NativeLibrary.onTouchEvent(0f, 0f, false)
|
||||
break // Up and down actions shouldn't loop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@
|
|||
|
||||
package org.citra.citra_emu.ui.main
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.WindowManager
|
||||
|
|
@ -36,6 +40,8 @@ import androidx.work.WorkManager
|
|||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||
import org.citra.citra_emu.databinding.ActivityMainBinding
|
||||
|
|
@ -43,6 +49,7 @@ import org.citra.citra_emu.features.settings.model.Settings
|
|||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||
import org.citra.citra_emu.fragments.GrantMissingFilesystemPermissionFragment
|
||||
import org.citra.citra_emu.fragments.SelectUserDirectoryDialogFragment
|
||||
import org.citra.citra_emu.fragments.UpdateUserDirectoryDialogFragment
|
||||
import org.citra.citra_emu.utils.CiaInstallWorker
|
||||
|
|
@ -185,14 +192,55 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||
|
||||
if (!firstTimeSetup && !PermissionsHandler.hasWriteAccess(this) &&
|
||||
!homeViewModel.isPickingUserDir.value
|
||||
) {
|
||||
if (firstTimeSetup) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||
fun requestMissingFilesystemPermission() =
|
||||
GrantMissingFilesystemPermissionFragment.newInstance()
|
||||
.show(supportFragmentManager, GrantMissingFilesystemPermissionFragment.TAG)
|
||||
|
||||
if (supportFragmentManager.findFragmentByTag(GrantMissingFilesystemPermissionFragment.TAG) == null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
requestMissingFilesystemPermission()
|
||||
}
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestMissingFilesystemPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (homeViewModel.isPickingUserDir.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!PermissionsHandler.hasWriteAccess(this)) {
|
||||
SelectUserDirectoryDialogFragment.newInstance(this)
|
||||
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||
} else if (!firstTimeSetup && !homeViewModel.isPickingUserDir.value && CitraDirectoryUtils.needToUpdateManually()) {
|
||||
return
|
||||
} else if (CitraDirectoryUtils.needToUpdateManually()) {
|
||||
UpdateUserDirectoryDialogFragment.newInstance(this)
|
||||
.show(supportFragmentManager,UpdateUserDirectoryDialogFragment.TAG)
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR != "googlePlay") {
|
||||
if (supportFragmentManager.findFragmentByTag(SelectUserDirectoryDialogFragment.TAG) == null) {
|
||||
if (NativeLibrary.getUserDirectory() == "") {
|
||||
SelectUserDirectoryDialogFragment.newInstance(this)
|
||||
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +364,15 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (NativeLibrary.getUserDirectory(result) == "") {
|
||||
SelectUserDirectoryDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.invalid_selection,
|
||||
R.string.invalid_user_directory
|
||||
).show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
CitraDirectoryHelper(this@MainActivity, permissionsLost)
|
||||
.showCitraDirectoryDialog(result, buttonState = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.utils
|
||||
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
|
||||
object BuildUtil {
|
||||
fun assertNotGooglePlay() {
|
||||
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR == "googlePlay") {
|
||||
error("Non-GooglePlay code being called in GooglePlay build")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,12 @@ package org.citra.citra_emu.utils
|
|||
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.model.CheapDocument
|
||||
import java.net.URLDecoder
|
||||
import java.nio.file.Paths
|
||||
import java.util.StringTokenizer
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
|
@ -191,7 +193,7 @@ class DocumentsTree {
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun renameFile(filepath: String, destinationFilename: String?): Boolean {
|
||||
fun renameFile(filepath: String, destinationFilename: String): Boolean {
|
||||
val node = resolvePath(filepath) ?: return false
|
||||
try {
|
||||
val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD)
|
||||
|
|
@ -203,6 +205,20 @@ class DocumentsTree {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun moveFile(filename: String, sourceDirPath: String, destDirPath: String): Boolean {
|
||||
val sourceFileNode = resolvePath(sourceDirPath + "/" + filename) ?: return false
|
||||
val sourceDirNode = resolvePath(sourceDirPath) ?: return false
|
||||
val destDirNode = resolvePath(destDirPath) ?: return false
|
||||
try {
|
||||
val newUri = DocumentsContract.moveDocument(context.contentResolver, sourceFileNode.uri!!, sourceDirNode.uri!!, destDirNode.uri!!)
|
||||
updateDocumentLocation("$sourceDirPath/$filename", "$destDirPath/$filename")
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
error("[DocumentsTree]: Cannot move file, error: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun deleteDocument(filepath: String): Boolean {
|
||||
val node = resolvePath(filepath) ?: return false
|
||||
|
|
@ -219,6 +235,29 @@ class DocumentsTree {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateDocumentLocation(sourcePath: String, destinationPath: String): Boolean {
|
||||
val sourceNode = resolvePath(sourcePath)
|
||||
val newName = Paths.get(destinationPath).fileName.toString()
|
||||
val parentPath = Paths.get(destinationPath).parent.toString()
|
||||
val newParent = resolvePath(parentPath)
|
||||
val newUri = (getUri(parentPath).toString() + "%2F$newName").toUri() // <- Is there a better way?
|
||||
|
||||
if (sourceNode == null || newParent == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
sourceNode.parent!!.removeChild(sourceNode)
|
||||
|
||||
sourceNode.name = newName
|
||||
sourceNode.parent = newParent
|
||||
sourceNode.uri = newUri
|
||||
|
||||
newParent.addChild(sourceNode)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun resolvePath(filepath: String): DocumentsNode? {
|
||||
root ?: return null
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -18,15 +18,27 @@ object EmulationLifecycleUtil {
|
|||
}
|
||||
|
||||
fun addShutdownHook(hook: Runnable) {
|
||||
shutdownHooks.add(hook)
|
||||
if (shutdownHooks.contains(hook)) {
|
||||
Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook for function that already existed. Skipping.")
|
||||
} else {
|
||||
shutdownHooks.add(hook)
|
||||
}
|
||||
}
|
||||
|
||||
fun addPauseResumeHook(hook: Runnable) {
|
||||
pauseResumeHooks.add(hook)
|
||||
if (pauseResumeHooks.contains(hook)) {
|
||||
Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook for function that already existed. Skipping.")
|
||||
} else {
|
||||
pauseResumeHooks.add(hook)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
pauseResumeHooks.clear()
|
||||
shutdownHooks.clear()
|
||||
fun removeHook(hook: Runnable) {
|
||||
if (pauseResumeHooks.contains(hook)) {
|
||||
pauseResumeHooks.remove(hook)
|
||||
}
|
||||
if (shutdownHooks.contains(hook)) {
|
||||
shutdownHooks.remove(hook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ import android.net.Uri
|
|||
import android.provider.DocumentsContract
|
||||
import android.system.Os
|
||||
import android.util.Pair
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.model.CheapDocument
|
||||
|
|
@ -434,6 +435,20 @@ object FileUtil {
|
|||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun moveFile(filename: String, sourceDirUriString: String, destDirUriString: String): Boolean {
|
||||
try {
|
||||
val sourceFileUri = ("$sourceDirUriString%2F$filename").toUri()
|
||||
val sourceDirUri = sourceDirUriString.toUri()
|
||||
val destDirUri = destDirUriString.toUri()
|
||||
DocumentsContract.moveDocument(context.contentResolver, sourceFileUri, sourceDirUri, destDirUri)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.error("[FileUtil]: Cannot move file, error: " + e.message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun deleteDocument(path: String): Boolean {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
|
|
@ -48,4 +51,17 @@ object PermissionsHandler {
|
|||
|
||||
fun setCitraDirectory(uriString: String?) =
|
||||
preferences.edit().putString(CITRA_DIRECTORY, uriString).apply()
|
||||
|
||||
fun compatibleSelectDirectory(activityLauncher: ActivityResultLauncher<Uri?>) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
activityLauncher.launch(null)
|
||||
} else {
|
||||
val initialUri = DocumentsContract.buildRootUri(
|
||||
"com.android.externalstorage.documents",
|
||||
"primary"
|
||||
)
|
||||
activityLauncher.launch(initialUri)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// 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.utils
|
||||
|
||||
import org.citra.citra_emu.utils.BuildUtil
|
||||
import java.io.File
|
||||
|
||||
object RemovableStorageHelper {
|
||||
// This really shouldn't be necessary, but the Android API seemingly
|
||||
// doesn't have a way of doing this?
|
||||
fun getRemovableStoragePath(idString: String): String? {
|
||||
BuildUtil.assertNotGooglePlay()
|
||||
|
||||
// On certain Android flavours the external storage mount location can
|
||||
// vary, so add extra cases here if we discover them.
|
||||
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||
|
||||
for (mountPath in possibleMountPaths) {
|
||||
val pathFile = File(mountPath);
|
||||
if (pathFile.exists()) {
|
||||
// TODO: Cache which mount location is being used for the remainder of the
|
||||
// session, as it should never change. -OS
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -657,6 +657,25 @@ void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEn
|
|||
window->OnTouchMoved((int)x, (int)y);
|
||||
}
|
||||
|
||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jfloat x, jfloat y,
|
||||
jboolean pressed) {
|
||||
if (!secondary_window) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return static_cast<jboolean>(secondary_window->OnTouchEvent(
|
||||
static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), pressed));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_onSecondaryTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jfloat x, jfloat y) {
|
||||
if (secondary_window) {
|
||||
secondary_window->OnTouchMoved((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
|
||||
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj,
|
||||
jstring j_filename) {
|
||||
std::string filepath = GetJString(env, j_filename);
|
||||
|
|
|
|||
|
|
@ -193,6 +193,10 @@
|
|||
<string name="region_mismatch">Advertiment Regió No Vàlida</string>
|
||||
<string name="region_mismatch_emulated">La configuració del país no és vàlida per a la regió emulada seleccionada.</string>
|
||||
<string name="region_mismatch_console">La configuració del país no és vàlida per a la consola vinculada actual.</string>
|
||||
<string name="storage">Emmagatzematge</string>
|
||||
<string name="compress_cia_installs">Comprimir el contingut de CIAs instal·lats</string>
|
||||
<string name="compress_cia_installs_description">Comprimix el contingut de fitxers CIA quan són instal·lats a la SD emulada. Només afecta contingut CIA instal·lat amb esta opció activada.</string>
|
||||
|
||||
<!-- Camera settings strings -->
|
||||
<string name="inner_camera">Càmera interior</string>
|
||||
<string name="outer_left_camera">Càmera esquerra externa</string>
|
||||
|
|
@ -397,6 +401,10 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
|||
<string name="emulation_configure_controls">Configurar Controls</string>
|
||||
<string name="emulation_edit_layout">Editar Estil</string>
|
||||
<string name="emulation_done">Fet</string>
|
||||
<string name="emulation_button_sliding">Lliscament de botons</string>
|
||||
<string name="emulation_button_sliding_disabled">Mantindre el botó pressionat originalment</string>
|
||||
<string name="emulation_button_sliding_enabled">Mantindre el botó pressionat actualment</string>
|
||||
<string name="emulation_button_sliding_alternative">Mantindre el botó original i actualment pressionat</string>
|
||||
<string name="emulation_toggle_controls">Activar Controls</string>
|
||||
<string name="emulation_control_scale">Ajustar Escala</string>
|
||||
<string name="emulation_control_scale_global">Escala Global</string>
|
||||
|
|
@ -409,6 +417,8 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
|||
<string name="emulation_aspect_ratio">Relació d\'Aspecte</string>
|
||||
<string name="emulation_switch_screen_layout">Estil de Pantalla Apaïsada</string>
|
||||
<string name="emulation_switch_portrait_layout">Estil de Pantalla de Perfil</string>
|
||||
<string name="emulation_switch_secondary_layout">Estil de Pantalla Secundària</string>
|
||||
<string name="emulation_switch_secondary_layout_description">La disposició de la pantalla secundària connectada, amb cable o sense fil (Chromecast, Miracast)</string>
|
||||
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
||||
<string name="emulation_screen_layout_portrait">Vertical</string>
|
||||
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
||||
|
|
@ -416,6 +426,7 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
|||
<string name="emulation_screen_layout_hybrid">Pantalles híbrides</string>
|
||||
<string name="emulation_screen_layout_original">Original</string>
|
||||
<string name="emulation_portrait_layout_top_full">Per omissió</string>
|
||||
<string name="emulation_secondary_display_default">Per defecte del sistema (espill)</string>
|
||||
<string name="emulation_screen_layout_custom">Estil Personalitzat</string>
|
||||
<string name="emulation_small_screen_position">Posició de Pantalla Xicoteta</string>
|
||||
<string name="small_screen_position_description">On hauria d\'aparéixer la pantalla xicoteta en relació amb la gran en Proporció de Pantalla Gran?</string>
|
||||
|
|
@ -536,6 +547,10 @@ S\'esperen errors gràfics temporals quan estigue activat.</string>
|
|||
<string name="create_shortcut">Crear drecera</string>
|
||||
<string name="shortcut_name_empty">El nom de la drecera no pot estar buit</string>
|
||||
<string name="shortcut_image_stretch_toggle">Allargar per a ajustar la imatge</string>
|
||||
<string name="game_context_id">ID:</string>
|
||||
<string name="game_context_file">Fitxer:</string>
|
||||
<string name="game_context_type">Tipus:</string>
|
||||
|
||||
<!-- Performance Overlay settings -->
|
||||
<string name="performance_overlay_show">Mostrar informació de rendiment</string>
|
||||
<string name="performance_overlay_options">Informació de rendiment</string>
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@
|
|||
<string name="region_mismatch_emulated">La configuración del país no es válida para la región emulada seleccionada.</string>
|
||||
<string name="region_mismatch_console">La configuración del país no es válida para la consola vinculada actual.</string>
|
||||
<string name="storage">Almacenamiento</string>
|
||||
<string name="compress_cia_installs">Comprimir el contenido CIA instalado</string>
|
||||
<string name="compress_cia_installs">Comprimir el contenido de CIAs instalados</string>
|
||||
<string name="compress_cia_installs_description">Comprime el contenido de archivos CIA cuando son instalados a la SD emulada. Solo afecta contenido CIA instalado con esta opción activada.</string>
|
||||
|
||||
<!-- Camera settings strings -->
|
||||
|
|
@ -402,9 +402,9 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
|||
<string name="emulation_edit_layout">Editar Estilo</string>
|
||||
<string name="emulation_done">Hecho</string>
|
||||
<string name="emulation_button_sliding">Deslizamiento de botones</string>
|
||||
<string name="emulation_button_sliding_disabled">Mantenga el botón presionado originalmente</string>
|
||||
<string name="emulation_button_sliding_enabled">Mantenga el botón presionado actualmente</string>
|
||||
<string name="emulation_button_sliding_alternative">Mantenga el botón original y actualmente presionado</string>
|
||||
<string name="emulation_button_sliding_disabled">Mantener el botón presionado originalmente</string>
|
||||
<string name="emulation_button_sliding_enabled">Mantener el botón presionado actualmente</string>
|
||||
<string name="emulation_button_sliding_alternative">Mantener el botón original y actualmente presionado</string>
|
||||
<string name="emulation_toggle_controls">Activar Controles</string>
|
||||
<string name="emulation_control_scale">Ajustar Escala</string>
|
||||
<string name="emulation_control_scale_global">Escala Global</string>
|
||||
|
|
@ -417,6 +417,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
|||
<string name="emulation_aspect_ratio">Relación de Aspecto</string>
|
||||
<string name="emulation_switch_screen_layout">Estilo de Pantalla Apaisada</string>
|
||||
<string name="emulation_switch_portrait_layout">Estilo de Pantalla de Perfil</string>
|
||||
<string name="emulation_switch_secondary_layout">Estilo de Pantalla Secundaria</string>
|
||||
<string name="emulation_switch_secondary_layout_description">El estilo de la pantalla secundaria conectada, con cable o inalámbrica (Chromecast, Miracast)</string>
|
||||
<string name="emulation_screen_layout_largescreen">Pantalla amplia</string>
|
||||
<string name="emulation_screen_layout_portrait">Retrato</string>
|
||||
<string name="emulation_screen_layout_single">Pantalla Única</string>
|
||||
|
|
@ -424,6 +426,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
|||
<string name="emulation_screen_layout_hybrid">Pantallas híbridas</string>
|
||||
<string name="emulation_screen_layout_original">Original</string>
|
||||
<string name="emulation_portrait_layout_top_full">Por defecto</string>
|
||||
<string name="emulation_secondary_display_default">Por defecto del sistema (espejo)</string>
|
||||
<string name="emulation_screen_layout_custom">Estilo personalizado</string>
|
||||
<string name="emulation_small_screen_position">Posición Pantalla Pequeña</string>
|
||||
<string name="small_screen_position_description">¿Dónde debería aparecer la pantalla pequeña en relación con la grande en Disposicion de Pantalla Grande?</string>
|
||||
|
|
|
|||
|
|
@ -417,6 +417,7 @@
|
|||
<string name="emulation_switch_screen_layout">Geometryczny Układ Ekranu</string>
|
||||
<string name="emulation_switch_portrait_layout">Pionowy Układ Ekranu</string>
|
||||
<string name="emulation_switch_secondary_layout">Układ ekranu wyświetlacza dodatkowego</string>
|
||||
<string name="emulation_switch_secondary_layout_description">Układ używany przez podłączony dodatkowy ekran, przewodowy lub bezprzewodowy (Chromecast, Miracast)</string>
|
||||
<string name="emulation_screen_layout_largescreen">Duży Ekran</string>
|
||||
<string name="emulation_screen_layout_portrait">Ekran</string>
|
||||
<string name="emulation_screen_layout_single">Pojedynczy ekran</string>
|
||||
|
|
@ -424,6 +425,7 @@
|
|||
<string name="emulation_screen_layout_hybrid">Hybrydowy Ekran</string>
|
||||
<string name="emulation_screen_layout_original">Oryginalny</string>
|
||||
<string name="emulation_portrait_layout_top_full">Domyślny</string>
|
||||
<string name="emulation_secondary_display_default">Ustawienia domyślne systemu (mirror)</string>
|
||||
<string name="emulation_screen_layout_custom">Niestandardowy Układ</string>
|
||||
<string name="emulation_small_screen_position">Pozycja małego ekranu</string>
|
||||
<string name="small_screen_position_description">Gdzie powinien być wyświetlany mały ekran względem dużego w układzie dużego ekranu?</string>
|
||||
|
|
|
|||
|
|
@ -76,10 +76,14 @@
|
|||
<string name="give_permission">Grant permission</string>
|
||||
<string name="notification_warning">Skip granting the notification permission?</string>
|
||||
<string name="notification_warning_description">Azahar won\'t be able to notify you of important information.</string>
|
||||
<string name="filesystem_permission_warning">Missing Permissions</string>
|
||||
<string name="filesystem_permission_warning_description">Azahar requires permission to manage files on this device in order to store and manage its data.\n\nPlease grant the \"Filesystem\" permission before continuing.</string>
|
||||
<string name="camera_permission">Camera</string>
|
||||
<string name="camera_permission_description">Grant the camera permission below to emulate the 3DS camera.</string>
|
||||
<string name="microphone_permission">Microphone</string>
|
||||
<string name="microphone_permission_description">Grant the microphone permission below to emulate the 3DS microphone.</string>
|
||||
<string name="filesystem_permission">Filesystem</string>
|
||||
<string name="filesystem_permission_description">Grant the filesystem permission below to allow Azahar to store files.</string>
|
||||
<string name="permission_denied">Permission denied</string>
|
||||
<string name="add_games_warning">Skip selecting applications folder?</string>
|
||||
<string name="add_games_warning_description">Software won\'t be displayed in the Applications list if a folder isn\'t selected.</string>
|
||||
|
|
@ -100,6 +104,9 @@
|
|||
<string name="cannot_skip">You can\'t skip setting up the user folder</string>
|
||||
<string name="cannot_skip_directory_description">This step is required to allow Azahar to work. Please select a directory and then you can continue.</string>
|
||||
<string name="selecting_user_directory_without_write_permissions">You have lost write permissions on your <a href="https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage">user data</a> directory, where saves and other information are stored. This can happen after some app or Android updates. Please re-select the directory to regain permissions so you can continue.</string>
|
||||
<string name="invalid_selection">Invalid Selection</string>
|
||||
<string name="invalid_user_directory">The user directory selection was invalid.\nPlease re-select the user directory, ensuring that you navigate to it from the root of your device\'s storage.</string>
|
||||
<string name="filesystem_permission_lost">Azahar has lost permission to manage files on this device. This can happen after some app or Android updates. Please re-grant this permission on the next screen to continue using the app.</string>
|
||||
<string name="cannot_skip_directory_help" translatable="false">https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage</string>
|
||||
<string name="set_up_theme_settings">Theme Settings</string>
|
||||
<string name="setup_theme_settings_description">Configure your theme preferences for Azahar.</string>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("com.android.library") version "8.11.1" apply false
|
||||
id("com.android.application") version "8.13.1" apply false
|
||||
id("com.android.library") version "8.13.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ if (ENABLE_QT AND UNIX AND NOT APPLE)
|
|||
target_link_libraries(citra_meta PRIVATE Qt6::DBus gamemode)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT AND APPLE)
|
||||
target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT AND USE_DISCORD_PRESENCE)
|
||||
target_link_libraries(citra_meta PRIVATE discord-rpc)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -172,12 +172,12 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
|||
multiplayer/state.h
|
||||
multiplayer/validation.h
|
||||
precompiled_headers.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
user_data_migration.cpp
|
||||
user_data_migration.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
util/clickable_label.cpp
|
||||
util/clickable_label.h
|
||||
util/graphics_device_info.cpp
|
||||
|
|
@ -190,6 +190,13 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
|||
util/util.h
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_sources(citra_qt PUBLIC
|
||||
qt_swizzle.h
|
||||
qt_swizzle.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMPAT_LIST
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||
|
|
@ -275,7 +282,13 @@ if (NOT WIN32)
|
|||
target_include_directories(citra_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (APPLE)
|
||||
target_link_libraries(citra_qt PRIVATE Qt6::GuiPrivate)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(citra_qt PRIVATE Qt6::QDarwinCameraPermissionPlugin)
|
||||
elseif (UNIX)
|
||||
target_link_libraries(citra_qt PRIVATE Qt6::DBus gamemode)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
#include "citra_qt/movie/movie_record_dialog.h"
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "citra_qt/qt_image_interface.h"
|
||||
#include "citra_qt/qt_swizzle.h"
|
||||
#include "citra_qt/uisettings.h"
|
||||
#include "common/play_time_manager.h"
|
||||
#ifdef ENABLE_QT_UPDATE_CHECKER
|
||||
|
|
@ -114,6 +115,7 @@
|
|||
|
||||
#ifdef __APPLE__
|
||||
#include "common/apple_authorization.h"
|
||||
Q_IMPORT_PLUGIN(QDarwinCameraPermissionPlugin);
|
||||
#endif
|
||||
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
|
|
@ -4112,6 +4114,11 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
|||
}
|
||||
|
||||
void LaunchQtFrontend(int argc, char* argv[]) {
|
||||
#ifdef __APPLE__
|
||||
// Ensure that the linker doesn't optimize qt_swizzle.mm out of existence.
|
||||
QtSwizzle::Dummy();
|
||||
#endif
|
||||
|
||||
Common::DetachedTasks detached_tasks;
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
|
|
|
|||
9
src/citra_qt/qt_swizzle.h
Normal file
9
src/citra_qt/qt_swizzle.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
namespace QtSwizzle {
|
||||
|
||||
void Dummy();
|
||||
|
||||
} // namespace QtSwizzle
|
||||
48
src/citra_qt/qt_swizzle.mm
Normal file
48
src/citra_qt/qt_swizzle.mm
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#import <QtGui/private/qmetallayer_p.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
namespace QtSwizzle {
|
||||
|
||||
void Dummy() {
|
||||
// Call this anywhere to make sure that qt_swizzle.mm is linked.
|
||||
// noop
|
||||
}
|
||||
|
||||
} // namespace QtSwizzle
|
||||
|
||||
@implementation QMetalLayer (AzaharPatch)
|
||||
|
||||
+ (void)load {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class targetClass = [self class];
|
||||
|
||||
// Get the original and swizzled methods
|
||||
Method originalMethod =
|
||||
class_getInstanceMethod(targetClass, @selector(setNeedsDisplayInRect:));
|
||||
Method swizzledMethod =
|
||||
class_getInstanceMethod(targetClass, @selector(swizzled_setNeedsDisplayInRect:));
|
||||
|
||||
// Swap the implementations
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)swizzled_setNeedsDisplayInRect:(CGRect)rect {
|
||||
constexpr auto tooBig = 1e10; // Arbitrary large number
|
||||
|
||||
// Check for problematic huge rectangles
|
||||
if ((!self.needsDisplay) && (rect.size.width > tooBig || rect.size.height > tooBig ||
|
||||
rect.origin.x < -tooBig || rect.origin.y < -tooBig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the original implementation
|
||||
[self swizzled_setNeedsDisplayInRect:rect];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
// 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.
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include "common/android_storage.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AndroidStorage {
|
||||
JNIEnv* GetEnvForThread() {
|
||||
|
|
@ -80,8 +84,9 @@ void CleanupJNI() {
|
|||
}
|
||||
|
||||
bool CreateFile(const std::string& directory, const std::string& filename) {
|
||||
if (create_file == nullptr)
|
||||
if (create_file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||
jstring j_filename = env->NewStringUTF(filename.c_str());
|
||||
|
|
@ -89,8 +94,9 @@ bool CreateFile(const std::string& directory, const std::string& filename) {
|
|||
}
|
||||
|
||||
bool CreateDir(const std::string& directory, const std::string& filename) {
|
||||
if (create_dir == nullptr)
|
||||
if (create_dir == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||
jstring j_directory_name = env->NewStringUTF(filename.c_str());
|
||||
|
|
@ -98,8 +104,9 @@ bool CreateDir(const std::string& directory, const std::string& filename) {
|
|||
}
|
||||
|
||||
int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
||||
if (open_content_uri == nullptr)
|
||||
if (open_content_uri == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* mode = "";
|
||||
switch (openmode) {
|
||||
|
|
@ -135,8 +142,9 @@ int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
|||
|
||||
std::vector<std::string> GetFilesName(const std::string& filepath) {
|
||||
auto vector = std::vector<std::string>();
|
||||
if (get_files_name == nullptr)
|
||||
if (get_files_name == nullptr) {
|
||||
return vector;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_filepath = env->NewStringUTF(filepath.c_str());
|
||||
auto j_object =
|
||||
|
|
@ -150,10 +158,37 @@ std::vector<std::string> GetFilesName(const std::string& filepath) {
|
|||
return vector;
|
||||
}
|
||||
|
||||
std::optional<std::string> GetUserDirectory() {
|
||||
if (get_user_directory == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Unable to locate user directory: Function with ID 'get_user_directory' is missing");
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
auto j_user_directory =
|
||||
(jstring)(env->CallStaticObjectMethod(native_library, get_user_directory, nullptr));
|
||||
auto result = env->GetStringUTFChars(j_user_directory, nullptr);
|
||||
if (result == "") {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetBuildFlavor() {
|
||||
if (get_build_flavor == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Unable get build flavor: Function with ID 'get_build_flavor' is missing");
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
const auto jflavor =
|
||||
(jstring)(env->CallStaticObjectMethod(native_library, get_build_flavor, nullptr));
|
||||
return env->GetStringUTFChars(jflavor, nullptr);
|
||||
}
|
||||
|
||||
bool CopyFile(const std::string& source, const std::string& destination_path,
|
||||
const std::string& destination_filename) {
|
||||
if (copy_file == nullptr)
|
||||
if (copy_file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
||||
|
|
@ -163,8 +198,14 @@ bool CopyFile(const std::string& source, const std::string& destination_path,
|
|||
}
|
||||
|
||||
bool RenameFile(const std::string& source, const std::string& filename) {
|
||||
if (rename_file == nullptr)
|
||||
if (rename_file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (std::string(FileUtil::GetFilename(source)) ==
|
||||
std::string(FileUtil::GetFilename(filename))) {
|
||||
// TODO: Should this be treated as a success or failure?
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||
jstring j_destination_path = env->NewStringUTF(filename.c_str());
|
||||
|
|
@ -172,6 +213,86 @@ bool RenameFile(const std::string& source, const std::string& filename) {
|
|||
j_destination_path);
|
||||
}
|
||||
|
||||
bool UpdateDocumentLocation(const std::string& source_path, const std::string& destination_path) {
|
||||
if (update_document_location == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_source_path = env->NewStringUTF(source_path.c_str());
|
||||
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, update_document_location, j_source_path,
|
||||
j_destination_path);
|
||||
}
|
||||
|
||||
bool MoveFile(const std::string& filename, const std::string& source_dir_path,
|
||||
const std::string& destination_dir_path) {
|
||||
if (move_file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (source_dir_path == destination_dir_path) {
|
||||
// TODO: Should this be treated as a success or failure?
|
||||
return false;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_filename = env->NewStringUTF(filename.c_str());
|
||||
jstring j_source_dir_path = env->NewStringUTF(source_dir_path.c_str());
|
||||
jstring j_destination_dir_path = env->NewStringUTF(destination_dir_path.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, move_file, j_filename, j_source_dir_path,
|
||||
j_destination_dir_path);
|
||||
}
|
||||
|
||||
bool MoveAndRenameFile(const std::string& src_full_path, const std::string& dest_full_path) {
|
||||
if (src_full_path == dest_full_path) {
|
||||
// TODO: Should this be treated as a success or failure?
|
||||
return false;
|
||||
}
|
||||
const auto src_filename = std::string(FileUtil::GetFilename(src_full_path));
|
||||
const auto src_parent_path = std::string(FileUtil::GetParentPath(src_full_path));
|
||||
const auto dest_filename = std::string(FileUtil::GetFilename(dest_full_path));
|
||||
const auto dest_parent_path = std::string(FileUtil::GetParentPath(dest_full_path));
|
||||
bool result;
|
||||
|
||||
const std::string tmp_path = "/tmp";
|
||||
AndroidStorage::CreateDir("/", "tmp");
|
||||
|
||||
// If a simultaneous move and rename are not necessary, use individual methods
|
||||
if (src_filename == dest_filename || src_parent_path == dest_parent_path) {
|
||||
if (src_filename != dest_filename) {
|
||||
return AndroidStorage::RenameFile(src_full_path, dest_filename);
|
||||
} else if (src_parent_path != dest_parent_path) {
|
||||
return AndroidStorage::MoveFile(src_filename, src_parent_path, dest_parent_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Create directory named after UUID inside /tmp to house the moved file.
|
||||
// This prevents clashes if files with the same name are moved simultaneously.
|
||||
const auto uuid = boost::uuids::to_string(boost::uuids::time_generator_v7()());
|
||||
const auto allocated_tmp_path = tmp_path + "/" + uuid;
|
||||
AndroidStorage::CreateDir(tmp_path, uuid);
|
||||
|
||||
// Step 2: Attempt to move to allocated temporary directory.
|
||||
// If this step fails, skip everything except the cleanup.
|
||||
result = AndroidStorage::MoveFile(src_filename, src_parent_path, allocated_tmp_path);
|
||||
if (result == true) {
|
||||
// Step 3: Rename to desired file name.
|
||||
AndroidStorage::RenameFile((allocated_tmp_path + "/" + src_filename), dest_filename);
|
||||
|
||||
// Step 4: If a file with the desired name in the destination exists, remove it.
|
||||
AndroidStorage::DeleteDocument(dest_full_path);
|
||||
|
||||
// Step 5: Attempt to move file to desired location.
|
||||
// If this step fails, move the file back to where it came from.
|
||||
result = AndroidStorage::MoveFile(dest_filename, allocated_tmp_path, dest_parent_path);
|
||||
if (result == false) {
|
||||
AndroidStorage::MoveAndRenameFile((allocated_tmp_path + "/" + dest_filename),
|
||||
src_full_path);
|
||||
}
|
||||
}
|
||||
// Step 6: Clean up the allocated temp directory.
|
||||
AndroidStorage::DeleteDocument(allocated_tmp_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(FunctionName, ReturnValue, JMethodID, Caller)
|
||||
#define F(FunctionName, ReturnValue, JMethodID, Caller) \
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -19,12 +19,23 @@
|
|||
open_content_uri, "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") \
|
||||
V(GetFilesName, std::vector<std::string>, (const std::string& filepath), get_files_name, \
|
||||
"getFilesName", "(Ljava/lang/String;)[Ljava/lang/String;") \
|
||||
V(GetUserDirectory, std::optional<std::string>, (), get_user_directory, "getUserDirectory", \
|
||||
"(Landroid/net/Uri;)Ljava/lang/String;") \
|
||||
V(CopyFile, bool, \
|
||||
(const std::string& source, const std::string& destination_path, \
|
||||
const std::string& destination_filename), \
|
||||
copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(RenameFile, bool, (const std::string& source, const std::string& filename), rename_file, \
|
||||
"renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z")
|
||||
"renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(UpdateDocumentLocation, bool, \
|
||||
(const std::string& source_path, const std::string& destination_path), \
|
||||
update_document_location, "updateDocumentLocation", \
|
||||
"(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(GetBuildFlavor, std::string, (), get_build_flavor, "getBuildFlavor", "()Ljava/lang/String;") \
|
||||
V(MoveFile, bool, \
|
||||
(const std::string& filename, const std::string& source_dir_path, \
|
||||
const std::string& destination_dir_path), \
|
||||
move_file, "moveFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z")
|
||||
#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
|
||||
V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
|
||||
"(Ljava/lang/String;)Z") \
|
||||
|
|
@ -44,6 +55,7 @@ ANDROID_STORAGE_FUNCTIONS(FS)
|
|||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
bool MoveAndRenameFile(const std::string& src_full_path, const std::string& dest_full_path);
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/os/ParcelFileDescriptor#parseMode(java.lang.String)
|
||||
enum class AndroidOpenMode {
|
||||
|
|
|
|||
|
|
@ -304,20 +304,31 @@ bool DeleteDir(const std::string& filename) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFullPath, destFullPath);
|
||||
#ifdef _WIN32
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFullPath).c_str(),
|
||||
Common::UTF8ToUTF16W(destFullPath).c_str()) == 0)
|
||||
return true;
|
||||
#elif ANDROID
|
||||
if (AndroidStorage::RenameFile(srcFilename, std::string(GetFilename(destFilename))))
|
||||
return true;
|
||||
// srcFullPath and destFullPath are relative to the user directory
|
||||
if (AndroidStorage::GetBuildFlavor() == "googlePlay") {
|
||||
if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath))
|
||||
return true;
|
||||
} else {
|
||||
std::optional<std::string> userDirLocation = AndroidStorage::GetUserDirectory();
|
||||
if (userDirLocation && rename((*userDirLocation + srcFullPath).c_str(),
|
||||
(*userDirLocation + destFullPath).c_str()) == 0) {
|
||||
AndroidStorage::UpdateDocumentLocation(srcFullPath, destFullPath);
|
||||
// ^ TODO: This shouldn't fail, but what should we do if it somehow does?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||
if (rename(srcFullPath.c_str(), destFullPath.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFullPath, destFullPath,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,13 +136,13 @@ bool Delete(const std::string& filename);
|
|||
// Deletes a directory filename, returns true on success
|
||||
bool DeleteDir(const std::string& filename);
|
||||
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
||||
// Renames file srcFullPath to destFullPath, returns true on success
|
||||
bool Rename(const std::string& srcFullPath, const std::string& destFullPath);
|
||||
|
||||
// copies file srcFilename to destFilename, returns true on success
|
||||
// Copies file srcFilename to destFilename, returns true on success
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// creates an empty file filename, returns true on success
|
||||
// Creates an empty file filename, returns true on success
|
||||
bool CreateEmptyFile(const std::string& filename);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns
|
|||
}
|
||||
#endif
|
||||
|
||||
if (!layout.bottom_screen_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
|
|
|
|||
|
|
@ -864,8 +864,10 @@ bool CIAFile::Close() {
|
|||
if (!complete) {
|
||||
LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install...");
|
||||
if (!is_additional_content) {
|
||||
FileUtil::DeleteDirRecursively(
|
||||
GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()));
|
||||
// Only delete the content folder as there may be user save data in the title folder.
|
||||
const std::string title_content_path =
|
||||
GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()) + "content/";
|
||||
FileUtil::DeleteDirRecursively(title_content_path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,31 +61,6 @@ constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS
|
|||
namespace {
|
||||
static bool IsLowRefreshRate() {
|
||||
#if defined(__APPLE__) || defined(ENABLE_SDL2)
|
||||
#ifdef __APPLE__ // Need a special implementation because MacOS kills itself in disgust if the
|
||||
// input thread calls SDL_PumpEvents at the same time as we're in SDL_Init here.
|
||||
const auto cur_refresh_rate = AppleUtils::GetRefreshRate();
|
||||
#elif defined(ENABLE_SDL2)
|
||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||
LOG_ERROR(Render_Vulkan, "SDL video failed to initialize, unable to check refresh rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_DisplayMode cur_display_mode;
|
||||
SDL_GetCurrentDisplayMode(0, &cur_display_mode); // TODO: Multimonitor handling. -OS
|
||||
const auto cur_refresh_rate = cur_display_mode.refresh_rate;
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
#endif // __APPLE__
|
||||
|
||||
if (cur_refresh_rate < SCREEN_REFRESH_RATE) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Detected refresh rate lower than the emulated 3DS screen: {}hz. FIFO will "
|
||||
"be disabled",
|
||||
cur_refresh_rate);
|
||||
return true;
|
||||
}
|
||||
#endif // defined(__APPLE__) || defined(ENABLE_SDL2)
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Apple's low power mode sometimes limits applications to 30fps without changing the refresh
|
||||
// rate, meaning the above code doesn't catch it.
|
||||
|
|
@ -94,8 +69,34 @@ static bool IsLowRefreshRate() {
|
|||
"framerate. FIFO will be disabled");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto cur_refresh_rate = AppleUtils::GetRefreshRate();
|
||||
#elif defined(ENABLE_SDL2)
|
||||
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
|
||||
LOG_ERROR(Render_Vulkan, "Attempted to check refresh rate via SDL, but failed because "
|
||||
"SDL_INIT_VIDEO wasn't initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_DisplayMode cur_display_mode;
|
||||
SDL_GetCurrentDisplayMode(0, &cur_display_mode); // TODO: Multimonitor handling. -OS
|
||||
|
||||
const auto cur_refresh_rate = cur_display_mode.refresh_rate;
|
||||
#endif // ENABLE_SDL2
|
||||
|
||||
if (cur_refresh_rate < SCREEN_REFRESH_RATE) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Detected refresh rate lower than the emulated 3DS screen: {}hz. FIFO will "
|
||||
"be disabled",
|
||||
cur_refresh_rate);
|
||||
return true;
|
||||
} else {
|
||||
LOG_INFO(Render_Vulkan, "Refresh rate is above emulated 3DS screen: {}hz. Good.",
|
||||
cur_refresh_rate);
|
||||
}
|
||||
#endif // defined(__APPLE__) || defined(ENABLE_SDL2)
|
||||
|
||||
// We have no available method of checking refresh rate. Just assume that everything is fine :)
|
||||
return false;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ public:
|
|||
}
|
||||
|
||||
/// Returns the maximum supported elements in a texel buffer
|
||||
u32 MaxTexelBufferElements() const {
|
||||
u64 MaxTexelBufferElements() const {
|
||||
return properties.limits.maxTexelBufferElements;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -556,12 +556,15 @@ bool PipelineCache::EnsureDirectories() const {
|
|||
};
|
||||
|
||||
return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
|
||||
create_dir(GetPipelineCacheDir());
|
||||
create_dir(GetVulkanDir()) && create_dir(GetPipelineCacheDir());
|
||||
}
|
||||
|
||||
std::string PipelineCache::GetVulkanDir() const {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP;
|
||||
}
|
||||
|
||||
std::string PipelineCache::GetPipelineCacheDir() const {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP + "pipeline" +
|
||||
DIR_SEP;
|
||||
return GetVulkanDir() + "pipeline" + DIR_SEP;
|
||||
}
|
||||
|
||||
void PipelineCache::SwitchPipelineCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ private:
|
|||
/// Create pipeline cache directories. Returns true on success.
|
||||
bool EnsureDirectories() const;
|
||||
|
||||
/// Returns the Vulkan shader directory
|
||||
std::string GetVulkanDir() const;
|
||||
|
||||
/// Returns the pipeline cache storage dir
|
||||
std::string GetPipelineCacheDir() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -16,6 +16,10 @@
|
|||
#endif
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#ifdef ENABLE_SDL2
|
||||
#include <SDL.h>
|
||||
#endif
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
|
||||
|
|
@ -29,6 +33,12 @@ std::unique_ptr<RendererBase> CreateRenderer(Frontend::EmuWindow& emu_window,
|
|||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
#if defined(ENABLE_SDL2) && !defined(__APPLE__)
|
||||
// TODO: When we migrate to SDL3, refactor so that we don't need to init here.
|
||||
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
}
|
||||
#endif // ENABLE_SDL2
|
||||
return std::make_unique<Vulkan::RendererVulkan>(system, pica, emu_window, secondary_window);
|
||||
#endif
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ The scripts in this directory assume that your current working directory is the
|
|||
## Pre-release checklist
|
||||
|
||||
- [ ] Update compatibility list
|
||||
- [ ] Update translations
|
||||
- [ ] If this is a major release (2123.1 -> major.minor), update translations
|
||||
|
||||
### Note:
|
||||
|
||||
|
|
|
|||
6
tools/purge-github-cache.sh
Executable file
6
tools/purge-github-cache.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
# This script assumes that the Github CLI is installed and that
|
||||
# the authenticated user has appropriate authorization.
|
||||
|
||||
gh cache delete --all
|
||||
Loading…
Add table
Reference in a new issue