mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-22 01:10:05 +00:00
Merge branch 'citra-emu:master' into arm64-take-2
This commit is contained in:
commit
a8a12918f3
@ -15,7 +15,7 @@ chmod +x ./gradlew
|
|||||||
./gradlew assemble${BUILD_FLAVOR}Release
|
./gradlew assemble${BUILD_FLAVOR}Release
|
||||||
./gradlew bundle${BUILD_FLAVOR}Release
|
./gradlew bundle${BUILD_FLAVOR}Release
|
||||||
|
|
||||||
ccache -s
|
ccache -s -v
|
||||||
|
|
||||||
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
||||||
rm "${ANDROID_KEYSTORE_FILE}"
|
rm "${ANDROID_KEYSTORE_FILE}"
|
||||||
|
@ -12,4 +12,4 @@ cmake .. -GNinja \
|
|||||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||||
ninja
|
ninja
|
||||||
|
|
||||||
ccache -s
|
ccache -s -v
|
||||||
|
13
.ci/linux.sh
13
.ci/linux.sh
@ -1,10 +1,15 @@
|
|||||||
#!/bin/sh -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
if [ "$TARGET" = "appimage" ]; then
|
||||||
|
export COMPILER_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_LINKER=/etc/bin/ld.lld)
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -G Ninja \
|
cmake .. -G Ninja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
|
"${COMPILER_FLAGS[@]}" \
|
||||||
-DENABLE_QT_TRANSLATION=ON \
|
-DENABLE_QT_TRANSLATION=ON \
|
||||||
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
|
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
|
||||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||||
@ -13,8 +18,10 @@ ninja
|
|||||||
|
|
||||||
if [ "$TARGET" = "appimage" ]; then
|
if [ "$TARGET" = "appimage" ]; then
|
||||||
ninja bundle
|
ninja bundle
|
||||||
|
# TODO: Our AppImage environment currently uses an older ccache version without the verbose flag.
|
||||||
|
ccache -s
|
||||||
|
else
|
||||||
|
ccache -s -v
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
ctest -VV -C Release
|
ctest -VV -C Release
|
||||||
|
@ -13,7 +13,7 @@ cmake .. -GNinja \
|
|||||||
ninja
|
ninja
|
||||||
ninja bundle
|
ninja bundle
|
||||||
|
|
||||||
ccache -s
|
ccache -s -v
|
||||||
|
|
||||||
CURRENT_ARCH=`arch`
|
CURRENT_ARCH=`arch`
|
||||||
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
|
||||||
|
77
.ci/pack.sh
77
.ci/pack.sh
@ -1,41 +1,72 @@
|
|||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
# Determine the full revision name.
|
||||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||||
GITREV="`git show -s --format='%h'`"
|
GITREV="`git show -s --format='%h'`"
|
||||||
REV_NAME="citra-${OS}-${TARGET}-${GITDATE}-${GITREV}"
|
REV_NAME="citra-$OS-$TARGET-$GITDATE-$GITREV"
|
||||||
|
|
||||||
# Find out what release we are building
|
# Determine the name of the release being built.
|
||||||
if [[ "$GITHUB_REF_NAME" =~ ^canary- ]] || [[ "$GITHUB_REF_NAME" =~ ^nightly- ]]; then
|
if [[ "$GITHUB_REF_NAME" =~ ^canary- ]] || [[ "$GITHUB_REF_NAME" =~ ^nightly- ]]; then
|
||||||
RELEASE_NAME=$(echo $GITHUB_REF_NAME | cut -d- -f1)
|
RELEASE_NAME=$(echo $GITHUB_REF_NAME | cut -d- -f1)
|
||||||
else
|
else
|
||||||
RELEASE_NAME=head
|
RELEASE_NAME=head
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p artifacts
|
# Archive and upload the artifacts.
|
||||||
|
mkdir artifacts
|
||||||
|
|
||||||
if [ -z "${UPLOAD_RAW}" ]; then
|
function pack_artifacts() {
|
||||||
# Archive and upload the artifacts.
|
ARTIFACTS_PATH="$1"
|
||||||
|
|
||||||
|
# Set up root directory for archive.
|
||||||
mkdir "$REV_NAME"
|
mkdir "$REV_NAME"
|
||||||
mv build/bundle/* "$REV_NAME"
|
if [ -f "$ARTIFACTS_PATH" ]; then
|
||||||
|
mv "$ARTIFACTS_PATH" "$REV_NAME"
|
||||||
|
|
||||||
if [ "$OS" = "windows" ]; then
|
# Use file extension to differentiate archives.
|
||||||
ARCHIVE_NAME="${REV_NAME}.zip"
|
|
||||||
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_NAME"
|
|
||||||
else
|
|
||||||
ARCHIVE_NAME="${REV_NAME}.tar.gz"
|
|
||||||
tar czvf "$ARCHIVE_NAME" "$REV_NAME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$REV_NAME" $RELEASE_NAME
|
|
||||||
7z a "$REV_NAME.7z" $RELEASE_NAME
|
|
||||||
|
|
||||||
mv "$ARCHIVE_NAME" artifacts/
|
|
||||||
mv "$REV_NAME.7z" artifacts/
|
|
||||||
else
|
|
||||||
# Directly upload the raw artifacts, renamed with the revision.
|
|
||||||
for ARTIFACT in build/bundle/*; do
|
|
||||||
FILENAME=$(basename "$ARTIFACT")
|
FILENAME=$(basename "$ARTIFACT")
|
||||||
EXTENSION="${FILENAME##*.}"
|
EXTENSION="${FILENAME##*.}"
|
||||||
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
|
ARCHIVE_NAME="$REV_NAME.$EXTENSION"
|
||||||
|
else
|
||||||
|
mv "$ARTIFACTS_PATH"/* "$REV_NAME"
|
||||||
|
|
||||||
|
ARCHIVE_NAME="$REV_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create .zip/.tar.gz
|
||||||
|
if [ "$OS" = "windows" ]; then
|
||||||
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||||
|
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
|
||||||
|
elif [ "$OS" = "android" ]; then
|
||||||
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
|
||||||
|
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||||
|
else
|
||||||
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.tar.gz"
|
||||||
|
tar czvf "$ARCHIVE_FULL_NAME" "$REV_NAME"
|
||||||
|
fi
|
||||||
|
mv "$ARCHIVE_FULL_NAME" artifacts/
|
||||||
|
|
||||||
|
if [ -z "$SKIP_7Z" ]; then
|
||||||
|
# Create .7z
|
||||||
|
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.7z"
|
||||||
|
mv "$REV_NAME" "$RELEASE_NAME"
|
||||||
|
7z a "$ARCHIVE_FULL_NAME" "$RELEASE_NAME"
|
||||||
|
mv "$ARCHIVE_FULL_NAME" artifacts/
|
||||||
|
|
||||||
|
# Clean up created release artifacts directory.
|
||||||
|
rm -rf "$RELEASE_NAME"
|
||||||
|
else
|
||||||
|
# Clean up created rev artifacts directory.
|
||||||
|
rm -rf "$REV_NAME"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$PACK_INDIVIDUALLY" ]; then
|
||||||
|
# Pack all of the artifacts at once.
|
||||||
|
pack_artifacts build/bundle
|
||||||
|
else
|
||||||
|
# Pack and upload the artifacts one-by-one.
|
||||||
|
for ARTIFACT in build/bundle/*; do
|
||||||
|
pack_artifacts "$ARTIFACT"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
@ -12,6 +12,6 @@ cmake .. -G Ninja \
|
|||||||
ninja
|
ninja
|
||||||
ninja bundle
|
ninja bundle
|
||||||
|
|
||||||
ccache -s
|
ccache -s -v
|
||||||
|
|
||||||
ctest -VV -C Release || echo "::error ::Test error occurred on Windows build"
|
ctest -VV -C Release || echo "::error ::Test error occurred on Windows build"
|
||||||
|
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
@ -32,6 +32,8 @@ jobs:
|
|||||||
options: -u 1001
|
options: -u 1001
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: linux
|
OS: linux
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
@ -128,14 +130,14 @@ jobs:
|
|||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: ["x86_64", "arm64"]
|
target: ["x86_64", "arm64"]
|
||||||
env:
|
env:
|
||||||
CCACHE_CPP2: yes
|
|
||||||
CCACHE_SLOPPINESS: time_macros
|
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: macos
|
OS: macos
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
@ -161,15 +163,13 @@ jobs:
|
|||||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
macos-universal:
|
macos-universal:
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
needs: macos
|
needs: macos
|
||||||
env:
|
env:
|
||||||
OS: macos
|
OS: macos
|
||||||
TARGET: universal
|
TARGET: universal
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- name: Download x86_64 build from cache
|
- name: Download x86_64 build from cache
|
||||||
uses: actions/cache/restore@v3
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
@ -203,6 +203,8 @@ jobs:
|
|||||||
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: windows
|
OS: windows
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
@ -227,7 +229,7 @@ jobs:
|
|||||||
if: ${{ matrix.target == 'msvc' }}
|
if: ${{ matrix.target == 'msvc' }}
|
||||||
with:
|
with:
|
||||||
vulkan-query-version: latest
|
vulkan-query-version: latest
|
||||||
vulkan-components: Glslang
|
vulkan-components: SPIRV-Tools, Glslang
|
||||||
vulkan-use-cache: true
|
vulkan-use-cache: true
|
||||||
- name: Set up MSYS2
|
- name: Set up MSYS2
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
@ -255,6 +257,9 @@ jobs:
|
|||||||
android:
|
android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: android
|
OS: android
|
||||||
TARGET: universal
|
TARGET: universal
|
||||||
steps:
|
steps:
|
||||||
@ -267,7 +272,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
~/.ccache
|
${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-android-${{ github.sha }}
|
key: ${{ runner.os }}-android-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-android-
|
${{ runner.os }}-android-
|
||||||
@ -292,19 +297,20 @@ jobs:
|
|||||||
run: ../../../.ci/pack.sh
|
run: ../../../.ci/pack.sh
|
||||||
working-directory: src/android/app
|
working-directory: src/android/app
|
||||||
env:
|
env:
|
||||||
UPLOAD_RAW: 1
|
PACK_INDIVIDUALLY: 1
|
||||||
|
SKIP_7Z: 1
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: src/android/app/artifacts/
|
path: src/android/app/artifacts/
|
||||||
ios:
|
ios:
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
env:
|
env:
|
||||||
CCACHE_CPP2: yes
|
|
||||||
CCACHE_SLOPPINESS: time_macros
|
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_SLOPPINESS: time_macros
|
||||||
OS: ios
|
OS: ios
|
||||||
TARGET: arm64
|
TARGET: arm64
|
||||||
steps:
|
steps:
|
||||||
|
8
.gitmodules
vendored
8
.gitmodules
vendored
@ -79,9 +79,15 @@
|
|||||||
[submodule "sirit"]
|
[submodule "sirit"]
|
||||||
path = externals/sirit
|
path = externals/sirit
|
||||||
url = https://github.com/yuzu-emu/sirit
|
url = https://github.com/yuzu-emu/sirit
|
||||||
|
[submodule "faad2"]
|
||||||
|
path = externals/faad2/faad2
|
||||||
|
url = https://github.com/knik0/faad2
|
||||||
[submodule "library-headers"]
|
[submodule "library-headers"]
|
||||||
path = externals/library-headers/library-headers
|
path = externals/library-headers
|
||||||
url = https://github.com/citra-emu/ext-library-headers.git
|
url = https://github.com/citra-emu/ext-library-headers.git
|
||||||
[submodule "libadrenotools"]
|
[submodule "libadrenotools"]
|
||||||
path = externals/libadrenotools
|
path = externals/libadrenotools
|
||||||
url = https://github.com/bylaws/libadrenotools
|
url = https://github.com/bylaws/libadrenotools
|
||||||
|
[submodule "oaknut"]
|
||||||
|
path = externals/oaknut
|
||||||
|
url = https://github.com/merryhime/oaknut.git
|
||||||
|
@ -17,6 +17,12 @@ include(CMakeDependentOption)
|
|||||||
|
|
||||||
project(citra LANGUAGES C CXX ASM)
|
project(citra LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
|
# Some submodules like to pick their own default build type if not specified.
|
||||||
|
# Make sure we default to Release build type always, unless the generator has custom types.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
|
||||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||||
@ -75,9 +81,6 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
|||||||
|
|
||||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder (preferred over FFmpeg)" ON "WIN32" OFF)
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_AUDIOTOOLBOX "Use AudioToolbox decoder (preferred over FFmpeg)" ON "APPLE" OFF)
|
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
||||||
|
|
||||||
# Compile options
|
# Compile options
|
||||||
@ -86,19 +89,16 @@ option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
|||||||
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||||
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
|
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
|
||||||
|
|
||||||
# System library options
|
include(CitraHandleSystemLibs)
|
||||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_QT "Use the system Qt lib (instead of the bundled one)" OFF "ENABLE_QT;MSVC OR APPLE" ON)
|
|
||||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
|
||||||
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
|
||||||
option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF)
|
|
||||||
option(USE_SYSTEM_OPENSSL "Use the system OpenSSL libs (instead of the bundled LibreSSL)" OFF)
|
|
||||||
option(USE_SYSTEM_LIBUSB "Use the system libusb (instead of the bundled libusb)" OFF)
|
|
||||||
option(USE_SYSTEM_CPP_JWT "Use the system cpp-jwt (instead of the bundled one)" OFF)
|
|
||||||
option(USE_SYSTEM_SOUNDTOUCH "Use the system SoundTouch (instead of the bundled one)" OFF)
|
|
||||||
|
|
||||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||||
message(STATUS "Using Precompiled Headers.")
|
message(STATUS "Using Precompiled Headers.")
|
||||||
set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
|
set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
|
||||||
|
|
||||||
|
# This ensures that pre-compiled headers won't invalidate build caches for every fresh checkout.
|
||||||
|
if(NOT MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
list(APPEND CMAKE_CXX_COMPILE_OPTIONS_CREATE_PCH -Xclang -fno-pch-timestamp)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||||
@ -233,7 +233,7 @@ find_package(Threads REQUIRED)
|
|||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (NOT USE_SYSTEM_QT)
|
if (NOT USE_SYSTEM_QT)
|
||||||
download_qt(6.5.1)
|
download_qt(6.6.0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
||||||
# Params:
|
# Params:
|
||||||
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
||||||
@ -52,28 +54,38 @@ function(download_qt target)
|
|||||||
get_external_prefix(qt base_path)
|
get_external_prefix(qt base_path)
|
||||||
file(MAKE_DIRECTORY "${base_path}")
|
file(MAKE_DIRECTORY "${base_path}")
|
||||||
|
|
||||||
|
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
|
||||||
if (DOWNLOAD_QT_TOOL)
|
if (DOWNLOAD_QT_TOOL)
|
||||||
set(prefix "${base_path}/Tools")
|
set(prefix "${base_path}/Tools")
|
||||||
set(install_args install-tool --outputdir ${base_path} ${host} desktop ${target})
|
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
|
||||||
else()
|
else()
|
||||||
set(prefix "${base_path}/${target}/${arch_path}")
|
set(prefix "${base_path}/${target}/${arch_path}")
|
||||||
if (host_arch_path)
|
if (host_arch_path)
|
||||||
set(host_flag "--autodesktop")
|
set(host_flag "--autodesktop")
|
||||||
set(host_prefix "${base_path}/${target}/${host_arch_path}")
|
set(host_prefix "${base_path}/${target}/${host_arch_path}")
|
||||||
endif()
|
endif()
|
||||||
set(install_args install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
|
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
|
||||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT EXISTS "${prefix}")
|
if (NOT EXISTS "${prefix}")
|
||||||
message(STATUS "Downloading binaries for Qt...")
|
message(STATUS "Downloading binaries for Qt...")
|
||||||
|
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(aqt_path "${base_path}/aqt.exe")
|
set(aqt_path "${base_path}/aqt.exe")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD
|
||||||
https://github.com/miurahr/aqtinstall/releases/download/v3.1.7/aqt.exe
|
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||||
${aqt_path} SHOW_PROGRESS)
|
${aqt_path} SHOW_PROGRESS)
|
||||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||||
WORKING_DIRECTORY ${base_path})
|
WORKING_DIRECTORY ${base_path})
|
||||||
|
elseif (APPLE)
|
||||||
|
set(aqt_path "${base_path}/aqt-macos")
|
||||||
|
file(DOWNLOAD
|
||||||
|
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||||
|
${aqt_path} SHOW_PROGRESS)
|
||||||
|
execute_process(COMMAND chmod +x ${aqt_path})
|
||||||
|
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||||
|
WORKING_DIRECTORY ${base_path})
|
||||||
else()
|
else()
|
||||||
# aqt does not offer binary releases for other platforms, so download and run from pip.
|
# aqt does not offer binary releases for other platforms, so download and run from pip.
|
||||||
set(aqt_install_path "${base_path}/aqt")
|
set(aqt_install_path "${base_path}/aqt")
|
||||||
|
@ -10,16 +10,20 @@ set(HASH_FILES
|
|||||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
||||||
|
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
|
||||||
|
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.h"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.h"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.cpp"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
||||||
|
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
||||||
|
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
|
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.h"
|
"${VIDEO_CORE}/shader/generator/shader_uniforms.h"
|
||||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.cpp"
|
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.cpp"
|
||||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.h"
|
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.h"
|
||||||
"${VIDEO_CORE}/shader/shader.cpp"
|
"${VIDEO_CORE}/shader/shader.cpp"
|
||||||
"${VIDEO_CORE}/shader/shader.h"
|
"${VIDEO_CORE}/shader/shader.h"
|
||||||
"${VIDEO_CORE}/pica.cpp"
|
"${VIDEO_CORE}/pica.cpp"
|
||||||
|
29
CMakeModules/aqt_config.ini
Normal file
29
CMakeModules/aqt_config.ini
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[aqt]
|
||||||
|
concurrency: 2
|
||||||
|
|
||||||
|
[mirrors]
|
||||||
|
trusted_mirrors:
|
||||||
|
https://download.qt.io
|
||||||
|
blacklist:
|
||||||
|
https://qt.mirror.constant.com
|
||||||
|
https://mirrors.ocf.berkeley.edu
|
||||||
|
https://mirrors.ustc.edu.cn
|
||||||
|
https://mirrors.tuna.tsinghua.edu.cn
|
||||||
|
https://mirrors.geekpie.club
|
||||||
|
https://mirrors-wan.geekpie.club
|
||||||
|
https://mirrors.sjtug.sjtu.edu.cn
|
||||||
|
fallbacks:
|
||||||
|
https://qtproject.mirror.liquidtelecom.com/
|
||||||
|
https://mirrors.aliyun.com/qt/
|
||||||
|
https://ftp.jaist.ac.jp/pub/qtproject/
|
||||||
|
https://ftp.yz.yamagata-u.ac.jp/pub/qtproject/
|
||||||
|
https://qt-mirror.dannhauer.de/
|
||||||
|
https://ftp.fau.de/qtproject/
|
||||||
|
https://mirror.netcologne.de/qtproject/
|
||||||
|
https://mirrors.dotsrc.org/qtproject/
|
||||||
|
https://www.nic.funet.fi/pub/mirrors/download.qt-project.org/
|
||||||
|
https://master.qt.io/
|
||||||
|
https://mirrors.ukfast.co.uk/sites/qt.io/
|
||||||
|
https://ftp2.nluug.nl/languages/qt/
|
||||||
|
https://ftp1.nluug.nl/languages/qt/
|
||||||
|
|
2
dist/apple/Info.plist.in
vendored
2
dist/apple/Info.plist.in
vendored
@ -21,6 +21,8 @@
|
|||||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||||
<!-- Fixed -->
|
<!-- Fixed -->
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.games</string>
|
<string>public.app-category.games</string>
|
||||||
|
4
dist/languages/.tx/config
vendored
4
dist/languages/.tx/config
vendored
@ -7,3 +7,7 @@ source_file = en.ts
|
|||||||
source_lang = en
|
source_lang = en
|
||||||
type = QT
|
type = QT
|
||||||
|
|
||||||
|
[o:citra:p:citra:r:android]
|
||||||
|
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||||
|
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||||
|
type = ANDROID
|
||||||
|
3385
dist/languages/da_DK.ts
vendored
3385
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load Diff
3392
dist/languages/de.ts
vendored
3392
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
3469
dist/languages/el.ts
vendored
3469
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
3356
dist/languages/es_ES.ts
vendored
3356
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load Diff
3386
dist/languages/fi.ts
vendored
3386
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
3602
dist/languages/fr.ts
vendored
3602
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
6930
dist/languages/hu_HU.ts
vendored
Normal file
6930
dist/languages/hu_HU.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3383
dist/languages/id.ts
vendored
3383
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
3530
dist/languages/it.ts
vendored
3530
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
3446
dist/languages/ja_JP.ts
vendored
3446
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
3586
dist/languages/ko_KR.ts
vendored
3586
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
3379
dist/languages/lt_LT.ts
vendored
3379
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load Diff
3391
dist/languages/nb.ts
vendored
3391
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
4243
dist/languages/nl.ts
vendored
4243
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
3386
dist/languages/pl_PL.ts
vendored
3386
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load Diff
3461
dist/languages/pt_BR.ts
vendored
3461
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
3414
dist/languages/ro_RO.ts
vendored
3414
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load Diff
3393
dist/languages/ru_RU.ts
vendored
3393
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
3402
dist/languages/tr_TR.ts
vendored
3402
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
3563
dist/languages/vi_VN.ts
vendored
3563
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
3376
dist/languages/zh_CN.ts
vendored
3376
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
3605
dist/languages/zh_TW.ts
vendored
3605
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
10
dist/qt_themes/colorful_dark/icons/index.theme
vendored
10
dist/qt_themes/colorful_dark/icons/index.theme
vendored
@ -2,7 +2,13 @@
|
|||||||
Name=colorful_dark
|
Name=colorful_dark
|
||||||
Comment=Colorful theme (Dark style)
|
Comment=Colorful theme (Dark style)
|
||||||
Inherits=default
|
Inherits=default
|
||||||
Directories=16x16
|
Directories=16x16,48x48,256x256
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
||||||
|
[48x48]
|
||||||
|
Size=48
|
||||||
|
|
||||||
|
[256x256]
|
||||||
|
Size=256
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
Name=colorful_midnight_blue
|
Name=colorful_midnight_blue
|
||||||
Comment=Colorful theme (Midnight Blue style)
|
Comment=Colorful theme (Midnight Blue style)
|
||||||
Inherits=default
|
Inherits=default
|
||||||
Directories=16x16
|
Directories=16x16,48x48,256x256
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
||||||
|
[48x48]
|
||||||
|
Size=48
|
||||||
|
|
||||||
|
[256x256]
|
||||||
|
Size=256
|
||||||
|
270
externals/CMakeLists.txt
vendored
270
externals/CMakeLists.txt
vendored
@ -34,32 +34,34 @@ if (NOT USE_SYSTEM_BOOST)
|
|||||||
)
|
)
|
||||||
target_link_libraries(boost_iostreams PUBLIC boost)
|
target_link_libraries(boost_iostreams PUBLIC boost)
|
||||||
# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
|
# 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)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
add_library(catch2 INTERFACE)
|
||||||
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
if(USE_SYSTEM_CATCH2)
|
||||||
add_subdirectory(catch2)
|
find_package(Catch2 3.0.0 REQUIRED)
|
||||||
|
else()
|
||||||
|
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
|
||||||
|
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||||
|
add_subdirectory(catch2)
|
||||||
|
endif()
|
||||||
|
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
|
||||||
|
|
||||||
# Crypto++
|
# Crypto++
|
||||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
if(USE_SYSTEM_CRYPTOPP)
|
||||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
find_package(cryptopp REQUIRED)
|
||||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
add_library(cryptopp INTERFACE)
|
||||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||||
add_subdirectory(cryptopp-cmake)
|
else()
|
||||||
|
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||||
# HACK: The logic to set up the base include directory for CryptoPP does not work with Android SDK CMake 3.22.1.
|
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||||
# Until there is a fixed version available, this code will detect and add in the proper include if it does not exist.
|
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||||
if(ANDROID)
|
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
||||||
message(STATUS "Applying CryptoPP include fix.")
|
add_subdirectory(cryptopp-cmake)
|
||||||
get_target_property(CRYPTOPP_INCLUDES cryptopp INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
foreach(CRYPTOPP_INCLUDE ${CRYPTOPP_INCLUDES})
|
|
||||||
if("${CRYPTOPP_INCLUDE}" MATCHES "\\$<BUILD_INTERFACE:(.*)/cryptopp>")
|
|
||||||
message(STATUS "Fixed include path: ${CMAKE_MATCH_1}")
|
|
||||||
target_include_directories(cryptopp PUBLIC $<BUILD_INTERFACE:${CMAKE_MATCH_1}>)
|
|
||||||
break()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# dds-ktx
|
# dds-ktx
|
||||||
@ -68,19 +70,49 @@ target_include_directories(dds-ktx INTERFACE ./dds-ktx)
|
|||||||
|
|
||||||
# fmt and Xbyak need to be added before dynarmic
|
# fmt and Xbyak need to be added before dynarmic
|
||||||
# libfmt
|
# libfmt
|
||||||
option(FMT_INSTALL "" ON)
|
if(USE_SYSTEM_FMT)
|
||||||
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
add_library(fmt INTERFACE)
|
||||||
|
find_package(fmt REQUIRED)
|
||||||
|
target_link_libraries(fmt INTERFACE fmt::fmt)
|
||||||
|
else()
|
||||||
|
option(FMT_INSTALL "" ON)
|
||||||
|
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
# Xbyak
|
# Xbyak
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
if ("x86_64" IN_LIST ARCHITECTURE)
|
||||||
add_subdirectory(xbyak EXCLUDE_FROM_ALL)
|
if(USE_SYSTEM_XBYAK)
|
||||||
|
find_package(xbyak REQUIRED)
|
||||||
|
add_library(xbyak INTERFACE)
|
||||||
|
target_link_libraries(xbyak INTERFACE xbyak::xbyak)
|
||||||
|
else()
|
||||||
|
add_subdirectory(xbyak EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Oaknut
|
||||||
|
if ("arm64" IN_LIST ARCHITECTURE)
|
||||||
|
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Dynarmic
|
# Dynarmic
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||||
set(DYNARMIC_TESTS OFF CACHE BOOL "")
|
if(USE_SYSTEM_DYNARMIC)
|
||||||
set(DYNARMIC_FRONTENDS "A32" CACHE STRING "")
|
find_package(dynarmic REQUIRED)
|
||||||
add_subdirectory(dynarmic EXCLUDE_FROM_ALL)
|
add_library(dynarmic INTERFACE)
|
||||||
|
target_link_libraries(dynarmic INTERFACE dynarmic::dynarmic)
|
||||||
|
# The dynarmic package's cmake files are helpfully completely silent
|
||||||
|
# so we have to inform the user of its status ourselves
|
||||||
|
if(TARGET dynarmic::dynarmic)
|
||||||
|
message(STATUS "Found dynarmic")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(DYNARMIC_TESTS OFF CACHE BOOL "")
|
||||||
|
set(DYNARMIC_FRONTENDS "A32" CACHE STRING "")
|
||||||
|
set(DYNARMIC_USE_PRECOMPILED_HEADERS ${CITRA_USE_PRECOMPILED_HEADERS} CACHE BOOL "")
|
||||||
|
add_subdirectory(dynarmic EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# getopt
|
# getopt
|
||||||
@ -92,16 +124,33 @@ endif()
|
|||||||
add_subdirectory(glad)
|
add_subdirectory(glad)
|
||||||
|
|
||||||
# glslang
|
# glslang
|
||||||
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
|
if(USE_SYSTEM_GLSLANG)
|
||||||
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
|
find_package(glslang REQUIRED)
|
||||||
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
|
add_library(glslang INTERFACE)
|
||||||
set(ENABLE_CTEST OFF CACHE BOOL "")
|
add_library(SPIRV INTERFACE)
|
||||||
set(ENABLE_HLSL OFF CACHE BOOL "")
|
target_link_libraries(glslang INTERFACE glslang::glslang)
|
||||||
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
target_link_libraries(SPIRV INTERFACE glslang::SPIRV)
|
||||||
add_subdirectory(glslang)
|
# System include path is different from submodule include path
|
||||||
|
get_target_property(GLSLANG_PREFIX glslang::SPIRV INTERFACE_INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories(SPIRV SYSTEM INTERFACE "${GLSLANG_PREFIX}/glslang")
|
||||||
|
else()
|
||||||
|
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
|
||||||
|
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
|
||||||
|
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
|
||||||
|
set(ENABLE_CTEST OFF CACHE BOOL "")
|
||||||
|
set(ENABLE_HLSL OFF CACHE BOOL "")
|
||||||
|
set(BUILD_EXTERNAL OFF CACHE BOOL "")
|
||||||
|
add_subdirectory(glslang)
|
||||||
|
endif()
|
||||||
|
|
||||||
# inih
|
# inih
|
||||||
add_subdirectory(inih)
|
if(USE_SYSTEM_INIH)
|
||||||
|
find_package(inih REQUIRED COMPONENTS inih inir)
|
||||||
|
add_library(inih INTERFACE)
|
||||||
|
target_link_libraries(inih INTERFACE inih::inih inih::inir)
|
||||||
|
else()
|
||||||
|
add_subdirectory(inih)
|
||||||
|
endif()
|
||||||
|
|
||||||
# MicroProfile
|
# MicroProfile
|
||||||
add_library(microprofile INTERFACE)
|
add_library(microprofile INTERFACE)
|
||||||
@ -118,8 +167,26 @@ endif()
|
|||||||
# Open Source Archives
|
# Open Source Archives
|
||||||
add_subdirectory(open_source_archives)
|
add_subdirectory(open_source_archives)
|
||||||
|
|
||||||
|
# faad2
|
||||||
|
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
# Dynamic library headers
|
# Dynamic library headers
|
||||||
add_subdirectory(library-headers EXCLUDE_FROM_ALL)
|
add_library(library-headers INTERFACE)
|
||||||
|
|
||||||
|
if (USE_SYSTEM_FFMPEG_HEADERS)
|
||||||
|
find_path(SYSTEM_FFMPEG_INCLUDES NAMES libavutil/avutil.h)
|
||||||
|
if (SYSTEM_FFMPEG_INCLUDES STREQUAL "SYSTEM_FFMPEG_INCLUDES-NOTFOUND")
|
||||||
|
message(WARNING "System FFmpeg headers not found. Falling back on bundled headers.")
|
||||||
|
else()
|
||||||
|
message(STATUS "Using system FFmpeg headers.")
|
||||||
|
target_include_directories(library-headers SYSTEM INTERFACE ${SYSTEM_FFMPEG_INCLUDES})
|
||||||
|
set(FOUND_FFMPEG_HEADERS ON)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if (NOT FOUND_FFMPEG_HEADERS)
|
||||||
|
message(STATUS "Using bundled ffmpeg headers.")
|
||||||
|
target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/ffmpeg/include)
|
||||||
|
endif()
|
||||||
|
|
||||||
# SoundTouch
|
# SoundTouch
|
||||||
if(NOT USE_SYSTEM_SOUNDTOUCH)
|
if(NOT USE_SYSTEM_SOUNDTOUCH)
|
||||||
@ -149,22 +216,47 @@ if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Zstandard
|
# Zstandard
|
||||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
if(USE_SYSTEM_ZSTD)
|
||||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
find_package(zstd REQUIRED)
|
||||||
set(ZSTD_BUILD_SHARED OFF)
|
add_library(zstd INTERFACE)
|
||||||
add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)
|
if(TARGET zstd::libzstd_shared)
|
||||||
target_include_directories(libzstd_static INTERFACE $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/externals/zstd/lib>)
|
message(STATUS "Found system Zstandard")
|
||||||
|
endif()
|
||||||
|
target_link_libraries(zstd INTERFACE zstd::libzstd_shared)
|
||||||
|
else()
|
||||||
|
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||||
|
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||||
|
set(ZSTD_BUILD_SHARED OFF)
|
||||||
|
add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||||
|
target_include_directories(libzstd_static INTERFACE $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/externals/zstd/lib>)
|
||||||
|
add_library(zstd ALIAS libzstd_static)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ENet
|
# ENet
|
||||||
add_subdirectory(enet)
|
if(USE_SYSTEM_ENET)
|
||||||
target_include_directories(enet INTERFACE ./enet/include)
|
find_package(libenet REQUIRED)
|
||||||
|
add_library(enet INTERFACE)
|
||||||
|
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||||
|
else()
|
||||||
|
add_subdirectory(enet)
|
||||||
|
target_include_directories(enet INTERFACE ./enet/include)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Cubeb
|
# Cubeb
|
||||||
if (ENABLE_CUBEB)
|
if (ENABLE_CUBEB)
|
||||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
if(USE_SYSTEM_CUBEB)
|
||||||
set(BUILD_TOOLS OFF CACHE BOOL "")
|
find_package(cubeb REQUIRED)
|
||||||
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
add_library(cubeb INTERFACE)
|
||||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
target_link_libraries(cubeb INTERFACE cubeb::cubeb)
|
||||||
|
if(TARGET cubeb::cubeb)
|
||||||
|
message(STATUS "Found system cubeb")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||||
|
set(BUILD_TOOLS OFF CACHE BOOL "")
|
||||||
|
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
||||||
|
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
@ -175,7 +267,16 @@ endif()
|
|||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
add_library(json-headers INTERFACE)
|
add_library(json-headers INTERFACE)
|
||||||
target_include_directories(json-headers INTERFACE ./json)
|
if (USE_SYSTEM_JSON)
|
||||||
|
find_package(nlohmann_json REQUIRED)
|
||||||
|
target_link_libraries(json-headers INTERFACE nlohmann_json::nlohmann_json)
|
||||||
|
get_target_property(NLOHMANN_PREFIX nlohmann_json::nlohmann_json INTERFACE_INCLUDE_DIRECTORIES)
|
||||||
|
# The nlohmann-json3 package expects "#include <nlohmann/json.hpp>"
|
||||||
|
# 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)
|
||||||
|
endif()
|
||||||
|
|
||||||
# OpenSSL
|
# OpenSSL
|
||||||
if (USE_SYSTEM_OPENSSL)
|
if (USE_SYSTEM_OPENSSL)
|
||||||
@ -190,7 +291,7 @@ if (NOT OPENSSL_FOUND)
|
|||||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||||
set(OPENSSLDIR "/etc/ssl/")
|
set(OPENSSLDIR "/etc/ssl/")
|
||||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
target_include_directories(ssl SYSTEM INTERFACE ./libressl/include)
|
||||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||||
get_directory_property(OPENSSL_LIBRARIES
|
get_directory_property(OPENSSL_LIBRARIES
|
||||||
DIRECTORY libressl
|
DIRECTORY libressl
|
||||||
@ -199,7 +300,17 @@ endif()
|
|||||||
|
|
||||||
# httplib
|
# httplib
|
||||||
add_library(httplib INTERFACE)
|
add_library(httplib INTERFACE)
|
||||||
target_include_directories(httplib INTERFACE ./httplib)
|
if(USE_SYSTEM_CPP_HTTPLIB)
|
||||||
|
find_package(CppHttp 0.14.1)
|
||||||
|
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()
|
||||||
|
else()
|
||||||
|
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||||
|
endif()
|
||||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||||
|
|
||||||
@ -216,13 +327,19 @@ if (ENABLE_WEB_SERVICE)
|
|||||||
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
target_link_libraries(cpp-jwt INTERFACE cpp-jwt::cpp-jwt)
|
||||||
else()
|
else()
|
||||||
add_library(cpp-jwt INTERFACE)
|
add_library(cpp-jwt INTERFACE)
|
||||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
target_include_directories(cpp-jwt SYSTEM INTERFACE ./cpp-jwt/include)
|
||||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# lodepng
|
# lodepng
|
||||||
add_subdirectory(lodepng)
|
if(USE_SYSTEM_LODEPNG)
|
||||||
|
add_library(lodepng INTERFACE)
|
||||||
|
find_package(lodepng REQUIRED)
|
||||||
|
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
|
||||||
|
else()
|
||||||
|
add_subdirectory(lodepng)
|
||||||
|
endif()
|
||||||
|
|
||||||
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
@ -233,29 +350,52 @@ endif()
|
|||||||
|
|
||||||
# OpenAL Soft
|
# OpenAL Soft
|
||||||
if (ENABLE_OPENAL)
|
if (ENABLE_OPENAL)
|
||||||
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
if(USE_SYSTEM_OPENAL)
|
||||||
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
add_library(OpenAL INTERFACE)
|
||||||
set(ALSOFT_INSTALL OFF CACHE BOOL "")
|
find_package(OpenAL REQUIRED)
|
||||||
set(ALSOFT_INSTALL_CONFIG OFF CACHE BOOL "")
|
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
|
||||||
set(ALSOFT_INSTALL_HRTF_DATA OFF CACHE BOOL "")
|
else()
|
||||||
set(ALSOFT_INSTALL_AMBDEC_PRESETS OFF CACHE BOOL "")
|
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
|
||||||
set(ALSOFT_UTILS OFF CACHE BOOL "")
|
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
|
||||||
set(LIBTYPE "STATIC" CACHE STRING "")
|
set(ALSOFT_INSTALL OFF CACHE BOOL "")
|
||||||
add_subdirectory(openal-soft EXCLUDE_FROM_ALL)
|
set(ALSOFT_INSTALL_CONFIG OFF CACHE BOOL "")
|
||||||
|
set(ALSOFT_INSTALL_HRTF_DATA OFF CACHE BOOL "")
|
||||||
|
set(ALSOFT_INSTALL_AMBDEC_PRESETS OFF CACHE BOOL "")
|
||||||
|
set(ALSOFT_UTILS OFF CACHE BOOL "")
|
||||||
|
set(LIBTYPE "STATIC" CACHE STRING "")
|
||||||
|
add_subdirectory(openal-soft EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# VMA
|
# VMA
|
||||||
add_library(vma INTERFACE)
|
if(USE_SYSTEM_VMA)
|
||||||
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
|
add_library(vma INTERFACE)
|
||||||
|
find_package(VulkanMemoryAllocator REQUIRED)
|
||||||
|
if(TARGET GPUOpen::VulkanMemoryAllocator)
|
||||||
|
message(STATUS "Found VulkanMemoryAllocator")
|
||||||
|
target_link_libraries(vma INTERFACE GPUOpen::VulkanMemoryAllocator)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
add_library(vma INTERFACE)
|
||||||
|
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
|
||||||
|
endif()
|
||||||
|
|
||||||
# vulkan-headers
|
# vulkan-headers
|
||||||
add_library(vulkan-headers INTERFACE)
|
add_library(vulkan-headers INTERFACE)
|
||||||
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
|
if(USE_SYSTEM_VULKAN_HEADERS)
|
||||||
|
find_package(Vulkan REQUIRED)
|
||||||
|
if(TARGET Vulkan::Headers)
|
||||||
|
message(STATUS "Found Vulkan headers")
|
||||||
|
target_link_libraries(vulkan-headers INTERFACE Vulkan::Headers)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
|
||||||
|
endif()
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
target_include_directories(vulkan-headers SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK)
|
target_include_directories(vulkan-headers SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# adrenotools
|
# adrenotools
|
||||||
if (ANDROID)
|
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
|
||||||
add_subdirectory(libadrenotools)
|
add_subdirectory(libadrenotools)
|
||||||
endif()
|
endif()
|
||||||
|
100
externals/cmake-modules/CitraHandleSystemLibs.cmake
vendored
Normal file
100
externals/cmake-modules/CitraHandleSystemLibs.cmake
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
option(USE_SYSTEM_LIBS "Use system libraries over bundled ones" OFF)
|
||||||
|
|
||||||
|
# System library options
|
||||||
|
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_QT "Use the system Qt lib (instead of the bundled one)" OFF "ENABLE_QT;MSVC OR APPLE" ON)
|
||||||
|
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
||||||
|
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF)
|
||||||
|
option(USE_SYSTEM_OPENSSL "Use the system OpenSSL libs (instead of the bundled LibreSSL)" OFF)
|
||||||
|
option(USE_SYSTEM_LIBUSB "Use the system libusb (instead of the bundled libusb)" OFF)
|
||||||
|
option(USE_SYSTEM_CPP_JWT "Use the system cpp-jwt (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_SOUNDTOUCH "Use the system SoundTouch (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_CPP_HTTPLIB "Use the system cpp-httplib (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
||||||
|
option(USE_SYSTEM_ZSTD "Use the system Zstandard library (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_ENET "Use the system libenet (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_CRYPTOPP "Use the system cryptopp (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_CUBEB "Use the system cubeb (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_LODEPNG "Use the system lodepng (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_OPENAL "Use the system OpenAL (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_VMA "Use the system VulkanMemoryAllocator (instead of the bundled one)" OFF)
|
||||||
|
option(USE_SYSTEM_VULKAN_HEADERS "Use the system Vulkan headers (instead of the bundled ones)" OFF)
|
||||||
|
option(USE_SYSTEM_CATCH2 "Use the system Catch2 (instead of the bundled one)" OFF)
|
||||||
|
|
||||||
|
# Qt and MoltenVK are handled separately
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_BOOST "Disable system Boost" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OPENSSL "Disable system OpenSSL" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_LIBUSB "Disable system LibUSB" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CPP_JWT "Disable system cpp-jwt" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SOUNDTOUCH "Disable system SoundTouch" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CPP_HTTPLIB "Disable system cpp-httplib" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ZSTD "Disable system Zstandard" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ENET "Disable system libenet" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CRYPTOPP "Disable system cryptopp" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CUBEB "Disable system cubeb" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_LODEPNG "Disable system lodepng" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OPENAL "Disable system OpenAL" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VMA "Disable system VulkanMemoryAllocator" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_VULKAN_HEADERS "Disable system Vulkan headers" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CATCH2 "Disable system Catch2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
|
|
||||||
|
set(LIB_VAR_LIST
|
||||||
|
SDL2
|
||||||
|
BOOST
|
||||||
|
OPENSSL
|
||||||
|
LIBUSB
|
||||||
|
CPP_JWT
|
||||||
|
SOUNDTOUCH
|
||||||
|
CPP_HTTPLIB
|
||||||
|
JSON
|
||||||
|
DYNARMIC
|
||||||
|
FMT
|
||||||
|
XBYAK
|
||||||
|
INIH
|
||||||
|
FFMPEG_HEADERS
|
||||||
|
GLSLANG
|
||||||
|
ZSTD
|
||||||
|
ENET
|
||||||
|
CRYPTOPP
|
||||||
|
CUBEB
|
||||||
|
LODEPNG
|
||||||
|
OPENAL
|
||||||
|
VMA
|
||||||
|
VULKAN_HEADERS
|
||||||
|
CATCH2
|
||||||
|
)
|
||||||
|
|
||||||
|
# First, check that USE_SYSTEM_XXX is not used with USE_SYSTEM_LIBS
|
||||||
|
|
||||||
|
if(USE_SYSTEM_LIBS)
|
||||||
|
foreach(CURRENT_LIB IN LISTS LIB_VAR_LIST)
|
||||||
|
if(USE_SYSTEM_${CURRENT_LIB})
|
||||||
|
unset(USE_SYSTEM_${CURRENT_LIB})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Next, set which libraries to use
|
||||||
|
|
||||||
|
foreach(CURRENT_LIB IN LISTS LIB_VAR_LIST)
|
||||||
|
if(NOT DISABLE_SYSTEM_${CURRENT_LIB})
|
||||||
|
set(USE_SYSTEM_${CURRENT_LIB} ON CACHE BOOL "Using system ${CURRENT_LIB}" FORCE)
|
||||||
|
else()
|
||||||
|
# Explicitly disable this in case of multiple CMake invocations
|
||||||
|
set(USE_SYSTEM_${CURRENT_LIB} OFF CACHE BOOL "Using system ${CURRENT_LIB}" FORCE)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
46
externals/cmake-modules/FindCppHttp.cmake
vendored
Normal file
46
externals/cmake-modules/FindCppHttp.cmake
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
if(NOT CppHttp_FOUND)
|
||||||
|
pkg_check_modules(CPP_HTTPLIB cpp-httplib)
|
||||||
|
|
||||||
|
if (CPP_HTTPLIB_FOUND)
|
||||||
|
find_path(HTTPLIB_INCLUDE_DIR NAMES httplib.h
|
||||||
|
PATHS
|
||||||
|
${CPP_HTTPLIB_INCLUDE_DIRS}
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(HTTPLIB_LIBRARY NAMES cpp-httplib
|
||||||
|
PATHS
|
||||||
|
${CPP_HTTPLIB_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HTTPLIB_VERSION ${CPP_HTTPLIB_VERSION})
|
||||||
|
|
||||||
|
if (NOT TARGET cpp-httplib::cpp-httplib)
|
||||||
|
add_library(cpp-httplib::cpp-httplib INTERFACE IMPORTED)
|
||||||
|
set_target_properties(cpp-httplib::cpp-httplib PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${HTTPLIB_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${HTTPLIB_LIBRARY}"
|
||||||
|
IMPORTED_LOCATION "${HTTPLIB_LIBRARY}"
|
||||||
|
)
|
||||||
|
add_library(httplib::httplib ALIAS cpp-httplib::cpp-httplib)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Cpp-httplib not found via pkg-config, trying CMake...")
|
||||||
|
find_package(httplib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package_handle_standard_args(CppHttp REQUIRED_VARS HTTPLIB_INCLUDE_DIR HTTPLIB_LIBRARY VERSION_VAR HTTPLIB_VERSION)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CppHttp_FOUND AND NOT TARGET cpp-httplib::cpp-httplib)
|
||||||
|
add_library(cpp-httplib::cpp-httplib INTERFACE IMPORTED)
|
||||||
|
set_target_properties(cpp-httplib::cpp-httplib PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${CPP-HTTP_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${CPP-HTTP_LIBRARIES}"
|
||||||
|
IMPORTED_LOCATION "${CPP-HTTP_LIBRARIES}"
|
||||||
|
)
|
||||||
|
endif()
|
36
externals/cmake-modules/FindOpenAL.cmake
vendored
Normal file
36
externals/cmake-modules/FindOpenAL.cmake
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
if(NOT OPENAL_FOUND)
|
||||||
|
pkg_check_modules(OPENAL_TMP openal)
|
||||||
|
|
||||||
|
find_path(OPENAL_INCLUDE_DIRS NAMES al.h
|
||||||
|
PATHS
|
||||||
|
${OPENAL_TMP_INCLUDE_DIRS}
|
||||||
|
/usr/include/AL
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include/AL
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(OPENAL_LIBRARY_DIRS NAMES openal
|
||||||
|
PATHS
|
||||||
|
${OPENAL_TMP_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(OPENAL_INCLUDE_DIRS AND OPENAL_LIBRARY_DIRS)
|
||||||
|
set(OPENAL_FOUND TRUE CACHE INTERNAL "OpenAL found")
|
||||||
|
message(STATUS "Found OpenAL: ${OPENAL_LIBRARY_DIRS}, ${OPENAL_INCLUDE_DIRS}")
|
||||||
|
else()
|
||||||
|
set(OPENAL_FOUND FALSE CACHE INTERNAL "OpenAL found")
|
||||||
|
message(STATUS "OpenAL not found.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(OPENAL_FOUND AND NOT TARGET OpenAL::OpenAL)
|
||||||
|
add_library(OpenAL::OpenAL UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(OpenAL::OpenAL PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES ${OPENAL_INCLUDE_DIRS}
|
||||||
|
INTERFACE_LINK_LIBRARIES ${OPENAL_LIBRARY_DIRS}
|
||||||
|
IMPORTED_LOCATION ${OPENAL_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
endif()
|
35
externals/cmake-modules/Findcryptopp.cmake
vendored
Normal file
35
externals/cmake-modules/Findcryptopp.cmake
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
if(NOT CRYPTOPP_FOUND)
|
||||||
|
pkg_search_module(CRYPTOPP_TMP crypto++ cryptopp)
|
||||||
|
|
||||||
|
find_path(CRYPTOPP_INCLUDE_DIRS NAMES cryptlib.h
|
||||||
|
PATHS
|
||||||
|
${CRYPTOPP_TMP_INCLUDE_DIRS}
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include
|
||||||
|
PATH_SUFFIXES crypto++ cryptopp
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(CRYPTOPP_LIBRARY_DIRS NAMES crypto++ cryptopp
|
||||||
|
PATHS
|
||||||
|
${CRYPTOPP_TMP_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CRYPTOPP_INCLUDE_DIRS AND CRYPTOPP_LIBRARY_DIRS)
|
||||||
|
set(CRYPTOPP_FOUND TRUE CACHE INTERNAL "Found cryptopp")
|
||||||
|
message(STATUS "Found cryptopp: ${CRYPTOPP_LIBRARY_DIRS}, ${CRYPTOPP_INCLUDE_DIRS}")
|
||||||
|
else()
|
||||||
|
set(CRYPTOPP_FOUND FALSE CACHE INTERNAL "Found cryptopp")
|
||||||
|
message(STATUS "Cryptopp not found.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CRYPTOPP_FOUND AND NOT TARGET cryptopp::cryptopp)
|
||||||
|
add_library(cryptopp::cryptopp UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(cryptopp::cryptopp PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES ${CRYPTOPP_INCLUDE_DIRS}
|
||||||
|
INTERFACE_LINK_LIBRARIES ${CRYPTOPP_LIBRARY_DIRS}
|
||||||
|
IMPORTED_LOCATION ${CRYPTOPP_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
endif()
|
63
externals/cmake-modules/Findinih.cmake
vendored
Normal file
63
externals/cmake-modules/Findinih.cmake
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
if(NOT inih_FOUND)
|
||||||
|
# Inih includes both a base library and INIReader
|
||||||
|
# We must link against both to avoid linker errors.
|
||||||
|
|
||||||
|
pkg_check_modules(INIR_TEMP INIReader)
|
||||||
|
pkg_check_modules(INIH_TEMP inih)
|
||||||
|
|
||||||
|
|
||||||
|
find_path(INIR_INCLUDE_DIR NAMES INIReader.h
|
||||||
|
PATHS
|
||||||
|
${INIR_TEMP_INCLUDE_DIRS}
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_path(INIH_INCLUDE_DIR NAMES ini.h
|
||||||
|
PATHS
|
||||||
|
${INIH_TEMP_INCLUDE_DIRS}
|
||||||
|
/usr/include
|
||||||
|
/use/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(INIR_LIBRARIES NAMES INIReader
|
||||||
|
PATHS
|
||||||
|
${INIR_TEMP_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(INIH_LIBRARIES NAMES inih
|
||||||
|
PATHS
|
||||||
|
${INIH_TEMP_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(INIR_INCLUDE_DIR AND INIH_INCLUDE_DIR AND INIR_LIBRARIES AND INIH_LIBRARIES)
|
||||||
|
set(inih_FOUND TRUE CACHE INTERNAL "Found inih library")
|
||||||
|
message(STATUS "Found inih ${INIR_INCLUDE_DIR} ${INIH_INCLUDE_DIR} ${INIR_LIBRARIES} ${INIH_LIBRARIES}")
|
||||||
|
else()
|
||||||
|
set(inih_FOUND FALSE CACHE INTERNAL "Found inih library")
|
||||||
|
message(STATUS "Inih not found.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(inih_FOUND AND NOT TARGET inih::inir OR NOT TARGET inih::inih)
|
||||||
|
add_library(inih::inir INTERFACE IMPORTED)
|
||||||
|
set_target_properties(inih::inir PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${INIR_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${INIR_LIBRARIES}"
|
||||||
|
IMPORTED_LOCATION "${INIR_LIBRARIES}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(inih::inih INTERFACE IMPORTED)
|
||||||
|
set_target_properties(inih::inih PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${INIH_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${INIH_LIBRARES}"
|
||||||
|
IMPORTED_LOCATION ${INIH_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
34
externals/cmake-modules/Findlibenet.cmake
vendored
Normal file
34
externals/cmake-modules/Findlibenet.cmake
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
if(NOT libenet_FOUND)
|
||||||
|
pkg_check_modules(ENET_TMP libenet)
|
||||||
|
|
||||||
|
find_path(libenet_INCLUDE_DIRS NAMES enet.h PATH_SUFFIXES enet
|
||||||
|
PATHS
|
||||||
|
${ENET_TMP_INCLUDE_DIRS}
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(libenet_LIBRARY_DIRS NAMES enet
|
||||||
|
PATHS
|
||||||
|
${ENET_TMP_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(libenet_INCLUDE_DIRS AND libenet_LIBRARY_DIRS)
|
||||||
|
set(libenet_FOUND TRUE CACHE INTERNAL "Found libenet")
|
||||||
|
message(STATUS "Found libenet ${libenet_LIBRARY_DIRS}, ${libenet_INCLUDE_DIRS}")
|
||||||
|
else()
|
||||||
|
set(libenet_FOUND FALSE CACHE INTERNAL "Found libenet")
|
||||||
|
message(STATUS "Libenet not found.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(libenet_FOUND AND NOT TARGET libenet::libenet)
|
||||||
|
add_library(libenet::libenet UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(libenet::libenet PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES ${libenet_INCLUDE_DIRS}
|
||||||
|
INTERFACE_LINK_LIBRARIES ${libenet_LIBRARY_DIRS}
|
||||||
|
IMPORTED_LOCATION ${libenet_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
endif()
|
31
externals/cmake-modules/Findlodepng.cmake
vendored
Normal file
31
externals/cmake-modules/Findlodepng.cmake
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
if(NOT LODEPNG_FOUND)
|
||||||
|
find_path(LODEPNG_INCLUDE_DIRS NAMES lodepng.h
|
||||||
|
PATHS
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(LODEPNG_LIBRARY_DIRS NAMES lodepng
|
||||||
|
PATHS
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(LODEPNG_INCLUDE_DIRS AND LODEPNG_LIBRARY_DIRS)
|
||||||
|
set(LODEPNG_FOUND TRUE CACHE INTERNAL "Found lodepng")
|
||||||
|
message(STATUS "Found lodepng: ${LODEPNG_LIBRARY_DIRS}, ${LODEPNG_INCLUDE_DIRS}")
|
||||||
|
else()
|
||||||
|
set(LODEPNG_FOUND FALSE CACHE INTERNAL "Found lodepng")
|
||||||
|
message(STATUS "Lodepng not found.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LODEPNG_FOUND AND NOT TARGET lodepng::lodepng)
|
||||||
|
add_library(lodepng::lodepng UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(lodepng::lodepng PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES ${LODEPNG_INCLUDE_DIRS}
|
||||||
|
INTERFACE_LINK_LIBRARIES ${LODEPNG_LIBRARY_DIRS}
|
||||||
|
IMPORTED_LOCATION ${LODEPNG_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
endif()
|
2
externals/cryptopp
vendored
2
externals/cryptopp
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 511806c0eba8ba5b5cedd4b4a814e96df92864a6
|
Subproject commit af7d1050bf2287072edd629be133da458a3cf978
|
2
externals/cryptopp-cmake
vendored
2
externals/cryptopp-cmake
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 15798ac9c2611d5c7f9ba832e2c9159bdd8945f2
|
Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48
|
88
externals/faad2/CMakeLists.txt
vendored
Normal file
88
externals/faad2/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Sources cut down to just what we need for AAC-LC.
|
||||||
|
set(FAAD2_SOURCE_DIR "faad2/libfaad")
|
||||||
|
add_library(faad2 STATIC EXCLUDE_FROM_ALL
|
||||||
|
"${FAAD2_SOURCE_DIR}/bits.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/cfft.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/common.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/decoder.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/drc.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/error.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/filtbank.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/huffman.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/is.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/mdct.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/mp4.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/ms.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/output.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/pns.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/pulse.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/specrec.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/syntax.c"
|
||||||
|
"${FAAD2_SOURCE_DIR}/tns.c"
|
||||||
|
)
|
||||||
|
target_include_directories(faad2 PUBLIC faad2/include PRIVATE "${FAAD2_SOURCE_DIR}")
|
||||||
|
|
||||||
|
# Configure compile definitions.
|
||||||
|
|
||||||
|
# Read version from properties file for configuring constant.
|
||||||
|
file(READ faad2/properties.json FAAD_PROPERTIES_JSON)
|
||||||
|
string(JSON FAAD_VERSION GET ${FAAD_PROPERTIES_JSON} PACKAGE_VERSION)
|
||||||
|
message(STATUS "Building faad2 version ${FAAD_VERSION}")
|
||||||
|
|
||||||
|
# Check for functions and headers.
|
||||||
|
include(CheckFunctionExists)
|
||||||
|
include(CheckIncludeFiles)
|
||||||
|
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||||
|
check_function_exists(lrintf HAVE_LRINTF)
|
||||||
|
check_function_exists(memcpy HAVE_MEMCPY)
|
||||||
|
check_function_exists(strchr HAVE_STRCHR)
|
||||||
|
check_function_exists(strsep HAVE_STRSEP)
|
||||||
|
check_include_files(dlfcn.h HAVE_DLFCN_H)
|
||||||
|
check_include_files(errno.h HAVE_ERRNO_H)
|
||||||
|
check_include_files(float.h HAVE_FLOAT_H)
|
||||||
|
check_include_files(inttypes.h HAVE_INTTYPES_H)
|
||||||
|
check_include_files(IOKit/IOKitLib.h HAVE_IOKIT_IOKITLIB_H)
|
||||||
|
check_include_files(limits.h HAVE_LIMITS_H)
|
||||||
|
check_include_files(mathf.h HAVE_MATHF_H)
|
||||||
|
check_include_files(stdint.h HAVE_STDINT_H)
|
||||||
|
check_include_files(stdio.h HAVE_STDIO_H)
|
||||||
|
check_include_files(stdlib.h HAVE_STDLIB_H)
|
||||||
|
check_include_files(strings.h HAVE_STRINGS_H)
|
||||||
|
check_include_files(string.h HAVE_STRING_H)
|
||||||
|
check_include_files(sysfs/libsysfs.h HAVE_SYSFS_LIBSYSFS_H)
|
||||||
|
check_include_files(sys/stat.h HAVE_SYS_STAT_H)
|
||||||
|
check_include_files(sys/time.h HAVE_SYS_TIME_H)
|
||||||
|
check_include_files(sys/types.h HAVE_SYS_TYPES_H)
|
||||||
|
check_include_files(unistd.h HAVE_UNISTD_H)
|
||||||
|
|
||||||
|
# faad2 uses a relative include for its config.h which breaks under CMake.
|
||||||
|
# We can use target_compile_definitions to pass on the configuration instead.
|
||||||
|
target_compile_definitions(faad2 PRIVATE
|
||||||
|
-DFAAD_VERSION=${FAAD_VERSION}
|
||||||
|
-DPACKAGE_VERSION=\"${FAAD_VERSION}\"
|
||||||
|
-DSTDC_HEADERS
|
||||||
|
-DHAVE_GETPWUID=${HAVE_GETPWUID}
|
||||||
|
-DHAVE_LRINTF=${HAVE_LRINTF}
|
||||||
|
-DHAVE_MEMCPY=${HAVE_MEMCPY}
|
||||||
|
-DHAVE_STRCHR=${HAVE_STRCHR}
|
||||||
|
-DHAVE_STRSEP=${HAVE_STRSEP}
|
||||||
|
-DHAVE_DLFCN_H=${HAVE_DLFCN_H}
|
||||||
|
-DHAVE_ERRNO_H=${HAVE_ERRNO_H}
|
||||||
|
-DHAVE_FLOAT_H=${HAVE_FLOAT_H}
|
||||||
|
-DHAVE_INTTYPES_H=${HAVE_INTTYPES_H}
|
||||||
|
-DHAVE_IOKIT_IOKITLIB_H=${HAVE_IOKIT_IOKITLIB_H}
|
||||||
|
-DHAVE_LIMITS_H=${HAVE_LIMITS_H}
|
||||||
|
-DHAVE_MATHF_H=${HAVE_MATHF_H}
|
||||||
|
-DHAVE_STDINT_H=${HAVE_STDINT_H}
|
||||||
|
-DHAVE_STDIO_H=${HAVE_STDIO_H}
|
||||||
|
-DHAVE_STDLIB_H=${HAVE_STDLIB_H}
|
||||||
|
-DHAVE_STRINGS_H=${HAVE_STRINGS_H}
|
||||||
|
-DHAVE_STRING_H=${HAVE_STRING_H}
|
||||||
|
-DHAVE_SYSFS_LIBSYSFS_H=${HAVE_SYSFS_LIBSYSFS_H}
|
||||||
|
-DHAVE_SYS_STAT_H=${HAVE_SYS_STAT_H}
|
||||||
|
-DHAVE_SYS_TIME_H=${HAVE_SYS_TIME_H}
|
||||||
|
-DHAVE_SYS_TYPES_H=${HAVE_SYS_TYPES_H}
|
||||||
|
-DHAVE_UNISTD_H=${HAVE_UNISTD_H}
|
||||||
|
# Only compile for AAC-LC decoding.
|
||||||
|
-DLC_ONLY_DECODER -DDISABLE_SBR
|
||||||
|
)
|
1
externals/faad2/faad2
vendored
Submodule
1
externals/faad2/faad2
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 09b3c850c606e7fedd06597223e54344e8d23c8c
|
6
externals/glad/Readme.md
vendored
6
externals/glad/Readme.md
vendored
@ -3,3 +3,9 @@ These files were generated by the [glad](https://github.com/Dav1dde/glad) OpenGL
|
|||||||
```
|
```
|
||||||
python -m glad --profile core --out-path glad/ --api "gl=4.3,gles2=3.2" --generator=c
|
python -m glad --profile core --out-path glad/ --api "gl=4.3,gles2=3.2" --generator=c
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also generate the source using [this site](https://glad.dav1d.de/):
|
||||||
|
1. Select '4.3' for GL, '3.2' for GLES2, and 'Core' for Profile.
|
||||||
|
2. Input the currently supported extensions from [here](https://github.com/citra-emu/citra/blob/master/externals/glad/include/glad/glad.h#L9), plus any new required extensions.
|
||||||
|
3. Click Generate and download the generated source zip.
|
||||||
|
4. Unzip the new source over the current glad source files.
|
27
externals/glad/include/glad/glad.h
vendored
27
externals/glad/include/glad/glad.h
vendored
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
OpenGL, OpenGL ES loader generated by glad 0.1.34 on Sat Aug 26 18:38:43 2023.
|
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Fri Nov 10 04:24:01 2023.
|
||||||
|
|
||||||
Language/Generator: C/C++
|
Language/Generator: C/C++
|
||||||
Specification: gl
|
Specification: gl
|
||||||
@ -10,6 +10,7 @@
|
|||||||
GL_AMD_blend_minmax_factor,
|
GL_AMD_blend_minmax_factor,
|
||||||
GL_ARB_buffer_storage,
|
GL_ARB_buffer_storage,
|
||||||
GL_ARB_clear_texture,
|
GL_ARB_clear_texture,
|
||||||
|
GL_ARB_fragment_shader_interlock,
|
||||||
GL_ARB_get_texture_sub_image,
|
GL_ARB_get_texture_sub_image,
|
||||||
GL_ARB_texture_compression_bptc,
|
GL_ARB_texture_compression_bptc,
|
||||||
GL_ARM_shader_framebuffer_fetch,
|
GL_ARM_shader_framebuffer_fetch,
|
||||||
@ -17,16 +18,18 @@
|
|||||||
GL_EXT_clip_cull_distance,
|
GL_EXT_clip_cull_distance,
|
||||||
GL_EXT_shader_framebuffer_fetch,
|
GL_EXT_shader_framebuffer_fetch,
|
||||||
GL_EXT_texture_compression_s3tc,
|
GL_EXT_texture_compression_s3tc,
|
||||||
GL_NV_blend_minmax_factor
|
GL_INTEL_fragment_shader_ordering,
|
||||||
|
GL_NV_blend_minmax_factor,
|
||||||
|
GL_NV_fragment_shader_interlock
|
||||||
Loader: True
|
Loader: True
|
||||||
Local files: False
|
Local files: False
|
||||||
Omit khrplatform: False
|
Omit khrplatform: False
|
||||||
Reproducible: False
|
Reproducible: False
|
||||||
|
|
||||||
Commandline:
|
Commandline:
|
||||||
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_AMD_blend_minmax_factor,GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_ARM_shader_framebuffer_fetch,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_shader_framebuffer_fetch,GL_EXT_texture_compression_s3tc,GL_NV_blend_minmax_factor"
|
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_AMD_blend_minmax_factor,GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_fragment_shader_interlock,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_ARM_shader_framebuffer_fetch,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_shader_framebuffer_fetch,GL_EXT_texture_compression_s3tc,GL_INTEL_fragment_shader_ordering,GL_NV_blend_minmax_factor,GL_NV_fragment_shader_interlock"
|
||||||
Online:
|
Online:
|
||||||
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_AMD_blend_minmax_factor&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARM_shader_framebuffer_fetch&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_shader_framebuffer_fetch&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_NV_blend_minmax_factor
|
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_AMD_blend_minmax_factor&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_fragment_shader_interlock&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARM_shader_framebuffer_fetch&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_shader_framebuffer_fetch&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_INTEL_fragment_shader_ordering&extensions=GL_NV_blend_minmax_factor&extensions=GL_NV_fragment_shader_interlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -3384,6 +3387,10 @@ typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC)(GLuint texture, GLint level,
|
|||||||
GLAPI PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage;
|
GLAPI PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage;
|
||||||
#define glClearTexSubImage glad_glClearTexSubImage
|
#define glClearTexSubImage glad_glClearTexSubImage
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef GL_ARB_fragment_shader_interlock
|
||||||
|
#define GL_ARB_fragment_shader_interlock 1
|
||||||
|
GLAPI int GLAD_GL_ARB_fragment_shader_interlock;
|
||||||
|
#endif
|
||||||
#ifndef GL_ARB_get_texture_sub_image
|
#ifndef GL_ARB_get_texture_sub_image
|
||||||
#define GL_ARB_get_texture_sub_image 1
|
#define GL_ARB_get_texture_sub_image 1
|
||||||
GLAPI int GLAD_GL_ARB_get_texture_sub_image;
|
GLAPI int GLAD_GL_ARB_get_texture_sub_image;
|
||||||
@ -3406,10 +3413,18 @@ GLAPI int GLAD_GL_EXT_shader_framebuffer_fetch;
|
|||||||
#define GL_EXT_texture_compression_s3tc 1
|
#define GL_EXT_texture_compression_s3tc 1
|
||||||
GLAPI int GLAD_GL_EXT_texture_compression_s3tc;
|
GLAPI int GLAD_GL_EXT_texture_compression_s3tc;
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef GL_INTEL_fragment_shader_ordering
|
||||||
|
#define GL_INTEL_fragment_shader_ordering 1
|
||||||
|
GLAPI int GLAD_GL_INTEL_fragment_shader_ordering;
|
||||||
|
#endif
|
||||||
#ifndef GL_NV_blend_minmax_factor
|
#ifndef GL_NV_blend_minmax_factor
|
||||||
#define GL_NV_blend_minmax_factor 1
|
#define GL_NV_blend_minmax_factor 1
|
||||||
GLAPI int GLAD_GL_NV_blend_minmax_factor;
|
GLAPI int GLAD_GL_NV_blend_minmax_factor;
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef GL_NV_fragment_shader_interlock
|
||||||
|
#define GL_NV_fragment_shader_interlock 1
|
||||||
|
GLAPI int GLAD_GL_NV_fragment_shader_interlock;
|
||||||
|
#endif
|
||||||
#ifndef GL_ARM_shader_framebuffer_fetch
|
#ifndef GL_ARM_shader_framebuffer_fetch
|
||||||
#define GL_ARM_shader_framebuffer_fetch 1
|
#define GL_ARM_shader_framebuffer_fetch 1
|
||||||
GLAPI int GLAD_GL_ARM_shader_framebuffer_fetch;
|
GLAPI int GLAD_GL_ARM_shader_framebuffer_fetch;
|
||||||
@ -3437,6 +3452,10 @@ GLAPI int GLAD_GL_EXT_texture_compression_s3tc;
|
|||||||
#define GL_NV_blend_minmax_factor 1
|
#define GL_NV_blend_minmax_factor 1
|
||||||
GLAPI int GLAD_GL_NV_blend_minmax_factor;
|
GLAPI int GLAD_GL_NV_blend_minmax_factor;
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef GL_NV_fragment_shader_interlock
|
||||||
|
#define GL_NV_fragment_shader_interlock 1
|
||||||
|
GLAPI int GLAD_GL_NV_fragment_shader_interlock;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
18
externals/glad/src/glad.c
vendored
18
externals/glad/src/glad.c
vendored
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
OpenGL, OpenGL ES loader generated by glad 0.1.34 on Sat Aug 26 18:38:43 2023.
|
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Fri Nov 10 04:24:01 2023.
|
||||||
|
|
||||||
Language/Generator: C/C++
|
Language/Generator: C/C++
|
||||||
Specification: gl
|
Specification: gl
|
||||||
@ -10,6 +10,7 @@
|
|||||||
GL_AMD_blend_minmax_factor,
|
GL_AMD_blend_minmax_factor,
|
||||||
GL_ARB_buffer_storage,
|
GL_ARB_buffer_storage,
|
||||||
GL_ARB_clear_texture,
|
GL_ARB_clear_texture,
|
||||||
|
GL_ARB_fragment_shader_interlock,
|
||||||
GL_ARB_get_texture_sub_image,
|
GL_ARB_get_texture_sub_image,
|
||||||
GL_ARB_texture_compression_bptc,
|
GL_ARB_texture_compression_bptc,
|
||||||
GL_ARM_shader_framebuffer_fetch,
|
GL_ARM_shader_framebuffer_fetch,
|
||||||
@ -17,16 +18,18 @@
|
|||||||
GL_EXT_clip_cull_distance,
|
GL_EXT_clip_cull_distance,
|
||||||
GL_EXT_shader_framebuffer_fetch,
|
GL_EXT_shader_framebuffer_fetch,
|
||||||
GL_EXT_texture_compression_s3tc,
|
GL_EXT_texture_compression_s3tc,
|
||||||
GL_NV_blend_minmax_factor
|
GL_INTEL_fragment_shader_ordering,
|
||||||
|
GL_NV_blend_minmax_factor,
|
||||||
|
GL_NV_fragment_shader_interlock
|
||||||
Loader: True
|
Loader: True
|
||||||
Local files: False
|
Local files: False
|
||||||
Omit khrplatform: False
|
Omit khrplatform: False
|
||||||
Reproducible: False
|
Reproducible: False
|
||||||
|
|
||||||
Commandline:
|
Commandline:
|
||||||
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_AMD_blend_minmax_factor,GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_ARM_shader_framebuffer_fetch,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_shader_framebuffer_fetch,GL_EXT_texture_compression_s3tc,GL_NV_blend_minmax_factor"
|
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_AMD_blend_minmax_factor,GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_fragment_shader_interlock,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_ARM_shader_framebuffer_fetch,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_shader_framebuffer_fetch,GL_EXT_texture_compression_s3tc,GL_INTEL_fragment_shader_ordering,GL_NV_blend_minmax_factor,GL_NV_fragment_shader_interlock"
|
||||||
Online:
|
Online:
|
||||||
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_AMD_blend_minmax_factor&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARM_shader_framebuffer_fetch&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_shader_framebuffer_fetch&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_NV_blend_minmax_factor
|
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_AMD_blend_minmax_factor&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_fragment_shader_interlock&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARM_shader_framebuffer_fetch&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_shader_framebuffer_fetch&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_INTEL_fragment_shader_ordering&extensions=GL_NV_blend_minmax_factor&extensions=GL_NV_fragment_shader_interlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -860,6 +863,7 @@ PFNGLWAITSYNCPROC glad_glWaitSync = NULL;
|
|||||||
int GLAD_GL_AMD_blend_minmax_factor = 0;
|
int GLAD_GL_AMD_blend_minmax_factor = 0;
|
||||||
int GLAD_GL_ARB_buffer_storage = 0;
|
int GLAD_GL_ARB_buffer_storage = 0;
|
||||||
int GLAD_GL_ARB_clear_texture = 0;
|
int GLAD_GL_ARB_clear_texture = 0;
|
||||||
|
int GLAD_GL_ARB_fragment_shader_interlock = 0;
|
||||||
int GLAD_GL_ARB_get_texture_sub_image = 0;
|
int GLAD_GL_ARB_get_texture_sub_image = 0;
|
||||||
int GLAD_GL_ARB_texture_compression_bptc = 0;
|
int GLAD_GL_ARB_texture_compression_bptc = 0;
|
||||||
int GLAD_GL_ARM_shader_framebuffer_fetch = 0;
|
int GLAD_GL_ARM_shader_framebuffer_fetch = 0;
|
||||||
@ -867,7 +871,9 @@ int GLAD_GL_EXT_buffer_storage = 0;
|
|||||||
int GLAD_GL_EXT_clip_cull_distance = 0;
|
int GLAD_GL_EXT_clip_cull_distance = 0;
|
||||||
int GLAD_GL_EXT_shader_framebuffer_fetch = 0;
|
int GLAD_GL_EXT_shader_framebuffer_fetch = 0;
|
||||||
int GLAD_GL_EXT_texture_compression_s3tc = 0;
|
int GLAD_GL_EXT_texture_compression_s3tc = 0;
|
||||||
|
int GLAD_GL_INTEL_fragment_shader_ordering = 0;
|
||||||
int GLAD_GL_NV_blend_minmax_factor = 0;
|
int GLAD_GL_NV_blend_minmax_factor = 0;
|
||||||
|
int GLAD_GL_NV_fragment_shader_interlock = 0;
|
||||||
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
|
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
|
||||||
PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage = NULL;
|
PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage = NULL;
|
||||||
PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage = NULL;
|
PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage = NULL;
|
||||||
@ -1509,11 +1515,14 @@ static int find_extensionsGL(void) {
|
|||||||
GLAD_GL_AMD_blend_minmax_factor = has_ext("GL_AMD_blend_minmax_factor");
|
GLAD_GL_AMD_blend_minmax_factor = has_ext("GL_AMD_blend_minmax_factor");
|
||||||
GLAD_GL_ARB_buffer_storage = has_ext("GL_ARB_buffer_storage");
|
GLAD_GL_ARB_buffer_storage = has_ext("GL_ARB_buffer_storage");
|
||||||
GLAD_GL_ARB_clear_texture = has_ext("GL_ARB_clear_texture");
|
GLAD_GL_ARB_clear_texture = has_ext("GL_ARB_clear_texture");
|
||||||
|
GLAD_GL_ARB_fragment_shader_interlock = has_ext("GL_ARB_fragment_shader_interlock");
|
||||||
GLAD_GL_ARB_get_texture_sub_image = has_ext("GL_ARB_get_texture_sub_image");
|
GLAD_GL_ARB_get_texture_sub_image = has_ext("GL_ARB_get_texture_sub_image");
|
||||||
GLAD_GL_ARB_texture_compression_bptc = has_ext("GL_ARB_texture_compression_bptc");
|
GLAD_GL_ARB_texture_compression_bptc = has_ext("GL_ARB_texture_compression_bptc");
|
||||||
GLAD_GL_EXT_shader_framebuffer_fetch = has_ext("GL_EXT_shader_framebuffer_fetch");
|
GLAD_GL_EXT_shader_framebuffer_fetch = has_ext("GL_EXT_shader_framebuffer_fetch");
|
||||||
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
|
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
|
||||||
|
GLAD_GL_INTEL_fragment_shader_ordering = has_ext("GL_INTEL_fragment_shader_ordering");
|
||||||
GLAD_GL_NV_blend_minmax_factor = has_ext("GL_NV_blend_minmax_factor");
|
GLAD_GL_NV_blend_minmax_factor = has_ext("GL_NV_blend_minmax_factor");
|
||||||
|
GLAD_GL_NV_fragment_shader_interlock = has_ext("GL_NV_fragment_shader_interlock");
|
||||||
free_exts();
|
free_exts();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -1988,6 +1997,7 @@ static int find_extensionsGLES2(void) {
|
|||||||
GLAD_GL_EXT_shader_framebuffer_fetch = has_ext("GL_EXT_shader_framebuffer_fetch");
|
GLAD_GL_EXT_shader_framebuffer_fetch = has_ext("GL_EXT_shader_framebuffer_fetch");
|
||||||
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
|
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
|
||||||
GLAD_GL_NV_blend_minmax_factor = has_ext("GL_NV_blend_minmax_factor");
|
GLAD_GL_NV_blend_minmax_factor = has_ext("GL_NV_blend_minmax_factor");
|
||||||
|
GLAD_GL_NV_fragment_shader_interlock = has_ext("GL_NV_fragment_shader_interlock");
|
||||||
free_exts();
|
free_exts();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
2
externals/httplib/README.md
vendored
2
externals/httplib/README.md
vendored
@ -1,4 +1,4 @@
|
|||||||
From https://github.com/yhirose/cpp-httplib/commit/8e10d4e8e7febafce0632810262e81e853b2065f
|
From https://github.com/yhirose/cpp-httplib/commit/0a629d739127dcc5d828474a5aedae1f234687d3
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
1850
externals/httplib/httplib.h
vendored
1850
externals/httplib/httplib.h
vendored
File diff suppressed because it is too large
Load Diff
2
externals/inih/CMakeLists.txt
vendored
2
externals/inih/CMakeLists.txt
vendored
@ -6,4 +6,4 @@ add_library(inih
|
|||||||
)
|
)
|
||||||
|
|
||||||
create_target_directory_groups(inih)
|
create_target_directory_groups(inih)
|
||||||
target_include_directories(inih INTERFACE .)
|
target_include_directories(inih INTERFACE ./inih/cpp)
|
||||||
|
48
externals/library-headers/CMakeLists.txt
vendored
48
externals/library-headers/CMakeLists.txt
vendored
@ -1,48 +0,0 @@
|
|||||||
add_library(library-headers INTERFACE)
|
|
||||||
|
|
||||||
# libfdk-aac headers
|
|
||||||
find_path(FDK_AAC_INCLUDES NAMES fdk-aac/aacdecoder_lib.h)
|
|
||||||
if (FDK_AAC_INCLUDES STREQUAL "FDK_AAC_INCLUDES-NOTFOUND")
|
|
||||||
message(STATUS "fdk_aac headers not found, using bundled headers.")
|
|
||||||
target_include_directories(library-headers INTERFACE ./library-headers/fdk-aac/include)
|
|
||||||
else()
|
|
||||||
message(STATUS "Using headers from system fdk_aac")
|
|
||||||
target_include_directories(library-headers SYSTEM INTERFACE ${FDK_AAC_INCLUDES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# FFmpeg headers
|
|
||||||
find_path(AVUTIL_INCLUDES NAMES libavutil/avutil.h)
|
|
||||||
find_path(AVCODEC_INCLUDES NAMES libavcodec/avcodec.h)
|
|
||||||
find_path(AVFORMAT_INCLUDES NAMES libavformat/avformat.h)
|
|
||||||
find_path(AVFILTER_INCLUDES NAMES libavfilter/avfilter.h)
|
|
||||||
find_path(SWRESAMPLE_INCLUDES NAMES libswresample/swresample.h)
|
|
||||||
# Make sure all the headers are found and from the same place, so we don't
|
|
||||||
# have missing or mismatched components.
|
|
||||||
if (AVUTIL_INCLUDES STREQUAL "AVUTIL_INCLUDES-NOTFOUND"
|
|
||||||
OR NOT AVCODEC_INCLUDES STREQUAL AVUTIL_INCLUDES
|
|
||||||
OR NOT AVFORMAT_INCLUDES STREQUAL AVUTIL_INCLUDES
|
|
||||||
OR NOT AVFILTER_INCLUDES STREQUAL AVUTIL_INCLUDES
|
|
||||||
OR NOT SWRESAMPLE_INCLUDES STREQUAL AVUTIL_INCLUDES)
|
|
||||||
message(STATUS "Complete FFmpeg headers not found, using bundled headers.")
|
|
||||||
target_include_directories(library-headers INTERFACE ./library-headers/ffmpeg/include)
|
|
||||||
else()
|
|
||||||
# Extract libavutil version from header.
|
|
||||||
file(STRINGS "${AVUTIL_INCLUDES}/libavutil/version.h" AVUTIL_VERSION_DATA
|
|
||||||
REGEX "#define LIBAVUTIL_VERSION_(MAJOR|MINOR|MICRO) ")
|
|
||||||
set(AVUTIL_VERSION_REGEX "([0-9]+)")
|
|
||||||
foreach(v MAJOR MINOR MICRO)
|
|
||||||
if("${AVUTIL_VERSION_DATA}" MATCHES "#define LIBAVUTIL_VERSION_${v}[\\t ]+${AVUTIL_VERSION_REGEX}")
|
|
||||||
set(AVUTIL_VERSION_${v} "${CMAKE_MATCH_1}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
set(AVUTIL_VERSION "${AVUTIL_VERSION_MAJOR}.${AVUTIL_VERSION_MINOR}.${AVUTIL_VERSION_MICRO}")
|
|
||||||
|
|
||||||
message(STATUS "Detected FFmpeg libavutil version is ${AVUTIL_VERSION}")
|
|
||||||
if ("${AVUTIL_VERSION}" VERSION_LESS "56.30.100")
|
|
||||||
message(WARNING "System FFmpeg version is too low (< 4.2), using bundled headers.")
|
|
||||||
target_include_directories(library-headers INTERFACE ./library-headers/ffmpeg/include)
|
|
||||||
else()
|
|
||||||
message(STATUS "Using headers from system FFmpeg")
|
|
||||||
target_include_directories(library-headers SYSTEM INTERFACE ${AVUTIL_INCLUDES})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
1
externals/oaknut
vendored
Submodule
1
externals/oaknut
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e6eecc3f9460728be0a8d3f63e66d31c0362f472
|
BIN
keys.tar.enc
BIN
keys.tar.enc
Binary file not shown.
@ -51,6 +51,10 @@ if (MSVC)
|
|||||||
/Zc:throwingNew
|
/Zc:throwingNew
|
||||||
/GT
|
/GT
|
||||||
|
|
||||||
|
# Some flags for more deterministic builds, to aid caching.
|
||||||
|
/experimental:deterministic
|
||||||
|
/d1trimfile:"${CMAKE_SOURCE_DIR}"
|
||||||
|
|
||||||
# External headers diagnostics
|
# External headers diagnostics
|
||||||
/experimental:external # Enables the external headers options. This option isn't required in Visual Studio 2019 version 16.10 and later
|
/experimental:external # Enables the external headers options. This option isn't required in Visual Studio 2019 version 16.10 and later
|
||||||
/external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
|
/external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
|
||||||
@ -87,7 +91,8 @@ if (MSVC)
|
|||||||
|
|
||||||
# Since MSVC's debugging information is not very deterministic, so we have to disable it
|
# Since MSVC's debugging information is not very deterministic, so we have to disable it
|
||||||
# when using ccache or other caching tools
|
# when using ccache or other caching tools
|
||||||
if (CITRA_USE_CCACHE OR CITRA_USE_PRECOMPILED_HEADERS)
|
if (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache" OR CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache"
|
||||||
|
OR CITRA_USE_PRECOMPILED_HEADERS)
|
||||||
# Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
|
# Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
|
||||||
add_compile_options(/Z7)
|
add_compile_options(/Z7)
|
||||||
else()
|
else()
|
||||||
@ -98,11 +103,17 @@ if (MSVC)
|
|||||||
add_compile_options("$<$<CONFIG:Release>:/GS->")
|
add_compile_options("$<$<CONFIG:Release>:/GS->")
|
||||||
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF /PDBALTPATH:%_PDB%" CACHE STRING "" FORCE)
|
||||||
else()
|
else()
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall
|
-Wall
|
||||||
-Wno-attributes
|
# In case a flag isn't supported on e.g. a certain architecture, don't error.
|
||||||
|
-Wno-unused-command-line-argument
|
||||||
|
# Build fortification options
|
||||||
|
-Wp,-D_FORTIFY_SOURCE=2
|
||||||
|
-Wp,-D_GLIBCXX_ASSERTIONS
|
||||||
|
-fstack-protector-strong
|
||||||
|
-fstack-clash-protection
|
||||||
)
|
)
|
||||||
|
|
||||||
if (CITRA_WARNINGS_AS_ERRORS)
|
if (CITRA_WARNINGS_AS_ERRORS)
|
||||||
@ -113,6 +124,13 @@ else()
|
|||||||
add_compile_options("-stdlib=libc++")
|
add_compile_options("-stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
|
||||||
|
# GCC may warn when it ignores attributes like maybe_unused,
|
||||||
|
# which is a problem for older versions (e.g. GCC 11).
|
||||||
|
add_compile_options("-Wno-attributes")
|
||||||
|
add_compile_options("-Wno-interference-size")
|
||||||
|
endif()
|
||||||
|
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||||
if (COMPILE_WITH_DWARF)
|
if (COMPILE_WITH_DWARF)
|
||||||
|
@ -3,10 +3,15 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
import android.databinding.tool.ext.capitalizeUS
|
import android.databinding.tool.ext.capitalizeUS
|
||||||
|
import de.undercouch.gradle.tasks.download.Download
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("de.undercouch.download") version "5.5.0"
|
||||||
|
id("kotlin-parcelize")
|
||||||
|
kotlin("plugin.serialization") version "1.8.21"
|
||||||
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,14 +20,16 @@ plugins {
|
|||||||
* next 680 years.
|
* next 680 years.
|
||||||
*/
|
*/
|
||||||
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
|
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
|
||||||
val abiFilter = listOf("arm64-v8a"/*, "x86", "x86_64"*/)
|
val abiFilter = listOf("arm64-v8a", "x86_64")
|
||||||
|
|
||||||
|
val downloadedJniLibsPath = "${buildDir}/downloadedJniLibs"
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
android {
|
android {
|
||||||
namespace = "org.citra.citra_emu"
|
namespace = "org.citra.citra_emu"
|
||||||
|
|
||||||
compileSdkVersion = "android-33"
|
compileSdkVersion = "android-34"
|
||||||
ndkVersion = "25.2.9519653"
|
ndkVersion = "26.1.10909125"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
@ -33,6 +40,11 @@ android {
|
|||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
// This is necessary for libadrenotools custom driver loading
|
||||||
|
jniLibs.useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
@ -47,7 +59,7 @@ android {
|
|||||||
// TODO If this is ever modified, change application_id in strings.xml
|
// TODO If this is ever modified, change application_id in strings.xml
|
||||||
applicationId = "org.citra.citra_emu"
|
applicationId = "org.citra.citra_emu"
|
||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 33
|
targetSdk = 34
|
||||||
versionCode = autoVersion
|
versionCode = autoVersion
|
||||||
versionName = getGitVersion()
|
versionName = getGitVersion()
|
||||||
|
|
||||||
@ -65,6 +77,9 @@ android {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
|
||||||
|
buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
|
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
|
||||||
@ -88,6 +103,12 @@ android {
|
|||||||
} else {
|
} else {
|
||||||
signingConfigs.getByName("debug")
|
signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds a release build that doesn't need signing
|
// builds a release build that doesn't need signing
|
||||||
@ -97,9 +118,15 @@ android {
|
|||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
versionNameSuffix = "-debug"
|
versionNameSuffix = "-debug"
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isJniDebuggable = true
|
isJniDebuggable = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
isDefault = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signed by debug key disallowing distribution on Play Store.
|
// Signed by debug key disallowing distribution on Play Store.
|
||||||
@ -131,11 +158,19 @@ android {
|
|||||||
path = file("../../../CMakeLists.txt")
|
path = file("../../../CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
named("main") {
|
||||||
|
// Set up path for downloaded native libraries
|
||||||
|
jniLibs.srcDir(downloadedJniLibsPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.activity:activity-ktx:1.7.2")
|
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.6.0")
|
implementation("androidx.activity:activity-ktx:1.8.0")
|
||||||
|
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||||
@ -147,15 +182,38 @@ dependencies {
|
|||||||
// For loading huge screenshots from the disk.
|
// For loading huge screenshots from the disk.
|
||||||
implementation("com.squareup.picasso:picasso:2.71828")
|
implementation("com.squareup.picasso:picasso:2.71828")
|
||||||
|
|
||||||
// Allows FRP-style asynchronous operations in Android.
|
|
||||||
implementation("io.reactivex:rxandroid:1.2.1")
|
|
||||||
|
|
||||||
implementation("org.ini4j:ini4j:0.5.4")
|
implementation("org.ini4j:ini4j:0.5.4")
|
||||||
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
|
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
|
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||||
|
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||||
|
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||||
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
|
implementation("io.coil-kt:coil:2.2.2")
|
||||||
|
}
|
||||||
|
|
||||||
// Please don't upgrade the billing library as the newer version is not GPL-compatible
|
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||||
implementation("com.android.billingclient:billing:2.0.3")
|
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
|
||||||
|
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/sdk-1.3.261.1/android-binaries-sdk-1.3.261.1-android.zip")
|
||||||
|
dest(file("${buildDir}/tmp/Vulkan-ValidationLayers.zip"))
|
||||||
|
onlyIfModified(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Vulkan Validation Layers into the downloaded native libraries directory.
|
||||||
|
val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLayers") {
|
||||||
|
dependsOn(downloadVulkanValidationLayers)
|
||||||
|
from(zipTree(downloadVulkanValidationLayers.get().dest)) {
|
||||||
|
// Exclude the top level directory in the zip as it violates the expected jniLibs directory structure.
|
||||||
|
eachFile {
|
||||||
|
relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())
|
||||||
|
}
|
||||||
|
includeEmptyDirs = false
|
||||||
|
}
|
||||||
|
into(downloadedJniLibsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("preBuild") {
|
||||||
|
dependsOn(unzipVulkanValidationLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGitVersion(): String {
|
fun getGitVersion(): String {
|
||||||
@ -181,6 +239,34 @@ fun getGitVersion(): String {
|
|||||||
return versionName
|
return versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getGitHash(): String =
|
||||||
|
runGitCommand(ProcessBuilder("git", "rev-parse", "--short", "HEAD")) ?: "dummy-hash"
|
||||||
|
|
||||||
|
fun getBranch(): String =
|
||||||
|
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
|
||||||
|
|
||||||
|
fun runGitCommand(command: ProcessBuilder) : String? {
|
||||||
|
try {
|
||||||
|
command.directory(project.rootDir)
|
||||||
|
val process = command.start()
|
||||||
|
val inputStream = process.inputStream
|
||||||
|
val errorStream = process.errorStream
|
||||||
|
process.waitFor()
|
||||||
|
|
||||||
|
return if (process.exitValue() == 0) {
|
||||||
|
inputStream.bufferedReader()
|
||||||
|
.use { it.readText().trim() } // return the value of gitHash
|
||||||
|
} else {
|
||||||
|
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
|
||||||
|
logger.error("Error running git command: $errorMessage")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("$e: Cannot find git")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android.applicationVariants.configureEach {
|
android.applicationVariants.configureEach {
|
||||||
val variant = this
|
val variant = this
|
||||||
val capitalizedName = variant.name.capitalizeUS()
|
val capitalizedName = variant.name.capitalizeUS()
|
||||||
|
40
src/android/app/proguard-rules.pro
vendored
40
src/android/app/proguard-rules.pro
vendored
@ -1,21 +1,25 @@
|
|||||||
# Add project specific ProGuard rules here.
|
# Copyright 2023 Citra Emulator Project
|
||||||
# You can control the set of applied configuration files using the
|
# Licensed under GPLv2 or any later version
|
||||||
# proguardFiles setting in build.gradle.
|
# Refer to the license.txt file included.
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
# To get usable stack traces
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
-dontobfuscate
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
# Prevents crashing when using Wini
|
||||||
# debugging stack traces.
|
-keep class org.ini4j.spi.IniParser
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
-keep class org.ini4j.spi.IniBuilder
|
||||||
|
-keep class org.ini4j.spi.IniFormatter
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# Suppress warnings for R8
|
||||||
# hide the original source file name.
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
#-renamesourcefileattribute SourceFile
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
|
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
|
-dontwarn org.conscrypt.Conscrypt$Version
|
||||||
|
-dontwarn org.conscrypt.Conscrypt
|
||||||
|
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||||
|
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||||
|
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||||
|
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||||
|
-dontwarn java.beans.Introspector
|
||||||
|
-dontwarn java.beans.VetoableChangeListener
|
||||||
|
-dontwarn java.beans.VetoableChangeSupport
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@ -44,8 +45,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||||
android:theme="@style/Theme.Citra.Splash.Main"
|
android:theme="@style/Theme.Citra.Splash.Main"
|
||||||
android:exported="true"
|
android:exported="true">
|
||||||
android:resizeableActivity="false">
|
|
||||||
|
|
||||||
<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
|
<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -64,25 +64,28 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.citra.citra_emu.activities.EmulationActivity"
|
android:name="org.citra.citra_emu.activities.EmulationActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:resizeableActivity="false"
|
|
||||||
android:theme="@style/Theme.Citra.Main"
|
android:theme="@style/Theme.Citra.Main"
|
||||||
android:launchMode="singleTop"/>
|
android:launchMode="singleTop">
|
||||||
|
|
||||||
<service android:name="org.citra.citra_emu.utils.ForegroundService"/>
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data
|
||||||
|
android:mimeType="application/octet-stream"
|
||||||
|
android:scheme="content" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service android:name="org.citra.citra_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
|
||||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.citra.citra_emu.features.cheats.ui.CheatsActivity"
|
android:name="org.citra.citra_emu.features.cheats.ui.CheatsActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.Citra.Main"
|
android:theme="@style/Theme.Citra.Main"
|
||||||
android:label="@string/cheats"/>
|
android:label="@string/cheats"/>
|
||||||
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="org.citra.citra_emu.model.GameProvider"
|
|
||||||
android:authorities="${applicationId}.provider"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false">
|
|
||||||
</provider>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
// Copyright 2019 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.model.GameDatabase;
|
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization;
|
|
||||||
import org.citra.citra_emu.utils.DocumentsTree;
|
|
||||||
import org.citra.citra_emu.utils.PermissionsHandler;
|
|
||||||
|
|
||||||
public class CitraApplication extends Application {
|
|
||||||
public static GameDatabase databaseHelper;
|
|
||||||
public static DocumentsTree documentsTree;
|
|
||||||
private static CitraApplication application;
|
|
||||||
|
|
||||||
private void createNotificationChannel() {
|
|
||||||
// Create the NotificationChannel, but only on API 26+ because
|
|
||||||
// the NotificationChannel class is new and not in the support library
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
|
||||||
{
|
|
||||||
// General notification
|
|
||||||
CharSequence name = getString(R.string.app_notification_channel_name);
|
|
||||||
String description = getString(R.string.app_notification_channel_description);
|
|
||||||
NotificationChannel channel = new NotificationChannel(
|
|
||||||
getString(R.string.app_notification_channel_id), name,
|
|
||||||
NotificationManager.IMPORTANCE_LOW);
|
|
||||||
channel.setDescription(description);
|
|
||||||
channel.setSound(null, null);
|
|
||||||
channel.setVibrationPattern(null);
|
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// CIA Install notifications
|
|
||||||
NotificationChannel channel = new NotificationChannel(
|
|
||||||
getString(R.string.cia_install_notification_channel_id),
|
|
||||||
getString(R.string.cia_install_notification_channel_name),
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT);
|
|
||||||
channel.setDescription(getString(R.string.cia_install_notification_channel_description));
|
|
||||||
channel.setSound(null, null);
|
|
||||||
channel.setVibrationPattern(null);
|
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
application = this;
|
|
||||||
documentsTree = new DocumentsTree();
|
|
||||||
|
|
||||||
if (PermissionsHandler.hasWriteAccess(getApplicationContext())) {
|
|
||||||
DirectoryInitialization.start(getApplicationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeLibrary.LogDeviceInfo();
|
|
||||||
createNotificationChannel();
|
|
||||||
|
|
||||||
databaseHelper = new GameDatabase(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context getAppContext() {
|
|
||||||
return application.getApplicationContext();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||||
|
import org.citra.citra_emu.utils.DocumentsTree
|
||||||
|
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||||
|
import org.citra.citra_emu.utils.PermissionsHandler
|
||||||
|
|
||||||
|
class CitraApplication : Application() {
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
with(getSystemService(NotificationManager::class.java)) {
|
||||||
|
// General notification
|
||||||
|
val name: CharSequence = getString(R.string.app_notification_channel_name)
|
||||||
|
val description = getString(R.string.app_notification_channel_description)
|
||||||
|
val generalChannel = NotificationChannel(
|
||||||
|
getString(R.string.app_notification_channel_id),
|
||||||
|
name,
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
generalChannel.description = description
|
||||||
|
generalChannel.setSound(null, null)
|
||||||
|
generalChannel.vibrationPattern = null
|
||||||
|
createNotificationChannel(generalChannel)
|
||||||
|
|
||||||
|
// CIA Install notifications
|
||||||
|
val ciaChannel = NotificationChannel(
|
||||||
|
getString(R.string.cia_install_notification_channel_id),
|
||||||
|
getString(R.string.cia_install_notification_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
ciaChannel.description =
|
||||||
|
getString(R.string.cia_install_notification_channel_description)
|
||||||
|
ciaChannel.setSound(null, null)
|
||||||
|
ciaChannel.vibrationPattern = null
|
||||||
|
createNotificationChannel(ciaChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
application = this
|
||||||
|
documentsTree = DocumentsTree()
|
||||||
|
if (PermissionsHandler.hasWriteAccess(applicationContext)) {
|
||||||
|
DirectoryInitialization.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.logDeviceInfo()
|
||||||
|
createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var application: CitraApplication? = null
|
||||||
|
|
||||||
|
val appContext: Context get() = application!!.applicationContext
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
lateinit var documentsTree: DocumentsTree
|
||||||
|
}
|
||||||
|
}
|
@ -1,720 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 Dolphin Emulator Project
|
|
||||||
* Licensed under GPLv2+
|
|
||||||
* Refer to the license.txt file included.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.citra.citra_emu;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
|
||||||
import org.citra.citra_emu.applets.SoftwareKeyboard;
|
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings;
|
|
||||||
import org.citra.citra_emu.utils.FileUtil;
|
|
||||||
import org.citra.citra_emu.utils.Log;
|
|
||||||
import org.citra.citra_emu.utils.PermissionsHandler;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static android.Manifest.permission.CAMERA;
|
|
||||||
import static android.Manifest.permission.RECORD_AUDIO;
|
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class which contains methods that interact
|
|
||||||
* with the native side of the Citra code.
|
|
||||||
*/
|
|
||||||
public final class NativeLibrary {
|
|
||||||
/**
|
|
||||||
* Default touchscreen device
|
|
||||||
*/
|
|
||||||
public static final String TouchScreenDevice = "Touchscreen";
|
|
||||||
public static WeakReference<EmulationActivity> sEmulationActivity = new WeakReference<>(null);
|
|
||||||
|
|
||||||
private static boolean alertResult = false;
|
|
||||||
private static String alertPromptResult = "";
|
|
||||||
private static int alertPromptButton = 0;
|
|
||||||
private static final Object alertPromptLock = new Object();
|
|
||||||
private static boolean alertPromptInProgress = false;
|
|
||||||
private static String alertPromptCaption = "";
|
|
||||||
private static int alertPromptButtonConfig = 0;
|
|
||||||
private static EditText alertPromptEditText = null;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
System.loadLibrary("citra-android");
|
|
||||||
} catch (UnsatisfiedLinkError ex) {
|
|
||||||
Log.error("[NativeLibrary] " + ex.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private NativeLibrary() {
|
|
||||||
// Disallows instantiation.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles button press events for a gamepad.
|
|
||||||
*
|
|
||||||
* @param Device The input descriptor of the gamepad.
|
|
||||||
* @param Button Key code identifying which button was pressed.
|
|
||||||
* @param Action Mask identifying which action is happening (button pressed down, or button released).
|
|
||||||
* @return If we handled the button press.
|
|
||||||
*/
|
|
||||||
public static native boolean onGamePadEvent(String Device, int Button, int Action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles gamepad movement events.
|
|
||||||
*
|
|
||||||
* @param Device The device ID of the gamepad.
|
|
||||||
* @param Axis The axis ID
|
|
||||||
* @param x_axis The value of the x-axis represented by the given ID.
|
|
||||||
* @param y_axis The value of the y-axis represented by the given ID
|
|
||||||
*/
|
|
||||||
public static native boolean onGamePadMoveEvent(String Device, int Axis, float x_axis, float y_axis);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles gamepad movement events.
|
|
||||||
*
|
|
||||||
* @param Device The device ID of the gamepad.
|
|
||||||
* @param Axis_id The axis ID
|
|
||||||
* @param axis_val The value of the axis represented by the given ID.
|
|
||||||
*/
|
|
||||||
public static native boolean onGamePadAxisEvent(String Device, int Axis_id, float axis_val);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch events.
|
|
||||||
*
|
|
||||||
* @param x_axis The value of the x-axis.
|
|
||||||
* @param y_axis 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
|
|
||||||
*/
|
|
||||||
public static native boolean onTouchEvent(float x_axis, float y_axis, boolean pressed);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch movement.
|
|
||||||
*
|
|
||||||
* @param x_axis The value of the instantaneous x-axis.
|
|
||||||
* @param y_axis The value of the instantaneous y-axis.
|
|
||||||
*/
|
|
||||||
public static native void onTouchMoved(float x_axis, float y_axis);
|
|
||||||
|
|
||||||
public static native void ReloadSettings();
|
|
||||||
|
|
||||||
public static native String GetUserSetting(String gameID, String Section, String Key);
|
|
||||||
|
|
||||||
public static native void SetUserSetting(String gameID, String Section, String Key, String Value);
|
|
||||||
|
|
||||||
public static native void InitGameIni(String gameID);
|
|
||||||
|
|
||||||
public static native long GetTitleId(String filename);
|
|
||||||
|
|
||||||
public static native String GetGitRevision();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current working user directory
|
|
||||||
* If not set, it auto-detects a location
|
|
||||||
*/
|
|
||||||
public static native void SetUserDirectory(String directory);
|
|
||||||
|
|
||||||
public static native String[] GetInstalledGamePaths();
|
|
||||||
|
|
||||||
// Create the config.ini file.
|
|
||||||
public static native void CreateConfigFile();
|
|
||||||
|
|
||||||
public static native void CreateLogFile();
|
|
||||||
|
|
||||||
public static native void LogUserDirectory(String directory);
|
|
||||||
|
|
||||||
public static native int DefaultCPUCore();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins emulation.
|
|
||||||
*/
|
|
||||||
public static native void Run(String path);
|
|
||||||
|
|
||||||
public static native String[] GetTextureFilterNames();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins emulation from the specified savestate.
|
|
||||||
*/
|
|
||||||
public static native void Run(String path, String savestatePath, boolean deleteSavestate);
|
|
||||||
|
|
||||||
// Surface Handling
|
|
||||||
public static native void SurfaceChanged(Surface surf);
|
|
||||||
|
|
||||||
public static native void SurfaceDestroyed();
|
|
||||||
|
|
||||||
public static native void DoFrame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpauses emulation from a paused state.
|
|
||||||
*/
|
|
||||||
public static native void UnPauseEmulation();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses emulation.
|
|
||||||
*/
|
|
||||||
public static native void PauseEmulation();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops emulation.
|
|
||||||
*/
|
|
||||||
public static native void StopEmulation();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if emulation is running (or is paused).
|
|
||||||
*/
|
|
||||||
public static native boolean IsRunning();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the title ID of the currently running title, or 0 on failure.
|
|
||||||
*/
|
|
||||||
public static native long GetRunningTitleId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the performance stats for the current game
|
|
||||||
**/
|
|
||||||
public static native double[] GetPerfStats();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the core emulation that the orientation has changed.
|
|
||||||
*/
|
|
||||||
public static native void NotifyOrientationChange(int layout_option, int rotation);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Swaps the top and bottom screens.
|
|
||||||
*/
|
|
||||||
public static native void SwapScreens(boolean swap_screens, int rotation);
|
|
||||||
|
|
||||||
public enum CoreError {
|
|
||||||
ErrorSystemFiles,
|
|
||||||
ErrorSavestate,
|
|
||||||
ErrorUnknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean coreErrorAlertResult = false;
|
|
||||||
private static final Object coreErrorAlertLock = new Object();
|
|
||||||
|
|
||||||
public static class CoreErrorDialogFragment extends DialogFragment {
|
|
||||||
static CoreErrorDialogFragment newInstance(String title, String message) {
|
|
||||||
CoreErrorDialogFragment frag = new CoreErrorDialogFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("title", title);
|
|
||||||
args.putString("message", message);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
final Activity emulationActivity = Objects.requireNonNull(getActivity());
|
|
||||||
|
|
||||||
final String title = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("title"));
|
|
||||||
final String message = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("message"));
|
|
||||||
|
|
||||||
return new MaterialAlertDialogBuilder(emulationActivity)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.setPositiveButton(R.string.continue_button, (dialog, which) -> {
|
|
||||||
coreErrorAlertResult = true;
|
|
||||||
synchronized (coreErrorAlertLock) {
|
|
||||||
coreErrorAlertLock.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.abort_button, (dialog, which) -> {
|
|
||||||
coreErrorAlertResult = false;
|
|
||||||
synchronized (coreErrorAlertLock) {
|
|
||||||
coreErrorAlertLock.notify();
|
|
||||||
}
|
|
||||||
}).setOnDismissListener(dialog -> {
|
|
||||||
coreErrorAlertResult = true;
|
|
||||||
synchronized (coreErrorAlertLock) {
|
|
||||||
coreErrorAlertLock.notify();
|
|
||||||
}
|
|
||||||
}).create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnCoreErrorImpl(String title, String message) {
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreErrorDialogFragment fragment = CoreErrorDialogFragment.newInstance(title, message);
|
|
||||||
fragment.show(emulationActivity.getSupportFragmentManager(), "coreError");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a core error.
|
|
||||||
* @return true: continue; false: abort
|
|
||||||
*/
|
|
||||||
public static boolean OnCoreError(CoreError error, String details) {
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String title, message;
|
|
||||||
switch (error) {
|
|
||||||
case ErrorSystemFiles: {
|
|
||||||
title = emulationActivity.getString(R.string.system_archive_not_found);
|
|
||||||
message = emulationActivity.getString(R.string.system_archive_not_found_message, details.isEmpty() ? emulationActivity.getString(R.string.system_archive_general) : details);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ErrorSavestate: {
|
|
||||||
title = emulationActivity.getString(R.string.save_load_error);
|
|
||||||
message = details;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ErrorUnknown: {
|
|
||||||
title = emulationActivity.getString(R.string.fatal_error);
|
|
||||||
message = emulationActivity.getString(R.string.fatal_error_message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread.
|
|
||||||
emulationActivity.runOnUiThread(() -> OnCoreErrorImpl(title, message));
|
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete.
|
|
||||||
synchronized (coreErrorAlertLock) {
|
|
||||||
try {
|
|
||||||
coreErrorAlertLock.wait();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return coreErrorAlertResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPortraitMode() {
|
|
||||||
return CitraApplication.getAppContext().getResources().getConfiguration().orientation ==
|
|
||||||
Configuration.ORIENTATION_PORTRAIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int landscapeScreenLayout() {
|
|
||||||
return EmulationMenuSettings.getLandscapeScreenLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean displayAlertMsg(final String caption, final String text,
|
|
||||||
final boolean yesNo) {
|
|
||||||
Log.error("[NativeLibrary] Alert: " + text);
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
boolean result = false;
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert.");
|
|
||||||
} else {
|
|
||||||
// Create object used for waiting.
|
|
||||||
final Object lock = new Object();
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
|
||||||
.setTitle(caption)
|
|
||||||
.setMessage(text);
|
|
||||||
|
|
||||||
// If not yes/no dialog just have one button that dismisses modal,
|
|
||||||
// otherwise have a yes and no button that sets alertResult accordingly.
|
|
||||||
if (!yesNo) {
|
|
||||||
builder
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) ->
|
|
||||||
{
|
|
||||||
dialog.dismiss();
|
|
||||||
synchronized (lock) {
|
|
||||||
lock.notify();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alertResult = false;
|
|
||||||
|
|
||||||
builder
|
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) ->
|
|
||||||
{
|
|
||||||
alertResult = true;
|
|
||||||
dialog.dismiss();
|
|
||||||
synchronized (lock) {
|
|
||||||
lock.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.no, (dialog, whichButton) ->
|
|
||||||
{
|
|
||||||
alertResult = false;
|
|
||||||
dialog.dismiss();
|
|
||||||
synchronized (lock) {
|
|
||||||
lock.notify();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread.
|
|
||||||
emulationActivity.runOnUiThread(builder::show);
|
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete.
|
|
||||||
synchronized (lock) {
|
|
||||||
try {
|
|
||||||
lock.wait();
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yesNo)
|
|
||||||
result = alertResult;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void retryDisplayAlertPrompt() {
|
|
||||||
if (!alertPromptInProgress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
displayAlertPromptImpl(alertPromptCaption, alertPromptEditText.getText().toString(), alertPromptButtonConfig).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String displayAlertPrompt(String caption, String text, int buttonConfig) {
|
|
||||||
alertPromptCaption = caption;
|
|
||||||
alertPromptButtonConfig = buttonConfig;
|
|
||||||
alertPromptInProgress = true;
|
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread
|
|
||||||
sEmulationActivity.get().runOnUiThread(() -> displayAlertPromptImpl(alertPromptCaption, text, alertPromptButtonConfig).show());
|
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete
|
|
||||||
synchronized (alertPromptLock) {
|
|
||||||
try {
|
|
||||||
alertPromptLock.wait();
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alertPromptInProgress = false;
|
|
||||||
|
|
||||||
return alertPromptResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MaterialAlertDialogBuilder displayAlertPromptImpl(String caption, String text, int buttonConfig) {
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
alertPromptResult = "";
|
|
||||||
alertPromptButton = 0;
|
|
||||||
|
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
params.leftMargin = params.rightMargin = CitraApplication.getAppContext().getResources().getDimensionPixelSize(R.dimen.dialog_margin);
|
|
||||||
|
|
||||||
// Set up the input
|
|
||||||
alertPromptEditText = new EditText(CitraApplication.getAppContext());
|
|
||||||
alertPromptEditText.setText(text);
|
|
||||||
alertPromptEditText.setSingleLine();
|
|
||||||
alertPromptEditText.setLayoutParams(params);
|
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(emulationActivity);
|
|
||||||
container.addView(alertPromptEditText);
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
|
||||||
.setTitle(caption)
|
|
||||||
.setView(container)
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) ->
|
|
||||||
{
|
|
||||||
alertPromptButton = buttonConfig;
|
|
||||||
alertPromptResult = alertPromptEditText.getText().toString();
|
|
||||||
synchronized (alertPromptLock) {
|
|
||||||
alertPromptLock.notifyAll();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setOnDismissListener(dialogInterface ->
|
|
||||||
{
|
|
||||||
alertPromptResult = "";
|
|
||||||
synchronized (alertPromptLock) {
|
|
||||||
alertPromptLock.notifyAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buttonConfig > 0) {
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
|
|
||||||
{
|
|
||||||
alertPromptResult = "";
|
|
||||||
synchronized (alertPromptLock) {
|
|
||||||
alertPromptLock.notifyAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int alertPromptButton() {
|
|
||||||
return alertPromptButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void exitEmulationActivity(int resultCode) {
|
|
||||||
final int Success = 0;
|
|
||||||
final int ErrorNotInitialized = 1;
|
|
||||||
final int ErrorGetLoader = 2;
|
|
||||||
final int ErrorSystemMode = 3;
|
|
||||||
final int ErrorLoader = 4;
|
|
||||||
final int ErrorLoader_ErrorEncrypted = 5;
|
|
||||||
final int ErrorLoader_ErrorInvalidFormat = 6;
|
|
||||||
final int ErrorSystemFiles = 7;
|
|
||||||
final int ShutdownRequested = 11;
|
|
||||||
final int ErrorUnknown = 12;
|
|
||||||
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int captionId = R.string.loader_error_invalid_format;
|
|
||||||
if (resultCode == ErrorLoader_ErrorEncrypted) {
|
|
||||||
captionId = R.string.loader_error_encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
|
||||||
.setTitle(captionId)
|
|
||||||
.setMessage(Html.fromHtml("Please follow the guides to redump your <a href=\"https://citra-emu.org/wiki/dumping-game-cartridges/\">game cartidges</a> or <a href=\"https://citra-emu.org/wiki/dumping-installed-titles/\">installed titles</a>.", Html.FROM_HTML_MODE_LEGACY))
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> emulationActivity.finish())
|
|
||||||
.setOnDismissListener(dialogInterface -> emulationActivity.finish());
|
|
||||||
emulationActivity.runOnUiThread(() -> {
|
|
||||||
AlertDialog alert = builder.create();
|
|
||||||
alert.show();
|
|
||||||
((TextView) alert.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setEmulationActivity(EmulationActivity emulationActivity) {
|
|
||||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.");
|
|
||||||
sEmulationActivity = new WeakReference<>(emulationActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearEmulationActivity() {
|
|
||||||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.");
|
|
||||||
|
|
||||||
sEmulationActivity.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Object cameraPermissionLock = new Object();
|
|
||||||
private static boolean cameraPermissionGranted = false;
|
|
||||||
public static final int REQUEST_CODE_NATIVE_CAMERA = 800;
|
|
||||||
|
|
||||||
public static boolean RequestCameraPermission() {
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ContextCompat.checkSelfPermission(emulationActivity, CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
// Permission already granted
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
emulationActivity.requestPermissions(new String[]{CAMERA}, REQUEST_CODE_NATIVE_CAMERA);
|
|
||||||
|
|
||||||
// Wait until result is returned
|
|
||||||
synchronized (cameraPermissionLock) {
|
|
||||||
try {
|
|
||||||
cameraPermissionLock.wait();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cameraPermissionGranted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CameraPermissionResult(boolean granted) {
|
|
||||||
cameraPermissionGranted = granted;
|
|
||||||
synchronized (cameraPermissionLock) {
|
|
||||||
cameraPermissionLock.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Object micPermissionLock = new Object();
|
|
||||||
private static boolean micPermissionGranted = false;
|
|
||||||
public static final int REQUEST_CODE_NATIVE_MIC = 900;
|
|
||||||
|
|
||||||
public static boolean RequestMicPermission() {
|
|
||||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
|
||||||
if (emulationActivity == null) {
|
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ContextCompat.checkSelfPermission(emulationActivity, RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
// Permission already granted
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
emulationActivity.requestPermissions(new String[]{RECORD_AUDIO}, REQUEST_CODE_NATIVE_MIC);
|
|
||||||
|
|
||||||
// Wait until result is returned
|
|
||||||
synchronized (micPermissionLock) {
|
|
||||||
try {
|
|
||||||
micPermissionLock.wait();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return micPermissionGranted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void MicPermissionResult(boolean granted) {
|
|
||||||
micPermissionGranted = granted;
|
|
||||||
synchronized (micPermissionLock) {
|
|
||||||
micPermissionLock.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notifies that the activity is now in foreground and camera devices can now be reloaded
|
|
||||||
public static native void ReloadCameraDevices();
|
|
||||||
|
|
||||||
public static native boolean LoadAmiibo(String path);
|
|
||||||
|
|
||||||
public static native void RemoveAmiibo();
|
|
||||||
|
|
||||||
public static final int SAVESTATE_SLOT_COUNT = 10;
|
|
||||||
|
|
||||||
public static final class SavestateInfo {
|
|
||||||
public int slot;
|
|
||||||
public Date time;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static native SavestateInfo[] GetSavestateInfo();
|
|
||||||
|
|
||||||
public static native void SaveState(int slot);
|
|
||||||
public static native void LoadState(int slot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the Citra version, Android version and, CPU.
|
|
||||||
*/
|
|
||||||
public static native void LogDeviceInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button type for use in onTouchEvent
|
|
||||||
*/
|
|
||||||
public static final class ButtonType {
|
|
||||||
public static final int BUTTON_A = 700;
|
|
||||||
public static final int BUTTON_B = 701;
|
|
||||||
public static final int BUTTON_X = 702;
|
|
||||||
public static final int BUTTON_Y = 703;
|
|
||||||
public static final int BUTTON_START = 704;
|
|
||||||
public static final int BUTTON_SELECT = 705;
|
|
||||||
public static final int BUTTON_HOME = 706;
|
|
||||||
public static final int BUTTON_ZL = 707;
|
|
||||||
public static final int BUTTON_ZR = 708;
|
|
||||||
public static final int DPAD_UP = 709;
|
|
||||||
public static final int DPAD_DOWN = 710;
|
|
||||||
public static final int DPAD_LEFT = 711;
|
|
||||||
public static final int DPAD_RIGHT = 712;
|
|
||||||
public static final int STICK_LEFT = 713;
|
|
||||||
public static final int STICK_LEFT_UP = 714;
|
|
||||||
public static final int STICK_LEFT_DOWN = 715;
|
|
||||||
public static final int STICK_LEFT_LEFT = 716;
|
|
||||||
public static final int STICK_LEFT_RIGHT = 717;
|
|
||||||
public static final int STICK_C = 718;
|
|
||||||
public static final int STICK_C_UP = 719;
|
|
||||||
public static final int STICK_C_DOWN = 720;
|
|
||||||
public static final int STICK_C_LEFT = 771;
|
|
||||||
public static final int STICK_C_RIGHT = 772;
|
|
||||||
public static final int TRIGGER_L = 773;
|
|
||||||
public static final int TRIGGER_R = 774;
|
|
||||||
public static final int DPAD = 780;
|
|
||||||
public static final int BUTTON_DEBUG = 781;
|
|
||||||
public static final int BUTTON_GPIO14 = 782;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button states
|
|
||||||
*/
|
|
||||||
public static final class ButtonState {
|
|
||||||
public static final int RELEASED = 0;
|
|
||||||
public static final int PRESSED = 1;
|
|
||||||
}
|
|
||||||
public static boolean createFile(String directory, String filename) {
|
|
||||||
if (FileUtil.isNativePath(directory)) {
|
|
||||||
return CitraApplication.documentsTree.createFile(directory, filename);
|
|
||||||
}
|
|
||||||
return FileUtil.createFile(CitraApplication.getAppContext(), directory, filename) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean createDir(String directory, String directoryName) {
|
|
||||||
if (FileUtil.isNativePath(directory)) {
|
|
||||||
return CitraApplication.documentsTree.createDir(directory, directoryName);
|
|
||||||
}
|
|
||||||
return FileUtil.createDir(CitraApplication.getAppContext(), directory, directoryName) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int openContentUri(String path, String openMode) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.openContentUri(path, openMode);
|
|
||||||
}
|
|
||||||
return FileUtil.openContentUri(CitraApplication.getAppContext(), path, openMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] getFilesName(String path) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.getFilesName(path);
|
|
||||||
}
|
|
||||||
return FileUtil.getFilesName(CitraApplication.getAppContext(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getSize(String path) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.getFileSize(path);
|
|
||||||
}
|
|
||||||
return FileUtil.getFileSize(CitraApplication.getAppContext(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean fileExists(String path) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.Exists(path);
|
|
||||||
}
|
|
||||||
return FileUtil.Exists(CitraApplication.getAppContext(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isDirectory(String path) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.isDirectory(path);
|
|
||||||
}
|
|
||||||
return FileUtil.isDirectory(CitraApplication.getAppContext(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean copyFile(String sourcePath, String destinationParentPath, String destinationFilename) {
|
|
||||||
if (FileUtil.isNativePath(sourcePath) && FileUtil.isNativePath(destinationParentPath)) {
|
|
||||||
return CitraApplication.documentsTree.copyFile(sourcePath, destinationParentPath, destinationFilename);
|
|
||||||
}
|
|
||||||
return FileUtil.copyFile(CitraApplication.getAppContext(), sourcePath, destinationParentPath, destinationFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean renameFile(String path, String destinationFilename) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.renameFile(path, destinationFilename);
|
|
||||||
}
|
|
||||||
return FileUtil.renameFile(CitraApplication.getAppContext(), path, destinationFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean deleteDocument(String path) {
|
|
||||||
if (FileUtil.isNativePath(path)) {
|
|
||||||
return CitraApplication.documentsTree.deleteDocument(path);
|
|
||||||
}
|
|
||||||
return FileUtil.deleteDocument(CitraApplication.getAppContext(), path);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,720 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu
|
||||||
|
|
||||||
|
import android.Manifest.permission
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.view.Surface
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import org.citra.citra_emu.activities.EmulationActivity
|
||||||
|
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||||
|
import org.citra.citra_emu.utils.FileUtil
|
||||||
|
import org.citra.citra_emu.utils.Log
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class which contains methods that interact
|
||||||
|
* with the native side of the Citra code.
|
||||||
|
*/
|
||||||
|
object NativeLibrary {
|
||||||
|
/**
|
||||||
|
* Default touchscreen device
|
||||||
|
*/
|
||||||
|
const val TouchScreenDevice = "Touchscreen"
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||||
|
private var alertResult = false
|
||||||
|
val alertLock = Object()
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
System.loadLibrary("citra-android")
|
||||||
|
} catch (ex: UnsatisfiedLinkError) {
|
||||||
|
Log.error("[NativeLibrary] $ex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles button press events for a gamepad.
|
||||||
|
*
|
||||||
|
* @param device The input descriptor of the gamepad.
|
||||||
|
* @param button Key code identifying which button was pressed.
|
||||||
|
* @param action Mask identifying which action is happening (button pressed down, or button released).
|
||||||
|
* @return If we handled the button press.
|
||||||
|
*/
|
||||||
|
external fun onGamePadEvent(device: String, button: Int, action: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles gamepad movement events.
|
||||||
|
*
|
||||||
|
* @param device The device ID of the gamepad.
|
||||||
|
* @param axis The axis ID
|
||||||
|
* @param xAxis The value of the x-axis represented by the given ID.
|
||||||
|
* @param yAxis The value of the y-axis represented by the given ID
|
||||||
|
*/
|
||||||
|
external fun onGamePadMoveEvent(device: String, axis: Int, xAxis: Float, yAxis: Float): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles gamepad movement events.
|
||||||
|
*
|
||||||
|
* @param device The device ID of the gamepad.
|
||||||
|
* @param axisId The axis ID
|
||||||
|
* @param axisVal The value of the axis represented by the given ID.
|
||||||
|
*/
|
||||||
|
external fun onGamePadAxisEvent(device: String?, axisId: Int, axisVal: Float): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch events.
|
||||||
|
*
|
||||||
|
* @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 onTouchEvent(xAxis: Float, yAxis: Float, pressed: Boolean): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch movement.
|
||||||
|
*
|
||||||
|
* @param xAxis The value of the instantaneous x-axis.
|
||||||
|
* @param yAxis The value of the instantaneous y-axis.
|
||||||
|
*/
|
||||||
|
external fun onTouchMoved(xAxis: Float, yAxis: Float)
|
||||||
|
|
||||||
|
external fun reloadSettings()
|
||||||
|
|
||||||
|
external fun getTitleId(filename: String): Long
|
||||||
|
|
||||||
|
external fun getIsSystemTitle(path: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current working user directory
|
||||||
|
* If not set, it auto-detects a location
|
||||||
|
*/
|
||||||
|
external fun setUserDirectory(directory: String)
|
||||||
|
external fun getInstalledGamePaths(): Array<String?>
|
||||||
|
|
||||||
|
// Create the config.ini file.
|
||||||
|
external fun createConfigFile()
|
||||||
|
external fun createLogFile()
|
||||||
|
external fun logUserDirectory(directory: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins emulation.
|
||||||
|
*/
|
||||||
|
external fun run(path: String)
|
||||||
|
|
||||||
|
// Surface Handling
|
||||||
|
external fun surfaceChanged(surf: Surface)
|
||||||
|
external fun surfaceDestroyed()
|
||||||
|
external fun doFrame()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpauses emulation from a paused state.
|
||||||
|
*/
|
||||||
|
external fun unPauseEmulation()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses emulation.
|
||||||
|
*/
|
||||||
|
external fun pauseEmulation()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops emulation.
|
||||||
|
*/
|
||||||
|
external fun stopEmulation()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if emulation is running (or is paused).
|
||||||
|
*/
|
||||||
|
external fun isRunning(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the title ID of the currently running title, or 0 on failure.
|
||||||
|
*/
|
||||||
|
external fun getRunningTitleId(): Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the performance stats for the current game
|
||||||
|
*/
|
||||||
|
external fun getPerfStats(): DoubleArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the core emulation that the orientation has changed.
|
||||||
|
*/
|
||||||
|
external fun notifyOrientationChange(layoutOption: Int, rotation: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the top and bottom screens.
|
||||||
|
*/
|
||||||
|
external fun swapScreens(swapScreens: Boolean, rotation: Int)
|
||||||
|
|
||||||
|
external fun initializeGpuDriver(
|
||||||
|
hookLibDir: String?,
|
||||||
|
customDriverDir: String?,
|
||||||
|
customDriverName: String?,
|
||||||
|
fileRedirectDir: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
external fun areKeysAvailable(): Boolean
|
||||||
|
|
||||||
|
external fun getHomeMenuPath(region: Int): String
|
||||||
|
|
||||||
|
external fun getSystemTitleIds(systemType: Int, region: Int): LongArray
|
||||||
|
|
||||||
|
external fun downloadTitleFromNus(title: Long): InstallStatus
|
||||||
|
|
||||||
|
private var coreErrorAlertResult = false
|
||||||
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
|
private fun onCoreErrorImpl(title: String, message: String) {
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val fragment = CoreErrorDialogFragment.newInstance(title, message)
|
||||||
|
fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a core error.
|
||||||
|
* @return true: continue; false: abort
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val title: String
|
||||||
|
val message: String
|
||||||
|
when (error) {
|
||||||
|
CoreError.ErrorSystemFiles -> {
|
||||||
|
title = emulationActivity.getString(R.string.system_archive_not_found)
|
||||||
|
message = emulationActivity.getString(
|
||||||
|
R.string.system_archive_not_found_message,
|
||||||
|
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreError.ErrorSavestate -> {
|
||||||
|
title = emulationActivity.getString(R.string.save_load_error)
|
||||||
|
message = details
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreError.ErrorUnknown -> {
|
||||||
|
title = emulationActivity.getString(R.string.fatal_error)
|
||||||
|
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the AlertDialog on the main thread.
|
||||||
|
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||||
|
|
||||||
|
// Wait for the lock to notify that it is complete.
|
||||||
|
synchronized(coreErrorAlertLock) {
|
||||||
|
try {
|
||||||
|
coreErrorAlertLock.wait()
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coreErrorAlertResult
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Keep
|
||||||
|
@get:JvmStatic
|
||||||
|
val isPortraitMode: Boolean
|
||||||
|
get() = CitraApplication.appContext.resources.configuration.orientation ==
|
||||||
|
Configuration.ORIENTATION_PORTRAIT
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun landscapeScreenLayout(): Int = EmulationMenuSettings.landscapeScreenLayout
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun displayAlertMsg(title: String, message: String, yesNo: Boolean): Boolean {
|
||||||
|
Log.error("[NativeLibrary] Alert: $message")
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
var result = false
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert.")
|
||||||
|
} else {
|
||||||
|
// Show the AlertDialog on the main thread.
|
||||||
|
emulationActivity.runOnUiThread {
|
||||||
|
AlertMessageDialogFragment.newInstance(title, message, yesNo).showNow(
|
||||||
|
emulationActivity.supportFragmentManager,
|
||||||
|
AlertMessageDialogFragment.TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the lock to notify that it is complete.
|
||||||
|
synchronized(alertLock) {
|
||||||
|
try {
|
||||||
|
alertLock.wait()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yesNo) result = alertResult
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlertMessageDialogFragment : DialogFragment() {
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
// Create object used for waiting.
|
||||||
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(requireArguments().getString(TITLE))
|
||||||
|
.setMessage(requireArguments().getString(MESSAGE))
|
||||||
|
|
||||||
|
// If not yes/no dialog just have one button that dismisses modal,
|
||||||
|
// otherwise have a yes and no button that sets alertResult accordingly.
|
||||||
|
if (!requireArguments().getBoolean(YES_NO)) {
|
||||||
|
builder
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
|
synchronized(alertLock) { alertLock.notify() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alertResult = false
|
||||||
|
builder
|
||||||
|
.setPositiveButton(android.R.string.yes) { _: DialogInterface, _: Int ->
|
||||||
|
alertResult = true
|
||||||
|
synchronized(alertLock) { alertLock.notify() }
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { _: DialogInterface, _: Int ->
|
||||||
|
alertResult = false
|
||||||
|
synchronized(alertLock) { alertLock.notify() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "AlertMessageDialogFragment"
|
||||||
|
|
||||||
|
const val TITLE = "title"
|
||||||
|
const val MESSAGE = "message"
|
||||||
|
const val YES_NO = "yesNo"
|
||||||
|
|
||||||
|
fun newInstance(
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
yesNo: Boolean
|
||||||
|
): AlertMessageDialogFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(TITLE, title)
|
||||||
|
args.putString(MESSAGE, message)
|
||||||
|
args.putBoolean(YES_NO, yesNo)
|
||||||
|
val fragment = AlertMessageDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun exitEmulationActivity(resultCode: Int) {
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emulationActivity.runOnUiThread {
|
||||||
|
EmulationErrorDialogFragment.newInstance(resultCode).showNow(
|
||||||
|
emulationActivity.supportFragmentManager,
|
||||||
|
EmulationErrorDialogFragment.TAG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmulationErrorDialogFragment : DialogFragment() {
|
||||||
|
private lateinit var emulationActivity: EmulationActivity
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
|
|
||||||
|
var captionId = R.string.loader_error_invalid_format
|
||||||
|
if (requireArguments().getInt(RESULT_CODE) == ErrorLoader_ErrorEncrypted) {
|
||||||
|
captionId = R.string.loader_error_encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(captionId)
|
||||||
|
.setMessage(
|
||||||
|
Html.fromHtml(
|
||||||
|
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
||||||
|
Html.FROM_HTML_MODE_LEGACY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
emulationActivity.finish()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
alert.show()
|
||||||
|
|
||||||
|
val alertMessage = alert.findViewById<View>(android.R.id.message) as TextView
|
||||||
|
alertMessage.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
|
||||||
|
isCancelable = false
|
||||||
|
return alert
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "EmulationErrorDialogFragment"
|
||||||
|
|
||||||
|
const val RESULT_CODE = "resultcode"
|
||||||
|
|
||||||
|
const val Success = 0
|
||||||
|
const val ErrorNotInitialized = 1
|
||||||
|
const val ErrorGetLoader = 2
|
||||||
|
const val ErrorSystemMode = 3
|
||||||
|
const val ErrorLoader = 4
|
||||||
|
const val ErrorLoader_ErrorEncrypted = 5
|
||||||
|
const val ErrorLoader_ErrorInvalidFormat = 6
|
||||||
|
const val ErrorSystemFiles = 7
|
||||||
|
const val ShutdownRequested = 11
|
||||||
|
const val ErrorUnknown = 12
|
||||||
|
|
||||||
|
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putInt(RESULT_CODE, resultCode)
|
||||||
|
val fragment = EmulationErrorDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||||
|
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
||||||
|
sEmulationActivity = WeakReference(emulationActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearEmulationActivity() {
|
||||||
|
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
||||||
|
sEmulationActivity.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cameraPermissionLock = Object()
|
||||||
|
private var cameraPermissionGranted = false
|
||||||
|
const val REQUEST_CODE_NATIVE_CAMERA = 800
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun requestCameraPermission(): Boolean {
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (ContextCompat.checkSelfPermission(emulationActivity, permission.CAMERA) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
// Permission already granted
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
emulationActivity.requestPermissions(arrayOf(permission.CAMERA), REQUEST_CODE_NATIVE_CAMERA)
|
||||||
|
|
||||||
|
// Wait until result is returned
|
||||||
|
synchronized(cameraPermissionLock) {
|
||||||
|
try {
|
||||||
|
cameraPermissionLock.wait()
|
||||||
|
} catch (ignored: InterruptedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cameraPermissionGranted
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cameraPermissionResult(granted: Boolean) {
|
||||||
|
cameraPermissionGranted = granted
|
||||||
|
synchronized(cameraPermissionLock) { cameraPermissionLock.notify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val micPermissionLock = Object()
|
||||||
|
private var micPermissionGranted = false
|
||||||
|
const val REQUEST_CODE_NATIVE_MIC = 900
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun requestMicPermission(): Boolean {
|
||||||
|
val emulationActivity = sEmulationActivity.get()
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (ContextCompat.checkSelfPermission(emulationActivity, permission.RECORD_AUDIO) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
// Permission already granted
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
emulationActivity.requestPermissions(
|
||||||
|
arrayOf(permission.RECORD_AUDIO),
|
||||||
|
REQUEST_CODE_NATIVE_MIC
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait until result is returned
|
||||||
|
synchronized(micPermissionLock) {
|
||||||
|
try {
|
||||||
|
micPermissionLock.wait()
|
||||||
|
} catch (ignored: InterruptedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return micPermissionGranted
|
||||||
|
}
|
||||||
|
|
||||||
|
fun micPermissionResult(granted: Boolean) {
|
||||||
|
micPermissionGranted = granted
|
||||||
|
synchronized(micPermissionLock) { micPermissionLock.notify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifies that the activity is now in foreground and camera devices can now be reloaded
|
||||||
|
external fun reloadCameraDevices()
|
||||||
|
|
||||||
|
external fun loadAmiibo(path: String?): Boolean
|
||||||
|
|
||||||
|
external fun removeAmiibo()
|
||||||
|
|
||||||
|
const val SAVESTATE_SLOT_COUNT = 10
|
||||||
|
|
||||||
|
external fun getSavestateInfo(): Array<SaveStateInfo>?
|
||||||
|
|
||||||
|
external fun saveState(slot: Int)
|
||||||
|
|
||||||
|
external fun loadState(slot: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the Citra version, Android version and, CPU.
|
||||||
|
*/
|
||||||
|
external fun logDeviceInfo()
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun createFile(directory: String, filename: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(directory)) {
|
||||||
|
CitraApplication.documentsTree.createFile(directory, filename)
|
||||||
|
} else {
|
||||||
|
FileUtil.createFile(directory, filename) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun createDir(directory: String, directoryName: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(directory)) {
|
||||||
|
CitraApplication.documentsTree.createDir(directory, directoryName)
|
||||||
|
} else {
|
||||||
|
FileUtil.createDir(directory, directoryName) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun openContentUri(path: String, openMode: String): Int =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.openContentUri(path, openMode)
|
||||||
|
} else {
|
||||||
|
FileUtil.openContentUri(path, openMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun getFilesName(path: String): Array<String?> =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.getFilesName(path)
|
||||||
|
} else {
|
||||||
|
FileUtil.getFilesName(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun getSize(path: String): Long =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.getFileSize(path)
|
||||||
|
} else {
|
||||||
|
FileUtil.getFileSize(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun fileExists(path: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.exists(path)
|
||||||
|
} else {
|
||||||
|
FileUtil.exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun isDirectory(path: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.isDirectory(path)
|
||||||
|
} else {
|
||||||
|
FileUtil.isDirectory(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun copyFile(
|
||||||
|
sourcePath: String,
|
||||||
|
destinationParentPath: String,
|
||||||
|
destinationFilename: String
|
||||||
|
): Boolean =
|
||||||
|
if (FileUtil.isNativePath(sourcePath) &&
|
||||||
|
FileUtil.isNativePath(destinationParentPath)
|
||||||
|
) {
|
||||||
|
CitraApplication.documentsTree
|
||||||
|
.copyFile(sourcePath, destinationParentPath, destinationFilename)
|
||||||
|
} else {
|
||||||
|
FileUtil.copyFile(
|
||||||
|
Uri.parse(sourcePath),
|
||||||
|
Uri.parse(destinationParentPath),
|
||||||
|
destinationFilename
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun renameFile(path: String, destinationFilename: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.renameFile(path, destinationFilename)
|
||||||
|
} else {
|
||||||
|
FileUtil.renameFile(path, destinationFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun deleteDocument(path: String): Boolean =
|
||||||
|
if (FileUtil.isNativePath(path)) {
|
||||||
|
CitraApplication.documentsTree.deleteDocument(path)
|
||||||
|
} else {
|
||||||
|
FileUtil.deleteDocument(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CoreError {
|
||||||
|
ErrorSystemFiles,
|
||||||
|
ErrorSavestate,
|
||||||
|
ErrorUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class InstallStatus {
|
||||||
|
Success,
|
||||||
|
ErrorFailedToOpenFile,
|
||||||
|
ErrorFileNotFound,
|
||||||
|
ErrorAborted,
|
||||||
|
ErrorInvalid,
|
||||||
|
ErrorEncrypted,
|
||||||
|
Cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoreErrorDialogFragment : DialogFragment() {
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val title = requireArguments().getString(TITLE)
|
||||||
|
val message = requireArguments().getString(MESSAGE)
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int ->
|
||||||
|
coreErrorAlertResult = true
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||||
|
coreErrorAlertResult = false
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
super.onDismiss(dialog)
|
||||||
|
coreErrorAlertResult = true
|
||||||
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "CoreErrorDialogFragment"
|
||||||
|
|
||||||
|
const val TITLE = "title"
|
||||||
|
const val MESSAGE = "message"
|
||||||
|
|
||||||
|
fun newInstance(title: String, message: String): CoreErrorDialogFragment {
|
||||||
|
val frag = CoreErrorDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(TITLE, title)
|
||||||
|
args.putString(MESSAGE, message)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
class SaveStateInfo {
|
||||||
|
var slot = 0
|
||||||
|
var time: Date? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button type for use in onTouchEvent
|
||||||
|
*/
|
||||||
|
object ButtonType {
|
||||||
|
const val BUTTON_A = 700
|
||||||
|
const val BUTTON_B = 701
|
||||||
|
const val BUTTON_X = 702
|
||||||
|
const val BUTTON_Y = 703
|
||||||
|
const val BUTTON_START = 704
|
||||||
|
const val BUTTON_SELECT = 705
|
||||||
|
const val BUTTON_HOME = 706
|
||||||
|
const val BUTTON_ZL = 707
|
||||||
|
const val BUTTON_ZR = 708
|
||||||
|
const val DPAD_UP = 709
|
||||||
|
const val DPAD_DOWN = 710
|
||||||
|
const val DPAD_LEFT = 711
|
||||||
|
const val DPAD_RIGHT = 712
|
||||||
|
const val STICK_LEFT = 713
|
||||||
|
const val STICK_LEFT_UP = 714
|
||||||
|
const val STICK_LEFT_DOWN = 715
|
||||||
|
const val STICK_LEFT_LEFT = 716
|
||||||
|
const val STICK_LEFT_RIGHT = 717
|
||||||
|
const val STICK_C = 718
|
||||||
|
const val STICK_C_UP = 719
|
||||||
|
const val STICK_C_DOWN = 720
|
||||||
|
const val STICK_C_LEFT = 771
|
||||||
|
const val STICK_C_RIGHT = 772
|
||||||
|
const val TRIGGER_L = 773
|
||||||
|
const val TRIGGER_R = 774
|
||||||
|
const val DPAD = 780
|
||||||
|
const val BUTTON_DEBUG = 781
|
||||||
|
const val BUTTON_GPIO14 = 782
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button states
|
||||||
|
*/
|
||||||
|
object ButtonState {
|
||||||
|
const val RELEASED = 0
|
||||||
|
const val PRESSED = 1
|
||||||
|
}
|
||||||
|
}
|
@ -1,785 +0,0 @@
|
|||||||
package org.citra.citra_emu.activities;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.util.SparseIntArray;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.SubMenu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultCallback;
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.CitraApplication;
|
|
||||||
import org.citra.citra_emu.NativeLibrary;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
import org.citra.citra_emu.contracts.OpenFileResultContract;
|
|
||||||
import org.citra.citra_emu.features.cheats.ui.CheatsActivity;
|
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting;
|
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity;
|
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile;
|
|
||||||
import org.citra.citra_emu.camera.StillImageCameraHelper;
|
|
||||||
import org.citra.citra_emu.fragments.EmulationFragment;
|
|
||||||
import org.citra.citra_emu.ui.main.MainActivity;
|
|
||||||
import org.citra.citra_emu.utils.ControllerMappingHelper;
|
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings;
|
|
||||||
import org.citra.citra_emu.utils.FileBrowserHelper;
|
|
||||||
import org.citra.citra_emu.utils.FileUtil;
|
|
||||||
import org.citra.citra_emu.utils.ForegroundService;
|
|
||||||
import org.citra.citra_emu.utils.ThemeUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static android.Manifest.permission.CAMERA;
|
|
||||||
import static android.Manifest.permission.RECORD_AUDIO;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import com.google.android.material.slider.Slider;
|
|
||||||
|
|
||||||
public final class EmulationActivity extends AppCompatActivity {
|
|
||||||
public static final String EXTRA_SELECTED_GAME = "SelectedGame";
|
|
||||||
public static final String EXTRA_SELECTED_TITLE = "SelectedTitle";
|
|
||||||
public static final int MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0;
|
|
||||||
public static final int MENU_ACTION_TOGGLE_CONTROLS = 1;
|
|
||||||
public static final int MENU_ACTION_ADJUST_SCALE = 2;
|
|
||||||
public static final int MENU_ACTION_EXIT = 3;
|
|
||||||
public static final int MENU_ACTION_SHOW_FPS = 4;
|
|
||||||
public static final int MENU_ACTION_SCREEN_LAYOUT_LANDSCAPE = 5;
|
|
||||||
public static final int MENU_ACTION_SCREEN_LAYOUT_PORTRAIT = 6;
|
|
||||||
public static final int MENU_ACTION_SCREEN_LAYOUT_SINGLE = 7;
|
|
||||||
public static final int MENU_ACTION_SCREEN_LAYOUT_SIDEBYSIDE = 8;
|
|
||||||
public static final int MENU_ACTION_SWAP_SCREENS = 9;
|
|
||||||
public static final int MENU_ACTION_RESET_OVERLAY = 10;
|
|
||||||
public static final int MENU_ACTION_SHOW_OVERLAY = 11;
|
|
||||||
public static final int MENU_ACTION_OPEN_SETTINGS = 12;
|
|
||||||
public static final int MENU_ACTION_LOAD_AMIIBO = 13;
|
|
||||||
public static final int MENU_ACTION_REMOVE_AMIIBO = 14;
|
|
||||||
public static final int MENU_ACTION_JOYSTICK_REL_CENTER = 15;
|
|
||||||
public static final int MENU_ACTION_DPAD_SLIDE_ENABLE = 16;
|
|
||||||
public static final int MENU_ACTION_OPEN_CHEATS = 17;
|
|
||||||
public static final int MENU_ACTION_CLOSE_GAME = 18;
|
|
||||||
|
|
||||||
public static final int REQUEST_SELECT_AMIIBO = 2;
|
|
||||||
private static final int EMULATION_RUNNING_NOTIFICATION = 0x1000;
|
|
||||||
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
|
|
||||||
|
|
||||||
private final ActivityResultLauncher<Boolean> mOpenFileLauncher =
|
|
||||||
registerForActivityResult(new OpenFileResultContract(), result -> {
|
|
||||||
if (result == null)
|
|
||||||
return;
|
|
||||||
String[] selectedFiles = FileBrowserHelper.getSelectedFiles(
|
|
||||||
result, getApplicationContext(), Collections.singletonList("bin"));
|
|
||||||
if (selectedFiles == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
onAmiiboSelected(selectedFiles[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
static {
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_edit_layout,
|
|
||||||
EmulationActivity.MENU_ACTION_EDIT_CONTROLS_PLACEMENT);
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_toggle_controls,
|
|
||||||
EmulationActivity.MENU_ACTION_TOGGLE_CONTROLS);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_adjust_scale, EmulationActivity.MENU_ACTION_ADJUST_SCALE);
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_show_fps,
|
|
||||||
EmulationActivity.MENU_ACTION_SHOW_FPS);
|
|
||||||
buttonsActionsMap.append(R.id.menu_screen_layout_landscape,
|
|
||||||
EmulationActivity.MENU_ACTION_SCREEN_LAYOUT_LANDSCAPE);
|
|
||||||
buttonsActionsMap.append(R.id.menu_screen_layout_portrait,
|
|
||||||
EmulationActivity.MENU_ACTION_SCREEN_LAYOUT_PORTRAIT);
|
|
||||||
buttonsActionsMap.append(R.id.menu_screen_layout_single,
|
|
||||||
EmulationActivity.MENU_ACTION_SCREEN_LAYOUT_SINGLE);
|
|
||||||
buttonsActionsMap.append(R.id.menu_screen_layout_sidebyside,
|
|
||||||
EmulationActivity.MENU_ACTION_SCREEN_LAYOUT_SIDEBYSIDE);
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_swap_screens,
|
|
||||||
EmulationActivity.MENU_ACTION_SWAP_SCREENS);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_reset_overlay, EmulationActivity.MENU_ACTION_RESET_OVERLAY);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_show_overlay, EmulationActivity.MENU_ACTION_SHOW_OVERLAY);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_open_settings, EmulationActivity.MENU_ACTION_OPEN_SETTINGS);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_amiibo_load, EmulationActivity.MENU_ACTION_LOAD_AMIIBO);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_amiibo_remove, EmulationActivity.MENU_ACTION_REMOVE_AMIIBO);
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_joystick_rel_center,
|
|
||||||
EmulationActivity.MENU_ACTION_JOYSTICK_REL_CENTER);
|
|
||||||
buttonsActionsMap.append(R.id.menu_emulation_dpad_slide_enable,
|
|
||||||
EmulationActivity.MENU_ACTION_DPAD_SLIDE_ENABLE);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_open_cheats, EmulationActivity.MENU_ACTION_OPEN_CHEATS);
|
|
||||||
buttonsActionsMap
|
|
||||||
.append(R.id.menu_emulation_close_game, EmulationActivity.MENU_ACTION_CLOSE_GAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EmulationFragment mEmulationFragment;
|
|
||||||
private SharedPreferences mPreferences;
|
|
||||||
private ControllerMappingHelper mControllerMappingHelper;
|
|
||||||
private Intent foregroundService;
|
|
||||||
private boolean activityRecreated;
|
|
||||||
private String mSelectedTitle;
|
|
||||||
private String mPath;
|
|
||||||
|
|
||||||
public static void launch(FragmentActivity activity, String path, String title) {
|
|
||||||
Intent launcher = new Intent(activity, EmulationActivity.class);
|
|
||||||
|
|
||||||
launcher.putExtra(EXTRA_SELECTED_GAME, path);
|
|
||||||
launcher.putExtra(EXTRA_SELECTED_TITLE, title);
|
|
||||||
activity.startActivity(launcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void tryDismissRunningNotification(Activity activity) {
|
|
||||||
NotificationManagerCompat.from(activity).cancel(EMULATION_RUNNING_NOTIFICATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
stopService(foregroundService);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
ThemeUtil.applyTheme(this);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
// Get params we were passed
|
|
||||||
Intent gameToEmulate = getIntent();
|
|
||||||
mPath = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME);
|
|
||||||
mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE);
|
|
||||||
activityRecreated = false;
|
|
||||||
} else {
|
|
||||||
activityRecreated = true;
|
|
||||||
restoreState(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
mControllerMappingHelper = new ControllerMappingHelper();
|
|
||||||
|
|
||||||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
|
||||||
enableFullscreenImmersive();
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_emulation);
|
|
||||||
|
|
||||||
// Find or create the EmulationFragment
|
|
||||||
mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
|
|
||||||
.findFragmentById(R.id.frame_emulation_fragment);
|
|
||||||
if (mEmulationFragment == null) {
|
|
||||||
mEmulationFragment = EmulationFragment.newInstance(mPath);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.add(R.id.frame_emulation_fragment, mEmulationFragment)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle(mSelectedTitle);
|
|
||||||
|
|
||||||
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
|
|
||||||
// Start a foreground service to prevent the app from getting killed in the background
|
|
||||||
foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
|
|
||||||
startForegroundService(foregroundService);
|
|
||||||
|
|
||||||
// Override Citra core INI with the one set by our in game menu
|
|
||||||
NativeLibrary.SwapScreens(EmulationMenuSettings.getSwapScreens(),
|
|
||||||
getWindowManager().getDefaultDisplay().getRotation());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
outState.putString(EXTRA_SELECTED_GAME, mPath);
|
|
||||||
outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle);
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void restoreState(Bundle savedInstanceState) {
|
|
||||||
mPath = savedInstanceState.getString(EXTRA_SELECTED_GAME);
|
|
||||||
mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE);
|
|
||||||
|
|
||||||
// If an alert prompt was in progress when state was restored, retry displaying it
|
|
||||||
NativeLibrary.retryDisplayAlertPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRestart() {
|
|
||||||
super.onRestart();
|
|
||||||
NativeLibrary.ReloadCameraDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
View anchor = findViewById(R.id.menu_anchor);
|
|
||||||
PopupMenu popupMenu = new PopupMenu(this, anchor);
|
|
||||||
onCreateOptionsMenu(popupMenu.getMenu(), popupMenu.getMenuInflater());
|
|
||||||
updateSavestateMenuOptions(popupMenu.getMenu());
|
|
||||||
popupMenu.setOnMenuItemClickListener(this::onOptionsItemSelected);
|
|
||||||
popupMenu.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case NativeLibrary.REQUEST_CODE_NATIVE_CAMERA:
|
|
||||||
if (grantResults[0] != PackageManager.PERMISSION_GRANTED &&
|
|
||||||
shouldShowRequestPermissionRationale(CAMERA)) {
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.camera)
|
|
||||||
.setMessage(R.string.camera_permission_needed)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
NativeLibrary.CameraPermissionResult(grantResults[0] == PackageManager.PERMISSION_GRANTED);
|
|
||||||
break;
|
|
||||||
case NativeLibrary.REQUEST_CODE_NATIVE_MIC:
|
|
||||||
if (grantResults[0] != PackageManager.PERMISSION_GRANTED &&
|
|
||||||
shouldShowRequestPermissionRationale(RECORD_AUDIO)) {
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.microphone)
|
|
||||||
.setMessage(R.string.microphone_permission_needed)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
NativeLibrary.MicPermissionResult(grantResults[0] == PackageManager.PERMISSION_GRANTED);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onEmulationStarted() {
|
|
||||||
Toast.makeText(this, getString(R.string.emulation_menu_help), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableFullscreenImmersive() {
|
|
||||||
getWindow().getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
|
||||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE |
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
|
||||||
onCreateOptionsMenu(menu, getMenuInflater());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_emulation, menu);
|
|
||||||
|
|
||||||
int layoutOptionMenuItem = R.id.menu_screen_layout_landscape;
|
|
||||||
switch (EmulationMenuSettings.getLandscapeScreenLayout()) {
|
|
||||||
case EmulationMenuSettings.LayoutOption_SingleScreen:
|
|
||||||
layoutOptionMenuItem = R.id.menu_screen_layout_single;
|
|
||||||
break;
|
|
||||||
case EmulationMenuSettings.LayoutOption_SideScreen:
|
|
||||||
layoutOptionMenuItem = R.id.menu_screen_layout_sidebyside;
|
|
||||||
break;
|
|
||||||
case EmulationMenuSettings.LayoutOption_MobilePortrait:
|
|
||||||
layoutOptionMenuItem = R.id.menu_screen_layout_portrait;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.findItem(layoutOptionMenuItem).setChecked(true);
|
|
||||||
menu.findItem(R.id.menu_emulation_joystick_rel_center).setChecked(EmulationMenuSettings.getJoystickRelCenter());
|
|
||||||
menu.findItem(R.id.menu_emulation_dpad_slide_enable).setChecked(EmulationMenuSettings.getDpadSlideEnable());
|
|
||||||
menu.findItem(R.id.menu_emulation_show_fps).setChecked(EmulationMenuSettings.getShowFps());
|
|
||||||
menu.findItem(R.id.menu_emulation_swap_screens).setChecked(EmulationMenuSettings.getSwapScreens());
|
|
||||||
menu.findItem(R.id.menu_emulation_show_overlay).setChecked(EmulationMenuSettings.getShowOverlay());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisplaySavestateWarning() {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext());
|
|
||||||
if (preferences.getBoolean("savestateWarningShown", false)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayoutInflater inflater = mEmulationFragment.requireActivity().getLayoutInflater();
|
|
||||||
View view = inflater.inflate(R.layout.dialog_checkbox, null);
|
|
||||||
CheckBox checkBox = view.findViewById(R.id.checkBox);
|
|
||||||
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.savestate_warning_title)
|
|
||||||
.setMessage(R.string.savestate_warning_message)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
preferences.edit().putBoolean("savestateWarningShown", checkBox.isChecked()).apply();
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
updateSavestateMenuOptions(menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSavestateMenuOptions(Menu menu) {
|
|
||||||
final NativeLibrary.SavestateInfo[] savestates = NativeLibrary.GetSavestateInfo();
|
|
||||||
if (savestates == null) {
|
|
||||||
menu.findItem(R.id.menu_emulation_save_state).setVisible(false);
|
|
||||||
menu.findItem(R.id.menu_emulation_load_state).setVisible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
menu.findItem(R.id.menu_emulation_save_state).setVisible(true);
|
|
||||||
menu.findItem(R.id.menu_emulation_load_state).setVisible(true);
|
|
||||||
|
|
||||||
final SubMenu saveStateMenu = menu.findItem(R.id.menu_emulation_save_state).getSubMenu();
|
|
||||||
final SubMenu loadStateMenu = menu.findItem(R.id.menu_emulation_load_state).getSubMenu();
|
|
||||||
saveStateMenu.clear();
|
|
||||||
loadStateMenu.clear();
|
|
||||||
|
|
||||||
// Update savestates information
|
|
||||||
for (int i = 0; i < NativeLibrary.SAVESTATE_SLOT_COUNT; ++i) {
|
|
||||||
final int slot = i + 1;
|
|
||||||
final String text = getString(R.string.emulation_empty_state_slot, slot);
|
|
||||||
saveStateMenu.add(text).setEnabled(true).setOnMenuItemClickListener((item) -> {
|
|
||||||
DisplaySavestateWarning();
|
|
||||||
NativeLibrary.SaveState(slot);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
loadStateMenu.add(text).setEnabled(false).setOnMenuItemClickListener((item) -> {
|
|
||||||
NativeLibrary.LoadState(slot);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (final NativeLibrary.SavestateInfo info : savestates) {
|
|
||||||
final String text = getString(R.string.emulation_occupied_state_slot, info.slot, info.time);
|
|
||||||
saveStateMenu.getItem(info.slot - 1).setTitle(text);
|
|
||||||
loadStateMenu.getItem(info.slot - 1).setTitle(text).setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("WrongConstant")
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int action = buttonsActionsMap.get(item.getItemId(), -1);
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
// Edit the placement of the controls
|
|
||||||
case MENU_ACTION_EDIT_CONTROLS_PLACEMENT:
|
|
||||||
editControlsPlacement();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Enable/Disable specific buttons or the entire input overlay.
|
|
||||||
case MENU_ACTION_TOGGLE_CONTROLS:
|
|
||||||
toggleControls();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Adjust the scale of the overlay controls.
|
|
||||||
case MENU_ACTION_ADJUST_SCALE:
|
|
||||||
adjustScale();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Toggle the visibility of the Performance stats TextView
|
|
||||||
case MENU_ACTION_SHOW_FPS: {
|
|
||||||
final boolean isEnabled = !EmulationMenuSettings.getShowFps();
|
|
||||||
EmulationMenuSettings.setShowFps(isEnabled);
|
|
||||||
item.setChecked(isEnabled);
|
|
||||||
|
|
||||||
mEmulationFragment.updateShowFpsOverlay();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Sets the screen layout to Landscape
|
|
||||||
case MENU_ACTION_SCREEN_LAYOUT_LANDSCAPE:
|
|
||||||
changeScreenOrientation(EmulationMenuSettings.LayoutOption_MobileLandscape, item);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Sets the screen layout to Portrait
|
|
||||||
case MENU_ACTION_SCREEN_LAYOUT_PORTRAIT:
|
|
||||||
changeScreenOrientation(EmulationMenuSettings.LayoutOption_MobilePortrait, item);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Sets the screen layout to Single
|
|
||||||
case MENU_ACTION_SCREEN_LAYOUT_SINGLE:
|
|
||||||
changeScreenOrientation(EmulationMenuSettings.LayoutOption_SingleScreen, item);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Sets the screen layout to Side by Side
|
|
||||||
case MENU_ACTION_SCREEN_LAYOUT_SIDEBYSIDE:
|
|
||||||
changeScreenOrientation(EmulationMenuSettings.LayoutOption_SideScreen, item);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Swap the top and bottom screen locations
|
|
||||||
case MENU_ACTION_SWAP_SCREENS: {
|
|
||||||
final boolean isEnabled = !EmulationMenuSettings.getSwapScreens();
|
|
||||||
EmulationMenuSettings.setSwapScreens(isEnabled);
|
|
||||||
item.setChecked(isEnabled);
|
|
||||||
|
|
||||||
NativeLibrary.SwapScreens(isEnabled, getWindowManager().getDefaultDisplay()
|
|
||||||
.getRotation());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset overlay placement
|
|
||||||
case MENU_ACTION_RESET_OVERLAY:
|
|
||||||
resetOverlay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Show or hide overlay
|
|
||||||
case MENU_ACTION_SHOW_OVERLAY: {
|
|
||||||
final boolean isEnabled = !EmulationMenuSettings.getShowOverlay();
|
|
||||||
EmulationMenuSettings.setShowOverlay(isEnabled);
|
|
||||||
item.setChecked(isEnabled);
|
|
||||||
|
|
||||||
mEmulationFragment.refreshInputOverlay();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MENU_ACTION_EXIT:
|
|
||||||
mEmulationFragment.stopEmulation();
|
|
||||||
finish();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_OPEN_SETTINGS:
|
|
||||||
SettingsActivity.launch(this, SettingsFile.FILE_NAME_CONFIG, "");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_LOAD_AMIIBO:
|
|
||||||
mOpenFileLauncher.launch(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_REMOVE_AMIIBO:
|
|
||||||
RemoveAmiibo();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_JOYSTICK_REL_CENTER:
|
|
||||||
final boolean isJoystickRelCenterEnabled = !EmulationMenuSettings.getJoystickRelCenter();
|
|
||||||
EmulationMenuSettings.setJoystickRelCenter(isJoystickRelCenterEnabled);
|
|
||||||
item.setChecked(isJoystickRelCenterEnabled);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_DPAD_SLIDE_ENABLE:
|
|
||||||
final boolean isDpadSlideEnabled = !EmulationMenuSettings.getDpadSlideEnable();
|
|
||||||
EmulationMenuSettings.setDpadSlideEnable(isDpadSlideEnabled);
|
|
||||||
item.setChecked(isDpadSlideEnabled);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_OPEN_CHEATS:
|
|
||||||
CheatsActivity.launch(this, NativeLibrary.GetRunningTitleId());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_ACTION_CLOSE_GAME:
|
|
||||||
NativeLibrary.PauseEmulation();
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.emulation_close_game)
|
|
||||||
.setMessage(R.string.emulation_close_game_message)
|
|
||||||
.setPositiveButton(android.R.string.yes, (dialogInterface, i) ->
|
|
||||||
{
|
|
||||||
mEmulationFragment.stopEmulation();
|
|
||||||
finish();
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> NativeLibrary.UnPauseEmulation())
|
|
||||||
.setOnCancelListener(dialogInterface -> NativeLibrary.UnPauseEmulation())
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeScreenOrientation(int layoutOption, MenuItem item) {
|
|
||||||
item.setChecked(true);
|
|
||||||
NativeLibrary.NotifyOrientationChange(layoutOption, getWindowManager().getDefaultDisplay()
|
|
||||||
.getRotation());
|
|
||||||
EmulationMenuSettings.setLandscapeScreenLayout(layoutOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void editControlsPlacement() {
|
|
||||||
if (mEmulationFragment.isConfiguringControls()) {
|
|
||||||
mEmulationFragment.stopConfiguringControls();
|
|
||||||
} else {
|
|
||||||
mEmulationFragment.startConfiguringControls();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets button presses
|
|
||||||
@Override
|
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
||||||
int action;
|
|
||||||
int button = mPreferences.getInt(InputBindingSetting.getInputButtonKey(event.getKeyCode()), event.getKeyCode());
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case KeyEvent.ACTION_DOWN:
|
|
||||||
// Handling the case where the back button is pressed.
|
|
||||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal key events.
|
|
||||||
action = NativeLibrary.ButtonState.PRESSED;
|
|
||||||
break;
|
|
||||||
case KeyEvent.ACTION_UP:
|
|
||||||
action = NativeLibrary.ButtonState.RELEASED;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
InputDevice input = event.getDevice();
|
|
||||||
|
|
||||||
if (input == null) {
|
|
||||||
// Controller was disconnected
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeLibrary.onGamePadEvent(input.getDescriptor(), button, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, result);
|
|
||||||
if (requestCode == StillImageCameraHelper.REQUEST_CAMERA_FILE_PICKER) {
|
|
||||||
StillImageCameraHelper.OnFilePickerResult(resultCode == RESULT_OK ? result : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAmiiboSelected(String selectedFile) {
|
|
||||||
boolean success = NativeLibrary.LoadAmiibo(selectedFile);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.amiibo_load_error)
|
|
||||||
.setMessage(R.string.amiibo_load_error_message)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveAmiibo() {
|
|
||||||
NativeLibrary.RemoveAmiibo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toggleControls() {
|
|
||||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
|
||||||
boolean[] enabledButtons = new boolean[14];
|
|
||||||
|
|
||||||
for (int i = 0; i < enabledButtons.length; i++) {
|
|
||||||
// Buttons that are disabled by default
|
|
||||||
boolean defaultValue = true;
|
|
||||||
switch (i) {
|
|
||||||
case 6: // ZL
|
|
||||||
case 7: // ZR
|
|
||||||
case 12: // C-stick
|
|
||||||
defaultValue = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledButtons[i] = mPreferences.getBoolean("buttonToggle" + i, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.emulation_toggle_controls)
|
|
||||||
.setMultiChoiceItems(R.array.n3dsButtons, enabledButtons,
|
|
||||||
(dialog, indexSelected, isChecked) -> editor
|
|
||||||
.putBoolean("buttonToggle" + indexSelected, isChecked))
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) ->
|
|
||||||
{
|
|
||||||
editor.apply();
|
|
||||||
mEmulationFragment.refreshInputOverlay();
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void adjustScale() {
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(this);
|
|
||||||
View view = inflater.inflate(R.layout.dialog_slider, null);
|
|
||||||
|
|
||||||
final Slider slider = view.findViewById(R.id.slider);
|
|
||||||
final TextView textValue = view.findViewById(R.id.text_value);
|
|
||||||
final TextView units = view.findViewById(R.id.text_units);
|
|
||||||
|
|
||||||
slider.setValueTo(150);
|
|
||||||
slider.setValue(mPreferences.getInt("controlScale", 50));
|
|
||||||
slider.addOnChangeListener((slider1, progress, fromUser) -> {
|
|
||||||
textValue.setText(String.valueOf((int) progress + 50));
|
|
||||||
setControlScale((int) slider1.getValue());
|
|
||||||
});
|
|
||||||
|
|
||||||
textValue.setText(String.valueOf((int) slider.getValue() + 50));
|
|
||||||
units.setText("%");
|
|
||||||
|
|
||||||
final int previousProgress = (int) slider.getValue();
|
|
||||||
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.emulation_control_scale)
|
|
||||||
.setView(view)
|
|
||||||
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> setControlScale(previousProgress))
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> setControlScale((int) slider.getValue()))
|
|
||||||
.setNeutralButton(R.string.slider_default, (dialogInterface, i) -> setControlScale(50))
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setControlScale(int scale) {
|
|
||||||
SharedPreferences.Editor editor = mPreferences.edit();
|
|
||||||
editor.putInt("controlScale", scale);
|
|
||||||
editor.apply();
|
|
||||||
mEmulationFragment.refreshInputOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetOverlay() {
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(getString(R.string.emulation_touch_overlay_reset))
|
|
||||||
.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> mEmulationFragment.resetInputOverlay())
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
|
||||||
if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)) {
|
|
||||||
return super.dispatchGenericMotionEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't attempt to do anything if we are disconnecting a device.
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDevice input = event.getDevice();
|
|
||||||
List<InputDevice.MotionRange> motions = input.getMotionRanges();
|
|
||||||
|
|
||||||
float[] axisValuesCirclePad = {0.0f, 0.0f};
|
|
||||||
float[] axisValuesCStick = {0.0f, 0.0f};
|
|
||||||
float[] axisValuesDPad = {0.0f, 0.0f};
|
|
||||||
boolean isTriggerPressedLMapped = false;
|
|
||||||
boolean isTriggerPressedRMapped = false;
|
|
||||||
boolean isTriggerPressedZLMapped = false;
|
|
||||||
boolean isTriggerPressedZRMapped = false;
|
|
||||||
boolean isTriggerPressedL = false;
|
|
||||||
boolean isTriggerPressedR = false;
|
|
||||||
boolean isTriggerPressedZL = false;
|
|
||||||
boolean isTriggerPressedZR = false;
|
|
||||||
|
|
||||||
for (InputDevice.MotionRange range : motions) {
|
|
||||||
int axis = range.getAxis();
|
|
||||||
float origValue = event.getAxisValue(axis);
|
|
||||||
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
|
|
||||||
int nextMapping = mPreferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1);
|
|
||||||
int guestOrientation = mPreferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1);
|
|
||||||
|
|
||||||
if (nextMapping == -1 || guestOrientation == -1) {
|
|
||||||
// Axis is unmapped
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((value > 0.f && value < 0.1f) || (value < 0.f && value > -0.1f)) {
|
|
||||||
// Skip joystick wobble
|
|
||||||
value = 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextMapping == NativeLibrary.ButtonType.STICK_LEFT) {
|
|
||||||
axisValuesCirclePad[guestOrientation] = value;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.STICK_C) {
|
|
||||||
axisValuesCStick[guestOrientation] = value;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.DPAD) {
|
|
||||||
axisValuesDPad[guestOrientation] = value;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.TRIGGER_L) {
|
|
||||||
isTriggerPressedLMapped = true;
|
|
||||||
isTriggerPressedL = value != 0.f;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.TRIGGER_R) {
|
|
||||||
isTriggerPressedRMapped = true;
|
|
||||||
isTriggerPressedR = value != 0.f;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.BUTTON_ZL) {
|
|
||||||
isTriggerPressedZLMapped = true;
|
|
||||||
isTriggerPressedZL = value != 0.f;
|
|
||||||
} else if (nextMapping == NativeLibrary.ButtonType.BUTTON_ZR) {
|
|
||||||
isTriggerPressedZRMapped = true;
|
|
||||||
isTriggerPressedZR = value != 0.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Circle-Pad and C-Stick status
|
|
||||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), NativeLibrary.ButtonType.STICK_LEFT, axisValuesCirclePad[0], axisValuesCirclePad[1]);
|
|
||||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), NativeLibrary.ButtonType.STICK_C, axisValuesCStick[0], axisValuesCStick[1]);
|
|
||||||
|
|
||||||
// Triggers L/R and ZL/ZR
|
|
||||||
if (isTriggerPressedLMapped) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.TRIGGER_L, isTriggerPressedL ? NativeLibrary.ButtonState.PRESSED : NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (isTriggerPressedRMapped) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.TRIGGER_R, isTriggerPressedR ? NativeLibrary.ButtonState.PRESSED : NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (isTriggerPressedZLMapped) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.BUTTON_ZL, isTriggerPressedZL ? NativeLibrary.ButtonState.PRESSED : NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (isTriggerPressedZRMapped) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.BUTTON_ZR, isTriggerPressedZR ? NativeLibrary.ButtonState.PRESSED : NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Work-around to allow D-pad axis to be bound to emulated buttons
|
|
||||||
if (axisValuesDPad[0] == 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (axisValuesDPad[0] < 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonState.PRESSED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (axisValuesDPad[0] > 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonState.PRESSED);
|
|
||||||
}
|
|
||||||
if (axisValuesDPad[1] == 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (axisValuesDPad[1] < 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonState.PRESSED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
}
|
|
||||||
if (axisValuesDPad[1] > 0.f) {
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonState.RELEASED);
|
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonState.PRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActivityRecreated() {
|
|
||||||
return activityRecreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Retention(SOURCE)
|
|
||||||
@IntDef({MENU_ACTION_EDIT_CONTROLS_PLACEMENT, MENU_ACTION_TOGGLE_CONTROLS, MENU_ACTION_ADJUST_SCALE,
|
|
||||||
MENU_ACTION_EXIT, MENU_ACTION_SHOW_FPS, MENU_ACTION_SCREEN_LAYOUT_LANDSCAPE,
|
|
||||||
MENU_ACTION_SCREEN_LAYOUT_PORTRAIT, MENU_ACTION_SCREEN_LAYOUT_SINGLE, MENU_ACTION_SCREEN_LAYOUT_SIDEBYSIDE,
|
|
||||||
MENU_ACTION_SWAP_SCREENS, MENU_ACTION_RESET_OVERLAY, MENU_ACTION_SHOW_OVERLAY, MENU_ACTION_OPEN_SETTINGS})
|
|
||||||
public @interface MenuAction {
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,453 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.activities
|
||||||
|
|
||||||
|
import android.Manifest.permission
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.InputDevice
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
|
||||||
|
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||||
|
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
||||||
|
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||||
|
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||||
|
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||||
|
import org.citra.citra_emu.utils.ControllerMappingHelper
|
||||||
|
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||||
|
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||||
|
import org.citra.citra_emu.utils.ForegroundService
|
||||||
|
import org.citra.citra_emu.utils.ThemeUtil
|
||||||
|
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
||||||
|
|
||||||
|
class EmulationActivity : AppCompatActivity() {
|
||||||
|
private val preferences: SharedPreferences
|
||||||
|
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
private var foregroundService: Intent? = null
|
||||||
|
var isActivityRecreated = false
|
||||||
|
|
||||||
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
ThemeUtil.setTheme(this)
|
||||||
|
|
||||||
|
settingsViewModel.settings.loadSettings()
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
val navHostFragment =
|
||||||
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
|
val navController = navHostFragment.navController
|
||||||
|
navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
||||||
|
|
||||||
|
isActivityRecreated = savedInstanceState != null
|
||||||
|
|
||||||
|
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||||
|
enableFullscreenImmersive()
|
||||||
|
|
||||||
|
// Override Citra core INI with the one set by our in game menu
|
||||||
|
NativeLibrary.swapScreens(
|
||||||
|
EmulationMenuSettings.swapScreens,
|
||||||
|
windowManager.defaultDisplay.rotation
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start a foreground service to prevent the app from getting killed in the background
|
||||||
|
foregroundService = Intent(this, ForegroundService::class.java)
|
||||||
|
startForegroundService(foregroundService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On some devices, the system bars will not disappear on first boot or after some
|
||||||
|
// rotations. Here we set full screen immersive repeatedly in onResume and in
|
||||||
|
// onWindowFocusChanged to prevent the unwanted status bar state.
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
enableFullscreenImmersive()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
super.onWindowFocusChanged(hasFocus)
|
||||||
|
enableFullscreenImmersive()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onRestart() {
|
||||||
|
super.onRestart()
|
||||||
|
NativeLibrary.reloadCameraDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
stopForegroundService(this)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
when (requestCode) {
|
||||||
|
NativeLibrary.REQUEST_CODE_NATIVE_CAMERA -> {
|
||||||
|
if (grantResults[0] != PackageManager.PERMISSION_GRANTED &&
|
||||||
|
shouldShowRequestPermissionRationale(permission.CAMERA)
|
||||||
|
) {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
R.string.camera,
|
||||||
|
R.string.camera_permission_needed
|
||||||
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
NativeLibrary.cameraPermissionResult(
|
||||||
|
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.REQUEST_CODE_NATIVE_MIC -> {
|
||||||
|
if (grantResults[0] != PackageManager.PERMISSION_GRANTED &&
|
||||||
|
shouldShowRequestPermissionRationale(permission.RECORD_AUDIO)
|
||||||
|
) {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
R.string.microphone,
|
||||||
|
R.string.microphone_permission_needed
|
||||||
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
NativeLibrary.micPermissionResult(
|
||||||
|
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onEmulationStarted() {
|
||||||
|
emulationViewModel.setEmulationStarted(true)
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
getString(R.string.emulation_menu_help),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableFullscreenImmersive() {
|
||||||
|
// TODO: Remove this once we properly account for display insets in the input overlay
|
||||||
|
window.attributes.layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||||
|
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
|
controller.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets button presses
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@SuppressLint("GestureBackNavigation")
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
|
||||||
|
if (!NativeLibrary.isRunning()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val button =
|
||||||
|
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
|
||||||
|
val action: Int = when (event.action) {
|
||||||
|
KeyEvent.ACTION_DOWN -> {
|
||||||
|
// On some devices, the back gesture / button press is not intercepted by androidx
|
||||||
|
// and fails to open the emulation menu. So we're stuck running deprecated code to
|
||||||
|
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
|
||||||
|
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal key events.
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
val input = event.device
|
||||||
|
?: // Controller was disconnected
|
||||||
|
return false
|
||||||
|
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAmiiboSelected(selectedFile: String) {
|
||||||
|
val success = NativeLibrary.loadAmiibo(selectedFile)
|
||||||
|
if (!success) {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
R.string.amiibo_load_error,
|
||||||
|
R.string.amiibo_load_error_message
|
||||||
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||||
|
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
|
||||||
|
if (!NativeLibrary.isRunning()) {
|
||||||
|
return super.dispatchGenericMotionEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) {
|
||||||
|
return super.dispatchGenericMotionEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't attempt to do anything if we are disconnecting a device.
|
||||||
|
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val input = event.device
|
||||||
|
val motions = input.motionRanges
|
||||||
|
val axisValuesCirclePad = floatArrayOf(0.0f, 0.0f)
|
||||||
|
val axisValuesCStick = floatArrayOf(0.0f, 0.0f)
|
||||||
|
val axisValuesDPad = floatArrayOf(0.0f, 0.0f)
|
||||||
|
var isTriggerPressedLMapped = false
|
||||||
|
var isTriggerPressedRMapped = false
|
||||||
|
var isTriggerPressedZLMapped = false
|
||||||
|
var isTriggerPressedZRMapped = false
|
||||||
|
var isTriggerPressedL = false
|
||||||
|
var isTriggerPressedR = false
|
||||||
|
var isTriggerPressedZL = false
|
||||||
|
var isTriggerPressedZR = false
|
||||||
|
for (range in motions) {
|
||||||
|
val axis = range.axis
|
||||||
|
val origValue = event.getAxisValue(axis)
|
||||||
|
var value = ControllerMappingHelper.scaleAxis(input, axis, origValue)
|
||||||
|
val nextMapping =
|
||||||
|
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
|
||||||
|
val guestOrientation =
|
||||||
|
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
|
||||||
|
if (nextMapping == -1 || guestOrientation == -1) {
|
||||||
|
// Axis is unmapped
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (value > 0f && value < 0.1f || value < 0f && value > -0.1f) {
|
||||||
|
// Skip joystick wobble
|
||||||
|
value = 0f
|
||||||
|
}
|
||||||
|
when (nextMapping) {
|
||||||
|
NativeLibrary.ButtonType.STICK_LEFT -> {
|
||||||
|
axisValuesCirclePad[guestOrientation] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.STICK_C -> {
|
||||||
|
axisValuesCStick[guestOrientation] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.DPAD -> {
|
||||||
|
axisValuesDPad[guestOrientation] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_L -> {
|
||||||
|
isTriggerPressedLMapped = true
|
||||||
|
isTriggerPressedL = value != 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_R -> {
|
||||||
|
isTriggerPressedRMapped = true
|
||||||
|
isTriggerPressedR = value != 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.BUTTON_ZL -> {
|
||||||
|
isTriggerPressedZLMapped = true
|
||||||
|
isTriggerPressedZL = value != 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.ButtonType.BUTTON_ZR -> {
|
||||||
|
isTriggerPressedZRMapped = true
|
||||||
|
isTriggerPressedZR = value != 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circle-Pad and C-Stick status
|
||||||
|
NativeLibrary.onGamePadMoveEvent(
|
||||||
|
input.descriptor,
|
||||||
|
NativeLibrary.ButtonType.STICK_LEFT,
|
||||||
|
axisValuesCirclePad[0],
|
||||||
|
axisValuesCirclePad[1]
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadMoveEvent(
|
||||||
|
input.descriptor,
|
||||||
|
NativeLibrary.ButtonType.STICK_C,
|
||||||
|
axisValuesCStick[0],
|
||||||
|
axisValuesCStick[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Triggers L/R and ZL/ZR
|
||||||
|
if (isTriggerPressedLMapped) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_L,
|
||||||
|
if (isTriggerPressedL) {
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
} else {
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isTriggerPressedRMapped) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_R,
|
||||||
|
if (isTriggerPressedR) {
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
} else {
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isTriggerPressedZLMapped) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.BUTTON_ZL,
|
||||||
|
if (isTriggerPressedZL) {
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
} else {
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isTriggerPressedZRMapped) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.BUTTON_ZR,
|
||||||
|
if (isTriggerPressedZR) {
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
} else {
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work-around to allow D-pad axis to be bound to emulated buttons
|
||||||
|
if (axisValuesDPad[0] == 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (axisValuesDPad[0] < 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (axisValuesDPad[0] > 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (axisValuesDPad[1] == 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (axisValuesDPad[1] < 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (axisValuesDPad[1] > 0f) {
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadEvent(
|
||||||
|
NativeLibrary.TouchScreenDevice,
|
||||||
|
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val openFileLauncher =
|
||||||
|
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
|
||||||
|
if (result == null) return@registerForActivityResult
|
||||||
|
val selectedFiles = FileBrowserHelper.getSelectedFiles(
|
||||||
|
result, applicationContext, listOf<String>("bin")
|
||||||
|
) ?: return@registerForActivityResult
|
||||||
|
onAmiiboSelected(selectedFiles[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
val openImageLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { result: Uri? ->
|
||||||
|
if (result == null) {
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
OnFilePickerResult(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun stopForegroundService(activity: Activity) {
|
||||||
|
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||||
|
startIntent.action = ForegroundService.ACTION_STOP
|
||||||
|
activity.startForegroundService(startIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import org.citra.citra_emu.databinding.CardDriverOptionBinding
|
||||||
|
import org.citra.citra_emu.utils.GpuDriverMetadata
|
||||||
|
import org.citra.citra_emu.viewmodel.DriverViewModel
|
||||||
|
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||||
|
|
||||||
|
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||||
|
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
|
||||||
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
|
) {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
|
||||||
|
val binding =
|
||||||
|
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return DriverViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
|
||||||
|
private fun onSelectDriver(position: Int) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(position)
|
||||||
|
notifyItemChanged(driverViewModel.previouslySelectedDriver)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDeleteDriver(driverData: Pair<Uri, GpuDriverMetadata>, position: Int) {
|
||||||
|
if (driverViewModel.selectedDriver > position) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
|
||||||
|
}
|
||||||
|
if (GpuDriverHelper.customDriverData == driverData.second) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(0)
|
||||||
|
}
|
||||||
|
driverViewModel.driversToDelete.add(driverData.first)
|
||||||
|
driverViewModel.removeDriver(driverData)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
private lateinit var driverData: Pair<Uri, GpuDriverMetadata>
|
||||||
|
|
||||||
|
fun bind(driverData: Pair<Uri, GpuDriverMetadata>) {
|
||||||
|
this.driverData = driverData
|
||||||
|
val driver = driverData.second
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
|
||||||
|
root.setOnClickListener {
|
||||||
|
onSelectDriver(bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
buttonDelete.setOnClickListener {
|
||||||
|
onDeleteDriver(driverData, bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay marquee by 3s
|
||||||
|
title.postDelayed(
|
||||||
|
{
|
||||||
|
title.isSelected = true
|
||||||
|
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
version.isSelected = true
|
||||||
|
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
description.isSelected = true
|
||||||
|
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
if (driver.name == null) {
|
||||||
|
title.setText(R.string.system_gpu_driver)
|
||||||
|
description.text = ""
|
||||||
|
version.text = ""
|
||||||
|
version.visibility = View.GONE
|
||||||
|
description.visibility = View.GONE
|
||||||
|
buttonDelete.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
title.text = driver.name
|
||||||
|
version.text = driver.version
|
||||||
|
description.text = driver.description
|
||||||
|
version.visibility = View.VISIBLE
|
||||||
|
description.visibility = View.VISIBLE
|
||||||
|
buttonDelete.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Pair<Uri, GpuDriverMetadata>>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<Uri, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.first == newItem.first
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: Pair<Uri, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<Uri, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.second == newItem.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,261 +0,0 @@
|
|||||||
package org.citra.citra_emu.adapters;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DataSetObserver;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.google.android.material.color.MaterialColors;
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.CitraApplication;
|
|
||||||
import org.citra.citra_emu.NativeLibrary;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
|
||||||
import org.citra.citra_emu.features.cheats.ui.CheatsActivity;
|
|
||||||
import org.citra.citra_emu.model.GameDatabase;
|
|
||||||
import org.citra.citra_emu.utils.FileUtil;
|
|
||||||
import org.citra.citra_emu.utils.Log;
|
|
||||||
import org.citra.citra_emu.utils.PicassoUtils;
|
|
||||||
import org.citra.citra_emu.viewholders.GameViewHolder;
|
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This adapter gets its information from a database Cursor. This fact, paired with the usage of
|
|
||||||
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
|
|
||||||
* large dataset.
|
|
||||||
*/
|
|
||||||
public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> {
|
|
||||||
private Cursor mCursor;
|
|
||||||
private GameDataSetObserver mObserver;
|
|
||||||
|
|
||||||
private boolean mDatasetValid;
|
|
||||||
private long mLastClickTime = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
|
|
||||||
* display no data until a Cursor is supplied by a CursorLoader.
|
|
||||||
*/
|
|
||||||
public GameAdapter() {
|
|
||||||
mDatasetValid = false;
|
|
||||||
mObserver = new GameDataSetObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the LayoutManager when it is necessary to create a new view.
|
|
||||||
*
|
|
||||||
* @param parent The RecyclerView (I think?) the created view will be thrown into.
|
|
||||||
* @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
|
|
||||||
* @return The created ViewHolder with references to all the child view's members.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public GameViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
// Create a new view.
|
|
||||||
View gameCard = LayoutInflater.from(parent.getContext())
|
|
||||||
.inflate(R.layout.card_game, parent, false);
|
|
||||||
|
|
||||||
gameCard.setOnClickListener(this::onClick);
|
|
||||||
gameCard.setOnLongClickListener(this::onLongClick);
|
|
||||||
|
|
||||||
// Use that view to create a ViewHolder.
|
|
||||||
return new GameViewHolder(gameCard);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the LayoutManager when a new view is not necessary because we can recycle
|
|
||||||
* an existing one (for example, if a view just scrolled onto the screen from the bottom, we
|
|
||||||
* can use the view that just scrolled off the top instead of inflating a new one.)
|
|
||||||
*
|
|
||||||
* @param holder A ViewHolder representing the view we're recycling.
|
|
||||||
* @param position The position of the 'new' view in the dataset.
|
|
||||||
*/
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull GameViewHolder holder, int position) {
|
|
||||||
if (mDatasetValid) {
|
|
||||||
if (mCursor.moveToPosition(position)) {
|
|
||||||
PicassoUtils.loadGameIcon(holder.imageIcon,
|
|
||||||
mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
|
|
||||||
|
|
||||||
holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " "));
|
|
||||||
holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
|
|
||||||
|
|
||||||
String filepath = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
|
|
||||||
String filename;
|
|
||||||
if (FileUtil.isNativePath(filepath)) {
|
|
||||||
filename = CitraApplication.documentsTree.getFilename(filepath);
|
|
||||||
} else {
|
|
||||||
filename = FileUtil.getFilename(CitraApplication.getAppContext(), filepath);
|
|
||||||
}
|
|
||||||
holder.textFileName.setText(filename);
|
|
||||||
|
|
||||||
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
|
|
||||||
holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
|
|
||||||
holder.path = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
|
|
||||||
holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
|
|
||||||
holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
|
|
||||||
holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS);
|
|
||||||
holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY);
|
|
||||||
|
|
||||||
final int backgroundColorId = isValidGame(holder.path) ? R.attr.colorSurface : R.attr.colorErrorContainer;
|
|
||||||
View itemView = holder.getItemView();
|
|
||||||
itemView.setBackgroundColor(MaterialColors.getColor(itemView, backgroundColorId));
|
|
||||||
} else {
|
|
||||||
Log.error("[GameAdapter] Can't bind view; Cursor is not valid.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.error("[GameAdapter] Can't bind view; dataset is not valid.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the LayoutManager to find out how much data we have.
|
|
||||||
*
|
|
||||||
* @return Size of the dataset.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
if (mDatasetValid && mCursor != null) {
|
|
||||||
return mCursor.getCount();
|
|
||||||
}
|
|
||||||
Log.error("[GameAdapter] Dataset is not valid.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the contents of the _id column for a given row.
|
|
||||||
*
|
|
||||||
* @param position The row for which Android wants an ID.
|
|
||||||
* @return A valid ID from the database, or 0 if not available.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
if (mDatasetValid && mCursor != null) {
|
|
||||||
if (mCursor.moveToPosition(position)) {
|
|
||||||
return mCursor.getLong(GameDatabase.COLUMN_DB_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.error("[GameAdapter] Dataset is not valid.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell Android whether or not each item in the dataset has a stable identifier.
|
|
||||||
* Which it does, because it's a database, so always tell Android 'true'.
|
|
||||||
*
|
|
||||||
* @param hasStableIds ignored.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setHasStableIds(boolean hasStableIds) {
|
|
||||||
super.setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a load is finished, call this to replace the existing data with the newly-loaded
|
|
||||||
* data.
|
|
||||||
*
|
|
||||||
* @param cursor The newly-loaded Cursor.
|
|
||||||
*/
|
|
||||||
public void swapCursor(Cursor cursor) {
|
|
||||||
// Sanity check.
|
|
||||||
if (cursor == mCursor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before getting rid of the old cursor, disassociate it from the Observer.
|
|
||||||
final Cursor oldCursor = mCursor;
|
|
||||||
if (oldCursor != null && mObserver != null) {
|
|
||||||
oldCursor.unregisterDataSetObserver(mObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
mCursor = cursor;
|
|
||||||
if (mCursor != null) {
|
|
||||||
// Attempt to associate the new Cursor with the Observer.
|
|
||||||
if (mObserver != null) {
|
|
||||||
mCursor.registerDataSetObserver(mObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
mDatasetValid = true;
|
|
||||||
} else {
|
|
||||||
mDatasetValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches the game that was clicked on.
|
|
||||||
*
|
|
||||||
* @param view The view representing the game the user wants to play.
|
|
||||||
*/
|
|
||||||
private void onClick(View view) {
|
|
||||||
// Double-click prevention, using threshold of 1000 ms
|
|
||||||
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mLastClickTime = SystemClock.elapsedRealtime();
|
|
||||||
|
|
||||||
GameViewHolder holder = (GameViewHolder) view.getTag();
|
|
||||||
|
|
||||||
EmulationActivity.launch((FragmentActivity) view.getContext(), holder.path, holder.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the cheats settings for the game that was clicked on.
|
|
||||||
*
|
|
||||||
* @param view The view representing the game the user wants to play.
|
|
||||||
*/
|
|
||||||
private boolean onLongClick(View view) {
|
|
||||||
Context context = view.getContext();
|
|
||||||
GameViewHolder holder = (GameViewHolder) view.getTag();
|
|
||||||
|
|
||||||
final long titleId = NativeLibrary.GetTitleId(holder.path);
|
|
||||||
|
|
||||||
if (titleId == 0) {
|
|
||||||
new MaterialAlertDialogBuilder(context)
|
|
||||||
.setIcon(R.mipmap.ic_launcher)
|
|
||||||
.setTitle(R.string.properties)
|
|
||||||
.setMessage(R.string.properties_not_loaded)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
CheatsActivity.launch(context, titleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidGame(String path) {
|
|
||||||
return Stream.of(
|
|
||||||
".rar", ".zip", ".7z", ".torrent", ".tar", ".gz").noneMatch(suffix -> path.toLowerCase().endsWith(suffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class GameDataSetObserver extends DataSetObserver {
|
|
||||||
@Override
|
|
||||||
public void onChanged() {
|
|
||||||
super.onChanged();
|
|
||||||
|
|
||||||
mDatasetValid = true;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInvalidated() {
|
|
||||||
super.onInvalidated();
|
|
||||||
|
|
||||||
mDatasetValid = false;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import org.citra.citra_emu.HomeNavigationDirections
|
||||||
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import org.citra.citra_emu.activities.EmulationActivity
|
||||||
|
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
|
||||||
|
import org.citra.citra_emu.databinding.CardGameBinding
|
||||||
|
import org.citra.citra_emu.features.cheats.ui.CheatsActivity
|
||||||
|
import org.citra.citra_emu.model.Game
|
||||||
|
import org.citra.citra_emu.utils.GameIconUtils
|
||||||
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
|
|
||||||
|
class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
|
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||||
|
View.OnClickListener, View.OnLongClickListener {
|
||||||
|
private var lastClickTime = 0L
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
|
||||||
|
// Create a new view.
|
||||||
|
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
binding.cardGame.setOnClickListener(this)
|
||||||
|
binding.cardGame.setOnLongClickListener(this)
|
||||||
|
|
||||||
|
// Use that view to create a ViewHolder.
|
||||||
|
return GameViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the game that was clicked on.
|
||||||
|
*
|
||||||
|
* @param view The card representing the game the user wants to play.
|
||||||
|
*/
|
||||||
|
override fun onClick(view: View) {
|
||||||
|
// Double-click prevention, using threshold of 1000 ms
|
||||||
|
if (SystemClock.elapsedRealtime() - lastClickTime < 1000) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastClickTime = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
|
val holder = view.tag as GameViewHolder
|
||||||
|
gameExists(holder)
|
||||||
|
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
preferences.edit()
|
||||||
|
.putLong(
|
||||||
|
holder.game.keyLastPlayedTime,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
|
||||||
|
view.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the cheats settings for the game that was clicked on.
|
||||||
|
*
|
||||||
|
* @param view The view representing the game the user wants to play.
|
||||||
|
*/
|
||||||
|
override fun onLongClick(view: View): Boolean {
|
||||||
|
val context = view.context
|
||||||
|
val holder = view.tag as GameViewHolder
|
||||||
|
gameExists(holder)
|
||||||
|
|
||||||
|
if (holder.game.titleId == 0L) {
|
||||||
|
MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.properties)
|
||||||
|
.setMessage(R.string.properties_not_loaded)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
CheatsActivity.launch(view.context, holder.game.titleId)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggers a library refresh if the user clicks on stale data
|
||||||
|
private fun gameExists(holder: GameViewHolder): Boolean {
|
||||||
|
if (holder.game.isInstalled) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val gameExists = DocumentFile.fromSingleUri(
|
||||||
|
CitraApplication.appContext,
|
||||||
|
Uri.parse(holder.game.path)
|
||||||
|
)?.exists() == true
|
||||||
|
return if (!gameExists) {
|
||||||
|
Toast.makeText(
|
||||||
|
CitraApplication.appContext,
|
||||||
|
R.string.loader_error_file_not_found,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
|
||||||
|
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var game: Game
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.cardGame.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(game: Game) {
|
||||||
|
this.game = game
|
||||||
|
|
||||||
|
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
GameIconUtils.loadGameIcon(activity, game, binding.imageGameScreen)
|
||||||
|
|
||||||
|
binding.textGameTitle.visibility = if (game.title.isEmpty()) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
|
binding.textCompany.visibility = if (game.company.isEmpty()) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.textGameTitle.text = game.title
|
||||||
|
binding.textCompany.text = game.company
|
||||||
|
binding.textFilename.text = game.filename
|
||||||
|
|
||||||
|
val backgroundColorId =
|
||||||
|
if (
|
||||||
|
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
|
||||||
|
) {
|
||||||
|
R.attr.colorSurface
|
||||||
|
} else {
|
||||||
|
R.attr.colorErrorContainer
|
||||||
|
}
|
||||||
|
binding.cardContents.setBackgroundColor(
|
||||||
|
MaterialColors.getColor(
|
||||||
|
binding.cardContents,
|
||||||
|
backgroundColorId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.textGameTitle.postDelayed(
|
||||||
|
{
|
||||||
|
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.textGameTitle.isSelected = true
|
||||||
|
|
||||||
|
binding.textCompany.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.textCompany.isSelected = true
|
||||||
|
|
||||||
|
binding.textFilename.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.textFilename.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidGame(extension: String): Boolean {
|
||||||
|
return Game.badExtensions.stream()
|
||||||
|
.noneMatch { extension == it.lowercase() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
|
return oldItem.titleId == newItem.titleId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import org.citra.citra_emu.databinding.CardHomeOptionBinding
|
||||||
|
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||||
|
import org.citra.citra_emu.model.HomeSetting
|
||||||
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
|
|
||||||
|
class HomeSettingAdapter(
|
||||||
|
private val activity: AppCompatActivity,
|
||||||
|
private val viewLifecycle: LifecycleOwner,
|
||||||
|
var options: List<HomeSetting>
|
||||||
|
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
||||||
|
val binding =
|
||||||
|
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
binding.root.setOnClickListener(this)
|
||||||
|
return HomeOptionViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return options.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
|
||||||
|
holder.bind(options[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(view: View) {
|
||||||
|
val holder = view.tag as HomeOptionViewHolder
|
||||||
|
if (holder.option.isEnabled.invoke()) {
|
||||||
|
holder.option.onClick.invoke()
|
||||||
|
} else {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
holder.option.disabledTitleId,
|
||||||
|
holder.option.disabledMessageId
|
||||||
|
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var option: HomeSetting
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(option: HomeSetting) {
|
||||||
|
this.option = option
|
||||||
|
|
||||||
|
binding.optionTitle.text = activity.resources.getString(option.titleId)
|
||||||
|
binding.optionDescription.text = activity.resources.getString(option.descriptionId)
|
||||||
|
binding.optionIcon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
activity.resources,
|
||||||
|
option.iconId,
|
||||||
|
activity.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
viewLifecycle.lifecycleScope.launch {
|
||||||
|
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
option.details.collect { updateOptionDetails(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.optionDetail.postDelayed(
|
||||||
|
{
|
||||||
|
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.optionDetail.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
|
if (option.isEnabled.invoke()) {
|
||||||
|
binding.optionTitle.alpha = 1f
|
||||||
|
binding.optionDescription.alpha = 1f
|
||||||
|
binding.optionIcon.alpha = 1f
|
||||||
|
} else {
|
||||||
|
binding.optionTitle.alpha = 0.5f
|
||||||
|
binding.optionDescription.alpha = 0.5f
|
||||||
|
binding.optionIcon.alpha = 0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOptionDetails(detailString: String) {
|
||||||
|
if (detailString != "") {
|
||||||
|
binding.optionDetail.text = detailString
|
||||||
|
binding.optionDetail.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.databinding.ListItemSettingBinding
|
||||||
|
import org.citra.citra_emu.fragments.LicenseBottomSheetDialogFragment
|
||||||
|
import org.citra.citra_emu.model.License
|
||||||
|
|
||||||
|
class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
|
||||||
|
RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
|
||||||
|
View.OnClickListener {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
|
||||||
|
val binding =
|
||||||
|
ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
binding.root.setOnClickListener(this)
|
||||||
|
return LicenseViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = licenses.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
|
||||||
|
holder.bind(licenses[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(view: View) {
|
||||||
|
val license = (view.tag as LicenseViewHolder).license
|
||||||
|
LicenseBottomSheetDialogFragment.newInstance(license)
|
||||||
|
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
|
||||||
|
lateinit var license: License
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(license: License) {
|
||||||
|
this.license = license
|
||||||
|
|
||||||
|
val context = CitraApplication.appContext
|
||||||
|
binding.textSettingName.text = context.getString(license.titleId)
|
||||||
|
binding.textSettingDescription.text = context.getString(license.descriptionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.adapters
|
||||||
|
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import org.citra.citra_emu.databinding.PageSetupBinding
|
||||||
|
import org.citra.citra_emu.model.SetupCallback
|
||||||
|
import org.citra.citra_emu.model.SetupPage
|
||||||
|
import org.citra.citra_emu.model.StepState
|
||||||
|
import org.citra.citra_emu.utils.ViewUtils
|
||||||
|
|
||||||
|
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
|
||||||
|
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
|
||||||
|
val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return SetupPageViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = pages.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
|
||||||
|
holder.bind(pages[position])
|
||||||
|
|
||||||
|
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root), SetupCallback {
|
||||||
|
lateinit var page: SetupPage
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(page: SetupPage) {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
|
if (page.stepCompleted.invoke() == StepState.STEP_COMPLETE) {
|
||||||
|
onStepCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.icon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
activity.resources,
|
||||||
|
page.iconId,
|
||||||
|
activity.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.textTitle.text = activity.resources.getString(page.titleId)
|
||||||
|
binding.textDescription.text =
|
||||||
|
Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
|
||||||
|
binding.textDescription.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
|
||||||
|
binding.buttonAction.apply {
|
||||||
|
text = activity.resources.getString(page.buttonTextId)
|
||||||
|
if (page.buttonIconId != 0) {
|
||||||
|
icon = ResourcesCompat.getDrawable(
|
||||||
|
activity.resources,
|
||||||
|
page.buttonIconId,
|
||||||
|
activity.theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconGravity =
|
||||||
|
if (page.leftAlignedIcon) {
|
||||||
|
MaterialButton.ICON_GRAVITY_START
|
||||||
|
} else {
|
||||||
|
MaterialButton.ICON_GRAVITY_END
|
||||||
|
}
|
||||||
|
setOnClickListener {
|
||||||
|
page.buttonAction.invoke(this@SetupPageViewHolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStepCompleted() {
|
||||||
|
ViewUtils.hideView(binding.buttonAction, 200)
|
||||||
|
ViewUtils.showView(binding.textConfirmation, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,13 +18,16 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
@Keep
|
||||||
public final class MiiSelector {
|
public final class MiiSelector {
|
||||||
|
@Keep
|
||||||
public static class MiiSelectorConfig implements java.io.Serializable {
|
public static class MiiSelectorConfig implements java.io.Serializable {
|
||||||
public boolean enable_cancel_button;
|
public boolean enable_cancel_button;
|
||||||
public String title;
|
public String title;
|
||||||
|
@ -7,13 +7,17 @@ package org.citra.citra_emu.applets;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.Keep;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@ -29,6 +33,7 @@ import org.citra.citra_emu.utils.Log;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Keep
|
||||||
public final class SoftwareKeyboard {
|
public final class SoftwareKeyboard {
|
||||||
/// Corresponds to Frontend::ButtonConfig
|
/// Corresponds to Frontend::ButtonConfig
|
||||||
private interface ButtonConfig {
|
private interface ButtonConfig {
|
||||||
@ -57,6 +62,7 @@ public final class SoftwareKeyboard {
|
|||||||
EmptyInputNotAllowed,
|
EmptyInputNotAllowed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
public static class KeyboardConfig implements java.io.Serializable {
|
public static class KeyboardConfig implements java.io.Serializable {
|
||||||
public int button_config;
|
public int button_config;
|
||||||
public int max_text_length;
|
public int max_text_length;
|
||||||
@ -109,20 +115,27 @@ public final class SoftwareKeyboard {
|
|||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
params.leftMargin = params.rightMargin =
|
params.leftMargin = params.rightMargin =
|
||||||
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
CitraApplication.Companion.getAppContext().getResources().getDimensionPixelSize(
|
||||||
R.dimen.dialog_margin);
|
R.dimen.dialog_margin);
|
||||||
|
|
||||||
KeyboardConfig config = Objects.requireNonNull(
|
KeyboardConfig config = Objects.requireNonNull(
|
||||||
(KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config"));
|
(KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config"));
|
||||||
|
|
||||||
// Set up the input
|
// Set up the input
|
||||||
EditText editText = new EditText(CitraApplication.getAppContext());
|
EditText editText = new EditText(CitraApplication.Companion.getAppContext());
|
||||||
editText.setHint(config.hint_text);
|
editText.setHint(config.hint_text);
|
||||||
editText.setSingleLine(!config.multiline_mode);
|
editText.setSingleLine(!config.multiline_mode);
|
||||||
editText.setLayoutParams(params);
|
editText.setLayoutParams(params);
|
||||||
editText.setFilters(new InputFilter[]{
|
editText.setFilters(new InputFilter[]{
|
||||||
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
||||||
|
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
Resources.Theme theme = requireContext().getTheme();
|
||||||
|
theme.resolveAttribute(R.attr.colorOnSurface, typedValue, true);
|
||||||
|
@ColorInt int color = typedValue.data;
|
||||||
|
editText.setHintTextColor(color);
|
||||||
|
editText.setTextColor(color);
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(emulationActivity);
|
FrameLayout container = new FrameLayout(emulationActivity);
|
||||||
container.addView(editText);
|
container.addView(editText);
|
||||||
|
|
||||||
@ -256,7 +269,7 @@ public final class SoftwareKeyboard {
|
|||||||
|
|
||||||
public static void ShowError(String error) {
|
public static void ShowError(String error) {
|
||||||
NativeLibrary.displayAlertMsg(
|
NativeLibrary.displayAlertMsg(
|
||||||
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
CitraApplication.Companion.getAppContext().getResources().getString(R.string.software_keyboard),
|
||||||
error, false);
|
error, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
// Copyright 2020 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
package org.citra.citra_emu.camera;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.NativeLibrary;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
|
||||||
import org.citra.citra_emu.utils.PicassoUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
// Used in native code.
|
|
||||||
public final class StillImageCameraHelper {
|
|
||||||
public static final int REQUEST_CAMERA_FILE_PICKER = 1;
|
|
||||||
private static final Object filePickerLock = new Object();
|
|
||||||
private static @Nullable
|
|
||||||
String filePickerPath;
|
|
||||||
|
|
||||||
// Opens file picker for camera.
|
|
||||||
public static @Nullable
|
|
||||||
String OpenFilePicker() {
|
|
||||||
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
|
||||||
|
|
||||||
// At this point, we are assuming that we already have permissions as they are
|
|
||||||
// needed to launch a game
|
|
||||||
emulationActivity.runOnUiThread(() -> {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK);
|
|
||||||
intent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
|
||||||
emulationActivity.startActivityForResult(
|
|
||||||
Intent.createChooser(intent,
|
|
||||||
emulationActivity.getString(R.string.camera_select_image)),
|
|
||||||
REQUEST_CAMERA_FILE_PICKER);
|
|
||||||
});
|
|
||||||
|
|
||||||
synchronized (filePickerLock) {
|
|
||||||
try {
|
|
||||||
filePickerLock.wait();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePickerPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called from EmulationActivity.
|
|
||||||
public static void OnFilePickerResult(Intent result) {
|
|
||||||
filePickerPath = result == null ? null : result.getDataString();
|
|
||||||
|
|
||||||
synchronized (filePickerLock) {
|
|
||||||
filePickerLock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocking call. Load image from file and crop/resize it to fit in width x height.
|
|
||||||
@Nullable
|
|
||||||
public static Bitmap LoadImageFromFile(String uri, int width, int height) {
|
|
||||||
return PicassoUtils.LoadBitmapFromFile(uri, width, height);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.camera
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import coil.executeBlocking
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
|
|
||||||
|
// Used in native code.
|
||||||
|
object StillImageCameraHelper {
|
||||||
|
private val filePickerLock = Object()
|
||||||
|
private var filePickerPath: String? = null
|
||||||
|
|
||||||
|
// Opens file picker for camera.
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun OpenFilePicker(): String? {
|
||||||
|
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||||
|
|
||||||
|
// At this point, we are assuming that we already have permissions as they are
|
||||||
|
// needed to launch a game
|
||||||
|
emulationActivity!!.runOnUiThread {
|
||||||
|
val request = PickVisualMediaRequest.Builder()
|
||||||
|
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly).build()
|
||||||
|
emulationActivity.openImageLauncher.launch(request)
|
||||||
|
}
|
||||||
|
synchronized(filePickerLock) {
|
||||||
|
try {
|
||||||
|
filePickerLock.wait()
|
||||||
|
} catch (ignored: InterruptedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filePickerPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from EmulationActivity.
|
||||||
|
@JvmStatic
|
||||||
|
fun OnFilePickerResult(result: String) {
|
||||||
|
filePickerPath = result
|
||||||
|
synchronized(filePickerLock) { filePickerLock.notifyAll() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocking call. Load image from file and crop/resize it to fit in width x height.
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
|
||||||
|
val context = CitraApplication.appContext
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(uri)
|
||||||
|
.size(width, height)
|
||||||
|
.build()
|
||||||
|
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,91 +0,0 @@
|
|||||||
package org.citra.citra_emu.dialogs;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
import org.citra.citra_emu.utils.FileUtil;
|
|
||||||
import org.citra.citra_emu.utils.PermissionsHandler;
|
|
||||||
|
|
||||||
public class CitraDirectoryDialog extends DialogFragment {
|
|
||||||
public static final String TAG = "citra_directory_dialog_fragment";
|
|
||||||
|
|
||||||
private static final String MOVE_DATE_ENABLE = "IS_MODE_DATA_ENABLE";
|
|
||||||
|
|
||||||
TextView pathView;
|
|
||||||
|
|
||||||
TextView spaceView;
|
|
||||||
|
|
||||||
CheckBox checkBox;
|
|
||||||
|
|
||||||
AlertDialog dialog;
|
|
||||||
|
|
||||||
Listener listener;
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void onPressPositiveButton(boolean moveData, Uri path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CitraDirectoryDialog newInstance(String path, Listener listener) {
|
|
||||||
CitraDirectoryDialog frag = new CitraDirectoryDialog();
|
|
||||||
frag.listener = listener;
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("path", path);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
final FragmentActivity activity = requireActivity();
|
|
||||||
final Uri path = Uri.parse(Objects.requireNonNull(requireArguments().getString("path")));
|
|
||||||
SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
|
||||||
String freeSpaceText =
|
|
||||||
getResources().getString(R.string.free_space, FileUtil.getFreeSpace(activity, path));
|
|
||||||
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
View view = inflater.inflate(R.layout.dialog_citra_directory, null);
|
|
||||||
|
|
||||||
checkBox = view.findViewById(R.id.checkBox);
|
|
||||||
pathView = view.findViewById(R.id.path);
|
|
||||||
spaceView = view.findViewById(R.id.space);
|
|
||||||
|
|
||||||
checkBox.setChecked(mPreferences.getBoolean(MOVE_DATE_ENABLE, true));
|
|
||||||
if (!PermissionsHandler.hasWriteAccess(activity)) {
|
|
||||||
checkBox.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
checkBox.setOnCheckedChangeListener(
|
|
||||||
(v, isChecked)
|
|
||||||
// record move data selection with SharedPreferences
|
|
||||||
-> mPreferences.edit().putBoolean(MOVE_DATE_ENABLE, checkBox.isChecked()).apply());
|
|
||||||
|
|
||||||
pathView.setText(path.getPath());
|
|
||||||
spaceView.setText(freeSpaceText);
|
|
||||||
|
|
||||||
setCancelable(false);
|
|
||||||
|
|
||||||
dialog = new MaterialAlertDialogBuilder(activity)
|
|
||||||
.setView(view)
|
|
||||||
.setIcon(R.mipmap.ic_launcher)
|
|
||||||
.setTitle(R.string.app_name)
|
|
||||||
.setPositiveButton(
|
|
||||||
android.R.string.ok,
|
|
||||||
(d, v) -> listener.onPressPositiveButton(checkBox.isChecked(), path))
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create();
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package org.citra.citra_emu.dialogs;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
|
|
||||||
public class CopyDirProgressDialog extends DialogFragment {
|
|
||||||
public static final String TAG = "copy_dir_progress_dialog";
|
|
||||||
ProgressBar progressBar;
|
|
||||||
|
|
||||||
TextView progressText;
|
|
||||||
|
|
||||||
AlertDialog dialog;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
final FragmentActivity activity = requireActivity();
|
|
||||||
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
View view = inflater.inflate(R.layout.dialog_progress_bar, null);
|
|
||||||
|
|
||||||
progressBar = view.findViewById(R.id.progress_bar);
|
|
||||||
progressText = view.findViewById(R.id.progress_text);
|
|
||||||
progressText.setText("");
|
|
||||||
|
|
||||||
setCancelable(false);
|
|
||||||
|
|
||||||
dialog = new MaterialAlertDialogBuilder(activity)
|
|
||||||
.setView(view)
|
|
||||||
.setIcon(R.mipmap.ic_launcher)
|
|
||||||
.setTitle(R.string.move_data)
|
|
||||||
.setMessage("")
|
|
||||||
.create();
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpdateSearchProgress(String msg) {
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
dialog.setMessage(getResources().getString(R.string.searching_direcotry, msg));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpdateCopyProgress(String msg, int progress, int max) {
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
progressBar.setProgress(progress);
|
|
||||||
progressBar.setMax(max);
|
|
||||||
progressText.setText(String.format("%d/%d", progress, max));
|
|
||||||
dialog.setMessage(getResources().getString(R.string.copy_file_name, msg));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
package org.citra.citra_emu.dialogs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting;
|
|
||||||
import org.citra.citra_emu.utils.Log;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AlertDialog} derivative that listens for
|
|
||||||
* motion events from controllers and joysticks.
|
|
||||||
*/
|
|
||||||
public final class MotionAlertDialog extends AlertDialog {
|
|
||||||
// The selected input preference
|
|
||||||
private final InputBindingSetting setting;
|
|
||||||
private final ArrayList<Float> mPreviousValues = new ArrayList<>();
|
|
||||||
private int mPrevDeviceId = 0;
|
|
||||||
private boolean mWaitingForEvent = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param context The current {@link Context}.
|
|
||||||
* @param setting The Preference to show this dialog for.
|
|
||||||
*/
|
|
||||||
public MotionAlertDialog(Context context, InputBindingSetting setting) {
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
this.setting = setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onKeyEvent(int keyCode, KeyEvent event) {
|
|
||||||
Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case KeyEvent.ACTION_UP:
|
|
||||||
setting.onKeyInput(event);
|
|
||||||
dismiss();
|
|
||||||
// Even if we ignore the key, we still consume it. Thus return true regardless.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
|
|
||||||
return super.onKeyLongPress(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
||||||
// Handle this key if we care about it, otherwise pass it down the framework
|
|
||||||
return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) {
|
|
||||||
// Handle this event if we care about it, otherwise pass it down the framework
|
|
||||||
return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onMotionEvent(MotionEvent event) {
|
|
||||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
|
|
||||||
return false;
|
|
||||||
if (event.getAction() != MotionEvent.ACTION_MOVE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
InputDevice input = event.getDevice();
|
|
||||||
|
|
||||||
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
|
|
||||||
|
|
||||||
if (input.getId() != mPrevDeviceId) {
|
|
||||||
mPreviousValues.clear();
|
|
||||||
}
|
|
||||||
mPrevDeviceId = input.getId();
|
|
||||||
boolean firstEvent = mPreviousValues.isEmpty();
|
|
||||||
|
|
||||||
int numMovedAxis = 0;
|
|
||||||
float axisMoveValue = 0.0f;
|
|
||||||
InputDevice.MotionRange lastMovedRange = null;
|
|
||||||
char lastMovedDir = '?';
|
|
||||||
if (mWaitingForEvent) {
|
|
||||||
for (int i = 0; i < motionRanges.size(); i++) {
|
|
||||||
InputDevice.MotionRange range = motionRanges.get(i);
|
|
||||||
int axis = range.getAxis();
|
|
||||||
float origValue = event.getAxisValue(axis);
|
|
||||||
float value = origValue;//ControllerMappingHelper.scaleAxis(input, axis, origValue);
|
|
||||||
if (firstEvent) {
|
|
||||||
mPreviousValues.add(value);
|
|
||||||
} else {
|
|
||||||
float previousValue = mPreviousValues.get(i);
|
|
||||||
|
|
||||||
// Only handle the axes that are not neutral (more than 0.5)
|
|
||||||
// but ignore any axis that has a constant value (e.g. always 1)
|
|
||||||
if (Math.abs(value) > 0.5f && value != previousValue) {
|
|
||||||
// It is common to have multiple axes with the same physical input. For example,
|
|
||||||
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
|
|
||||||
// To handle this, we ignore an axis motion that's the exact same as a motion
|
|
||||||
// we already saw. This way, we ignore axes with two names, but catch the case
|
|
||||||
// where a joystick is moved in two directions.
|
|
||||||
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
|
|
||||||
if (value != axisMoveValue) {
|
|
||||||
axisMoveValue = value;
|
|
||||||
numMovedAxis++;
|
|
||||||
lastMovedRange = range;
|
|
||||||
lastMovedDir = value < 0.0f ? '-' : '+';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Special case for d-pads (axis value jumps between 0 and 1 without any values
|
|
||||||
// in between). Without this, the user would need to press the d-pad twice
|
|
||||||
// due to the first press being caught by the "if (firstEvent)" case further up.
|
|
||||||
else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f) {
|
|
||||||
numMovedAxis++;
|
|
||||||
lastMovedRange = range;
|
|
||||||
lastMovedDir = previousValue < 0.0f ? '-' : '+';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mPreviousValues.set(i, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If only one axis moved, that's the winner.
|
|
||||||
if (numMovedAxis == 1) {
|
|
||||||
mWaitingForEvent = false;
|
|
||||||
setting.onMotionInput(input, lastMovedRange, lastMovedDir);
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,8 +51,7 @@ public class CheatsActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
ThemeUtil.applyTheme(this);
|
ThemeUtil.INSTANCE.setTheme(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
interface AbstractBooleanSetting : AbstractSetting {
|
||||||
|
var boolean: Boolean
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
interface AbstractFloatSetting : AbstractSetting {
|
||||||
|
var float: Float
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
interface AbstractIntSetting : AbstractSetting {
|
||||||
|
var int: Int
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
interface AbstractSetting {
|
||||||
|
val key: String?
|
||||||
|
val section: String?
|
||||||
|
val isRuntimeEditable: Boolean
|
||||||
|
val valueAsString: String
|
||||||
|
val defaultValue: Any
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
interface AbstractStringSetting : AbstractSetting {
|
||||||
|
var string: String
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
public final class BooleanSetting extends Setting {
|
|
||||||
private boolean mValue;
|
|
||||||
|
|
||||||
public BooleanSetting(String key, String section, boolean value) {
|
|
||||||
super(key, section);
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getValue() {
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(boolean value) {
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValueAsString() {
|
|
||||||
return mValue ? "True" : "False";
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
enum class BooleanSetting(
|
||||||
|
override val key: String,
|
||||||
|
override val section: String,
|
||||||
|
override val defaultValue: Boolean
|
||||||
|
) : AbstractBooleanSetting {
|
||||||
|
SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
|
||||||
|
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
|
||||||
|
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
|
||||||
|
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true);
|
||||||
|
|
||||||
|
override var boolean: Boolean = defaultValue
|
||||||
|
|
||||||
|
override val valueAsString: String
|
||||||
|
get() = boolean.toString()
|
||||||
|
|
||||||
|
override val isRuntimeEditable: Boolean
|
||||||
|
get() {
|
||||||
|
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||||
|
if (setting == this) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||||
|
PLUGIN_LOADER,
|
||||||
|
ALLOW_PLUGIN_LOADER
|
||||||
|
)
|
||||||
|
|
||||||
|
fun from(key: String): BooleanSetting? =
|
||||||
|
BooleanSetting.values().firstOrNull { it.key == key }
|
||||||
|
|
||||||
|
fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
public final class FloatSetting extends Setting {
|
|
||||||
private float mValue;
|
|
||||||
|
|
||||||
public FloatSetting(String key, String section, float value) {
|
|
||||||
super(key, section);
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getValue() {
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(float value) {
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValueAsString() {
|
|
||||||
return Float.toString(mValue);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
enum class FloatSetting(
|
||||||
|
override val key: String,
|
||||||
|
override val section: String,
|
||||||
|
override val defaultValue: Float
|
||||||
|
) : AbstractFloatSetting {
|
||||||
|
// There are no float settings currently
|
||||||
|
EMPTY_SETTING("", "", 0.0f);
|
||||||
|
|
||||||
|
override var float: Float = defaultValue
|
||||||
|
|
||||||
|
override val valueAsString: String
|
||||||
|
get() = float.toString()
|
||||||
|
|
||||||
|
override val isRuntimeEditable: Boolean
|
||||||
|
get() {
|
||||||
|
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||||
|
if (setting == this) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
|
||||||
|
|
||||||
|
fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
|
||||||
|
|
||||||
|
fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
public final class IntSetting extends Setting {
|
|
||||||
private int mValue;
|
|
||||||
|
|
||||||
public IntSetting(String key, String section, int value) {
|
|
||||||
super(key, section);
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue() {
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(int value) {
|
|
||||||
mValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValueAsString() {
|
|
||||||
return Integer.toString(mValue);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
enum class IntSetting(
|
||||||
|
override val key: String,
|
||||||
|
override val section: String,
|
||||||
|
override val defaultValue: Int
|
||||||
|
) : AbstractIntSetting {
|
||||||
|
FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
|
||||||
|
EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
|
||||||
|
INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
|
||||||
|
CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
|
||||||
|
CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
|
||||||
|
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
|
||||||
|
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
|
||||||
|
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
|
||||||
|
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0),
|
||||||
|
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
|
||||||
|
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
|
||||||
|
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
|
||||||
|
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
|
||||||
|
AUDIO_INPUT_TYPE("output_type", Settings.SECTION_AUDIO, 0),
|
||||||
|
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1),
|
||||||
|
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
|
||||||
|
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, 1),
|
||||||
|
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, 0),
|
||||||
|
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, 1),
|
||||||
|
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, 0),
|
||||||
|
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, 0),
|
||||||
|
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, 1),
|
||||||
|
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, 0),
|
||||||
|
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, 1),
|
||||||
|
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, 1),
|
||||||
|
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, 1),
|
||||||
|
VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1),
|
||||||
|
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0),
|
||||||
|
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
|
||||||
|
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1);
|
||||||
|
|
||||||
|
override var int: Int = defaultValue
|
||||||
|
|
||||||
|
override val valueAsString: String
|
||||||
|
get() = int.toString()
|
||||||
|
|
||||||
|
override val isRuntimeEditable: Boolean
|
||||||
|
get() {
|
||||||
|
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||||
|
if (setting == this) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||||
|
EMULATED_REGION,
|
||||||
|
INIT_CLOCK,
|
||||||
|
NEW_3DS,
|
||||||
|
GRAPHICS_API,
|
||||||
|
VSYNC,
|
||||||
|
DEBUG_RENDERER,
|
||||||
|
CPU_JIT,
|
||||||
|
ASYNC_CUSTOM_LOADING,
|
||||||
|
AUDIO_INPUT_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
|
||||||
|
|
||||||
|
fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
enum class ScaledFloatSetting(
|
||||||
|
override val key: String,
|
||||||
|
override val section: String,
|
||||||
|
override val defaultValue: Float,
|
||||||
|
val scale: Int
|
||||||
|
) : AbstractFloatSetting {
|
||||||
|
AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
|
||||||
|
|
||||||
|
override var float: Float = defaultValue
|
||||||
|
get() = field * scale
|
||||||
|
set(value) {
|
||||||
|
field = value / scale
|
||||||
|
}
|
||||||
|
|
||||||
|
override val valueAsString: String get() = (float / scale).toString()
|
||||||
|
|
||||||
|
override val isRuntimeEditable: Boolean
|
||||||
|
get() {
|
||||||
|
for (setting in NOT_RUNTIME_EDITABLE) {
|
||||||
|
if (setting == this) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NOT_RUNTIME_EDITABLE = emptyList<ScaledFloatSetting>()
|
||||||
|
|
||||||
|
fun from(key: String): ScaledFloatSetting? =
|
||||||
|
ScaledFloatSetting.values().firstOrNull { it.key == key }
|
||||||
|
|
||||||
|
fun clear() = ScaledFloatSetting.values().forEach { it.float = it.defaultValue * it.scale }
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for a setting item as read from / written to Citra's configuration ini files.
|
|
||||||
* These files generally consist of a key/value pair, though the type of value is ambiguous and
|
|
||||||
* must be inferred at read-time. The type of value determines which child of this class is used
|
|
||||||
* to represent the Setting.
|
|
||||||
*/
|
|
||||||
public abstract class Setting {
|
|
||||||
private String mKey;
|
|
||||||
private String mSection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base constructor.
|
|
||||||
*
|
|
||||||
* @param key Everything to the left of the = in a line from the ini file.
|
|
||||||
* @param section The corresponding recent section header; e.g. [Core] or [Enhancements] without the brackets.
|
|
||||||
*/
|
|
||||||
public Setting(String key, String section) {
|
|
||||||
mKey = key;
|
|
||||||
mSection = section;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The identifier used to write this setting to the ini file.
|
|
||||||
*/
|
|
||||||
public String getKey() {
|
|
||||||
return mKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The name of the header under which this Setting should be written in the ini file.
|
|
||||||
*/
|
|
||||||
public String getSection() {
|
|
||||||
return mSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A representation of this Setting's backing value converted to a String (e.g. for serialization).
|
|
||||||
*/
|
|
||||||
public abstract String getValueAsString();
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A semantically-related group of Settings objects. These Settings are
|
|
||||||
* internally stored as a HashMap.
|
|
||||||
*/
|
|
||||||
public final class SettingSection {
|
|
||||||
private String mName;
|
|
||||||
|
|
||||||
private HashMap<String, Setting> mSettings = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new SettingSection with no Settings in it.
|
|
||||||
*
|
|
||||||
* @param name The header of this section; e.g. [Core] or [Enhancements] without the brackets.
|
|
||||||
*/
|
|
||||||
public SettingSection(String name) {
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method; inserts a value directly into the backing HashMap.
|
|
||||||
*
|
|
||||||
* @param setting The Setting to be inserted.
|
|
||||||
*/
|
|
||||||
public void putSetting(Setting setting) {
|
|
||||||
mSettings.put(setting.getKey(), setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method; gets a value directly from the backing HashMap.
|
|
||||||
*
|
|
||||||
* @param key Used to retrieve the Setting.
|
|
||||||
* @return A Setting object (you should probably cast this before using)
|
|
||||||
*/
|
|
||||||
public Setting getSetting(String key) {
|
|
||||||
return mSettings.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Setting> getSettings() {
|
|
||||||
return mSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mergeSection(SettingSection settingSection) {
|
|
||||||
for (Setting setting : settingSection.mSettings.values()) {
|
|
||||||
putSetting(setting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.features.settings.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A semantically-related group of Settings objects. These Settings are
|
||||||
|
* internally stored as a HashMap.
|
||||||
|
*/
|
||||||
|
class SettingSection(val name: String) {
|
||||||
|
val settings = HashMap<String, AbstractSetting>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method; inserts a value directly into the backing HashMap.
|
||||||
|
*
|
||||||
|
* @param setting The Setting to be inserted.
|
||||||
|
*/
|
||||||
|
fun putSetting(setting: AbstractSetting) {
|
||||||
|
settings[setting.key!!] = setting
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method; gets a value directly from the backing HashMap.
|
||||||
|
*
|
||||||
|
* @param key Used to retrieve the Setting.
|
||||||
|
* @return A Setting object (you should probably cast this before using)
|
||||||
|
*/
|
||||||
|
fun getSetting(key: String): AbstractSetting? {
|
||||||
|
return settings[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mergeSection(settingSection: SettingSection) {
|
||||||
|
for (setting in settingSection.settings.values) {
|
||||||
|
putSetting(setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,132 +0,0 @@
|
|||||||
package org.citra.citra_emu.features.settings.model;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.citra.citra_emu.CitraApplication;
|
|
||||||
import org.citra.citra_emu.R;
|
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivityView;
|
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
public class Settings {
|
|
||||||
public static final String SECTION_PREMIUM = "Premium";
|
|
||||||
public static final String SECTION_CORE = "Core";
|
|
||||||
public static final String SECTION_SYSTEM = "System";
|
|
||||||
public static final String SECTION_CAMERA = "Camera";
|
|
||||||
public static final String SECTION_CONTROLS = "Controls";
|
|
||||||
public static final String SECTION_RENDERER = "Renderer";
|
|
||||||
public static final String SECTION_LAYOUT = "Layout";
|
|
||||||
public static final String SECTION_UTILITY = "Utility";
|
|
||||||
public static final String SECTION_AUDIO = "Audio";
|
|
||||||
public static final String SECTION_DEBUG = "Debug";
|
|
||||||
|
|
||||||
private String gameId;
|
|
||||||
|
|
||||||
private static final Map<String, List<String>> configFileSectionsMap = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
configFileSectionsMap.put(SettingsFile.FILE_NAME_CONFIG, Arrays.asList(SECTION_PREMIUM, SECTION_CORE, SECTION_SYSTEM, SECTION_CAMERA, SECTION_CONTROLS, SECTION_RENDERER, SECTION_LAYOUT, SECTION_UTILITY, SECTION_AUDIO, SECTION_DEBUG));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A HashMap<String, SettingSection> that constructs a new SettingSection instead of returning null
|
|
||||||
* when getting a key not already in the map
|
|
||||||
*/
|
|
||||||
public static final class SettingsSectionMap extends HashMap<String, SettingSection> {
|
|
||||||
@Override
|
|
||||||
public SettingSection get(Object key) {
|
|
||||||
if (!(key instanceof String)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String stringKey = (String) key;
|
|
||||||
|
|
||||||
if (!super.containsKey(stringKey)) {
|
|
||||||
SettingSection section = new SettingSection(stringKey);
|
|
||||||
super.put(stringKey, section);
|
|
||||||
return section;
|
|
||||||
}
|
|
||||||
return super.get(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HashMap<String, SettingSection> sections = new Settings.SettingsSectionMap();
|
|
||||||
|
|
||||||
public SettingSection getSection(String sectionName) {
|
|
||||||
return sections.get(sectionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return sections.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, SettingSection> getSections() {
|
|
||||||
return sections;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadSettings(SettingsActivityView view) {
|
|
||||||
sections = new Settings.SettingsSectionMap();
|
|
||||||
loadCitraSettings(view);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(gameId)) {
|
|
||||||
loadCustomGameSettings(gameId, view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadCitraSettings(SettingsActivityView view) {
|
|
||||||
for (Map.Entry<String, List<String>> entry : configFileSectionsMap.entrySet()) {
|
|
||||||
String fileName = entry.getKey();
|
|
||||||
sections.putAll(SettingsFile.readFile(fileName, view));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadCustomGameSettings(String gameId, SettingsActivityView view) {
|
|
||||||
// custom game settings
|
|
||||||
mergeSections(SettingsFile.readCustomGameSettings(gameId, view));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mergeSections(HashMap<String, SettingSection> updatedSections) {
|
|
||||||
for (Map.Entry<String, SettingSection> entry : updatedSections.entrySet()) {
|
|
||||||
if (sections.containsKey(entry.getKey())) {
|
|
||||||
SettingSection originalSection = sections.get(entry.getKey());
|
|
||||||
SettingSection updatedSection = entry.getValue();
|
|
||||||
originalSection.mergeSection(updatedSection);
|
|
||||||
} else {
|
|
||||||
sections.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadSettings(String gameId, SettingsActivityView view) {
|
|
||||||
this.gameId = gameId;
|
|
||||||
loadSettings(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveSettings(SettingsActivityView view) {
|
|
||||||
if (TextUtils.isEmpty(gameId)) {
|
|
||||||
view.showToastMessage(CitraApplication.getAppContext().getString(R.string.ini_saved), false);
|
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> entry : configFileSectionsMap.entrySet()) {
|
|
||||||
String fileName = entry.getKey();
|
|
||||||
List<String> sectionNames = entry.getValue();
|
|
||||||
TreeMap<String, SettingSection> iniSections = new TreeMap<>();
|
|
||||||
for (String section : sectionNames) {
|
|
||||||
iniSections.put(section, sections.get(section));
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsFile.saveFile(fileName, iniSections, view);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// custom game settings
|
|
||||||
view.showToastMessage(CitraApplication.getAppContext().getString(R.string.gameid_saved, gameId), false);
|
|
||||||
|
|
||||||
SettingsFile.saveCustomGameSettings(gameId, sections);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user