Compare commits
263 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa89ec9214 | ||
|
|
e146a00345 | ||
|
|
a10bdaf934 | ||
|
|
9252ad4e10 | ||
|
|
596a6132b9 | ||
|
|
45be4c3214 | ||
|
|
5e70db0d43 | ||
|
|
4c705db73e | ||
|
|
7de778ad39 | ||
|
|
13506e7782 | ||
|
|
b62121fd60 | ||
|
|
ac755476cd | ||
|
|
217e958a95 | ||
|
|
ed93cbd462 | ||
|
|
b76b698c17 | ||
|
|
b60b70e86d | ||
|
|
df9685a21c | ||
|
|
295fc7d0f8 | ||
|
|
2b68a3cbbf | ||
|
|
3d868baaa4 | ||
|
|
4303ed614d | ||
|
|
ddcc958336 | ||
|
|
eac46ad7ce | ||
|
|
b8c96cee5f | ||
|
|
220a42896d | ||
|
|
1522b95658 | ||
|
|
c339af37a7 | ||
|
|
a9b44d37e1 | ||
|
|
72e7f5b4dd | ||
|
|
0fe44071f8 | ||
|
|
e0d406042c | ||
|
|
0b2798be27 | ||
|
|
21675c9b68 | ||
|
|
e3c548d081 | ||
|
|
32475efbc4 | ||
|
|
bd4fefe5e7 | ||
|
|
30544fbfa5 | ||
|
|
33cd3a0db0 | ||
|
|
112b660456 | ||
|
|
2b3bfafb9e | ||
|
|
c6959449d1 | ||
|
|
20111c86b6 | ||
|
|
f254ce2c60 | ||
|
|
dafbc86366 | ||
|
|
02882d9251 | ||
|
|
9074a70b01 | ||
|
|
2ed8b3cbb9 | ||
|
|
b82c649b0f | ||
|
|
ce990adae5 | ||
|
|
c280a2fe1c | ||
|
|
28df6ac0aa | ||
|
|
0030fa9721 | ||
|
|
0f31039831 | ||
|
|
b6c6dcc576 | ||
|
|
ffbaf574ca | ||
|
|
4f160633d3 | ||
|
|
405eae3734 | ||
|
|
b198339580 | ||
|
|
82107b33a2 | ||
|
|
f5569bfed9 | ||
|
|
b6e89bdf2c | ||
|
|
b6025cf62f | ||
|
|
bf641e2964 | ||
|
|
5aa208e264 | ||
|
|
ec9a71b12a | ||
|
|
474fa13a1a | ||
|
|
482fbded9b | ||
|
|
e5769e9467 | ||
|
|
3a991f3aef | ||
|
|
142c1b72f9 | ||
|
|
b53945a99f | ||
|
|
a58a1403ba | ||
|
|
5ab4aa1edb | ||
|
|
a674022434 | ||
|
|
87b9b5d10f | ||
|
|
575d467d95 | ||
|
|
1dd166f766 | ||
|
|
2fc5dedf69 | ||
|
|
3f3e4efb30 | ||
|
|
5558fc4aa5 | ||
|
|
7f12c6159f | ||
|
|
8e9c6429b0 | ||
|
|
dfa0e9479d | ||
|
|
c133509368 | ||
|
|
75fb29e08e | ||
|
|
1586f1c0b1 | ||
|
|
5da70f7197 | ||
|
|
e3122c5b46 | ||
|
|
7eb7d56b1b | ||
|
|
8cb6b33809 | ||
|
|
8ad64bc253 | ||
|
|
84d43489c5 | ||
|
|
106b61b1e0 | ||
|
|
1a85d8804a | ||
|
|
6c7e284f64 | ||
|
|
8b841aa7ba | ||
|
|
699e78c666 | ||
|
|
cfc6ef42d9 | ||
|
|
e35371e50c | ||
|
|
e31152ee34 | ||
|
|
eea2145698 | ||
|
|
e684515578 | ||
|
|
ae1a8a7dc7 | ||
|
|
fd5d7947f6 | ||
|
|
a67bdeb2c2 | ||
|
|
f1e12e3b08 | ||
|
|
93061d1ea1 | ||
|
|
6d12e7320b | ||
|
|
78ff2862f6 | ||
|
|
197e13d93d | ||
|
|
bedb5135c0 | ||
|
|
256c7ec0a7 | ||
|
|
e5f1b22e16 | ||
|
|
b0beca52a3 | ||
|
|
711190bb67 | ||
|
|
b9a86b040b | ||
|
|
346c253cd2 | ||
|
|
ce191ba32b | ||
|
|
23371fa187 | ||
|
|
af7f3f078c | ||
|
|
66b8042b59 | ||
|
|
8acf728d5d | ||
|
|
ee0d68300e | ||
|
|
7b2f680468 | ||
|
|
48e39756f1 | ||
|
|
c60eed36b7 | ||
|
|
6448eade2e | ||
|
|
6e293be20b | ||
|
|
20db91f0fc | ||
|
|
8a526b2c26 | ||
|
|
565a1226d7 | ||
|
|
fd0ef5411c | ||
|
|
b99c4dd568 | ||
|
|
c0fd793ef6 | ||
|
|
27a36cd51b | ||
|
|
e681f5678c | ||
|
|
e34e1b1c95 | ||
|
|
76a676883a | ||
|
|
3e47ebe2e9 | ||
|
|
c309a1c69b | ||
|
|
4cbdce17b6 | ||
|
|
ec423c6919 | ||
|
|
24e1e4dcee | ||
|
|
975122f4bb | ||
|
|
a1adcc31d3 | ||
|
|
94e7cb05da | ||
|
|
9f92104f3e | ||
|
|
330358cd16 | ||
|
|
fc6a2fe779 | ||
|
|
bf47f777b1 | ||
|
|
734242c5bc | ||
|
|
13a4de647d | ||
|
|
3ac2c74e85 | ||
|
|
5aca03d0ff | ||
|
|
4112031c81 | ||
|
|
c89be0dfab | ||
|
|
b77a247e8c | ||
|
|
2dc0ff79ec | ||
|
|
1fa16bc594 | ||
|
|
d9e2824c4e | ||
|
|
d35c989902 | ||
|
|
7ffb96f474 | ||
|
|
e9701a3cda | ||
|
|
b23c358e3d | ||
|
|
cdc73498e3 | ||
|
|
c7fc5b9348 | ||
|
|
41295ff8fe | ||
|
|
9dd52019a9 | ||
|
|
8d8f850bd6 | ||
|
|
03e8d9aca7 | ||
|
|
d0be850f25 | ||
|
|
d85129aa17 | ||
|
|
a29fa119e0 | ||
|
|
9a04793ae8 | ||
|
|
0114abad9a | ||
|
|
ca1cb9fd19 | ||
|
|
0c90a0926f | ||
|
|
5384fa4998 | ||
|
|
3c217a5574 | ||
|
|
a3b7b5b22a | ||
|
|
745d16132b | ||
|
|
3e8cd91d54 | ||
|
|
3304d58edb | ||
|
|
42c944b250 | ||
|
|
2c01669046 | ||
|
|
ce0510913a | ||
|
|
61b4588517 | ||
|
|
b1b13ddc6b | ||
|
|
fb97aec26b | ||
|
|
702a2ac631 | ||
|
|
0869099da4 | ||
|
|
0e957c2e35 | ||
|
|
e20c4fbbd4 | ||
|
|
f34535f362 | ||
|
|
724823c193 | ||
|
|
0b442b6dd2 | ||
|
|
2b5dde162a | ||
|
|
fb28f9fd96 | ||
|
|
eb4026e3db | ||
|
|
0ef93541b4 | ||
|
|
de9100ea81 | ||
|
|
a10a091928 | ||
|
|
b79c993328 | ||
|
|
8d6aefdcc4 | ||
|
|
278336af63 | ||
|
|
cc4334870b | ||
|
|
1cae01f5d5 | ||
|
|
0de6b9e3f5 | ||
|
|
a9e4dddad5 | ||
|
|
44f616edb9 | ||
|
|
ed7c4af915 | ||
|
|
dbbe237668 | ||
|
|
698a3eda50 | ||
|
|
190eed8199 | ||
|
|
b9bba3ac89 | ||
|
|
0644c9d6cb | ||
|
|
14d25e2c75 | ||
|
|
0e7eaaba5a | ||
|
|
f25236a4d6 | ||
|
|
322ac1c20c | ||
|
|
925586f97b | ||
|
|
e2f6199225 | ||
|
|
8506915208 | ||
|
|
0eacf547c0 | ||
|
|
e0de6dd63f | ||
|
|
bcdd35e8be | ||
|
|
f8a33f85ef | ||
|
|
306ad012c8 | ||
|
|
f9197f4dae | ||
|
|
9bee930045 | ||
|
|
0c04e27df3 | ||
|
|
a338de7850 | ||
|
|
8e3d4e3396 | ||
|
|
05b66877d1 | ||
|
|
3e6d81a008 | ||
|
|
2e1e725443 | ||
|
|
907507886d | ||
|
|
9dcc7bde8b | ||
|
|
8e56a84566 | ||
|
|
bbd502f67a | ||
|
|
1492a65454 | ||
|
|
dd12dd4c67 | ||
|
|
013c34cb32 | ||
|
|
f9fc996083 | ||
|
|
fc0c4db20d | ||
|
|
8f9afbcd91 | ||
|
|
3218313c22 | ||
|
|
78a47f1ee8 | ||
|
|
dea61f5d00 | ||
|
|
63c51abe42 | ||
|
|
de1fe66d81 | ||
|
|
84642bdd3f | ||
|
|
73036c83a3 | ||
|
|
a40e0fdf9e | ||
|
|
8d52dc163a | ||
|
|
5d9dd88387 | ||
|
|
3979c7daa4 | ||
|
|
011438fa95 | ||
|
|
a39b9134db | ||
|
|
3e68a284ae | ||
|
|
9e2164be74 | ||
|
|
c378cbbc2d | ||
|
|
a84ad180e8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,6 +26,8 @@ CMakeSettings.json
|
||||
# OSX global filetypes
|
||||
# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
.Spotlight-V100
|
||||
|
||||
46
.gitmodules
vendored
46
.gitmodules
vendored
@@ -2,35 +2,35 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
[submodule "enet"]
|
||||
path = externals/enet
|
||||
url = https://github.com/lsalzman/enet.git
|
||||
path = externals/enet
|
||||
url = https://github.com/lsalzman/enet.git
|
||||
[submodule "inih"]
|
||||
path = externals/inih/inih
|
||||
url = https://github.com/benhoyt/inih.git
|
||||
path = externals/inih/inih
|
||||
url = https://github.com/benhoyt/inih.git
|
||||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/mozilla/cubeb.git
|
||||
path = externals/cubeb
|
||||
url = https://github.com/mozilla/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/MerryMage/dynarmic.git
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/merryhime/dynarmic.git
|
||||
[submodule "libusb"]
|
||||
path = externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/yuzu-emu/discord-rpc.git
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/yuzu-emu/discord-rpc.git
|
||||
[submodule "Vulkan-Headers"]
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/yuzu-emu/sirit
|
||||
path = externals/sirit
|
||||
url = https://github.com/yuzu-emu/sirit.git
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/yuzu-emu/mbedtls
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/yuzu-emu/mbedtls.git
|
||||
[submodule "xbyak"]
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "opus"]
|
||||
path = externals/opus/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
@@ -45,10 +45,16 @@
|
||||
url = https://github.com/FFmpeg/FFmpeg.git
|
||||
[submodule "vcpkg"]
|
||||
path = externals/vcpkg
|
||||
url = https://github.com/Microsoft/vcpkg.git
|
||||
url = https://github.com/microsoft/vcpkg.git
|
||||
[submodule "cpp-jwt"]
|
||||
path = externals/cpp-jwt
|
||||
url = https://github.com/arun11299/cpp-jwt.git
|
||||
[submodule "libadrenotools"]
|
||||
path = externals/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools.git
|
||||
[submodule "tzdb_to_nx"]
|
||||
path = externals/nx_tzdb/tzdb_to_nx
|
||||
url = https://github.com/lat9nq/tzdb_to_nx.git
|
||||
[submodule "VulkanMemoryAllocator"]
|
||||
path = externals/vma/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
|
||||
@@ -59,6 +59,8 @@ option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
|
||||
|
||||
option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
|
||||
|
||||
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
||||
|
||||
# On Android, fetch and compile libcxx before doing anything else
|
||||
@@ -487,7 +489,7 @@ if (ENABLE_SDL2)
|
||||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.0.18")
|
||||
set(SDL2_VER "SDL2-2.28.0")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
@@ -507,7 +509,7 @@ if (ENABLE_SDL2)
|
||||
elseif (YUZU_USE_EXTERNAL_SDL2)
|
||||
message(STATUS "Using SDL2 from externals.")
|
||||
else()
|
||||
find_package(SDL2 2.0.18 REQUIRED)
|
||||
find_package(SDL2 2.26.4 REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
14
externals/CMakeLists.txt
vendored
14
externals/CMakeLists.txt
vendored
@@ -63,8 +63,9 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
|
||||
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
|
||||
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
|
||||
# CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
|
||||
set(SDL_UNUSED_SUBSYSTEMS
|
||||
CPUinfo File Filesystem
|
||||
File Filesystem
|
||||
Locale Power Render)
|
||||
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
||||
string(TOUPPER ${_SUB} _OPT)
|
||||
@@ -139,6 +140,14 @@ if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||
add_subdirectory(Vulkan-Headers)
|
||||
endif()
|
||||
|
||||
# TZDB (Time Zone Database)
|
||||
add_subdirectory(nx_tzdb)
|
||||
|
||||
# VMA
|
||||
add_library(vma vma/vma.cpp)
|
||||
target_include_directories(vma PUBLIC ./vma/VulkanMemoryAllocator/include)
|
||||
target_link_libraries(vma PRIVATE Vulkan::Headers)
|
||||
|
||||
if (NOT TARGET LLVM::Demangle)
|
||||
add_library(demangle demangle/ItaniumDemangle.cpp)
|
||||
target_include_directories(demangle PUBLIC ./demangle)
|
||||
@@ -148,6 +157,9 @@ endif()
|
||||
add_library(stb stb/stb_dxt.cpp)
|
||||
target_include_directories(stb PUBLIC ./stb)
|
||||
|
||||
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
|
||||
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
|
||||
|
||||
if (ANDROID)
|
||||
if (ARCHITECTURE_arm64)
|
||||
add_subdirectory(libadrenotools)
|
||||
|
||||
2
externals/SDL
vendored
2
externals/SDL
vendored
Submodule externals/SDL updated: f17058b562...491fba1d06
1522
externals/bc_decoder/bc_decoder.cpp
vendored
Normal file
1522
externals/bc_decoder/bc_decoder.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
externals/bc_decoder/bc_decoder.h
vendored
Normal file
43
externals/bc_decoder/bc_decoder.h
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bcn {
|
||||
/**
|
||||
* @brief Decodes a BC1 encoded image to R8G8B8A8
|
||||
*/
|
||||
void DecodeBc1(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC2 encoded image to R8G8B8A8
|
||||
*/
|
||||
void DecodeBc2(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC3 encoded image to R8G8B8A8
|
||||
*/
|
||||
void DecodeBc3(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC4 encoded image to R8
|
||||
*/
|
||||
void DecodeBc4(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC5 encoded image to R8G8
|
||||
*/
|
||||
void DecodeBc5(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC6 encoded image to R16G16B16A16
|
||||
*/
|
||||
void DecodeBc6(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
|
||||
|
||||
/**
|
||||
* @brief Decodes a BC7 encoded image to R8G8B8A8
|
||||
*/
|
||||
void DecodeBc7(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
|
||||
}
|
||||
101
externals/nx_tzdb/CMakeLists.txt
vendored
Normal file
101
externals/nx_tzdb/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
|
||||
|
||||
add_library(nx_tzdb INTERFACE)
|
||||
|
||||
find_program(GIT git)
|
||||
find_program(GNU_MAKE make)
|
||||
find_program(DATE_PROG date)
|
||||
|
||||
set(CAN_BUILD_NX_TZDB true)
|
||||
|
||||
if (NOT GIT)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (NOT GNU_MAKE)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (NOT DATE_PROG)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
|
||||
# tzdb_to_nx currently requires a posix-compliant host
|
||||
# MinGW and Android are handled here due to the executable format being different from the host system
|
||||
# TODO (lat9nq): cross-compiling support
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
|
||||
set(NX_TZDB_VERSION "220816")
|
||||
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
|
||||
|
||||
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
||||
|
||||
if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE})
|
||||
set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
|
||||
|
||||
message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
|
||||
file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}
|
||||
STATUS NX_TZDB_DOWNLOAD_STATUS)
|
||||
list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE)
|
||||
if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0)
|
||||
message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})")
|
||||
endif()
|
||||
|
||||
file(ARCHIVE_EXTRACT
|
||||
INPUT
|
||||
${NX_TZDB_ARCHIVE}
|
||||
DESTINATION
|
||||
${NX_TZDB_ROMFS_DIR})
|
||||
elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA)
|
||||
add_subdirectory(tzdb_to_nx)
|
||||
add_dependencies(nx_tzdb x80e)
|
||||
|
||||
set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}")
|
||||
endif()
|
||||
|
||||
target_include_directories(nx_tzdb
|
||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
INTERFACE ${NX_TZDB_INCLUDE_DIR})
|
||||
|
||||
function(CreateHeader ZONE_PATH HEADER_NAME)
|
||||
set(HEADER_PATH "${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h")
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/NxTzdbCreateHeader.cmake
|
||||
${ZONE_PATH}
|
||||
${HEADER_NAME}
|
||||
${NX_TZDB_INCLUDE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS
|
||||
tzdb_template.h.in
|
||||
NxTzdbCreateHeader.cmake)
|
||||
|
||||
target_sources(nx_tzdb PRIVATE ${HEADER_PATH})
|
||||
endfunction()
|
||||
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR} base)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific)
|
||||
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/US us)
|
||||
8
externals/nx_tzdb/ListFilesInDirectory.cmake
vendored
Normal file
8
externals/nx_tzdb/ListFilesInDirectory.cmake
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# CMake does not have a way to list the files in a specific directory,
|
||||
# so we need this script to do that for us in a platform-agnostic fashion
|
||||
|
||||
file(GLOB FILE_LIST LIST_DIRECTORIES false RELATIVE ${CMAKE_SOURCE_DIR} "*")
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${FILE_LIST};")
|
||||
46
externals/nx_tzdb/NxTzdbCreateHeader.cmake
vendored
Normal file
46
externals/nx_tzdb/NxTzdbCreateHeader.cmake
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(ZONE_PATH ${CMAKE_ARGV3})
|
||||
set(HEADER_NAME ${CMAKE_ARGV4})
|
||||
set(NX_TZDB_INCLUDE_DIR ${CMAKE_ARGV5})
|
||||
set(NX_TZDB_SOURCE_DIR ${CMAKE_ARGV6})
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -P ${NX_TZDB_SOURCE_DIR}/ListFilesInDirectory.cmake
|
||||
WORKING_DIRECTORY ${ZONE_PATH}
|
||||
OUTPUT_VARIABLE FILE_LIST)
|
||||
|
||||
set(DIRECTORY_NAME ${HEADER_NAME})
|
||||
|
||||
set(FILE_DATA "")
|
||||
foreach(ZONE_FILE ${FILE_LIST})
|
||||
if (ZONE_FILE STREQUAL "\n")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
string(APPEND FILE_DATA "{\"${ZONE_FILE}\",\n{")
|
||||
|
||||
file(READ ${ZONE_PATH}/${ZONE_FILE} ZONE_DATA HEX)
|
||||
string(LENGTH "${ZONE_DATA}" ZONE_DATA_LEN)
|
||||
foreach(I RANGE 0 ${ZONE_DATA_LEN} 2)
|
||||
math(EXPR BREAK_LINE "(${I} + 2) % 38")
|
||||
|
||||
string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA)
|
||||
if (NOT HEX_DATA)
|
||||
break()
|
||||
endif()
|
||||
|
||||
string(APPEND FILE_DATA "0x${HEX_DATA},")
|
||||
if (BREAK_LINE EQUAL 0)
|
||||
string(APPEND FILE_DATA "\n")
|
||||
else()
|
||||
string(APPEND FILE_DATA " ")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
string(APPEND FILE_DATA "}},\n")
|
||||
endforeach()
|
||||
|
||||
file(READ ${NX_TZDB_SOURCE_DIR}/tzdb_template.h.in NX_TZDB_TEMPLATE_H_IN)
|
||||
file(CONFIGURE OUTPUT ${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h CONTENT "${NX_TZDB_TEMPLATE_H_IN}")
|
||||
27
externals/nx_tzdb/include/nx_tzdb.h
vendored
Normal file
27
externals/nx_tzdb/include/nx_tzdb.h
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nx_tzdb/africa.h"
|
||||
#include "nx_tzdb/america.h"
|
||||
#include "nx_tzdb/america_argentina.h"
|
||||
#include "nx_tzdb/america_indiana.h"
|
||||
#include "nx_tzdb/america_kentucky.h"
|
||||
#include "nx_tzdb/america_north_dakota.h"
|
||||
#include "nx_tzdb/antarctica.h"
|
||||
#include "nx_tzdb/arctic.h"
|
||||
#include "nx_tzdb/asia.h"
|
||||
#include "nx_tzdb/atlantic.h"
|
||||
#include "nx_tzdb/australia.h"
|
||||
#include "nx_tzdb/base.h"
|
||||
#include "nx_tzdb/brazil.h"
|
||||
#include "nx_tzdb/canada.h"
|
||||
#include "nx_tzdb/chile.h"
|
||||
#include "nx_tzdb/etc.h"
|
||||
#include "nx_tzdb/europe.h"
|
||||
#include "nx_tzdb/indian.h"
|
||||
#include "nx_tzdb/mexico.h"
|
||||
#include "nx_tzdb/pacific.h"
|
||||
#include "nx_tzdb/us.h"
|
||||
#include "nx_tzdb/zoneinfo.h"
|
||||
18
externals/nx_tzdb/tzdb_template.h.in
vendored
Normal file
18
externals/nx_tzdb/tzdb_template.h.in
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace NxTzdb {
|
||||
|
||||
// clang-format off
|
||||
const static std::map<const char*, const std::vector<uint8_t>> @DIRECTORY_NAME@ =
|
||||
{
|
||||
@FILE_DATA@};
|
||||
// clang-format on
|
||||
|
||||
} // namespace NxTzdb
|
||||
1
externals/nx_tzdb/tzdb_to_nx
vendored
Submodule
1
externals/nx_tzdb/tzdb_to_nx
vendored
Submodule
Submodule externals/nx_tzdb/tzdb_to_nx added at 212afa2394
2
externals/vcpkg
vendored
2
externals/vcpkg
vendored
Submodule externals/vcpkg updated: 656fcc6ab2...cbf56573a9
1
externals/vma/VulkanMemoryAllocator
vendored
Submodule
1
externals/vma/VulkanMemoryAllocator
vendored
Submodule
Submodule externals/vma/VulkanMemoryAllocator added at 0aa3989b8f
8
externals/vma/vma.cpp
vendored
Normal file
8
externals/vma/vma.cpp
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
@@ -2,13 +2,17 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import kotlin.collections.setOf
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +26,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
|
||||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
|
||||
compileSdkVersion = "android-33"
|
||||
compileSdkVersion = "android-34"
|
||||
ndkVersion = "25.2.9519653"
|
||||
|
||||
buildFeatures {
|
||||
@@ -43,21 +47,11 @@ android {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
|
||||
lint {
|
||||
// This is important as it will run lint but not abort on error
|
||||
// Lint has some overly obnoxious "errors" that should really be warnings
|
||||
abortOnError = false
|
||||
|
||||
//Uncomment disable lines for test builds...
|
||||
//disable 'MissingTranslation'bin
|
||||
//disable 'ExtraTranslation'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO If this is ever modified, change application_id in strings.xml
|
||||
applicationId = "org.yuzu.yuzu_emu"
|
||||
minSdk = 30
|
||||
targetSdk = 33
|
||||
targetSdk = 34
|
||||
versionName = getGitVersion()
|
||||
|
||||
// If you want to use autoVersion for the versionCode, create a property in local.properties
|
||||
@@ -166,6 +160,24 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
|
||||
|
||||
ktlint {
|
||||
version.set("0.47.1")
|
||||
android.set(true)
|
||||
ignoreFailures.set(false)
|
||||
disabledRules.set(
|
||||
setOf(
|
||||
"no-wildcard-imports",
|
||||
"package-name",
|
||||
"import-ordering"
|
||||
)
|
||||
)
|
||||
reporters {
|
||||
reporter(ReporterType.CHECKSTYLE)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
|
||||
@@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
@@ -53,8 +54,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<activity
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -68,7 +70,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
|
||||
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>
|
||||
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".features.DocumentProvider"
|
||||
|
||||
@@ -14,16 +14,18 @@ import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.exists
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
||||
import org.yuzu.yuzu_emu.utils.Log.error
|
||||
import org.yuzu.yuzu_emu.utils.Log.verbose
|
||||
import org.yuzu.yuzu_emu.utils.Log.warning
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Class which contains methods that interact
|
||||
@@ -74,7 +76,9 @@ object NativeLibrary {
|
||||
fun openContentUri(path: String?, openmode: String?): Int {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
||||
} else openContentUri(appContext, path, openmode)
|
||||
} else {
|
||||
openContentUri(appContext, path, openmode)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -82,7 +86,29 @@ object NativeLibrary {
|
||||
fun getSize(path: String?): Long {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.getFileSize(path)
|
||||
} else getFileSize(appContext, path)
|
||||
} else {
|
||||
getFileSize(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun exists(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.exists(path)
|
||||
} else {
|
||||
exists(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.isDirectory(path)
|
||||
} else {
|
||||
isDirectory(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,6 +253,8 @@ object NativeLibrary {
|
||||
|
||||
external fun setAppDirectory(directory: String)
|
||||
|
||||
external fun installFileToNand(filename: String): Int
|
||||
|
||||
external fun initializeGpuDriver(
|
||||
hookLibDir: String?,
|
||||
customDriverDir: String?,
|
||||
@@ -258,7 +286,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Unpauses emulation from a paused state.
|
||||
*/
|
||||
external fun unPauseEmulation()
|
||||
external fun unpauseEmulation()
|
||||
|
||||
/**
|
||||
* Pauses emulation.
|
||||
@@ -280,6 +308,26 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun isRunning(): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if emulation is paused.
|
||||
*/
|
||||
external fun isPaused(): Boolean
|
||||
|
||||
/**
|
||||
* Mutes emulation sound
|
||||
*/
|
||||
external fun muteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Unmutes emulation sound
|
||||
*/
|
||||
external fun unmuteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if emulation audio is muted.
|
||||
*/
|
||||
external fun isMuted(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the performance stats for the current game
|
||||
*/
|
||||
@@ -429,7 +477,9 @@ object NativeLibrary {
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() }
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
emulationActivity.finish()
|
||||
}
|
||||
.setOnDismissListener { emulationActivity.finish() }
|
||||
emulationActivity.runOnUiThread {
|
||||
val alert = builder.create()
|
||||
@@ -507,4 +557,15 @@ object NativeLibrary {
|
||||
const val RELEASED = 0
|
||||
const val PRESSED = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from installFileToNand
|
||||
*/
|
||||
object InstallFileToNandResult {
|
||||
const val Success = 0
|
||||
const val SuccessFileOverwritten = 1
|
||||
const val Error = 2
|
||||
const val ErrorBaseGame = 3
|
||||
const val ErrorFilenameExtension = 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import java.io.File
|
||||
|
||||
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
|
||||
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
||||
|
||||
class YuzuApplication : Application() {
|
||||
private fun createNotificationChannels() {
|
||||
@@ -21,7 +21,9 @@ class YuzuApplication : Application() {
|
||||
getString(R.string.emulation_notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
emulationChannel.description = getString(R.string.emulation_notification_channel_description)
|
||||
emulationChannel.description = getString(
|
||||
R.string.emulation_notification_channel_description
|
||||
)
|
||||
emulationChannel.setSound(null, null)
|
||||
emulationChannel.vibrationPattern = null
|
||||
|
||||
@@ -48,7 +50,7 @@ class YuzuApplication : Application() {
|
||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
||||
NativeLibrary.logDeviceInfo()
|
||||
|
||||
createNotificationChannels();
|
||||
createNotificationChannels()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -4,49 +4,57 @@
|
||||
package org.yuzu.yuzu_emu.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.PictureInPictureParams
|
||||
import android.app.RemoteAction
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Rational
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.window.layout.WindowInfoTracker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
||||
private var controllerMappingHelper: ControllerMappingHelper? = null
|
||||
|
||||
var isActivityRecreated = false
|
||||
private var emulationFragment: EmulationFragment? = null
|
||||
private lateinit var nfcReader: NfcReader
|
||||
private lateinit var inputHandler: InputHandler
|
||||
|
||||
@@ -55,7 +63,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private var motionTimestamp: Long = 0
|
||||
private var flipMotionOrientation: Boolean = false
|
||||
|
||||
private lateinit var game: Game
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
@@ -70,45 +81,42 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
settingsViewModel.settings.loadSettings()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
// Get params we were passed
|
||||
game = intent.parcelable(EXTRA_SELECTED_GAME)!!
|
||||
isActivityRecreated = false
|
||||
} else {
|
||||
isActivityRecreated = true
|
||||
restoreState(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
|
||||
|
||||
controllerMappingHelper = ControllerMappingHelper()
|
||||
|
||||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||
enableFullscreenImmersive()
|
||||
|
||||
setContentView(R.layout.activity_emulation)
|
||||
window.decorView.setBackgroundColor(getColor(android.R.color.black))
|
||||
|
||||
// Find or create the EmulationFragment
|
||||
emulationFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
|
||||
if (emulationFragment == null) {
|
||||
emulationFragment = EmulationFragment.newInstance(game)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.frame_emulation_fragment, emulationFragment!!)
|
||||
.commit()
|
||||
}
|
||||
title = game.title
|
||||
|
||||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
inputHandler = InputHandler()
|
||||
inputHandler.initialize()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(this@EmulationActivity)
|
||||
.windowLayoutInfo(this@EmulationActivity)
|
||||
.collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) }
|
||||
}
|
||||
val memoryUtil = MemoryUtil(this)
|
||||
if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(
|
||||
R.string.device_memory_inadequate,
|
||||
memoryUtil.getDeviceRAM(),
|
||||
"8 ${getString(R.string.memory_gigabyte)}"
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
// Start a foreground service to prevent the app from getting killed in the background
|
||||
@@ -143,6 +151,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -151,17 +161,22 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
stopMotionSensorListener()
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
|
||||
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
nfcReader.onNewIntent(intent)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putParcelable(EXTRA_SELECTED_GAME, game)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
||||
@@ -248,10 +263,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
||||
|
||||
private fun restoreState(savedInstanceState: Bundle) {
|
||||
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
|
||||
}
|
||||
|
||||
private fun enableFullscreenImmersive() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
@@ -262,6 +273,144 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
|
||||
PictureInPictureParams.Builder {
|
||||
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
else -> null // Best fit
|
||||
}
|
||||
return this.apply { aspectRatio?.let { setAspectRatio(it) } }
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder():
|
||||
PictureInPictureParams.Builder {
|
||||
val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf()
|
||||
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
if (NativeLibrary.isPaused()) {
|
||||
val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play)
|
||||
val playPendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_play,
|
||||
Intent(actionPlay),
|
||||
pendingFlags
|
||||
)
|
||||
val playRemoteAction = RemoteAction(
|
||||
playIcon,
|
||||
getString(R.string.play),
|
||||
getString(R.string.play),
|
||||
playPendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(playRemoteAction)
|
||||
} else {
|
||||
val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause)
|
||||
val pausePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_pause,
|
||||
Intent(actionPause),
|
||||
pendingFlags
|
||||
)
|
||||
val pauseRemoteAction = RemoteAction(
|
||||
pauseIcon,
|
||||
getString(R.string.pause),
|
||||
getString(R.string.pause),
|
||||
pausePendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
}
|
||||
|
||||
if (NativeLibrary.isMuted()) {
|
||||
val unmuteIcon = Icon.createWithResource(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute
|
||||
)
|
||||
val unmutePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute,
|
||||
Intent(actionUnmute),
|
||||
pendingFlags
|
||||
)
|
||||
val unmuteRemoteAction = RemoteAction(
|
||||
unmuteIcon,
|
||||
getString(R.string.unmute),
|
||||
getString(R.string.unmute),
|
||||
unmutePendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(unmuteRemoteAction)
|
||||
} else {
|
||||
val muteIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_mute)
|
||||
val mutePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_mute,
|
||||
Intent(actionMute),
|
||||
pendingFlags
|
||||
)
|
||||
val muteRemoteAction = RemoteAction(
|
||||
muteIcon,
|
||||
getString(R.string.mute),
|
||||
getString(R.string.mute),
|
||||
mutePendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(muteRemoteAction)
|
||||
}
|
||||
|
||||
return this.apply { setActions(pictureInPictureActions) }
|
||||
}
|
||||
|
||||
fun buildPictureInPictureParams() {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
pictureInPictureParamsBuilder.setAutoEnterEnabled(
|
||||
BooleanSetting.PICTURE_IN_PICTURE.boolean
|
||||
)
|
||||
}
|
||||
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
|
||||
private var pictureInPictureReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
if (intent.action == actionPlay) {
|
||||
if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation()
|
||||
} else if (intent.action == actionPause) {
|
||||
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
|
||||
}
|
||||
if (intent.action == actionUnmute) {
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
} else if (intent.action == actionMute) {
|
||||
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
|
||||
}
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(
|
||||
isInPictureInPictureMode: Boolean,
|
||||
newConfig: Configuration
|
||||
) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
if (isInPictureInPictureMode) {
|
||||
IntentFilter().apply {
|
||||
addAction(actionPause)
|
||||
addAction(actionPlay)
|
||||
addAction(actionMute)
|
||||
addAction(actionUnmute)
|
||||
}.also {
|
||||
registerReceiver(pictureInPictureReceiver, it)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
// Always resume audio, since there is no UI button
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startMotionSensorListener() {
|
||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
@@ -23,13 +24,13 @@ import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
|
||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity) :
|
||||
@@ -58,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
override fun onClick(view: View) {
|
||||
val holder = view.tag as GameViewHolder
|
||||
|
||||
val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true
|
||||
val gameExists = DocumentFile.fromSingleUri(
|
||||
YuzuApplication.appContext,
|
||||
Uri.parse(holder.game.path)
|
||||
)?.exists() == true
|
||||
if (!gameExists) {
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
@@ -78,7 +82,8 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
)
|
||||
.apply()
|
||||
|
||||
EmulationActivity.launch(activity, holder.game)
|
||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
|
||||
view.findNavController().navigate(action)
|
||||
}
|
||||
|
||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||
|
||||
@@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
|
||||
)
|
||||
|
||||
when (option.titleId) {
|
||||
R.string.get_early_access -> binding.optionLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
binding.optionCard.context,
|
||||
R.drawable.premium_background
|
||||
)
|
||||
R.string.get_early_access ->
|
||||
binding.optionLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
binding.optionCard.context,
|
||||
R.drawable.premium_background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import android.view.WindowInsets
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.view.ViewCompat
|
||||
import java.io.Serializable
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object SoftwareKeyboard {
|
||||
@@ -40,19 +40,22 @@ object SoftwareKeyboard {
|
||||
// There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
|
||||
val handler = Handler(Looper.myLooper()!!)
|
||||
val delayMs = 500
|
||||
handler.postDelayed(object : Runnable {
|
||||
override fun run() {
|
||||
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
||||
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
||||
if (isKeyboardVisible) {
|
||||
handler.postDelayed(this, delayMs.toLong())
|
||||
return
|
||||
}
|
||||
handler.postDelayed(
|
||||
object : Runnable {
|
||||
override fun run() {
|
||||
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
||||
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
||||
if (isKeyboardVisible) {
|
||||
handler.postDelayed(this, delayMs.toLong())
|
||||
return
|
||||
}
|
||||
|
||||
// No longer visible, submit the result.
|
||||
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
|
||||
}
|
||||
}, delayMs.toLong())
|
||||
// No longer visible, submit the result.
|
||||
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
|
||||
}
|
||||
},
|
||||
delayMs.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -20,7 +20,10 @@ object DiskShaderCacheProgress {
|
||||
emulationActivity.getString(R.string.loading),
|
||||
emulationActivity.getString(R.string.preparing_shaders)
|
||||
)
|
||||
fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG)
|
||||
fragment.show(
|
||||
emulationActivity.supportFragmentManager,
|
||||
ShaderProgressDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
synchronized(finishLock) { finishLock.wait() }
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() {
|
||||
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
|
||||
alertDialog.setMessage(msg)
|
||||
}
|
||||
synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() }
|
||||
synchronized(DiskShaderCacheProgress.finishLock) {
|
||||
DiskShaderCacheProgress.finishLock.notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsProvider
|
||||
import android.webkit.MimeTypeMap
|
||||
import java.io.*
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.getPublicFilesDir
|
||||
import java.io.*
|
||||
|
||||
class DocumentProvider : DocumentsProvider() {
|
||||
private val baseDirectory: File
|
||||
@@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() {
|
||||
DocumentsContract.Document.COLUMN_SIZE
|
||||
)
|
||||
|
||||
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user"
|
||||
const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user"
|
||||
const val ROOT_ID: String = "root"
|
||||
}
|
||||
|
||||
@@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() {
|
||||
private fun getFile(documentId: String): File {
|
||||
if (documentId.startsWith(ROOT_ID)) {
|
||||
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
|
||||
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
|
||||
if (!file.exists()) {
|
||||
throw FileNotFoundException(
|
||||
"${file.absolutePath} ($documentId) not found"
|
||||
)
|
||||
}
|
||||
return file
|
||||
} else {
|
||||
throw FileNotFoundException("'$documentId' is not in any known root")
|
||||
@@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() {
|
||||
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
|
||||
add(
|
||||
DocumentsContract.Root.COLUMN_FLAGS,
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
|
||||
)
|
||||
add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
|
||||
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
|
||||
@@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() {
|
||||
|
||||
try {
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
|
||||
if (!newFile.mkdir())
|
||||
if (!newFile.mkdir()) {
|
||||
throw IOException("Failed to create directory")
|
||||
}
|
||||
} else {
|
||||
if (!newFile.createNewFile())
|
||||
if (!newFile.createNewFile()) {
|
||||
throw IOException("Failed to create file")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
|
||||
@@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() {
|
||||
|
||||
override fun deleteDocument(documentId: String?) {
|
||||
val file = getFile(documentId!!)
|
||||
if (!file.delete())
|
||||
if (!file.delete()) {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeDocument(documentId: String, parentDocumentId: String?) {
|
||||
@@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() {
|
||||
val file = getFile(documentId)
|
||||
|
||||
if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
|
||||
if (!file.delete())
|
||||
if (!file.delete()) {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
} else {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
}
|
||||
|
||||
override fun renameDocument(documentId: String?, displayName: String?): String {
|
||||
if (displayName == null)
|
||||
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
|
||||
if (displayName == null) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document '$documentId' as the new name is null"
|
||||
)
|
||||
}
|
||||
|
||||
val sourceFile = getFile(documentId!!)
|
||||
val sourceParentFile = sourceFile.parentFile
|
||||
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
|
||||
?: throw FileNotFoundException(
|
||||
"Couldn't rename document '$documentId' as it has no parent"
|
||||
)
|
||||
val destFile = sourceParentFile.resolve(displayName)
|
||||
|
||||
try {
|
||||
if (!sourceFile.renameTo(destFile))
|
||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
|
||||
if (!sourceFile.renameTo(destFile)) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " +
|
||||
"${e.message}"
|
||||
)
|
||||
}
|
||||
|
||||
return getDocumentId(destFile)
|
||||
}
|
||||
|
||||
private fun copyDocument(
|
||||
sourceDocumentId: String, sourceParentDocumentId: String,
|
||||
sourceDocumentId: String,
|
||||
sourceParentDocumentId: String,
|
||||
targetParentDocumentId: String?
|
||||
): String {
|
||||
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
|
||||
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
|
||||
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't copy document '$sourceDocumentId' as its parent is not " +
|
||||
"'$sourceParentDocumentId'"
|
||||
)
|
||||
}
|
||||
|
||||
return copyDocument(sourceDocumentId, targetParentDocumentId)
|
||||
}
|
||||
@@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() {
|
||||
val newFile = parent.resolveWithoutConflict(oldFile.name)
|
||||
|
||||
try {
|
||||
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
|
||||
if (!(
|
||||
newFile.createNewFile() && newFile.setWritable(true) &&
|
||||
newFile.setReadable(true)
|
||||
)
|
||||
) {
|
||||
throw IOException("Couldn't create new file")
|
||||
}
|
||||
|
||||
FileInputStream(oldFile).use { inStream ->
|
||||
FileOutputStream(newFile).use { outStream ->
|
||||
@@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() {
|
||||
}
|
||||
|
||||
override fun moveDocument(
|
||||
sourceDocumentId: String, sourceParentDocumentId: String?,
|
||||
sourceDocumentId: String,
|
||||
sourceParentDocumentId: String?,
|
||||
targetParentDocumentId: String?
|
||||
): String {
|
||||
try {
|
||||
val newDocumentId = copyDocument(
|
||||
sourceDocumentId, sourceParentDocumentId!!,
|
||||
sourceDocumentId,
|
||||
sourceParentDocumentId!!,
|
||||
targetParentDocumentId
|
||||
)
|
||||
removeDocument(sourceDocumentId, sourceParentDocumentId)
|
||||
@@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() {
|
||||
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
|
||||
add(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name
|
||||
if (localFile == baseDirectory) {
|
||||
context!!.getString(R.string.app_name)
|
||||
} else {
|
||||
localFile.name
|
||||
}
|
||||
)
|
||||
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
|
||||
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
|
||||
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
|
||||
add(DocumentsContract.Document.COLUMN_FLAGS, flags)
|
||||
if (localFile == baseDirectory)
|
||||
if (localFile == baseDirectory) {
|
||||
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
|
||||
}
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
private fun getTypeForFile(file: File): Any {
|
||||
return if (file.isDirectory)
|
||||
return if (file.isDirectory) {
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
else
|
||||
} else {
|
||||
getTypeForName(file.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTypeForName(name: String): Any {
|
||||
@@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() {
|
||||
if (lastDot >= 0) {
|
||||
val extension = name.substring(lastDot + 1)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (mime != null)
|
||||
if (mime != null) {
|
||||
return mime
|
||||
}
|
||||
}
|
||||
return "application/octect-stream"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ enum class BooleanSetting(
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
|
||||
FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
|
||||
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
@@ -27,6 +31,7 @@ enum class BooleanSetting(
|
||||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||
PICTURE_IN_PICTURE,
|
||||
USE_CUSTOM_RTC
|
||||
)
|
||||
|
||||
|
||||
@@ -26,13 +26,18 @@ enum class IntSetting(
|
||||
RENDERER_FORCE_MAX_CLOCK(
|
||||
"force_max_clock",
|
||||
Settings.SECTION_RENDERER,
|
||||
1
|
||||
0
|
||||
),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS(
|
||||
"use_asynchronous_shaders",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_REACTIVE_FLUSHING(
|
||||
"use_reactive_flushing",
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_DEBUG(
|
||||
"debug",
|
||||
Settings.SECTION_RENDERER,
|
||||
@@ -88,6 +93,11 @@ enum class IntSetting(
|
||||
Settings.SECTION_RENDERER,
|
||||
0
|
||||
),
|
||||
RENDERER_SCREEN_LAYOUT(
|
||||
"screen_layout",
|
||||
Settings.SECTION_RENDERER,
|
||||
Settings.LayoutOption_MobileLandscape
|
||||
),
|
||||
RENDERER_ASPECT_RATIO(
|
||||
"aspect_ratio",
|
||||
Settings.SECTION_RENDERER,
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import java.util.*
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import java.util.*
|
||||
|
||||
class Settings {
|
||||
private var gameId: String? = null
|
||||
@@ -133,7 +133,6 @@ class Settings {
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
const val PREF_MENU_SETTINGS_LANDSCAPE = "EmulationMenuSettings_LandscapeScreenLayout"
|
||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||
|
||||
@@ -144,6 +143,10 @@ class Settings {
|
||||
|
||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||
|
||||
const val LayoutOption_Unspecified = 0
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
||||
init {
|
||||
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
|
||||
listOf(
|
||||
|
||||
@@ -8,6 +8,7 @@ enum class StringSetting(
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
|
||||
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class HeaderSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
titleId: Int
|
||||
) : SettingsItem(null, titleId, 0) {
|
||||
override val type = TYPE_HEADER
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
class SingleChoiceSetting(
|
||||
setting: AbstractIntSetting?,
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting?,
|
||||
@@ -19,7 +17,7 @@ class SliderSetting(
|
||||
val max: Int,
|
||||
val units: String,
|
||||
val key: String? = null,
|
||||
val defaultValue: Int? = null,
|
||||
val defaultValue: Int? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SLIDER
|
||||
|
||||
|
||||
@@ -5,24 +5,25 @@ package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
|
||||
class StringSingleChoiceSetting(
|
||||
val key: String? = null,
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Array<String>,
|
||||
private val valuesId: Array<String>?,
|
||||
val choices: Array<String>,
|
||||
val values: Array<String>?,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||
|
||||
fun getValueAt(index: Int): String? {
|
||||
if (valuesId == null) return null
|
||||
return if (index >= 0 && index < valuesId.size) {
|
||||
valuesId[index]
|
||||
} else ""
|
||||
if (values == null) return null
|
||||
return if (index >= 0 && index < values.size) {
|
||||
values[index]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
val selectedValue: String
|
||||
@@ -35,8 +36,8 @@ class StringSingleChoiceSetting(
|
||||
val selectValueIndex: Int
|
||||
get() {
|
||||
val selectedValue = selectedValue
|
||||
for (i in valuesId!!.indices) {
|
||||
if (valuesId[i] == selectedValue) {
|
||||
for (i in values!!.indices) {
|
||||
if (values[i] == selectedValue) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class SubmenuSetting(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
|
||||
@@ -8,17 +8,18 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
@@ -29,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
private val presenter = SettingsActivityPresenter(this)
|
||||
@@ -59,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
setSupportActionBar(binding.toolbarSettings)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||
InsetsHelper.GESTURE_NAVIGATION
|
||||
) {
|
||||
binding.navigationBarShade.setBackgroundColor(
|
||||
ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
@@ -75,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() = navigateBack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
@@ -148,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
private fun areSystemAnimationsEnabled(): Boolean {
|
||||
val duration = android.provider.Settings.Global.getFloat(
|
||||
contentResolver,
|
||||
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f
|
||||
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1f
|
||||
)
|
||||
val transition = android.provider.Settings.Global.getFloat(
|
||||
contentResolver,
|
||||
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f
|
||||
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
|
||||
1f
|
||||
)
|
||||
return duration != 0f && transition != 0f
|
||||
}
|
||||
@@ -207,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.frameContent
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
view.updatePadding(
|
||||
@@ -239,5 +246,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
settings.putExtra(ARG_GAME_ID, gameId)
|
||||
context.startActivity(settings)
|
||||
}
|
||||
|
||||
fun launch(
|
||||
context: Context,
|
||||
launcher: ActivityResultLauncher<Intent>,
|
||||
menuTag: String?,
|
||||
gameId: String?
|
||||
) {
|
||||
val settings = Intent(context, SettingsActivity::class.java)
|
||||
settings.putExtra(ARG_MENU_TAG, menuTag)
|
||||
settings.putExtra(ARG_GAME_ID, gameId)
|
||||
launcher.launch(settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.io.File
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
val settings: Settings get() = activityView.settings
|
||||
@@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
|
||||
private fun prepareDirectoriesIfNeeded() {
|
||||
val configFile =
|
||||
File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
|
||||
File(
|
||||
"${DirectoryInitialization.userDirectory}/config/" +
|
||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
||||
)
|
||||
if (!configFile.exists()) {
|
||||
Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
|
||||
Log.error(
|
||||
"${DirectoryInitialization.userDirectory}/config/" +
|
||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
||||
)
|
||||
Log.error("yuzu config file could not be found!")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -139,7 +138,7 @@ class SettingsAdapter(
|
||||
clickedItem = item
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setSingleChoiceItems(item.choicesId, item.selectValueIndex, this)
|
||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
settingsAdapter = SettingsAdapter(this, requireActivity())
|
||||
val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
|
||||
val dividerDecoration = MaterialDividerItemDecoration(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL
|
||||
)
|
||||
dividerDecoration.isLastItemDecorated = false
|
||||
binding.listSettings.apply {
|
||||
adapter = settingsAdapter
|
||||
@@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.listSettings
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.updatePadding(bottom = insets.bottom)
|
||||
windowInsets
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
@@ -43,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
}
|
||||
|
||||
fun putSetting(setting: AbstractSetting) {
|
||||
if (setting.section == null) {
|
||||
if (setting.section == null || setting.key == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -166,6 +165,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.CPU_ACCURACY.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.PICTURE_IN_PICTURE,
|
||||
R.string.picture_in_picture,
|
||||
R.string.picture_in_picture_description,
|
||||
BooleanSetting.PICTURE_IN_PICTURE.key,
|
||||
BooleanSetting.PICTURE_IN_PICTURE.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
|
||||
sl.apply {
|
||||
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ACCURACY,
|
||||
@@ -283,6 +290,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||
R.string.renderer_screen_layout,
|
||||
0,
|
||||
R.array.rendererScreenLayoutNames,
|
||||
R.array.rendererScreenLayoutValues,
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT.key,
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ASPECT_RATIO,
|
||||
@@ -321,23 +339,45 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING,
|
||||
R.string.renderer_reactive_flushing,
|
||||
R.string.renderer_reactive_flushing_description,
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING.key,
|
||||
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
|
||||
sl.add(
|
||||
SliderSetting(
|
||||
IntSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
IntSetting.AUDIO_VOLUME.key,
|
||||
IntSetting.AUDIO_VOLUME.defaultValue
|
||||
sl.apply {
|
||||
add(
|
||||
StringSingleChoiceSetting(
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE,
|
||||
R.string.audio_output_engine,
|
||||
0,
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineValues),
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.key,
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
|
||||
)
|
||||
)
|
||||
)
|
||||
add(
|
||||
SliderSetting(
|
||||
IntSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
IntSetting.AUDIO_VOLUME.key,
|
||||
IntSetting.AUDIO_VOLUME.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||
@@ -440,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.gpu))
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_BACKEND,
|
||||
@@ -460,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.RENDERER_DEBUG.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.CPU_DEBUG_MODE,
|
||||
R.string.cpu_debug_mode,
|
||||
R.string.cpu_debug_mode_description,
|
||||
BooleanSetting.CPU_DEBUG_MODE.key,
|
||||
BooleanSetting.CPU_DEBUG_MODE.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
val fastmem = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
get() =
|
||||
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
|
||||
set(value) {
|
||||
BooleanSetting.FASTMEM.boolean = value
|
||||
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String = Settings.SECTION_CPU
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String = ""
|
||||
override val defaultValue: Any = true
|
||||
}
|
||||
add(
|
||||
SwitchSetting(
|
||||
fastmem,
|
||||
R.string.fastmem,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
||||
@@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
for (i in values.indices) {
|
||||
if (values[i] == item.selectedValue) {
|
||||
binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if (item is StringSingleChoiceSetting) {
|
||||
for (i in item.values!!.indices) {
|
||||
if (item.values[i] == item.selectedValue) {
|
||||
binding.textSettingDescription.text = item.choices[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.utils
|
||||
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import org.ini4j.Wini
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.utils.BiMap
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Contains static methods for interacting with .ini files in which settings are stored.
|
||||
@@ -137,9 +137,12 @@ object SettingsFile {
|
||||
for (settingKey in sortedKeySet) {
|
||||
val setting = settings[settingKey]
|
||||
NativeLibrary.setUserSetting(
|
||||
gameId, mapSectionNameFromIni(
|
||||
gameId,
|
||||
mapSectionNameFromIni(
|
||||
section.name
|
||||
), setting!!.key, setting.valueAsString
|
||||
),
|
||||
setting!!.key,
|
||||
setting.valueAsString
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -148,13 +151,17 @@ object SettingsFile {
|
||||
private fun mapSectionNameFromIni(generalSectionName: String): String? {
|
||||
return if (sectionsMap.getForward(generalSectionName) != null) {
|
||||
sectionsMap.getForward(generalSectionName)
|
||||
} else generalSectionName
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSectionNameToIni(generalSectionName: String): String {
|
||||
return if (sectionsMap.getBackward(generalSectionName) != null) {
|
||||
sectionsMap.getBackward(generalSectionName).toString()
|
||||
} else generalSectionName
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
fun getSettingsFile(fileName: String): File {
|
||||
@@ -237,5 +244,21 @@ object SettingsFile {
|
||||
val setting = settings[key]
|
||||
parser.put(header, setting!!.key, setting.valueAsString)
|
||||
}
|
||||
|
||||
BooleanSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
IntSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
StringSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,11 @@ class AboutFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) }
|
||||
binding.buttonContributors.setOnClickListener {
|
||||
openLink(
|
||||
getString(R.string.contributors_link)
|
||||
)
|
||||
}
|
||||
binding.buttonLicenses.setOnClickListener {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
|
||||
@@ -101,7 +105,9 @@ class AboutFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() {
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
|
||||
}
|
||||
|
||||
binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) }
|
||||
binding.getEarlyAccessButton.setOnClickListener {
|
||||
openLink(
|
||||
getString(R.string.play_store_link)
|
||||
)
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
@@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -7,30 +7,39 @@ import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Rational
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import androidx.window.layout.WindowInfoTracker
|
||||
import androidx.window.layout.WindowLayoutInfo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@@ -41,9 +50,8 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||
|
||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
@@ -54,13 +62,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private var _binding: FragmentEmulationBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var game: Game
|
||||
val args by navArgs<EmulationFragmentArgs>()
|
||||
|
||||
private var isInFoldableLayout = false
|
||||
|
||||
private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
.collect { updateFoldableLayout(context, it) }
|
||||
}
|
||||
}
|
||||
|
||||
onReturnFromSettings = context.activityResultRegistry.register(
|
||||
"SettingsResult",
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { updateScreenLayout() }
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
@@ -75,8 +100,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||
retainInstance = true
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!!
|
||||
emulationState = EmulationState(game.path)
|
||||
emulationState = EmulationState(args.game.path)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +124,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
updateShowFpsOverlay()
|
||||
|
||||
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
|
||||
game.title
|
||||
args.game.title
|
||||
binding.inGameMenu.setNavigationItemSelectedListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_pause_emulation -> {
|
||||
@@ -125,7 +149,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
R.id.menu_settings -> {
|
||||
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
|
||||
SettingsActivity.launch(
|
||||
requireContext(),
|
||||
onReturnFromSettings,
|
||||
SettingsFile.FILE_NAME_CONFIG,
|
||||
""
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -150,9 +179,48 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
requireActivity(),
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
|
||||
if (binding.drawerLayout.isOpen) {
|
||||
binding.drawerLayout.close()
|
||||
} else {
|
||||
binding.drawerLayout.open()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (emulationActivity?.isInPictureInPictureMode == true) {
|
||||
if (binding.drawerLayout.isOpen) {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
|
||||
}
|
||||
} else {
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
|
||||
}
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT
|
||||
} else {
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE
|
||||
}
|
||||
}
|
||||
if (!binding.surfaceInputOverlay.isInEditMode) {
|
||||
refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -161,16 +229,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
DirectoryInitialization.start(requireContext())
|
||||
}
|
||||
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
)
|
||||
updateScreenLayout()
|
||||
|
||||
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||
}
|
||||
@@ -231,31 +290,72 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt()
|
||||
|
||||
fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
|
||||
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||
if (it.isSeparating) {
|
||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||
binding.surfaceEmulation.layoutParams.height = it.bounds.top
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx
|
||||
binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx)
|
||||
}
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
private fun updateOrientation() {
|
||||
emulationActivity?.let {
|
||||
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
|
||||
Settings.LayoutOption_MobileLandscape ->
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
Settings.LayoutOption_MobilePortrait ->
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
it.isSeparating
|
||||
} ?: false
|
||||
if (!isFolding) {
|
||||
binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.updatePadding(0, 0, 0, 0)
|
||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
}
|
||||
binding.surfaceInputOverlay.requestLayout()
|
||||
binding.inGameMenu.requestLayout()
|
||||
}
|
||||
|
||||
private fun updateScreenLayout() {
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
)
|
||||
emulationActivity?.buildPictureInPictureParams()
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
private fun updateFoldableLayout(
|
||||
emulationActivity: EmulationActivity,
|
||||
newLayoutInfo: WindowLayoutInfo
|
||||
) {
|
||||
val isFolding =
|
||||
(newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||
if (it.isSeparating) {
|
||||
emulationActivity.requestedOrientation =
|
||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||
// Restrict emulation and overlays to the top of the screen
|
||||
binding.emulationContainer.layoutParams.height = it.bounds.top
|
||||
binding.overlayContainer.layoutParams.height = it.bounds.top
|
||||
// Restrict input and menu drawer to the bottom of the screen
|
||||
binding.inputContainer.layoutParams.height = it.bounds.bottom
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
|
||||
isInFoldableLayout = true
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
||||
refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
it.isSeparating
|
||||
} ?: false
|
||||
if (!isFolding) {
|
||||
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
isInFoldableLayout = false
|
||||
updateOrientation()
|
||||
onConfigurationChanged(resources.configuration)
|
||||
}
|
||||
binding.emulationContainer.requestLayout()
|
||||
binding.inputContainer.requestLayout()
|
||||
binding.overlayContainer.requestLayout()
|
||||
binding.inGameMenu.requestLayout()
|
||||
}
|
||||
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
@@ -385,7 +485,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
popup.show()
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
private fun startConfiguringControls() {
|
||||
// Lock the current orientation to prevent editing inconsistencies
|
||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
|
||||
emulationActivity?.let {
|
||||
it.requestedOrientation =
|
||||
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
}
|
||||
@@ -393,6 +505,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private fun stopConfiguringControls() {
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
// Unlock the orientation if it was locked for editing
|
||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
|
||||
emulationActivity?.let {
|
||||
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -402,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
inputScaleSlider.apply {
|
||||
valueTo = 150F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
setControlScale(value.toInt())
|
||||
})
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
setControlScale(value.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
inputOpacitySlider.apply {
|
||||
valueTo = 100F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
setControlOpacity(value.toInt())
|
||||
})
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
setControlOpacity(value.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
|
||||
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
|
||||
@@ -445,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.inGameMenu
|
||||
) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
var left = 0
|
||||
var right = 0
|
||||
@@ -565,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
state = State.PAUSED
|
||||
}
|
||||
|
||||
State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.")
|
||||
else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.")
|
||||
State.PAUSED -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation paused."
|
||||
)
|
||||
else -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation stopped."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,7 +714,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
State.PAUSED -> {
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
NativeLibrary.unPauseEmulation()
|
||||
NativeLibrary.unpauseEmulation()
|
||||
}
|
||||
|
||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||
@@ -601,13 +729,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
||||
|
||||
fun newInstance(game: Game): EmulationFragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game)
|
||||
val fragment = EmulationFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,67 +68,109 @@ class HomeSettingsFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() },
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() },
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() },
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.supportsCustomDriverLoading()) {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) {
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!BuildConfig.PREMIUM) {
|
||||
optionsList.add(
|
||||
@@ -215,7 +257,11 @@ class HomeSettingsFragment : Fragment() {
|
||||
val intent = Intent(action)
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
return intent
|
||||
}
|
||||
|
||||
@@ -297,7 +343,9 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
|
||||
|
||||
@@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FilenameFilter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.getPublicFilesDir
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FilenameFilter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ImportExportSavesFragment : DialogFragment() {
|
||||
private val context = YuzuApplication.appContext
|
||||
@@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
val outputZipFile = File(
|
||||
tempFolder,
|
||||
"yuzu saves - ${
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
}.zip"
|
||||
)
|
||||
outputZipFile.createNewFile()
|
||||
@@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
saveFolder.walkTopDown().forEach { file ->
|
||||
val zipFileName =
|
||||
file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
|
||||
if (zipFileName == "")
|
||||
if (zipFileName == "") {
|
||||
return@forEach
|
||||
}
|
||||
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
|
||||
zos.putNextEntry(entry)
|
||||
if (file.isFile)
|
||||
if (file.isFile) {
|
||||
file.inputStream().use { fis -> fis.copyTo(zos) }
|
||||
}
|
||||
}
|
||||
}
|
||||
lastZipCreated = outputZipFile
|
||||
@@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val file = DocumentFile.fromSingleUri(
|
||||
context, DocumentsContract.buildDocumentUri(
|
||||
context,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
|
||||
|
||||
class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ class LicensesFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
||||
class LongMessageDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val titleId = requireArguments().getInt(TITLE)
|
||||
val description = requireArguments().getString(DESCRIPTION)
|
||||
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setPositiveButton(R.string.close, null)
|
||||
.setTitle(titleId)
|
||||
.setMessage(description)
|
||||
|
||||
if (helpLinkId != 0) {
|
||||
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
|
||||
openLink(getString(helpLinkId))
|
||||
}
|
||||
}
|
||||
|
||||
return dialog.show()
|
||||
}
|
||||
|
||||
private fun openLink(link: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "LongMessageDialogFragment"
|
||||
|
||||
private const val TITLE = "Title"
|
||||
private const val DESCRIPTION = "Description"
|
||||
private const val HELP_LINK = "Link"
|
||||
|
||||
fun newInstance(
|
||||
titleId: Int,
|
||||
description: String,
|
||||
helpLinkId: Int = 0
|
||||
): LongMessageDialogFragment {
|
||||
val dialog = LongMessageDialogFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.apply {
|
||||
putInt(TITLE, titleId)
|
||||
putString(DESCRIPTION, description)
|
||||
putInt(HELP_LINK, helpLinkId)
|
||||
}
|
||||
dialog.arguments = bundle
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
@@ -28,9 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.util.Locale
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
@@ -129,16 +127,13 @@ class SearchFragment : Fragment() {
|
||||
|
||||
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
|
||||
|
||||
R.id.chip_retail -> baseList.filter {
|
||||
FileUtil.hasExtension(it.path, "xci")
|
||||
|| FileUtil.hasExtension(it.path, "nsp")
|
||||
}
|
||||
R.id.chip_retail -> baseList.filter { !it.isHomebrew }
|
||||
|
||||
else -> baseList
|
||||
}
|
||||
|
||||
if (binding.searchText.text.toString().isEmpty()
|
||||
&& binding.chipGroup.checkedChipId != View.NO_ID
|
||||
if (binding.searchText.text.toString().isEmpty() &&
|
||||
binding.chipGroup.checkedChipId != View.NO_ID
|
||||
) {
|
||||
gamesViewModel.setSearchedGames(filteredList)
|
||||
return
|
||||
@@ -173,14 +168,16 @@ class SearchFragment : Fragment() {
|
||||
private fun focusSearch() {
|
||||
if (_binding != null) {
|
||||
binding.searchText.requestFocus()
|
||||
val imm =
|
||||
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
val imm = requireActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.SetupAdapter
|
||||
@@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import java.io.File
|
||||
|
||||
class SetupFragment : Fragment() {
|
||||
private var _binding: FragmentSetupBinding? = null
|
||||
@@ -82,7 +82,8 @@ class SetupFragment : Fragment() {
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
requireActivity().window.navigationBarColor =
|
||||
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||
@@ -148,14 +149,20 @@ class SetupFragment : Fragment() {
|
||||
R.drawable.ic_add,
|
||||
true,
|
||||
R.string.add_games,
|
||||
{ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
{
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
},
|
||||
true,
|
||||
R.string.add_games_warning,
|
||||
R.string.add_games_warning_description,
|
||||
R.string.add_games_warning_help,
|
||||
{
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
PreferenceManager.getDefaultSharedPreferences(
|
||||
YuzuApplication.appContext
|
||||
)
|
||||
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
|
||||
}
|
||||
)
|
||||
@@ -260,7 +267,9 @@ class SetupFragment : Fragment() {
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private val permissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
|
||||
if (!it &&
|
||||
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
) {
|
||||
PermissionDeniedDialogFragment().show(
|
||||
childFragmentManager,
|
||||
PermissionDeniedDialogFragment.TAG
|
||||
@@ -315,7 +324,9 @@ class SetupFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
view.setPadding(
|
||||
|
||||
@@ -44,7 +44,9 @@ class AutofitGridLayoutManager(
|
||||
override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
|
||||
val width = width
|
||||
val height = height
|
||||
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
|
||||
if (columnWidth > 0 && width > 0 && height > 0 &&
|
||||
(isColumnWidthChanged || lastWidth != width || lastHeight != height)
|
||||
) {
|
||||
val totalSpace: Int = if (orientation == VERTICAL) {
|
||||
width - paddingRight - paddingLeft
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import java.util.HashSet
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.HashSet
|
||||
|
||||
@Parcelize
|
||||
@Serializable
|
||||
@@ -23,8 +23,9 @@ class Game(
|
||||
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Game)
|
||||
if (other !is Game) {
|
||||
return false
|
||||
}
|
||||
|
||||
return hashCode() == other.hashCode()
|
||||
}
|
||||
@@ -42,7 +43,7 @@ class Game(
|
||||
|
||||
companion object {
|
||||
val extensions: Set<String> = HashSet(
|
||||
listOf(".xci", ".nsp", ".nca", ".nro")
|
||||
listOf("xci", "nsp", "nca", "nro")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GamesViewModel : ViewModel() {
|
||||
@@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun reloadGames(directoryChanged: Boolean) {
|
||||
if (isReloading.value == true)
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.postValue(true)
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Point
|
||||
@@ -24,6 +23,8 @@ import android.view.WindowInsets
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
|
||||
import org.yuzu.yuzu_emu.NativeLibrary.StickType
|
||||
@@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Draws the interactive input overlay on top of the
|
||||
* [SurfaceView] that is rendering emulation.
|
||||
*/
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs),
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
SurfaceView(context, attrs),
|
||||
OnTouchListener {
|
||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
@@ -51,12 +51,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
private lateinit var windowInsets: WindowInsets
|
||||
|
||||
var orientation = LANDSCAPE
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
|
||||
windowInsets = rootWindowInsets
|
||||
|
||||
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
|
||||
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||
defaultOverlay()
|
||||
}
|
||||
|
||||
@@ -93,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
var shouldUpdateView = false
|
||||
val playerIndex =
|
||||
if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
|
||||
if (NativeLibrary.isHandheldOnly()) {
|
||||
NativeLibrary.ConsoleDevice
|
||||
} else {
|
||||
NativeLibrary.Player1Device
|
||||
}
|
||||
|
||||
for (button in overlayButtons) {
|
||||
if (!button.updateStatus(event)) {
|
||||
@@ -156,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
shouldUpdateView = true
|
||||
}
|
||||
|
||||
if (shouldUpdateView)
|
||||
if (shouldUpdateView) {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
|
||||
return true
|
||||
@@ -233,10 +240,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
val fingerPositionX = event.getX(pointerIndex).toInt()
|
||||
val fingerPositionY = event.getY(pointerIndex).toInt()
|
||||
|
||||
// TODO: Provide support for portrait layout
|
||||
//val orientation =
|
||||
// if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
|
||||
|
||||
for (button in overlayButtons) {
|
||||
// Determine the button state to apply based on the MotionEvent action flag.
|
||||
when (event.action and MotionEvent.ACTION_MASK) {
|
||||
@@ -245,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
// If no button is being moved now, remember the currently touched button to move.
|
||||
if (buttonBeingConfigured == null &&
|
||||
button.bounds.contains(
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
) {
|
||||
buttonBeingConfigured = button
|
||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||
@@ -266,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
buttonBeingConfigured!!.buttonId,
|
||||
buttonBeingConfigured!!.bounds.centerX(),
|
||||
buttonBeingConfigured!!.bounds.centerY(),
|
||||
""
|
||||
orientation
|
||||
)
|
||||
buttonBeingConfigured = null
|
||||
}
|
||||
@@ -299,7 +302,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
dpadBeingConfigured!!.upId,
|
||||
dpadBeingConfigured!!.bounds.centerX(),
|
||||
dpadBeingConfigured!!.bounds.centerY(),
|
||||
""
|
||||
orientation
|
||||
)
|
||||
dpadBeingConfigured = null
|
||||
}
|
||||
@@ -311,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
|
||||
joystick.bounds.contains(
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
) {
|
||||
joystickBeingConfigured = joystick
|
||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
||||
@@ -330,7 +333,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
joystickBeingConfigured!!.buttonId,
|
||||
joystickBeingConfigured!!.bounds.centerX(),
|
||||
joystickBeingConfigured!!.bounds.centerY(),
|
||||
""
|
||||
orientation
|
||||
)
|
||||
joystickBeingConfigured = null
|
||||
}
|
||||
@@ -533,8 +536,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.clear()
|
||||
overlayDpads.clear()
|
||||
overlayJoysticks.clear()
|
||||
val orientation =
|
||||
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
|
||||
|
||||
// Add all the enabled overlay items back to the HashSet.
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
@@ -548,8 +549,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||
.putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
|
||||
.putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
|
||||
.putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x)
|
||||
.putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y)
|
||||
.apply()
|
||||
}
|
||||
|
||||
@@ -558,145 +559,250 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
}
|
||||
|
||||
private fun defaultOverlay() {
|
||||
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
|
||||
defaultOverlayLandscape()
|
||||
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||
defaultOverlayByLayout(orientation)
|
||||
}
|
||||
|
||||
resetButtonPlacement()
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_OVERLAY_INIT, true)
|
||||
.putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun resetButtonPlacement() {
|
||||
defaultOverlayLandscape()
|
||||
defaultOverlayByLayout(orientation)
|
||||
refreshControls()
|
||||
}
|
||||
|
||||
private fun defaultOverlayLandscape() {
|
||||
private val landscapeResources = arrayOf(
|
||||
R.integer.SWITCH_BUTTON_A_X,
|
||||
R.integer.SWITCH_BUTTON_A_Y,
|
||||
R.integer.SWITCH_BUTTON_B_X,
|
||||
R.integer.SWITCH_BUTTON_B_Y,
|
||||
R.integer.SWITCH_BUTTON_X_X,
|
||||
R.integer.SWITCH_BUTTON_X_Y,
|
||||
R.integer.SWITCH_BUTTON_Y_X,
|
||||
R.integer.SWITCH_BUTTON_Y_Y,
|
||||
R.integer.SWITCH_TRIGGER_ZL_X,
|
||||
R.integer.SWITCH_TRIGGER_ZL_Y,
|
||||
R.integer.SWITCH_TRIGGER_ZR_X,
|
||||
R.integer.SWITCH_TRIGGER_ZR_Y,
|
||||
R.integer.SWITCH_BUTTON_DPAD_X,
|
||||
R.integer.SWITCH_BUTTON_DPAD_Y,
|
||||
R.integer.SWITCH_TRIGGER_L_X,
|
||||
R.integer.SWITCH_TRIGGER_L_Y,
|
||||
R.integer.SWITCH_TRIGGER_R_X,
|
||||
R.integer.SWITCH_TRIGGER_R_Y,
|
||||
R.integer.SWITCH_BUTTON_PLUS_X,
|
||||
R.integer.SWITCH_BUTTON_PLUS_Y,
|
||||
R.integer.SWITCH_BUTTON_MINUS_X,
|
||||
R.integer.SWITCH_BUTTON_MINUS_Y,
|
||||
R.integer.SWITCH_BUTTON_HOME_X,
|
||||
R.integer.SWITCH_BUTTON_HOME_Y,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_X,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_Y,
|
||||
R.integer.SWITCH_STICK_R_X,
|
||||
R.integer.SWITCH_STICK_R_Y,
|
||||
R.integer.SWITCH_STICK_L_X,
|
||||
R.integer.SWITCH_STICK_L_Y
|
||||
)
|
||||
|
||||
private val portraitResources = arrayOf(
|
||||
R.integer.SWITCH_BUTTON_A_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_A_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_B_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_B_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_X_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_X_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_Y_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_L_X_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_R_X_PORTRAIT,
|
||||
R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_R_X_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_R_Y_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_L_X_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_L_Y_PORTRAIT
|
||||
)
|
||||
|
||||
private val foldableResources = arrayOf(
|
||||
R.integer.SWITCH_BUTTON_A_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_A_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_B_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_B_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_X_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_X_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_Y_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_L_X_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_R_X_FOLDABLE,
|
||||
R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_R_X_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_R_Y_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_L_X_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_L_Y_FOLDABLE
|
||||
)
|
||||
|
||||
private fun getResourceValue(orientation: String, position: Int): Float {
|
||||
return when (orientation) {
|
||||
PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
|
||||
FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
|
||||
else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultOverlayByLayout(orientation: String) {
|
||||
// Each value represents the position of the button in relation to the screen size without insets.
|
||||
preferences.edit()
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_A.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 0)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_A.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 1)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_B.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 2)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_B.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 3)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_X.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 4)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_X.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 5)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_Y.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 6)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_Y.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 7)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000
|
||||
ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 8)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000
|
||||
ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 9)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000
|
||||
ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 10)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000
|
||||
ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 11)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000
|
||||
ButtonType.DPAD_UP.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 12)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000
|
||||
ButtonType.DPAD_UP.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 13)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000
|
||||
ButtonType.TRIGGER_L.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 14)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000
|
||||
ButtonType.TRIGGER_L.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 15)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000
|
||||
ButtonType.TRIGGER_R.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 16)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000
|
||||
ButtonType.TRIGGER_R.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 17)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 18)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 19)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 20)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 21)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000
|
||||
ButtonType.BUTTON_HOME.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 22)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000
|
||||
ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 23)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
|
||||
.toFloat() / 1000
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 24)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
|
||||
.toFloat() / 1000
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 25)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000
|
||||
ButtonType.STICK_R.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 26)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000
|
||||
ButtonType.STICK_R.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 27)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000
|
||||
ButtonType.STICK_L.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 28)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000
|
||||
ButtonType.STICK_L.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 29)
|
||||
)
|
||||
.apply()
|
||||
}
|
||||
@@ -709,13 +815,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
private val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
const val LANDSCAPE = ""
|
||||
const val PORTRAIT = "_Portrait"
|
||||
const val FOLDABLE = "_Foldable"
|
||||
|
||||
/**
|
||||
* Resizes a [Bitmap] by a given scale factor
|
||||
*
|
||||
* @param context Context for getting the vector drawable
|
||||
* @param drawableId The ID of the drawable to scale.
|
||||
* @param scale The scale factor for the bitmap.
|
||||
* @return The scaled [Bitmap]
|
||||
* @return The scaled [Bitmap]
|
||||
*/
|
||||
private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
|
||||
val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
|
||||
@@ -749,14 +859,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* Gets the safe screen size for drawing the overlay
|
||||
*
|
||||
* @param context Context for getting the window metrics
|
||||
* @return A pair of points, the first being the top left corner of the safe area,
|
||||
* @return A pair of points, the first being the top left corner of the safe area,
|
||||
* the second being the bottom right corner of the safe area
|
||||
*/
|
||||
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||
// Get screen size
|
||||
val windowMetrics =
|
||||
WindowMetricsCalculator.getOrCreate()
|
||||
.computeCurrentWindowMetrics(context as Activity)
|
||||
val windowMetrics = WindowMetricsCalculator.getOrCreate()
|
||||
.computeCurrentWindowMetrics(context as Activity)
|
||||
var maxY = windowMetrics.bounds.height().toFloat()
|
||||
var maxX = windowMetrics.bounds.width().toFloat()
|
||||
var minY = 0
|
||||
@@ -768,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
|
||||
if (insets != null) {
|
||||
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
|
||||
insets.boundingRectTop.bottom.toFloat() else maxY
|
||||
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
|
||||
insets.boundingRectRight.left.toFloat() else maxX
|
||||
if (insets.boundingRectTop.bottom != 0 &&
|
||||
insets.boundingRectTop.bottom > maxY / 2
|
||||
) {
|
||||
maxY = insets.boundingRectTop.bottom.toFloat()
|
||||
}
|
||||
if (insets.boundingRectRight.left != 0 &&
|
||||
insets.boundingRectRight.left > maxX / 2
|
||||
) {
|
||||
maxX = insets.boundingRectRight.left.toFloat()
|
||||
}
|
||||
|
||||
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
|
||||
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
|
||||
@@ -878,8 +993,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val xKey = "$buttonId$orientation-X"
|
||||
val yKey = "$buttonId$orientation-Y"
|
||||
val xKey = "$buttonId-X$orientation"
|
||||
val yKey = "$buttonId-Y$orientation"
|
||||
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
||||
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
@@ -959,8 +1074,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f)
|
||||
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val width = overlayDrawable.width
|
||||
@@ -1026,8 +1141,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f)
|
||||
val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val outerScale = 1.66f
|
||||
|
||||
@@ -133,7 +133,10 @@ class InputOverlayDrawableDpad(
|
||||
downButtonState = axisY > VIRT_AXIS_DEADZONE
|
||||
leftButtonState = axisX < -VIRT_AXIS_DEADZONE
|
||||
rightButtonState = axisX > VIRT_AXIS_DEADZONE
|
||||
return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState
|
||||
return oldUpState != upButtonState ||
|
||||
oldDownState != downButtonState ||
|
||||
oldLeftState != leftButtonState ||
|
||||
oldRightState != rightButtonState
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick(
|
||||
private fun setInnerBounds() {
|
||||
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
|
||||
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
|
||||
if (x > virtBounds.centerX() + virtBounds.width() / 2) x =
|
||||
virtBounds.centerX() + virtBounds.width() / 2
|
||||
if (x < virtBounds.centerX() - virtBounds.width() / 2) x =
|
||||
virtBounds.centerX() - virtBounds.width() / 2
|
||||
if (y > virtBounds.centerY() + virtBounds.height() / 2) y =
|
||||
virtBounds.centerY() + virtBounds.height() / 2
|
||||
if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
|
||||
virtBounds.centerY() - virtBounds.height() / 2
|
||||
if (x > virtBounds.centerX() + virtBounds.width() / 2) {
|
||||
x =
|
||||
virtBounds.centerX() + virtBounds.width() / 2
|
||||
}
|
||||
if (x < virtBounds.centerX() - virtBounds.width() / 2) {
|
||||
x =
|
||||
virtBounds.centerX() - virtBounds.width() / 2
|
||||
}
|
||||
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
|
||||
y =
|
||||
virtBounds.centerY() + virtBounds.height() / 2
|
||||
}
|
||||
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
|
||||
y =
|
||||
virtBounds.centerY() - virtBounds.height() / 2
|
||||
}
|
||||
val width = pressedStateInnerBitmap.bounds.width() / 2
|
||||
val height = pressedStateInnerBitmap.bounds.height() / 2
|
||||
defaultStateInnerBitmap.setBounds(
|
||||
|
||||
@@ -99,7 +99,9 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
|
||||
if (shouldSwapData) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!)
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value!!
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +130,9 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
@@ -26,6 +27,9 @@ import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -39,13 +43,11 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
|
||||
class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -86,7 +88,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
ThemeHelper.SYSTEM_BAR_ALPHA
|
||||
)
|
||||
)
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||
InsetsHelper.GESTURE_NAVIGATION
|
||||
) {
|
||||
binding.navigationBarShade.setBackgroundColor(
|
||||
ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
@@ -172,7 +176,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
binding.navigationView.height.toFloat() * 2
|
||||
translationY(0f)
|
||||
} else {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR
|
||||
) {
|
||||
binding.navigationView.translationX =
|
||||
binding.navigationView.width.toFloat() * -2
|
||||
translationX(0f)
|
||||
@@ -189,7 +195,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (smallLayout) {
|
||||
translationY(binding.navigationView.height.toFloat() * 2)
|
||||
} else {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR
|
||||
) {
|
||||
translationX(binding.navigationView.width.toFloat() * -2)
|
||||
} else {
|
||||
translationX(binding.navigationView.width.toFloat() * 2)
|
||||
@@ -234,7 +242,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
|
||||
mlpStatusShade.height = insets.top
|
||||
@@ -256,8 +266,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
@@ -281,10 +292,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getProdKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "keys")) {
|
||||
if (FileUtil.getExtension(result) != "keys") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_prod_keys_failure_extension_description
|
||||
@@ -324,8 +336,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getFirmware =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val inputZip = contentResolver.openInputStream(result)
|
||||
if (inputZip == null) {
|
||||
@@ -376,10 +389,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getAmiiboKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "bin")) {
|
||||
if (FileUtil.getExtension(result) != "bin") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_amiibo_keys_failure_extension_description
|
||||
@@ -418,8 +432,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getDriver =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
@@ -467,4 +482,111 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val installGameUpdate = registerForActivityResult(
|
||||
ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { documents: List<Uri> ->
|
||||
if (documents.isNotEmpty()) {
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
this@MainActivity,
|
||||
R.string.install_game_content
|
||||
) {
|
||||
var installSuccess = 0
|
||||
var installOverwrite = 0
|
||||
var errorBaseGame = 0
|
||||
var errorExtension = 0
|
||||
var errorOther = 0
|
||||
var errorTotal = 0
|
||||
lifecycleScope.launch {
|
||||
documents.forEach {
|
||||
when (NativeLibrary.installFileToNand(it.toString())) {
|
||||
NativeLibrary.InstallFileToNandResult.Success -> {
|
||||
installSuccess += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
|
||||
installOverwrite += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
|
||||
errorBaseGame += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
|
||||
errorExtension += 1
|
||||
}
|
||||
|
||||
else -> {
|
||||
errorOther += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
val separator = System.getProperty("line.separator") ?: "\n"
|
||||
val installResult = StringBuilder()
|
||||
if (installSuccess > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_install,
|
||||
installSuccess
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (installOverwrite > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_overwrite,
|
||||
installOverwrite
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
errorTotal = errorBaseGame + errorExtension + errorOther
|
||||
if (errorTotal > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_failed_count,
|
||||
errorTotal
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
if (errorBaseGame > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_base)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (errorExtension > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_file_extension)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (errorOther > 0) {
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_description)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
LongMessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
installResult.toString().trim(),
|
||||
R.string.install_game_content_help_link
|
||||
).show(supportFragmentManager, LongMessageDialogFragment.TAG)
|
||||
} else {
|
||||
LongMessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_success,
|
||||
installResult.toString().trim()
|
||||
).show(supportFragmentManager, LongMessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
return@newInstance installSuccess + installOverwrite + errorTotal
|
||||
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ class ControllerMappingHelper {
|
||||
// The two analog triggers generate analog motion events as well as a keycode.
|
||||
// We always prefer to use the analog values, so throw away the button press
|
||||
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
object DirectoryInitialization {
|
||||
private var userPath: String? = null
|
||||
|
||||
@@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
|
||||
class DocumentsTree {
|
||||
private var root: DocumentsNode? = null
|
||||
@@ -29,13 +29,20 @@ class DocumentsTree {
|
||||
val node = resolvePath(filepath)
|
||||
return if (node == null || node.isDirectory) {
|
||||
0
|
||||
} else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
|
||||
} else {
|
||||
FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun exists(filepath: String): Boolean {
|
||||
return resolvePath(filepath) != null
|
||||
}
|
||||
|
||||
fun isDirectory(filepath: String): Boolean {
|
||||
val node = resolvePath(filepath)
|
||||
return node != null && node.isDirectory
|
||||
}
|
||||
|
||||
private fun resolvePath(filepath: String): DocumentsNode? {
|
||||
val tokens = StringTokenizer(filepath, File.separator, false)
|
||||
var iterator = root
|
||||
@@ -106,7 +113,9 @@ class DocumentsTree {
|
||||
fun isNativePath(path: String): Boolean {
|
||||
return if (path.isNotEmpty()) {
|
||||
path[0] == '/'
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,6 @@ object EmulationMenuSettings {
|
||||
private val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
// These must match what is defined in src/core/settings.h
|
||||
const val LayoutOption_Default = 0
|
||||
const val LayoutOption_SingleScreen = 1
|
||||
const val LayoutOption_LargeScreen = 2
|
||||
const val LayoutOption_SideScreen = 3
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
||||
var joystickRelCenter: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
||||
set(value) {
|
||||
@@ -41,16 +33,6 @@ object EmulationMenuSettings {
|
||||
.apply()
|
||||
}
|
||||
|
||||
var landscapeScreenLayout: Int
|
||||
get() = preferences.getInt(
|
||||
Settings.PREF_MENU_SETTINGS_LANDSCAPE,
|
||||
LayoutOption_MobileLandscape
|
||||
)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_MENU_SETTINGS_LANDSCAPE, value)
|
||||
.apply()
|
||||
}
|
||||
var showFps: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
||||
set(value) {
|
||||
|
||||
@@ -7,10 +7,7 @@ import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -19,6 +16,8 @@ import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
|
||||
object FileUtil {
|
||||
const val PATH_TREE = "tree"
|
||||
@@ -185,19 +184,18 @@ object FileUtil {
|
||||
|
||||
/**
|
||||
* Get file display name from given path
|
||||
* @param path content uri path
|
||||
* @param uri content uri
|
||||
* @return String display name
|
||||
*/
|
||||
fun getFilename(context: Context, path: String): String {
|
||||
val resolver = context.contentResolver
|
||||
fun getFilename(uri: Uri): String {
|
||||
val resolver = YuzuApplication.appContext.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME
|
||||
)
|
||||
var filename = ""
|
||||
var c: Cursor? = null
|
||||
try {
|
||||
val mUri = Uri.parse(path)
|
||||
c = resolver.query(mUri, columns, null, null, null)
|
||||
c = resolver.query(uri, columns, null, null, null)
|
||||
c!!.moveToNext()
|
||||
filename = c.getString(0)
|
||||
} catch (e: Exception) {
|
||||
@@ -326,25 +324,9 @@ object FileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasExtension(path: String, extension: String): Boolean =
|
||||
path.substring(path.lastIndexOf(".") + 1).contains(extension)
|
||||
|
||||
fun hasExtension(uri: Uri, extension: String): Boolean {
|
||||
val fileName: String?
|
||||
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
|
||||
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor?.moveToFirst()
|
||||
|
||||
if (nameIndex == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
fileName = cursor.getString(nameIndex)
|
||||
cursor.close()
|
||||
|
||||
if (fileName == null) {
|
||||
return false
|
||||
}
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
|
||||
fun getExtension(uri: Uri): String {
|
||||
val fileName = getFilename(uri)
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
||||
.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class ForegroundService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) {
|
||||
return START_NOT_STICKY;
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
if (intent.action == ACTION_STOP) {
|
||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
||||
|
||||
@@ -11,7 +11,6 @@ import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import java.util.*
|
||||
|
||||
object GameHelper {
|
||||
const val KEY_GAME_PATH = "game_path"
|
||||
@@ -33,15 +32,9 @@ object GameHelper {
|
||||
val children = FileUtil.listFiles(context, gamesUri)
|
||||
for (file in children) {
|
||||
if (!file.isDirectory) {
|
||||
val filename = file.uri.toString()
|
||||
val extensionStart = filename.lastIndexOf('.')
|
||||
if (extensionStart > 0) {
|
||||
val fileExtension = filename.substring(extensionStart)
|
||||
|
||||
// Check that the file has an extension we care about before trying to read out of it.
|
||||
if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
|
||||
games.add(getGame(filename))
|
||||
}
|
||||
// Check that the file has an extension we care about before trying to read out of it.
|
||||
if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
|
||||
games.add(getGame(file.uri))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,21 +52,19 @@ object GameHelper {
|
||||
return games.toList()
|
||||
}
|
||||
|
||||
private fun getGame(filePath: String): Game {
|
||||
private fun getGame(uri: Uri): Game {
|
||||
val filePath = uri.toString()
|
||||
var name = NativeLibrary.getTitle(filePath)
|
||||
|
||||
// If the game's title field is empty, use the filename.
|
||||
if (name.isEmpty()) {
|
||||
name = filePath.substring(filePath.lastIndexOf("/") + 1)
|
||||
name = FileUtil.getFilename(uri)
|
||||
}
|
||||
var gameId = NativeLibrary.getGameId(filePath)
|
||||
|
||||
// If the game's ID field is empty, use the filename without extension.
|
||||
if (gameId.isEmpty()) {
|
||||
gameId = filePath.substring(
|
||||
filePath.lastIndexOf("/") + 1,
|
||||
filePath.lastIndexOf(".")
|
||||
)
|
||||
gameId = name.substring(0, name.lastIndexOf("."))
|
||||
}
|
||||
|
||||
val newGame = Game(
|
||||
|
||||
@@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipInputStream
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
||||
|
||||
object GpuDriverHelper {
|
||||
private const val META_JSON_FILENAME = "meta.json"
|
||||
@@ -113,6 +113,8 @@ object GpuDriverHelper {
|
||||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
external fun supportsCustomDriverLoading(): Boolean
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val customDriverName: String?
|
||||
get() {
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class GpuDriverMetadata(metadataFilePath: String) {
|
||||
var name: String? = null
|
||||
|
||||
@@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
class InputHandler {
|
||||
fun initialize() {
|
||||
@@ -68,7 +68,11 @@ class InputHandler {
|
||||
6 -> NativeLibrary.Player6Device
|
||||
7 -> NativeLibrary.Player7Device
|
||||
8 -> NativeLibrary.Player8Device
|
||||
else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
|
||||
else -> if (NativeLibrary.isHandheldOnly()) {
|
||||
NativeLibrary.ConsoleDevice
|
||||
} else {
|
||||
NativeLibrary.Player1Device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +111,11 @@ class InputHandler {
|
||||
}
|
||||
|
||||
private fun getAxisToButton(axis: Float): Int {
|
||||
return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
|
||||
return if (axis > 0.5f) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
} else {
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
|
||||
@@ -287,7 +295,6 @@ class InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
||||
// Joycon support is half dead. Right joystick doesn't work
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
||||
@@ -355,6 +362,4 @@ class InputHandler {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
|
||||
object InsetsHelper {
|
||||
const val THREE_BUTTON_NAVIGATION = 0
|
||||
@@ -20,12 +18,8 @@ object InsetsHelper {
|
||||
resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
|
||||
return if (resourceId != 0) {
|
||||
resources.getInteger(resourceId)
|
||||
} else 0
|
||||
}
|
||||
|
||||
fun getBottomPaddingRequired(activity: Activity): Int {
|
||||
val visibleFrame = Rect()
|
||||
activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
|
||||
return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import java.util.Locale
|
||||
|
||||
class MemoryUtil(val context: Context) {
|
||||
|
||||
private val Long.floatForm: String
|
||||
get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
|
||||
|
||||
private fun bytesToSizeUnit(size: Long): String {
|
||||
return when {
|
||||
size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
|
||||
size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
|
||||
size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
|
||||
size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
|
||||
size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
|
||||
size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
|
||||
else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
|
||||
}
|
||||
}
|
||||
|
||||
private val totalMemory =
|
||||
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
|
||||
val memInfo = ActivityManager.MemoryInfo()
|
||||
getMemoryInfo(memInfo)
|
||||
memInfo.totalMem
|
||||
}
|
||||
|
||||
fun isLessThan(minimum: Int, size: Long): Boolean {
|
||||
return when (size) {
|
||||
Kb -> totalMemory < Mb && totalMemory < minimum
|
||||
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
|
||||
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
|
||||
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
|
||||
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
|
||||
Eb -> totalMemory / Eb < minimum
|
||||
else -> totalMemory < Kb && totalMemory < minimum
|
||||
}
|
||||
}
|
||||
|
||||
fun getDeviceRAM(): String {
|
||||
return bytesToSizeUnit(totalMemory)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Kb: Long = 1024
|
||||
const val Mb = Kb * 1024
|
||||
const val Gb = Mb * 1024
|
||||
const val Tb = Gb * 1024
|
||||
const val Pb = Tb * 1024
|
||||
const val Eb = Pb * 1024
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ import android.nfc.tech.NfcA
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
class NfcReader(private val activity: Activity) {
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
@@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) {
|
||||
|
||||
pendingIntent = PendingIntent.getActivity(
|
||||
activity,
|
||||
0, Intent(activity, activity.javaClass),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
0,
|
||||
Intent(activity, activity.javaClass),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
)
|
||||
|
||||
val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
|
||||
@@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) {
|
||||
|
||||
fun onNewIntent(intent: Intent) {
|
||||
val action = intent.action
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED != action
|
||||
&& NfcAdapter.ACTION_TECH_DISCOVERED != action
|
||||
&& NfcAdapter.ACTION_NDEF_DISCOVERED != action
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED != action &&
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED != action &&
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED != action
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) {
|
||||
}
|
||||
|
||||
private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
|
||||
val bufferSize = amiibo.maxTransceiveLength;
|
||||
val bufferSize = amiibo.maxTransceiveLength
|
||||
val tagSize = 0x21C
|
||||
val pageSize = 4
|
||||
val lastPage = tagSize / pageSize - 1
|
||||
@@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) {
|
||||
val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
|
||||
System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
|
||||
} catch (e: IOException) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
return tagData
|
||||
|
||||
@@ -11,30 +11,34 @@ import java.io.Serializable
|
||||
|
||||
object SerializableHelper {
|
||||
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getSerializable(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getSerializable(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getSerializableExtra(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getSerializableExtra(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelable(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getParcelable(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelableExtra(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getParcelableExtra(key) as? T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.ui.main.ThemeProvider
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object ThemeHelper {
|
||||
const val SYSTEM_BAR_ALPHA = 0.9f
|
||||
@@ -36,8 +34,8 @@ object ThemeHelper {
|
||||
// Using a specific night mode check because this could apply incorrectly when using the
|
||||
// light app mode, dark system mode, and black backgrounds. Launching the settings activity
|
||||
// will then show light mode colors/navigation bars but with black backgrounds.
|
||||
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
|
||||
&& isNightMode(activity)
|
||||
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
|
||||
isNightMode(activity)
|
||||
) {
|
||||
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
|
||||
}
|
||||
@@ -46,8 +44,10 @@ object ThemeHelper {
|
||||
@ColorInt
|
||||
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
|
||||
return Color.argb(
|
||||
(alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color),
|
||||
Color.green(color), Color.blue(color)
|
||||
(alphaFactor * Color.alpha(color)).roundToInt(),
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,11 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
|
||||
newWidth = width
|
||||
newHeight = (width / aspectRatio).roundToInt()
|
||||
}
|
||||
setMeasuredDimension(newWidth, newHeight)
|
||||
val left = (width - newWidth) / 2
|
||||
val top = (height - newHeight) / 2
|
||||
setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
|
||||
} else {
|
||||
setMeasuredDimension(width, height)
|
||||
setLeftTopRightBottom(0, 0, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ add_library(yuzu-android SHARED
|
||||
id_cache.cpp
|
||||
id_cache.h
|
||||
native.cpp
|
||||
native.h
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
||||
@@ -235,9 +235,13 @@ void Config::ReadValues() {
|
||||
Settings::values.async_presentation =
|
||||
config->GetBoolean("Renderer", "async_presentation", true);
|
||||
|
||||
// Enable force_max_clock by default on Android
|
||||
// Disable force_max_clock by default on Android
|
||||
Settings::values.renderer_force_max_clock =
|
||||
config->GetBoolean("Renderer", "force_max_clock", true);
|
||||
config->GetBoolean("Renderer", "force_max_clock", false);
|
||||
|
||||
// Disable use_reactive_flushing by default on Android
|
||||
Settings::values.use_reactive_flushing =
|
||||
config->GetBoolean("Renderer", "use_reactive_flushing", false);
|
||||
|
||||
// Audio
|
||||
ReadSetting("Audio", Settings::values.sink_id);
|
||||
|
||||
@@ -251,7 +251,7 @@ backend =
|
||||
# 0: Off, 1 (default): On
|
||||
async_presentation =
|
||||
|
||||
# Enable graphics API debugging mode.
|
||||
# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
|
||||
# 0 (default): Disabled, 1: Enabled
|
||||
force_max_clock =
|
||||
|
||||
@@ -328,6 +328,10 @@ shader_backend =
|
||||
# 0 (default): Off, 1: On
|
||||
use_asynchronous_shaders =
|
||||
|
||||
# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
|
||||
# 0 (default): Off, 1: On
|
||||
use_reactive_flushing =
|
||||
|
||||
# NVDEC emulation.
|
||||
# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
|
||||
nvdec_emulation =
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <android/api-level.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <core/loader/nro.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
@@ -28,7 +29,10 @@
|
||||
#include "core/core.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
@@ -56,6 +60,9 @@
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#define jconst [[maybe_unused]] const auto
|
||||
#define jauto [[maybe_unused]] auto
|
||||
|
||||
namespace {
|
||||
|
||||
class EmulationSession final {
|
||||
@@ -94,6 +101,74 @@ public:
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
int InstallFileToNand(std::string filename) {
|
||||
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
|
||||
std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!dest->Resize(src->GetSize())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
[[maybe_unused]] std::vector<u8> buffer(1_MiB);
|
||||
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||
jconst read = src->Read(buffer.data(), buffer.size(), i);
|
||||
dest->Write(buffer.data(), read, i);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
enum InstallResult {
|
||||
Success = 0,
|
||||
SuccessFileOverwritten = 1,
|
||||
InstallError = 2,
|
||||
ErrorBaseGame = 3,
|
||||
ErrorFilenameExtension = 4,
|
||||
};
|
||||
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
|
||||
if (filename.ends_with("nsp")) {
|
||||
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
if (nsp->IsExtractedType()) {
|
||||
return InstallError;
|
||||
}
|
||||
} else if (filename.ends_with("xci")) {
|
||||
jconst xci =
|
||||
std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
nsp = xci->GetSecurePartitionNSP();
|
||||
} else {
|
||||
return ErrorFilenameExtension;
|
||||
}
|
||||
|
||||
if (!nsp) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
*nsp, true, copy_func);
|
||||
|
||||
switch (res) {
|
||||
case FileSys::InstallResult::Success:
|
||||
return Success;
|
||||
case FileSys::InstallResult::OverwriteExisting:
|
||||
return SuccessFileOverwritten;
|
||||
case FileSys::InstallResult::ErrorBaseInstall:
|
||||
return ErrorBaseGame;
|
||||
default:
|
||||
return InstallError;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||
const std::string& custom_driver_name,
|
||||
const std::string& file_redirect_dir) {
|
||||
@@ -131,6 +206,11 @@ public:
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
bool IsPaused() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_is_running && m_is_paused;
|
||||
}
|
||||
|
||||
const Core::PerfStatsResults& PerfStats() const {
|
||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||
return m_perf_stats;
|
||||
@@ -154,14 +234,15 @@ public:
|
||||
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
|
||||
m_vulkan_library);
|
||||
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
|
||||
// Initialize system.
|
||||
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
m_software_keyboard = android_keyboard.get();
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
m_system.SetAppletFrontendSet({
|
||||
nullptr, // Amiibo Settings
|
||||
nullptr, // Controller Selector
|
||||
@@ -173,7 +254,8 @@ public:
|
||||
std::move(android_keyboard), // Software Keyboard
|
||||
nullptr, // Web Browser
|
||||
});
|
||||
m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem());
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
// Initialize account manager
|
||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
||||
@@ -215,11 +297,13 @@ public:
|
||||
void PauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Pause();
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void UnPauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Run();
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void HaltEmulation() {
|
||||
@@ -251,7 +335,7 @@ public:
|
||||
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||
if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
@@ -283,7 +367,7 @@ public:
|
||||
}
|
||||
|
||||
bool IsHandheldOnly() {
|
||||
const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
return false;
|
||||
@@ -296,17 +380,17 @@ public:
|
||||
return !Settings::values.use_docked_mode.GetValue();
|
||||
}
|
||||
|
||||
void SetDeviceType(int index, int type) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void OnGamepadConnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
auto handheld =
|
||||
jauto handheld =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
@@ -318,7 +402,8 @@ public:
|
||||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||
auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
jauto player1 =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
@@ -332,8 +417,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void OnGamepadDisconnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
|
||||
@@ -349,7 +434,7 @@ private:
|
||||
};
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path) {
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
@@ -357,14 +442,14 @@ private:
|
||||
}
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
jconst file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadIcon(entry.icon);
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
jauto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
@@ -398,9 +483,10 @@ private:
|
||||
InputCommon::InputSubsystem m_input_subsystem;
|
||||
Common::DetachedTasks m_detached_tasks;
|
||||
Core::PerfStatsResults m_perf_stats{};
|
||||
std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
|
||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||
bool m_is_running{};
|
||||
bool m_is_paused{};
|
||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
||||
|
||||
@@ -434,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
|
||||
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
|
||||
|
||||
const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
@@ -446,72 +532,104 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jobject surf) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jobject surf) {
|
||||
EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_directory) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_directory) {
|
||||
Common::FS::SetAppDirectory(GetJString(env, j_directory));
|
||||
}
|
||||
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
|
||||
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||
jstring custom_driver_name, jstring file_redirect_dir) {
|
||||
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_file) {
|
||||
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
|
||||
}
|
||||
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
|
||||
jstring hook_lib_dir,
|
||||
jstring custom_driver_dir,
|
||||
jstring custom_driver_name,
|
||||
jstring file_redirect_dir) {
|
||||
EmulationSession::GetInstance().InitializeGpuDriver(
|
||||
GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
|
||||
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||
constexpr auto KgslPath{"/dev/kgsl-3d0"};
|
||||
|
||||
return access(KgslPath, F_OK) == 0;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool SupportsCustomDriver() {
|
||||
return android_get_device_api_level() >= 28 && CheckKgslPresent();
|
||||
}
|
||||
|
||||
jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
|
||||
JNIEnv* env, jobject instance) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
// If the KGSL device exists custom drivers can be loaded using adrenotools
|
||||
return SupportsCustomDriver();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().UnPauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().PauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().ResetRomMetadata();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = true;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = false;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
|
||||
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_type) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
|
||||
@@ -519,8 +637,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JN
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
@@ -528,17 +645,16 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unu
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jint j_device,
|
||||
jint j_button, jint action) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_button,
|
||||
jint action) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
// Ensure gamepad is connected
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
@@ -548,8 +664,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unus
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint stick_id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
@@ -559,9 +674,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_un
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device,
|
||||
jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x,
|
||||
jfloat accel_y, jfloat accel_z) {
|
||||
JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
|
||||
jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
|
||||
j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||
@@ -569,8 +683,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
|
||||
jbyteArray j_data) {
|
||||
jboolean isCopy{false};
|
||||
std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
|
||||
@@ -582,108 +695,92 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNI
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnRemoveNfcTag();
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased(id);
|
||||
}
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
return env->NewStringUTF(title.c_str());
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
|
||||
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
|
||||
// Create the default config.ini.
|
||||
Config{};
|
||||
// Initialize the emulated system.
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file,
|
||||
[[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
|
||||
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
|
||||
Config{};
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
@@ -697,8 +794,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JN
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key, jstring j_value) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
@@ -712,20 +808,18 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEn
|
||||
env->ReleaseStringUTFChars(j_value, value.data());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(j_game_id, game_id.data());
|
||||
}
|
||||
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
|
||||
jdoubleArray j_stats = env->NewDoubleArray(4);
|
||||
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
const auto results = EmulationSession::GetInstance().PerfStats();
|
||||
jconst results = EmulationSession::GetInstance().PerfStats();
|
||||
|
||||
// Converting the structure into an array makes it easier to pass it to the frontend
|
||||
double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
|
||||
@@ -737,11 +831,11 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]]
|
||||
return j_stats;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {}
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_path) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
|
||||
jstring j_path) {
|
||||
const std::string path = GetJString(env, j_path);
|
||||
|
||||
@@ -752,8 +846,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unus
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
|
||||
LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
|
||||
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
// Function calls from the Java side
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_device,
|
||||
jstring j_type);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jbyteArray j_data);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jfloat x, jfloat y,
|
||||
jboolean pressed);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
|
||||
jfloat x, jfloat y);
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_file);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetRegions(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetCompany(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_directory);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeGpuDriver(
|
||||
JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||
jstring custom_driver_name, jstring file_redirect_dir);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_SetSysDirectory(
|
||||
JNIEnv* env, jclass clazz, jstring path_);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetSysDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring path);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
|
||||
jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetProfiling(JNIEnv* env, jclass clazz,
|
||||
jboolean enable);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_WriteProfileResults(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_NotifyOrientationChange(
|
||||
JNIEnv* env, jclass clazz, jint layout_option, jint rotation);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2(
|
||||
JNIEnv* env, jclass clazz, jstring j_path);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jobject surf);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetUserSetting(
|
||||
JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key,
|
||||
jstring j_value);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetUserSetting(
|
||||
JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key);
|
||||
|
||||
JNIEXPORT jdoubleArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_LogDeviceInfo(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardText(
|
||||
JNIEnv* env, jclass clazz, jstring j_text);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardInput(
|
||||
JNIEnv* env, jclass clazz, jint j_key_code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
9
src/android/app/src/main/res/drawable/ic_pip_mute.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_mute.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,9v6h4l5,5V4l-5,5H7z" />
|
||||
</vector>
|
||||
9
src/android/app/src/main/res/drawable/ic_pip_pause.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_pause.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
|
||||
</vector>
|
||||
9
src/android/app/src/main/res/drawable/ic_pip_play.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_play.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
9
src/android/app/src/main/res/drawable/ic_pip_unmute.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_unmute.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h250v60L140,220v520h680v-520L570,220v-60h250q24,0 42,18t18,42v520q0,24 -18,42t-42,18L140,800ZM480,615L280,415l43,-43 127,127v-339h60v339l127,-127 43,43 -200,200Z"/>
|
||||
</vector>
|
||||
@@ -1,13 +1,9 @@
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/frame_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/fragment_container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/frame_emulation_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
android:keepScreenOn="true"
|
||||
app:defaultNavHost="true" />
|
||||
|
||||
@@ -12,49 +12,65 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- This is what everything is rendered to during emulation -->
|
||||
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
|
||||
android:id="@+id/surface_emulation"
|
||||
<FrameLayout
|
||||
android:id="@+id/emulation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- This is what everything is rendered to during emulation -->
|
||||
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
|
||||
android:id="@+id/surface_emulation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_container"
|
||||
android:id="@+id/input_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<!-- This is the onscreen input overlay -->
|
||||
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
android:id="@+id/surface_input_overlay"
|
||||
<!-- This is the onscreen input overlay -->
|
||||
<org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
android:id="@+id/surface_input_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
|
||||
<Button
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:id="@+id/done_control_config"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/emulation_done"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/show_fps_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
<TextView
|
||||
android:id="@+id/show_fps_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:id="@+id/done_control_config"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/emulation_done"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:minHeight="72dp"
|
||||
android:paddingVertical="@dimen/spacing_large"
|
||||
android:paddingStart="@dimen/spacing_large"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingVertical="@dimen/spacing_large">
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_widget"
|
||||
@@ -19,32 +19,35 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:id="@+id/text_setting_description"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignStart="@+id/text_setting_name"
|
||||
android:layout_below="@+id/text_setting_name"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_toStartOf="@+id/switch_widget"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:id="@+id/text_setting_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_toStartOf="@+id/switch_widget"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_description"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/text_header_name"
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:paddingHorizontal="@dimen/spacing_large">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:id="@+id/text_header_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textAlignment="viewStart"
|
||||
android:textStyle="bold"
|
||||
tools:text="CPU Settings" />
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingHorizontal="@dimen/spacing_large"
|
||||
android:paddingVertical="16dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textStyle="bold"
|
||||
tools:text="CPU Settings" />
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/emulation_navigation"
|
||||
app:startDestination="@id/emulationFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/emulationFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment"
|
||||
android:label="fragment_emulation"
|
||||
tools:layout="@layout/fragment_emulation" >
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
@@ -56,4 +56,18 @@
|
||||
android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment"
|
||||
android:label="LicensesFragment" />
|
||||
|
||||
<activity
|
||||
android:id="@+id/emulationActivity"
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:label="EmulationActivity">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_global_emulationActivity"
|
||||
app:destination="@id/emulationActivity"
|
||||
app:launchSingleTop="true" />
|
||||
|
||||
</navigation>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="installing_driver">Treiber wird installiert...</string>
|
||||
|
||||
<!-- Preferences Screen -->
|
||||
<string name="preferences_advanced_settings">Erweiterte Einstellungen</string>
|
||||
<string name="preferences_settings">Einstellungen</string>
|
||||
<string name="preferences_general">Allgemein</string>
|
||||
<string name="preferences_system">System</string>
|
||||
@@ -228,7 +227,6 @@
|
||||
<string name="performance_warning">Das Deaktivieren dieser Einstellung führt zu erheblichen Leistungsverlusten! Für ein optimales Erlebnis wird empfohlen, sie aktiviert zu lassen.</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_auto">Automatisch auswählen</string>
|
||||
<string name="region_japan">Japan</string>
|
||||
<string name="region_usa">USA</string>
|
||||
<string name="region_europe">Europa</string>
|
||||
@@ -301,7 +299,6 @@
|
||||
<string name="ratio_stretch">Auf Fenster anpassen</string>
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_auto">Auto</string>
|
||||
<string name="cpu_accuracy_accurate">Akkurat</string>
|
||||
<string name="cpu_accuracy_unsafe">Unsicher</string>
|
||||
<string name="cpu_accuracy_paranoid">Paranoid (Langsam)</string>
|
||||
|
||||
@@ -61,11 +61,6 @@
|
||||
<string name="invalid_keys_file">Archivo de claves inválido seleccionado</string>
|
||||
<string name="install_keys_success">Claves instaladas correctamente</string>
|
||||
<string name="reading_keys_failure">Error al leer las claves de cifrado</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. Verifique que sus claves acaben con la extensión .keys.\n\n
|
||||
2. Las claves no deben de estar almacenadas en la carpeta Descargas.\n\n
|
||||
Resuelva el/los problema(s) y vuelva a intentarlo.
|
||||
</string>
|
||||
<string name="invalid_keys_error">Claves de cifrado no válidas</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">El archivo seleccionado es incorrecto o está corrupto. Vuelva a redumpear sus claves.</string>
|
||||
@@ -184,7 +179,6 @@
|
||||
<string name="installing_driver">Instalando driver...</string>
|
||||
|
||||
<!-- Preferences Screen -->
|
||||
<string name="preferences_advanced_settings">Configuración avanzada</string>
|
||||
<string name="preferences_settings">Ajustes</string>
|
||||
<string name="preferences_general">General</string>
|
||||
<string name="preferences_system">Sistema</string>
|
||||
@@ -238,7 +232,6 @@
|
||||
<string name="performance_warning">¡Desactivar esta configuración reducirá significativamente el rendimiento de la emulación! Para obtener la mejor experiencia, se recomienda dejar esta configuración habilitada.</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_auto">Auto seleccionar</string>
|
||||
<string name="region_japan">Japón</string>
|
||||
<string name="region_usa">EEUU</string>
|
||||
<string name="region_europe">Europa</string>
|
||||
@@ -311,7 +304,6 @@
|
||||
<string name="ratio_stretch">Ajustar a la ventana</string>
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_auto">Auto</string>
|
||||
<string name="cpu_accuracy_accurate">Preciso</string>
|
||||
<string name="cpu_accuracy_unsafe">Impreciso</string>
|
||||
<string name="cpu_accuracy_paranoid">Paranoico (Lento)</string>
|
||||
|
||||
@@ -61,11 +61,6 @@
|
||||
<string name="invalid_keys_file">Fichier de clés sélectionné invalide</string>
|
||||
<string name="install_keys_success">Clés installées avec succès</string>
|
||||
<string name="reading_keys_failure">Erreur lors de la lecture des clés de chiffrement</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. Vérifiez que vos clés ont l\'extension .keys.\n\n
|
||||
2. Les clés ne doivent pas être stockées dans le dossier Téléchargements.\n\n
|
||||
Résolvez le(s) problème(s) et réessayez.
|
||||
</string>
|
||||
<string name="invalid_keys_error">Clés de chiffrement invalides</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">Le fichier sélectionné est incorrect ou corrompu. Veuillez dumper à nouveau vos clés.</string>
|
||||
@@ -184,7 +179,6 @@
|
||||
<string name="installing_driver">Installation du pilote...</string>
|
||||
|
||||
<!-- Preferences Screen -->
|
||||
<string name="preferences_advanced_settings">Paramètres avancés</string>
|
||||
<string name="preferences_settings">Paramètres</string>
|
||||
<string name="preferences_general">Général</string>
|
||||
<string name="preferences_system">Système</string>
|
||||
@@ -238,7 +232,6 @@
|
||||
<string name="performance_warning">La désactivation de ce paramètre réduira considérablement les performances d\'émulation ! Pour une expérience optimale, il est recommandé de laisser ce paramètre activé.</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_auto">Sélection automatique</string>
|
||||
<string name="region_japan">Japon</string>
|
||||
<string name="region_usa">É.-U.A.</string>
|
||||
<string name="region_europe">Europe</string>
|
||||
@@ -311,7 +304,6 @@
|
||||
<string name="ratio_stretch">Étirer à la fenêtre</string>
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_auto">Auto</string>
|
||||
<string name="cpu_accuracy_accurate">Précis</string>
|
||||
<string name="cpu_accuracy_unsafe">Risqué</string>
|
||||
<string name="cpu_accuracy_paranoid">Paranoïaque (Lent)</string>
|
||||
|
||||
@@ -61,10 +61,6 @@
|
||||
<string name="invalid_keys_file">Selezionate chiavi non valide</string>
|
||||
<string name="install_keys_success">Chiavi installate correttamente</string>
|
||||
<string name="reading_keys_failure">Errore durante la lettura delle chiavi di crittografia</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. Verifica che le tue chiavi abbiano l\'estensione .keys.\n\n
|
||||
2. Le chiavi non devono essere archiviate nella cartella Download.\n\n
|
||||
Risolvi i problemi e riprova.</string>
|
||||
<string name="invalid_keys_error">Chiavi di crittografia non valide</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">Il file selezionato è incorretto o corrotto. Per favore riesegui il dump delle tue chiavi.</string>
|
||||
@@ -183,7 +179,6 @@ Risolvi i problemi e riprova.</string>
|
||||
<string name="installing_driver">Installando i driver...</string>
|
||||
|
||||
<!-- Preferences Screen -->
|
||||
<string name="preferences_advanced_settings">Impostazioni Avanzate</string>
|
||||
<string name="preferences_settings">Impostazioni</string>
|
||||
<string name="preferences_general">Generali</string>
|
||||
<string name="preferences_system">Sistema</string>
|
||||
@@ -237,7 +232,6 @@ Risolvi i problemi e riprova.</string>
|
||||
<string name="performance_warning">Disattivare questa impostazione può ridurre significativamente le performance di emulazione! Per una migliore esperienza, è consigliato lasciare questa impostazione attivata.</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_auto">Selezione automatica</string>
|
||||
<string name="region_japan">Giappone</string>
|
||||
<string name="region_usa">USA</string>
|
||||
<string name="region_europe">Europa</string>
|
||||
@@ -310,7 +304,6 @@ Risolvi i problemi e riprova.</string>
|
||||
<string name="ratio_stretch">Allunga a finestra</string>
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_auto">Automatico</string>
|
||||
<string name="cpu_accuracy_accurate">Accurata</string>
|
||||
<string name="cpu_accuracy_unsafe">Non sicura</string>
|
||||
<string name="cpu_accuracy_paranoid">Paranoico (Lento)</string>
|
||||
|
||||
@@ -60,11 +60,6 @@
|
||||
<string name="invalid_keys_file">無効なキーファイルが選択されました</string>
|
||||
<string name="install_keys_success">正常にインストールされました</string>
|
||||
<string name="reading_keys_failure">暗号化キーの読み取りエラー</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. キーの拡張子が .keys であることを確認します。\n\n
|
||||
2. キーはダウンロードフォルダに保存しないでください。\n\n
|
||||
問題を解決して、再度お試しください。
|
||||
</string>
|
||||
<string name="invalid_keys_error">暗号化キーが無効です</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">選択されたファイルが不正または破損しています。キーを再ダンプしてください。</string>
|
||||
@@ -183,7 +178,6 @@
|
||||
<string name="installing_driver">インストール中…</string>
|
||||
|
||||
<!-- Preferences Screen -->
|
||||
<string name="preferences_advanced_settings">詳細設定</string>
|
||||
<string name="preferences_settings">設定</string>
|
||||
<string name="preferences_general">全般</string>
|
||||
<string name="preferences_system">システム</string>
|
||||
@@ -236,7 +230,6 @@
|
||||
<string name="performance_warning">この設定をオフにすると、エミュレーションのパフォーマンスが著しく低下します!最高の体験を得るためには、この設定を有効にしておくことをお勧めします。</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_auto">自動選択</string>
|
||||
<string name="region_japan">日本</string>
|
||||
<string name="region_usa">アメリカ</string>
|
||||
<string name="region_europe">ヨーロッパ</string>
|
||||
@@ -309,7 +302,6 @@
|
||||
<string name="ratio_stretch">ウィンドウに合わせる</string>
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_auto">自動</string>
|
||||
<string name="cpu_accuracy_accurate">正確</string>
|
||||
<string name="cpu_accuracy_unsafe">不安定</string>
|
||||
<string name="cpu_accuracy_paranoid">パラノイド (低速)</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user