Compare commits
44 Commits
v0.0.4
...
v0.1.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c3f2d2af5 | |||
| 6f045383c5 | |||
| 73f2535edc | |||
| d9874fa4bc | |||
| 7234875a53 | |||
| 14951348bf | |||
| b0cd47c005 | |||
| 18af560a43 | |||
| 8f430d25a6 | |||
| e6ff711613 | |||
| 966fcc0c30 | |||
| 80327312dd | |||
| 742622a98d | |||
| 011278b6a8 | |||
| 76be55bc2f | |||
| a79eab9564 | |||
| 46b32b7688 | |||
| 55646657e1 | |||
| 2b4d13c205 | |||
| 8ad1d0c317 | |||
| fb1600b7d1 | |||
| 8d31484f64 | |||
| e12a73e8a0 | |||
| e4cbcec2f1 | |||
| 006f97f207 | |||
| 6d80a5f823 | |||
| 29717da45b | |||
| 28f08e43f1 | |||
| b53aa06505 | |||
| b95c4d2d5d | |||
| d76edfc683 | |||
| 3bc714a392 | |||
| 1d629fe83c | |||
| ba3eda83d3 | |||
| e6850cbad5 | |||
| cfae726289 | |||
| bb94cff886 | |||
| 370997f42e | |||
| 5213cc5689 | |||
| bc9af86269 | |||
| 00ec67d65b | |||
| fbd28a9d34 | |||
| 3413fbd9da | |||
| bccc46a325 |
@@ -13,7 +13,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Find Unused Strings
|
||||
run: ./tools/find-unused-strings.sh
|
||||
run: ./tools/unused-strings.sh
|
||||
|
||||
- name: Find Stale Translations
|
||||
run: ./tools/stale-translations.sh
|
||||
|
||||
@@ -174,8 +174,8 @@ if ((MSVC AND NOT (CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo") OR ANDROID))
|
||||
endif()
|
||||
option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
|
||||
|
||||
# Re-allow on FreeBSD once its on mainline ports
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR APPLE" OFF)
|
||||
# FreeBSD 15+ has libusb, versions below should disable it
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
|
||||
|
||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF)
|
||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 crueter
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
if (MSVC OR ANDROID)
|
||||
set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE)
|
||||
|
||||
if(MSVC OR ANDROID)
|
||||
set(BUNDLED_DEFAULT ON)
|
||||
else()
|
||||
set(BUNDLED_DEFAULT OFF)
|
||||
@@ -19,7 +21,7 @@ include(CPM)
|
||||
# cpmfile parsing
|
||||
set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json")
|
||||
|
||||
if (EXISTS ${CPMUTIL_JSON_FILE})
|
||||
if(EXISTS ${CPMUTIL_JSON_FILE})
|
||||
file(READ ${CPMUTIL_JSON_FILE} CPMFILE_CONTENT)
|
||||
else()
|
||||
message(WARNING "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} does not exist, AddJsonPackage will be a no-op")
|
||||
@@ -45,14 +47,14 @@ endfunction()
|
||||
function(get_json_element object out member default)
|
||||
string(JSON out_type ERROR_VARIABLE err TYPE "${object}" ${member})
|
||||
|
||||
if (err)
|
||||
if(err)
|
||||
set("${out}" "${default}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(JSON outvar GET "${object}" ${member})
|
||||
|
||||
if (out_type STREQUAL "ARRAY")
|
||||
if(out_type STREQUAL "ARRAY")
|
||||
string(JSON _len LENGTH "${object}" ${member})
|
||||
# array_to_list("${outvar}" ${_len} outvar)
|
||||
set("${out}_LENGTH" "${_len}" PARENT_SCOPE)
|
||||
@@ -74,7 +76,7 @@ function(AddJsonPackage)
|
||||
set(multiValueArgs OPTIONS)
|
||||
|
||||
cmake_parse_arguments(JSON "" "${oneValueArgs}" "${multiValueArgs}"
|
||||
"${ARGN}")
|
||||
"${ARGN}")
|
||||
|
||||
list(LENGTH ARGN argnLength)
|
||||
|
||||
@@ -83,18 +85,18 @@ function(AddJsonPackage)
|
||||
set(JSON_NAME "${ARGV0}")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CPMFILE_CONTENT)
|
||||
if(NOT DEFINED CPMFILE_CONTENT)
|
||||
cpm_utils_message(WARNING ${name} "No cpmfile, AddJsonPackage is a no-op")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED JSON_NAME)
|
||||
if(NOT DEFINED JSON_NAME)
|
||||
cpm_utils_message(FATAL_ERROR "json package" "No name specified")
|
||||
endif()
|
||||
|
||||
string(JSON object ERROR_VARIABLE err GET "${CPMFILE_CONTENT}" "${JSON_NAME}")
|
||||
|
||||
if (err)
|
||||
if(err)
|
||||
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile")
|
||||
endif()
|
||||
|
||||
@@ -103,13 +105,13 @@ function(AddJsonPackage)
|
||||
get_json_element("${object}" ci ci OFF)
|
||||
get_json_element("${object}" version version "")
|
||||
|
||||
if (ci)
|
||||
if(ci)
|
||||
get_json_element("${object}" name name "${JSON_NAME}")
|
||||
get_json_element("${object}" extension extension "tar.zst")
|
||||
get_json_element("${object}" min_version min_version "")
|
||||
get_json_element("${object}" raw_disabled disabled_platforms "")
|
||||
|
||||
if (raw_disabled)
|
||||
if(raw_disabled)
|
||||
array_to_list("${raw_disabled}" ${raw_disabled_LENGTH} disabled_platforms)
|
||||
else()
|
||||
set(disabled_platforms "")
|
||||
@@ -154,31 +156,31 @@ function(AddJsonPackage)
|
||||
# first: tag gets %VERSION% replaced if applicable, with either git_version (preferred) or version
|
||||
# second: artifact gets %VERSION% and %TAG% replaced accordingly (same rules for VERSION)
|
||||
|
||||
if (git_version)
|
||||
if(git_version)
|
||||
set(version_replace ${git_version})
|
||||
else()
|
||||
set(version_replace ${version})
|
||||
endif()
|
||||
|
||||
# TODO(crueter): fmt module for cmake
|
||||
if (tag)
|
||||
if(tag)
|
||||
string(REPLACE "%VERSION%" "${version_replace}" tag ${tag})
|
||||
endif()
|
||||
|
||||
if (artifact)
|
||||
if(artifact)
|
||||
string(REPLACE "%VERSION%" "${version_replace}" artifact ${artifact})
|
||||
string(REPLACE "%TAG%" "${tag}" artifact ${artifact})
|
||||
endif()
|
||||
|
||||
# format patchdir
|
||||
if (raw_patches)
|
||||
if(raw_patches)
|
||||
math(EXPR range "${raw_patches_LENGTH} - 1")
|
||||
|
||||
foreach(IDX RANGE ${range})
|
||||
string(JSON _patch GET "${raw_patches}" "${IDX}")
|
||||
|
||||
set(full_patch "${CMAKE_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}")
|
||||
if (NOT EXISTS ${full_patch})
|
||||
if(NOT EXISTS ${full_patch})
|
||||
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "specifies patch ${full_patch} which does not exist")
|
||||
endif()
|
||||
|
||||
@@ -190,7 +192,7 @@ function(AddJsonPackage)
|
||||
# options
|
||||
get_json_element("${object}" raw_options options "")
|
||||
|
||||
if (raw_options)
|
||||
if(raw_options)
|
||||
array_to_list("${raw_options}" ${raw_options_LENGTH} options)
|
||||
endif()
|
||||
|
||||
@@ -198,7 +200,7 @@ function(AddJsonPackage)
|
||||
# end options
|
||||
|
||||
# system/bundled
|
||||
if (bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
|
||||
if(bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
|
||||
set(bundled ${JSON_BUNDLED_PACKAGE})
|
||||
endif()
|
||||
|
||||
@@ -284,37 +286,37 @@ function(AddPackage)
|
||||
set(multiValueArgs OPTIONS PATCHES)
|
||||
|
||||
cmake_parse_arguments(PKG_ARGS "" "${oneValueArgs}" "${multiValueArgs}"
|
||||
"${ARGN}")
|
||||
"${ARGN}")
|
||||
|
||||
if (NOT DEFINED PKG_ARGS_NAME)
|
||||
if(NOT DEFINED PKG_ARGS_NAME)
|
||||
cpm_utils_message(FATAL_ERROR "package" "No package name defined")
|
||||
endif()
|
||||
|
||||
option(${PKG_ARGS_NAME}_FORCE_SYSTEM "Force the system package for ${PKG_ARGS_NAME}")
|
||||
option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}")
|
||||
|
||||
if (NOT DEFINED PKG_ARGS_GIT_HOST)
|
||||
if(NOT DEFINED PKG_ARGS_GIT_HOST)
|
||||
set(git_host github.com)
|
||||
else()
|
||||
set(git_host ${PKG_ARGS_GIT_HOST})
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_URL)
|
||||
if(DEFINED PKG_ARGS_URL)
|
||||
set(pkg_url ${PKG_ARGS_URL})
|
||||
|
||||
if (DEFINED PKG_ARGS_REPO)
|
||||
if(DEFINED PKG_ARGS_REPO)
|
||||
set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO})
|
||||
else()
|
||||
if (DEFINED PKG_ARGS_GIT_URL)
|
||||
if(DEFINED PKG_ARGS_GIT_URL)
|
||||
set(pkg_git_url ${PKG_ARGS_GIT_URL})
|
||||
else()
|
||||
set(pkg_git_url ${pkg_url})
|
||||
endif()
|
||||
endif()
|
||||
elseif (DEFINED PKG_ARGS_REPO)
|
||||
elseif(DEFINED PKG_ARGS_REPO)
|
||||
set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO})
|
||||
|
||||
if (DEFINED PKG_ARGS_TAG)
|
||||
if(DEFINED PKG_ARGS_TAG)
|
||||
set(pkg_key ${PKG_ARGS_TAG})
|
||||
|
||||
if(DEFINED PKG_ARGS_ARTIFACT)
|
||||
@@ -324,14 +326,14 @@ function(AddPackage)
|
||||
set(pkg_url
|
||||
${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz)
|
||||
endif()
|
||||
elseif (DEFINED PKG_ARGS_SHA)
|
||||
elseif(DEFINED PKG_ARGS_SHA)
|
||||
set(pkg_url "${pkg_git_url}/archive/${PKG_ARGS_SHA}.tar.gz")
|
||||
else()
|
||||
if (DEFINED PKG_ARGS_BRANCH)
|
||||
if(DEFINED PKG_ARGS_BRANCH)
|
||||
set(PKG_BRANCH ${PKG_ARGS_BRANCH})
|
||||
else()
|
||||
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
|
||||
"REPO defined but no TAG, SHA, BRANCH, or URL specified, defaulting to master")
|
||||
"REPO defined but no TAG, SHA, BRANCH, or URL specified, defaulting to master")
|
||||
set(PKG_BRANCH master)
|
||||
endif()
|
||||
|
||||
@@ -343,72 +345,72 @@ function(AddPackage)
|
||||
|
||||
cpm_utils_message(STATUS ${PKG_ARGS_NAME} "Download URL is ${pkg_url}")
|
||||
|
||||
if (NOT DEFINED PKG_ARGS_KEY)
|
||||
if (DEFINED PKG_ARGS_SHA)
|
||||
if(NOT DEFINED PKG_ARGS_KEY)
|
||||
if(DEFINED PKG_ARGS_SHA)
|
||||
string(SUBSTRING ${PKG_ARGS_SHA} 0 4 pkg_key)
|
||||
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
|
||||
"No custom key defined, using ${pkg_key} from sha")
|
||||
"No custom key defined, using ${pkg_key} from sha")
|
||||
elseif(DEFINED PKG_ARGS_GIT_VERSION)
|
||||
set(pkg_key ${PKG_ARGS_GIT_VERSION})
|
||||
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
elseif (DEFINED PKG_ARGS_TAG)
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
elseif(DEFINED PKG_ARGS_TAG)
|
||||
set(pkg_key ${PKG_ARGS_TAG})
|
||||
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
elseif (DEFINED PKG_ARGS_VERSION)
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
elseif(DEFINED PKG_ARGS_VERSION)
|
||||
set(pkg_key ${PKG_ARGS_VERSION})
|
||||
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
"No custom key defined, using ${pkg_key}")
|
||||
else()
|
||||
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
|
||||
"Could not determine cache key, using CPM defaults")
|
||||
"Could not determine cache key, using CPM defaults")
|
||||
endif()
|
||||
else()
|
||||
set(pkg_key ${PKG_ARGS_KEY})
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_HASH_ALGO)
|
||||
if(DEFINED PKG_ARGS_HASH_ALGO)
|
||||
set(hash_algo ${PKG_ARGS_HASH_ALGO})
|
||||
else()
|
||||
set(hash_algo SHA512)
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_HASH)
|
||||
if(DEFINED PKG_ARGS_HASH)
|
||||
set(pkg_hash "${hash_algo}=${PKG_ARGS_HASH}")
|
||||
elseif (DEFINED PKG_ARGS_HASH_SUFFIX)
|
||||
elseif(DEFINED PKG_ARGS_HASH_SUFFIX)
|
||||
# funny sanity check
|
||||
string(TOLOWER ${hash_algo} hash_algo_lower)
|
||||
string(TOLOWER ${PKG_ARGS_HASH_SUFFIX} suffix_lower)
|
||||
if (NOT ${suffix_lower} MATCHES ${hash_algo_lower})
|
||||
if(NOT ${suffix_lower} MATCHES ${hash_algo_lower})
|
||||
cpm_utils_message(WARNING
|
||||
"Hash algorithm and hash suffix do not match, errors may occur")
|
||||
"Hash algorithm and hash suffix do not match, errors may occur")
|
||||
endif()
|
||||
|
||||
set(hash_url ${pkg_url}.${PKG_ARGS_HASH_SUFFIX})
|
||||
elseif (DEFINED PKG_ARGS_HASH_URL)
|
||||
elseif(DEFINED PKG_ARGS_HASH_URL)
|
||||
set(hash_url ${PKG_ARGS_HASH_URL})
|
||||
else()
|
||||
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
|
||||
"No hash or hash URL found")
|
||||
"No hash or hash URL found")
|
||||
endif()
|
||||
|
||||
if (DEFINED hash_url)
|
||||
if(DEFINED hash_url)
|
||||
set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${PKG_ARGS_NAME}.hash)
|
||||
|
||||
# TODO(crueter): This is kind of a bad solution
|
||||
# because "technically" the hash is invalidated each week
|
||||
# but it works for now kjsdnfkjdnfjksdn
|
||||
string(TOLOWER ${PKG_ARGS_NAME} lowername)
|
||||
if (NOT EXISTS ${outfile} AND NOT EXISTS ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key})
|
||||
if(NOT EXISTS ${outfile} AND NOT EXISTS ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key})
|
||||
file(DOWNLOAD ${hash_url} ${outfile})
|
||||
endif()
|
||||
|
||||
if (EXISTS ${outfile})
|
||||
if(EXISTS ${outfile})
|
||||
file(READ ${outfile} pkg_hash_tmp)
|
||||
endif()
|
||||
|
||||
if (DEFINED ${pkg_hash_tmp})
|
||||
if(DEFINED ${pkg_hash_tmp})
|
||||
set(pkg_hash "${hash_algo}=${pkg_hash_tmp}")
|
||||
endif()
|
||||
endif()
|
||||
@@ -426,19 +428,19 @@ function(AddPackage)
|
||||
- CPMUTIL_FORCE_BUNDLED
|
||||
- BUNDLED_PACKAGE
|
||||
- default to allow local
|
||||
]]#
|
||||
if (PKG_ARGS_FORCE_BUNDLED_PACKAGE)
|
||||
]] #
|
||||
if(PKG_ARGS_FORCE_BUNDLED_PACKAGE)
|
||||
set_precedence(OFF OFF)
|
||||
elseif (${PKG_ARGS_NAME}_FORCE_SYSTEM)
|
||||
elseif(${PKG_ARGS_NAME}_FORCE_SYSTEM)
|
||||
set_precedence(ON ON)
|
||||
elseif (${PKG_ARGS_NAME}_FORCE_BUNDLED)
|
||||
elseif(${PKG_ARGS_NAME}_FORCE_BUNDLED)
|
||||
set_precedence(OFF OFF)
|
||||
elseif (CPMUTIL_FORCE_SYSTEM)
|
||||
elseif(CPMUTIL_FORCE_SYSTEM)
|
||||
set_precedence(ON ON)
|
||||
elseif(CPMUTIL_FORCE_BUNDLED)
|
||||
set_precedence(OFF OFF)
|
||||
elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset")
|
||||
if (PKG_ARGS_BUNDLED_PACKAGE)
|
||||
elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset")
|
||||
if(PKG_ARGS_BUNDLED_PACKAGE)
|
||||
set(local OFF)
|
||||
else()
|
||||
set(local ON)
|
||||
@@ -449,7 +451,7 @@ function(AddPackage)
|
||||
set_precedence(ON OFF)
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_VERSION)
|
||||
if(DEFINED PKG_ARGS_VERSION)
|
||||
list(APPEND EXTRA_ARGS
|
||||
VERSION ${PKG_ARGS_VERSION}
|
||||
)
|
||||
@@ -475,32 +477,32 @@ function(AddPackage)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${PKG_ARGS_NAME})
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${pkg_git_url})
|
||||
|
||||
if (${PKG_ARGS_NAME}_ADDED)
|
||||
if (DEFINED PKG_ARGS_SHA)
|
||||
if(${PKG_ARGS_NAME}_ADDED)
|
||||
if(DEFINED PKG_ARGS_SHA)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_SHA})
|
||||
elseif (DEFINED PKG_ARGS_GIT_VERSION)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_GIT_VERSION})
|
||||
elseif (DEFINED PKG_ARGS_TAG)
|
||||
${PKG_ARGS_SHA})
|
||||
elseif(DEFINED PKG_ARGS_GIT_VERSION)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_TAG})
|
||||
elseif(DEFINED PKG_ARGS_VERSION)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_VERSION})
|
||||
${PKG_ARGS_GIT_VERSION})
|
||||
elseif(DEFINED PKG_ARGS_TAG)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_TAG})
|
||||
elseif(DEFINED PKG_ARGS_VERSION)
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
${PKG_ARGS_VERSION})
|
||||
else()
|
||||
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
|
||||
"Package has no specified sha, tag, or version")
|
||||
"Package has no specified sha, tag, or version")
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS "unknown")
|
||||
endif()
|
||||
else()
|
||||
if (DEFINED CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION AND NOT
|
||||
if(DEFINED CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION AND NOT
|
||||
"${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION}" STREQUAL "")
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
"${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION} (system)")
|
||||
"${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION} (system)")
|
||||
else()
|
||||
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
|
||||
"unknown (system)")
|
||||
"unknown (system)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -561,7 +563,7 @@ function(AddCIPackage)
|
||||
message(FATAL_ERROR "[CPMUtil] PACKAGE is required")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED PKG_ARGS_CMAKE_FILENAME)
|
||||
if(NOT DEFINED PKG_ARGS_CMAKE_FILENAME)
|
||||
set(ARTIFACT_CMAKE ${PKG_ARGS_NAME})
|
||||
else()
|
||||
set(ARTIFACT_CMAKE ${PKG_ARGS_CMAKE_FILENAME})
|
||||
@@ -573,11 +575,11 @@ function(AddCIPackage)
|
||||
set(ARTIFACT_EXT ${PKG_ARGS_EXTENSION})
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_MIN_VERSION)
|
||||
if(DEFINED PKG_ARGS_MIN_VERSION)
|
||||
set(ARTIFACT_MIN_VERSION ${PKG_ARGS_MIN_VERSION})
|
||||
endif()
|
||||
|
||||
if (DEFINED PKG_ARGS_DISABLED_PLATFORMS)
|
||||
if(DEFINED PKG_ARGS_DISABLED_PLATFORMS)
|
||||
set(DISABLED_PLATFORMS ${PKG_ARGS_DISABLED_PLATFORMS})
|
||||
endif()
|
||||
|
||||
@@ -587,19 +589,19 @@ function(AddCIPackage)
|
||||
set(ARTIFACT_REPO ${PKG_ARGS_REPO})
|
||||
set(ARTIFACT_PACKAGE ${PKG_ARGS_PACKAGE})
|
||||
|
||||
if ((MSVC AND ARCHITECTURE_x86_64) AND NOT "windows-amd64" IN_LIST DISABLED_PLATFORMS)
|
||||
if((MSVC AND ARCHITECTURE_x86_64) AND NOT "windows-amd64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(windows-amd64)
|
||||
endif()
|
||||
|
||||
if ((MSVC AND ARCHITECTURE_arm64) AND NOT "windows-arm64" IN_LIST DISABLED_PLATFORMS)
|
||||
if((MSVC AND ARCHITECTURE_arm64) AND NOT "windows-arm64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(windows-arm64)
|
||||
endif()
|
||||
|
||||
if ((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS)
|
||||
if((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(mingw-amd64)
|
||||
endif()
|
||||
|
||||
if ((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS)
|
||||
if((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(mingw-arm64)
|
||||
endif()
|
||||
|
||||
@@ -628,11 +630,11 @@ function(AddCIPackage)
|
||||
endif()
|
||||
|
||||
# TODO(crueter): macOS amd64/aarch64 split mayhaps
|
||||
if (APPLE AND NOT "macos-universal" IN_LIST DISABLED_PLATFORMS)
|
||||
if(APPLE AND NOT "macos-universal" IN_LIST DISABLED_PLATFORMS)
|
||||
add_ci_package(macos-universal)
|
||||
endif()
|
||||
|
||||
if (DEFINED ARTIFACT_DIR)
|
||||
if(DEFINED ARTIFACT_DIR)
|
||||
set(${ARTIFACT_PACKAGE}_ADDED TRUE PARENT_SCOPE)
|
||||
set(${ARTIFACT_PACKAGE}_SOURCE_DIR "${ARTIFACT_DIR}" PARENT_SCOPE)
|
||||
else()
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
"package": "VVL",
|
||||
"repo": "KhronosGroup/Vulkan-ValidationLayers",
|
||||
"tag": "vulkan-sdk-%VERSION%",
|
||||
"git_version": "1.4.328.1",
|
||||
"git_version": "1.4.335.0",
|
||||
"artifact": "android-binaries-%VERSION%.zip",
|
||||
"hash": "5ec895a453cb7c2f156830b9766953a0c2bd44dea99e6a3dac4160305041ccd3e87534b4ce0bd102392178d2a8eca48411856298f9395e60117cdfe89f72137e"
|
||||
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
<component type="desktop-application">
|
||||
<id>org.yuzu_emu.yuzu</id>
|
||||
<id>org.eden_emu.eden</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<name>yuzu</name>
|
||||
<name>eden</name>
|
||||
<summary>Nintendo Switch emulator</summary>
|
||||
<description>
|
||||
<p>yuzu is the world's most popular, open-source, Nintendo Switch emulator — started by the creators of Citra.</p>
|
||||
<p>The emulator is capable of running most commercial games at full speed, provided you meet the necessary hardware requirements.</p>
|
||||
<p>For a full list of games yuzu support, please visit our Compatibility page.</p>
|
||||
<p>Check out our website for the latest news on exciting features, monthly progress reports, and more!</p>
|
||||
<p>Nintendo Switch video game console emulator</p>
|
||||
</description>
|
||||
<categories>
|
||||
<category>Game</category>
|
||||
@@ -24,16 +26,16 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<keyword>switch</keyword>
|
||||
<keyword>emulator</keyword>
|
||||
</keywords>
|
||||
<url type="homepage">https://yuzu-emu.org/</url>
|
||||
<url type="bugtracker">https://github.com/yuzu-emu/yuzu/issues</url>
|
||||
<url type="faq">https://yuzu-emu.org/wiki/faq/</url>
|
||||
<url type="help">https://yuzu-emu.org/wiki/home/</url>
|
||||
<url type="donation">https://yuzu-emu.org/donate/</url>
|
||||
<url type="translate">https://www.transifex.com/projects/p/yuzu</url>
|
||||
<url type="contact">https://community.citra-emu.org/</url>
|
||||
<url type="vcs-browser">https://github.com/yuzu-emu/yuzu</url>
|
||||
<url type="contribute">https://yuzu-emu.org/wiki/contributing/</url>
|
||||
<launchable type="desktop-id">org.yuzu_emu.yuzu.desktop</launchable>
|
||||
<url type="homepage">https://eden-emu.dev/</url>
|
||||
<url type="bugtracker">https://git.eden-emu.dev/eden-emu/eden/issues</url>
|
||||
<url type="faq">https://eden-emu.dev/docs</url>
|
||||
<url type="help">https://eden-emu.dev/docs</url>
|
||||
<url type="donation">https://eden-emu.dev/donations</url>
|
||||
<url type="translate">https://explore.transifex.com/edenemu/eden-emulator</url>
|
||||
<url type="contact">https://discord.gg/edenemu</url>
|
||||
<url type="vcs-browser">https://git.eden-emu.dev</url>
|
||||
<url type="contribute">https://git.eden-emu.dev/eden-emu/eden</url>
|
||||
<launchable type="desktop-id">org.eden_emu.eden.desktop</launchable>
|
||||
<provides>
|
||||
<binary>yuzu</binary>
|
||||
<binary>yuzu-cmd</binary>
|
||||
@@ -50,13 +52,6 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<memory>16384</memory>
|
||||
</recommends>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<developer_name>yuzu Emulator Team</developer_name>
|
||||
<developer_name>Eden Emulator Team</developer_name>
|
||||
<content_rating type="oars-1.0"/>
|
||||
<screenshots>
|
||||
<screenshot type="default"><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/001-Super%20Mario%20Odyssey%20.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/004-The%20Legend%20of%20Zelda%20Skyward%20Sword%20HD.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/007-Pokemon%20Sword.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/010-Hyrule%20Warriors%20Age%20of%20Calamity.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/039-Pok%C3%A9mon%20Mystery%20Dungeon%20Rescue%20Team%20DX.png.png.png</image></screenshot>
|
||||
</screenshots>
|
||||
</component>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -1,17 +0,0 @@
|
||||
# AddPackage
|
||||
|
||||
- `VERSION` (required): The version to get (the tag will be `v${VERSION}`)
|
||||
- `NAME` (required): Name used within the artifacts
|
||||
- `REPO` (required): CI repository, e.g. `crueter-ci/OpenSSL`
|
||||
- `PACKAGE` (required): `find_package` package name
|
||||
- `EXTENSION`: Artifact extension (default `tar.zst`)
|
||||
- `MIN_VERSION`: Minimum version for `find_package`. Only used if platform does not support this package as a bundled artifact
|
||||
- `DISABLED_PLATFORMS`: List of platforms that lack artifacts for this package. Options:
|
||||
* `windows-amd64`
|
||||
* `windows-arm64`
|
||||
* `android`
|
||||
* `solaris-amd64`
|
||||
* `freebsd-amd64`
|
||||
* `linux-amd64`
|
||||
* `linux-aarch64`
|
||||
* `macos-universal`
|
||||
@@ -10,12 +10,13 @@ Global Options:
|
||||
|
||||
You are highly encouraged to read AddPackage first, even if you plan to only interact with CPMUtil via `AddJsonPackage`.
|
||||
|
||||
<!-- TOC -->
|
||||
- [AddPackage](#addpackage)
|
||||
- [AddCIPackage](#addcipackage)
|
||||
- [AddJsonPackage](#addjsonpackage)
|
||||
- [Lists](#lists)
|
||||
<!-- /TOC -->
|
||||
- [For Packagers](#for-packagers)
|
||||
- [Network Sandbox](#network-sandbox)
|
||||
- [Unsandboxed](#unsandboxed)
|
||||
|
||||
## AddPackage
|
||||
|
||||
@@ -43,4 +44,16 @@ For an example of how this might be implemented in an application, see Eden's im
|
||||
|
||||
- [`dep_hashes.h.in`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/dep_hashes.h.in)
|
||||
- [`GenerateDepHashes.cmake`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CMakeModules/GenerateDepHashes.cmake)
|
||||
- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp)
|
||||
- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp)
|
||||
|
||||
## For Packagers
|
||||
|
||||
If you are packaging a project that uses CPMUtil, read this!
|
||||
|
||||
### Network Sandbox
|
||||
|
||||
For sandboxed environments (e.g. Gentoo, nixOS) you must install all dependencies to the system beforehand and set `-DCPMUTIL_FORCE_SYSTEM=ON`. If a dependency is missing, get creating!
|
||||
|
||||
### Unsandboxed
|
||||
|
||||
For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. That is pretty much half the reason I created this behemoth, after all.
|
||||
@@ -1,17 +1,20 @@
|
||||
# Caveats
|
||||
|
||||
<!-- TOC -->
|
||||
- [Caveats](#caveats)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Gentoo Linux](#gentoo-linux)
|
||||
- [macOS](#macos)
|
||||
- [Solaris](#solaris)
|
||||
- [HaikuOS](#haikuos)
|
||||
- [OpenBSD](#openbsd)
|
||||
- [FreeBSD](#freebsd)
|
||||
- [NetBSD](#netbsd)
|
||||
- [MSYS2](#msys2)
|
||||
- [Windows 8.1 and below](#windows-81-and-below)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Gentoo Linux](#gentoo-linux)
|
||||
- [macOS](#macos)
|
||||
- [Solaris](#solaris)
|
||||
- [HaikuOS](#haikuos)
|
||||
- [OpenBSD](#openbsd)
|
||||
- [FreeBSD](#freebsd)
|
||||
- [NetBSD](#netbsd)
|
||||
- [MSYS2](#msys2)
|
||||
- [RedoxOS](#redoxos)
|
||||
- [Windows](#windows)
|
||||
- [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81)
|
||||
- [Windows Vista and below](#windows-vista-and-below)
|
||||
- [Windows on ARM](#windows-on-arm)
|
||||
<!-- /TOC -->
|
||||
|
||||
## Arch Linux
|
||||
@@ -49,6 +52,7 @@ export PATH="$PATH:$PWD"
|
||||
```
|
||||
|
||||
Default MESA is a bit outdated, the following environment variables should be set for a smoother experience:
|
||||
|
||||
```sh
|
||||
export MESA_GL_VERSION_OVERRIDE=4.6
|
||||
export MESA_GLSL_VERSION_OVERRIDE=460
|
||||
@@ -93,9 +97,16 @@ Eden is not currently available as a port on FreeBSD, though it is in the works.
|
||||
|
||||
The available OpenSSL port (3.0.17) is out-of-date, and using a bundled static library instead is recommended; to do so, add `-DYUZU_USE_BUNDLED_OPENSSL=ON` to your CMake configure command.
|
||||
|
||||
Gamepad/controllers may not work on 15.0, this is due to an outdated SDL not responding well to the new `usbhid(2)` driver. To workaround this simply disable `usbhid(2)` (add the following to `/boot/loader.conf`):
|
||||
|
||||
```sh
|
||||
hw.usb.usbhid.enable="0"
|
||||
```
|
||||
|
||||
## NetBSD
|
||||
|
||||
Install `pkgin` if not already `pkg_add pkgin`, see also the general [pkgsrc guide](https://www.netbsd.org/docs/pkgsrc/using.html). For NetBSD 10.1 provide `echo 'PKG_PATH="https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/10.0_2025Q3/All/"' >/etc/pkg_install.conf`. If `pkgin` is taking too much time consider adding the following to `/etc/rc.conf`:
|
||||
Install `pkgin` if not already `pkg_add pkgin`, see also the general [pkgsrc guide](https://www.netbsd.org/docs/pkgsrc/using.html). For NetBSD 10.1 provide `echo 'PKG_PATH="https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/"' >/etc/pkg_install.conf`. If `pkgin` is taking too much time consider adding the following to `/etc/rc.conf`:
|
||||
|
||||
```sh
|
||||
ip6addrctl=YES
|
||||
ip6addrctl_policy=ipv4_prefer
|
||||
@@ -109,9 +120,10 @@ System provides a default `g++-10` which doesn't support the current C++ codebas
|
||||
|
||||
Make may error out when generating C++ headers of SPIRV shaders, hence it's recommended to use `gmake` over the default system one.
|
||||
|
||||
[parallel/spirv-tools](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/parallel/spirv-tools/index.html) isn't available in binary form and must be build from source.
|
||||
[parallel/spirv-tools](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/parallel/spirv-tools/index.html) isn't available in binary form and must be built from source.
|
||||
|
||||
glslang is not available on NetBSD, to circumvent this simply build glslang by yourself:
|
||||
|
||||
Such that glslang is not available on NetBSD, to circumvent this simply build glslang by yourself:
|
||||
```sh
|
||||
pkgin python313
|
||||
git clone --depth=1 https://github.com/KhronosGroup/glslang.git
|
||||
@@ -124,13 +136,15 @@ cmake --install build
|
||||
|
||||
However, pkgsrc is highly recommended, see [getting pkgsrc](https://iso.us.netbsd.org/pub/pkgsrc/current/pkgsrc/doc/pkgsrc.html#getting). You must get `current` not the `2025Q2` version.
|
||||
|
||||
# DragonFlyBSD
|
||||
## DragonFlyBSD
|
||||
|
||||
If `libstdc++.so.6` is not found (`GLIBCXX_3.4.30`) then attempt:
|
||||
|
||||
```sh
|
||||
rm /usr/local/lib/gcc11/libstdc++.so.6
|
||||
ln -s /usr/local/lib/gcc14/libstdc++.so /usr/local/lib/gcc11/libstdc++.so.6
|
||||
```
|
||||
|
||||
This may have unforeseen consequences of which we don't need to worry about for now.
|
||||
|
||||
Default `g++` (and the libstdc++) is too outdated - so install `gcc14` and redirect CMake to the new compiler toolchain `-DCMAKE_CXX_COMPILER=gcc14 -DCMAKE_C_COMPILER=g++14`.
|
||||
@@ -141,13 +155,9 @@ If build hangs, use `hammer2 bulkfree`.
|
||||
|
||||
## MSYS2
|
||||
|
||||
`qt6-static` isn't supported yet.
|
||||
Only the `MINGW64` environment is tested (or `CLANGARM64` on ARM); however, all of the others should work (in theory) sans `MINGW32`.
|
||||
|
||||
Only the `MINGW64` environment is tested; however, all of the others should work (in theory) sans `MINGW32`.
|
||||
|
||||
Currently, only FFmpeg can be used as a system dependency; the others will result in linker errors.
|
||||
|
||||
When packaging an MSYS2 build, you will need to copy all dependent DLLs recursively alongside the `windeployqt6`; for example:
|
||||
When packaging an MSYS2 build that is NOT fully static, you will need to copy all dependent DLLs recursively alongside the `windeployqt6`; for example:
|
||||
|
||||
```sh
|
||||
# MSYS_TOOLCHAIN is typically just mingw64
|
||||
@@ -196,14 +206,24 @@ windeployqt6 --no-compiler-runtime --no-opengl-sw --no-system-dxc-compiler \
|
||||
find ./*/ -name "*.dll" | while read -r dll; do deps "$dll"; done
|
||||
```
|
||||
|
||||
## Windows 8.1 and below
|
||||
|
||||
DirectX 12 is not available - simply copy and paste a random DLL and name it `d3d12.dll`.
|
||||
|
||||
Install [Qt6 compatibility libraries](github.com/ANightly/qt6windows7) specifically Qt 6.9.5.
|
||||
|
||||
## RedoxOS
|
||||
|
||||
The package install may randomly hang at times, in which case it has to be restarted. ALWAYS do a `sudo pkg update` or the chances of it hanging will be close to 90%. If "multiple" installs fail at once, try installing 1 by 1 the packages.
|
||||
|
||||
When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake.
|
||||
|
||||
## Windows
|
||||
|
||||
### Windows 7, Windows 8 and Windows 8.1
|
||||
|
||||
DirectX 12 is not available - simply copy and paste a random DLL and name it `d3d12.dll`.
|
||||
|
||||
Install [Qt6 compatibility libraries](github.com/ANightly/qt6windows7) specifically Qt 6.9.5.
|
||||
|
||||
### Windows Vista and below
|
||||
|
||||
No support for Windows Vista (or below) is present at the moment. Check back later.
|
||||
|
||||
### Windows on ARM
|
||||
|
||||
If you're using Snapdragon X or 8CX, use the [the Vulkan translation layer](https://apps.microsoft.com/detail/9nqpsl29bfff?hl=en-us&gl=USE) only if the stock drivers do not work. And of course always keep your system up-to-date.
|
||||
|
||||
@@ -10,7 +10,13 @@ Simply put, types/classes are named as `PascalCase`, same for methods and functi
|
||||
|
||||
Except for Qt MOC where `functionName` is preferred.
|
||||
|
||||
Template typenames prefer short names like `T`, `I`, `U`, if a longer name is required either `Iterator` or `perform_action` are fine as well.
|
||||
Template typenames prefer short names like `T`, `I`, `U`, if a longer name is required either `Iterator` or `perform_action` are fine as well. Do not use names like `SS` as systems like solaris define it for registers, in general do not use any of the following for short names:
|
||||
- `SS`, `DS`, `GS`, `FS`: Segment registers, defined by Solaris `<ucontext.h>`
|
||||
- `EAX`, `EBX`, `ECX`, `EDX`, `ESI`, `EDI`, `ESP`, `EBP`, `EIP`: Registers, defined by Solaris.
|
||||
- `X`: Defined by some utility headers, avoid.
|
||||
- `_`: Defined by gettext, avoid.
|
||||
- `N`, `M`, `S`: Preferably don't use this for types, use it for numeric constants.
|
||||
- `TR`: Used by some weird `<ucontext.h>` whom define the Task Register as a logical register to provide to the user... (Need to remember which OS in specific).
|
||||
|
||||
Macros must always be in `SCREAMING_CASE`. Do not use short letter macros as systems like Solaris will conflict with them; a good rule of thumb is >5 characters per macro - i.e `THIS_MACRO_IS_GOOD`, `AND_ALSO_THIS_ONE`.
|
||||
|
||||
@@ -18,25 +24,45 @@ Try not using hungarian notation, if you're able.
|
||||
|
||||
## Formatting
|
||||
|
||||
Formatting is extremelly lax, the general rule of thumb is: Don't add new lines just to increase line count. The less lines we have to look at, the better. This means also packing densely your code while not making it a clusterfuck. Strike a balance of "this is a short and comprehensible piece of code" and "my eyes are actually happy to see this!". Don't just drop the entire thing in a single line and call it "dense code", that's just spaghetti posing as code. In general, be mindful of what other devs need to look at.
|
||||
|
||||
Do not put if/while/etc braces after lines:
|
||||
```c++
|
||||
// no dont do this
|
||||
// this is more lines of code for no good reason (why braces need their separate lines?)
|
||||
// and those take space in someone's screen, cumulatively
|
||||
if (thing)
|
||||
{
|
||||
{ //<--
|
||||
some(); // ...
|
||||
}
|
||||
} //<-- 2 lines of code for basically "opening" and "closing" an statment
|
||||
|
||||
// do this
|
||||
if (thing) {
|
||||
if (thing) { //<-- [...] and with your brain you can deduce it's this piece of code
|
||||
// that's being closed
|
||||
some(); // ...
|
||||
}
|
||||
} //<-- only one line, and it's clearer since you know its closing something [...]
|
||||
|
||||
// or this
|
||||
// or this, albeit the extra line isn't needed (at your discretion of course)
|
||||
if (thing)
|
||||
some(); // ...
|
||||
|
||||
// this is also ok
|
||||
// this is also ok, keeps things in one line and makes it extremely clear
|
||||
if (thing) some();
|
||||
|
||||
// NOT ok, don't be "clever" and use the comma operator to stash a bunch of statments
|
||||
// in a single line, doing this will definitely ruin someone's day - just do the thing below
|
||||
// vvv
|
||||
if (thing) some(), thing(), a2(a1(), y1(), j1()), do_complex_shit(wa(), wo(), ploo());
|
||||
// ... and in general don't use the comma operator for "multiple statments", EXCEPT if you think
|
||||
// that it makes the code more readable (the situation may be rare however)
|
||||
|
||||
// Wow so much clearer! Now I can actually see what each statment is meant to do!
|
||||
if (thing) {
|
||||
some();
|
||||
thing();
|
||||
a2(a1(), y1(), j1());
|
||||
do_complex_shit(wa(), wo(), ploo());
|
||||
}
|
||||
```
|
||||
|
||||
Brace rules are lax, if you can get the point across, do it:
|
||||
@@ -77,3 +103,21 @@ if (device_name.empty()) {
|
||||
SDL_AudioSpec obtained;
|
||||
device = SDL_OpenAudioDevice(device_name.empty() ? nullptr : device_name.c_str(), capture, &spec, &obtained, false);
|
||||
```
|
||||
|
||||
A note about operators: Use them sparingly, yes, the language is lax on them, but some usages can be... tripping to say the least.
|
||||
```c++
|
||||
a, b, c; //<-- NOT OK multiple statments with comma operator is definitely a recipe for disaster
|
||||
return c ? a : b; //<-- OK ternaries at end of return statments are clear and fine
|
||||
return a, b; //<-- NOT OK return will take value of `b` but also evaluate `a`, just use a separate statment
|
||||
void f(int a[]) //<-- OK? if you intend to use the pointer as an array, otherwise just mark it as *
|
||||
```
|
||||
|
||||
And about templates, use them sparingly, don't just do meta-templating for the sake of it, do it when you actually need it. This isn't a competition to see who can make the most complicated and robust meta-templating system. Just use what works, and preferably stick to the standard libary instead of reinventing the wheel. Additionally:
|
||||
|
||||
```c++
|
||||
// NOT OK This will create (T * N * C * P) versions of the same function. DO. NOT. DO. THIS.
|
||||
template<typename T, size_t N, size_t C, size_t P> inline void what() const noexcept;
|
||||
|
||||
// OK use parameters like a normal person, don't be afraid to use them :)
|
||||
template<typename T> inline void what(size_t n, size_t c, size_t p) const noexcept;
|
||||
```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Cross Compile
|
||||
# Cross compiling
|
||||
|
||||
## ARM64
|
||||
General guide for cross compiling.
|
||||
|
||||
## Debian ARM64
|
||||
|
||||
A painless guide for cross compilation (or to test NCE) from a x86_64 system without polluting your main.
|
||||
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
# Dependencies
|
||||
|
||||
To build Eden, you MUST have a C++ compiler.
|
||||
|
||||
* On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+
|
||||
- GCC 12 also requires Clang 14+
|
||||
* GCC 12 also requires Clang 14+
|
||||
* On Windows, we support:
|
||||
- **[MSVC](https://visualstudio.microsoft.com/downloads/)** (default)
|
||||
- It's STRONGLY RECOMMENDED to use the **Community** option and **Visual Studio 2022**
|
||||
- You need to install: **[Desktop development with C++](https://learn.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=msvc-170)**
|
||||
- **[clang-cl](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-180)**
|
||||
- You need to install: **C++ Clang tools for Windows**
|
||||
- **[MSYS2](https://www.msys2.org)** (experimental)
|
||||
* **[MSVC](https://visualstudio.microsoft.com/downloads/)** (default)
|
||||
* It's STRONGLY RECOMMENDED to use the **Community** option and **Visual Studio 2022**
|
||||
* You need to install: **[Desktop development with C++](https://learn.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=msvc-170)**
|
||||
* **[clang-cl](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-180)**
|
||||
* You need to install: **C++ Clang tools for Windows**
|
||||
* **[MSYS2](https://www.msys2.org)**
|
||||
* On macOS, this is Apple Clang
|
||||
- This can be installed with `xcode-select --install`
|
||||
* This can be installed with `xcode-select --install`
|
||||
|
||||
The following additional tools are also required:
|
||||
|
||||
* **[CMake](https://www.cmake.org/)** 3.22+ - already included with the Android SDK
|
||||
* **[Git](https://git-scm.com/)** for version control
|
||||
- **[Windows installer](https://gitforwindows.org)**
|
||||
* **[Windows installer](https://gitforwindows.org)**
|
||||
* **[Python3](https://www.python.org/downloads/)** 3.10+ - necessary to download external repositories
|
||||
* On Windows, you must install the **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** as well
|
||||
- *A convenience script to install the latest SDK is provided in:*
|
||||
- `tools/windows/install-vulkan-sdk.ps1` (for PowerShell 5+)
|
||||
- `tools/windows/install-vulkan-sdk.sh` (for MSYS2, Git Bash, etc)
|
||||
* *A convenience script to install the latest SDK is provided in:*
|
||||
* `tools/windows/install-vulkan-sdk.ps1` (for PowerShell 5+)
|
||||
* `tools/windows/install-vulkan-sdk.sh` (for Git Bash, etc)
|
||||
|
||||
If you are on desktop and plan to use the Qt frontend, you *must* install Qt 6, and optionally Qt Creator (the **RECOMMENDED** IDE for building)
|
||||
|
||||
* On Linux, *BSD and macOS, this can be done by the package manager
|
||||
- If you wish to use Qt Creator, append `qtcreator` or `qt-creator` to the commands seen below.
|
||||
* If you wish to use Qt Creator, append `qtcreator` or `qt-creator` to the commands seen below.
|
||||
* MSVC/clang-cl users on Windows must install through the official [Qt](https://www.qt.io/download-qt-installer-oss) installer
|
||||
* Linux and macOS users may choose to use the installer as well.
|
||||
* MSYS2 can also install Qt 6 via the package manager
|
||||
|
||||
If you are on Windows, a convenience script to install MSVC, MSYS2, Qt, all necessary packages for MSYS2, and set up a zsh environment with useful keybinds and aliases can be found [here](https://git.crueter.xyz/scripts/windev).
|
||||
- For help setting up Qt Creator, run `./install.sh -h qtcreator`
|
||||
* For help setting up Qt Creator, run `./install.sh -h qtcreator`
|
||||
|
||||
If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go [back home](Build.md) and continue.
|
||||
|
||||
## Externals
|
||||
|
||||
The following are handled by Eden's externals:
|
||||
|
||||
* [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`)
|
||||
@@ -57,6 +59,7 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
|
||||
* [MbedTLS](https://github.com/Mbed-TLS/mbedtls) 3+
|
||||
|
||||
Vulkan 1.3.274+ is also needed:
|
||||
|
||||
* [VulkanUtilityLibraries](https://github.com/KhronosGroup/Vulkan-Utility-Libraries)
|
||||
* [VulkanHeaders](https://github.com/KhronosGroup/Vulkan-Headers)
|
||||
* [SPIRV-Tools](https://github.com/KhronosGroup/SPIRV-Tools)
|
||||
@@ -71,18 +74,21 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
|
||||
* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
|
||||
* [sirit](https://github.com/eden-emulator/sirit)
|
||||
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
|
||||
- This package is known to be broken on the AUR.
|
||||
* This package is known to be broken on the AUR.
|
||||
* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on
|
||||
* [unordered-dense](https://github.com/martinus/unordered_dense)
|
||||
* [mcl](https://github.com/azahar-emu/mcl) - subject to removal
|
||||
|
||||
On amd64:
|
||||
|
||||
* [xbyak](https://github.com/herumi/xbyak) - 7.22 or earlier is recommended
|
||||
|
||||
On aarch64 OR if `DYNARMIC_TESTS` is on:
|
||||
|
||||
* [oaknut](https://github.com/merryhime/oaknut) 2.0.1+
|
||||
|
||||
On riscv64:
|
||||
|
||||
* [biscuit](https://github.com/lioncash/biscuit) 0.9.1+
|
||||
|
||||
## Commands
|
||||
@@ -98,7 +104,7 @@ Click on the arrows to expand.
|
||||
|
||||
GURU must be enabled:
|
||||
|
||||
```
|
||||
```sh
|
||||
sudo emerge -a app-eselect/eselect-repository
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
@@ -124,17 +130,18 @@ sudo emerge -a \
|
||||
virtual/pkgconfig
|
||||
```
|
||||
|
||||
- On `amd64`, also add `dev-libs/xbyak`
|
||||
- On `riscv64`, also add `dev-libs/biscuit` (currently unavailable)
|
||||
- On `aarch64`, also add `dev-libs/oaknut`
|
||||
- If tests are enabled, also add `dev-libs/oaknut` and `dev-cpp/catch`
|
||||
* On `amd64`, also add `dev-libs/xbyak`
|
||||
* On `riscv64`, also add `dev-libs/biscuit` (currently unavailable)
|
||||
* On `aarch64`, also add `dev-libs/oaknut`
|
||||
* If tests are enabled, also add `dev-libs/oaknut` and `dev-cpp/catch`
|
||||
|
||||
Required USE flags:
|
||||
- `dev-qt/qtbase network concurrent dbus gui widgets`
|
||||
- `dev-libs/quazip qt6`
|
||||
- `net-libs/mbedtls cmac`
|
||||
- `media-libs/libsdl2 haptic joystick sound video`
|
||||
- `dev-cpp/cpp-httplib ssl`
|
||||
|
||||
* `dev-qt/qtbase network concurrent dbus gui widgets`
|
||||
* `dev-libs/quazip qt6`
|
||||
* `net-libs/mbedtls cmac`
|
||||
* `media-libs/libsdl2 haptic joystick sound video`
|
||||
* `dev-cpp/cpp-httplib ssl`
|
||||
|
||||
[Caveats](./Caveats.md#gentoo-linux)
|
||||
|
||||
@@ -150,6 +157,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||
* Proper Wayland support requires `qt6-wayland`
|
||||
* GCC 11 or later is required.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -161,17 +169,20 @@ sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev l
|
||||
|
||||
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
|
||||
* To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>AlmaLinux, Fedora, Red Hat Linux</summary>
|
||||
|
||||
Fedora:
|
||||
|
||||
```sh
|
||||
sudo dnf install autoconf cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel boost jq
|
||||
```
|
||||
|
||||
AlmaLinux (use `YUZU_USE_CPM=ON`):
|
||||
|
||||
```sh
|
||||
# vvv - Only if RPMfusion is not installed or EPEL isn't either
|
||||
sudo dnf install epel-release dnf-utils
|
||||
@@ -188,12 +199,14 @@ For systems like OpenEuler or derivates, don't forget to also install: `SDL2-dev
|
||||
* [RPM Fusion](https://rpmfusion.org/Configuration) is required for `ffmpeg-devel`
|
||||
* Fedora 32 or later is required.
|
||||
* Fedora 36+ users with GCC 12 need Clang and should configure CMake with: `cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -B build`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Alpine Linux</summary>
|
||||
|
||||
First, enable the community repository; [see here](https://wiki.alpinelinux.org/wiki/Repositories#Enabling_the_community_repository).
|
||||
|
||||
```sh
|
||||
# Enable the community repository
|
||||
setup-apkrepos -c
|
||||
@@ -233,8 +246,9 @@ brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz
|
||||
If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`.
|
||||
|
||||
To run with MoltenVK, install additional dependencies:
|
||||
|
||||
```sh
|
||||
brew install molten-vk vulkan-loader
|
||||
brew install molten-vk
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#macos).
|
||||
@@ -253,7 +267,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
<details>
|
||||
<summary>NetBSD</summary>
|
||||
|
||||
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers ffmpeg7 libva nlohmann-json jq libopus qt6 mbedtls3 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`.
|
||||
For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 mbedtls3 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`.
|
||||
|
||||
[Caveats](./Caveats.md#netbsd).
|
||||
|
||||
@@ -294,23 +308,24 @@ sudo pkg install qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json ope
|
||||
|
||||
* Open the `MSYS2 MinGW 64-bit` shell (`mingw64.exe`)
|
||||
* Download and install all dependencies:
|
||||
```
|
||||
|
||||
```sh
|
||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||
|
||||
MINGW="qt6-base qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus mbedtls libusb unordered_dense"
|
||||
|
||||
MINGW="qt6-base qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus mbedtls libusb unordered_dense openssl SDL2"
|
||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||
packages="$BASE"
|
||||
for pkg in $MINGW; do
|
||||
packages="$packages mingw-w64-x86_64-$pkg"
|
||||
#packages="$packages mingw-w64-clang-aarch64-$pkg"
|
||||
done
|
||||
|
||||
pacman -Syuu --needed --noconfirm $packages
|
||||
```
|
||||
|
||||
* Notes:
|
||||
- Using `qt6-static` is possible but currently untested.
|
||||
- Other environments are entirely untested, but should theoretically work provided you install all the necessary packages.
|
||||
- GCC is proven to work better with the MinGW environment. If you choose to use Clang, you *may* be better off using the clang64 environment.
|
||||
- Add `qt-creator` to the `MINGW` variable to install Qt Creator. You can then create a Start Menu shortcut to the MinGW Qt Creator by running `powershell "\$s=(New-Object -COM WScript.Shell).CreateShortcut('C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Qt Creator.lnk');\$s.TargetPath='C:\\msys64\\mingw64\\bin\\qtcreator.exe';\$s.Save()"` in Git Bash or MSYS2.
|
||||
* Using `qt6-static` is possible as well, provided you build with `-DYUZU_STATIC_BUILD=ON`.
|
||||
* Other environments are entirely untested, but should theoretically work provided you install all the necessary packages.
|
||||
* GCC is proven to work better with the MinGW environment. On ARM, only Clang is available through the CLANGARM64 environment, so use that until a GNU ARM environment is available.
|
||||
* Add `qt-creator` to the `MINGW` variable to install Qt Creator. You can then create a Start Menu shortcut to the MinGW Qt Creator by running `powershell "\$s=(New-Object -COM WScript.Shell).CreateShortcut('C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Qt Creator.lnk');\$s.TargetPath='C:\\msys64\\mingw64\\bin\\qtcreator.exe';\$s.Save()"` in Git Bash or MSYS2.
|
||||
* Add MinGW binaries to the PATH if they aren't already:
|
||||
* `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc`
|
||||
* or `echo 'PATH=/mingw64/bin:$PATH' >> ~/.zshrc`
|
||||
|
||||
@@ -13,3 +13,5 @@ This contains documentation created by developers. This contains build instructi
|
||||
- **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)**
|
||||
- **[User Handbook](./user)**
|
||||
- **[Release Policy](./ReleasePolicy.md)**
|
||||
- **[Dynarmic](./dynarmic)**
|
||||
- **[Cross compilation](./CrossCompile.md)**
|
||||
|
||||
@@ -343,3 +343,279 @@ SetTerm(IR::Term::If{cond, term_then, term_else})
|
||||
|
||||
This terminal instruction conditionally executes one terminal or another depending
|
||||
on the run-time state of the ARM flags.
|
||||
|
||||
# Register Allocation (x64 Backend)
|
||||
|
||||
`HostLoc`s contain values. A `HostLoc` ("host value location") is either a host CPU register or a host spill location.
|
||||
|
||||
Values once set cannot be changed. Values can however be moved by the register allocator between `HostLoc`s. This is
|
||||
handled by the register allocator itself and code that uses the register allocator need not and should not move values
|
||||
between registers.
|
||||
|
||||
The register allocator is based on three concepts: `Use`, `Def` and `Scratch`.
|
||||
|
||||
* `Use`: The use of a value.
|
||||
* `Define`: The definition of a value, this is the only time when a value is set.
|
||||
* `Scratch`: Allocate a register that can be freely modified as one wishes.
|
||||
|
||||
Note that `Use`ing a value decrements its `use_count` by one. When the `use_count` reaches zero the value is discarded and no longer exists.
|
||||
|
||||
The member functions on `RegAlloc` are just a combination of the above concepts.
|
||||
|
||||
The following registers are reserved for internal use and should NOT participate in register allocation:
|
||||
- `%xmm0`, `%xmm1`, `%xmm2`: Used as scratch in exclusive memory access.
|
||||
- `%rsp`: Stack pointer.
|
||||
- `%r15`: JIT pointer
|
||||
- `%r14`: Page table pointer.
|
||||
- `%r13`: Fastmem pointer.
|
||||
|
||||
The layout convenes `%r15` as the JIT state pointer - while it may be tempting to turn it into a synthetic pointer, keeping an entire register (out of 12 available) is preferable over inlining a directly computed immediate.
|
||||
|
||||
Do NEVER modify `%r15`, we must make it clear that this register is "immutable" for the entirety of the JIT block duration.
|
||||
|
||||
### `Scratch`
|
||||
|
||||
```c++
|
||||
Xbyak::Reg64 ScratchGpr(HostLocList desired_locations = any_gpr);
|
||||
Xbyak::Xmm ScratchXmm(HostLocList desired_locations = any_xmm);
|
||||
```
|
||||
|
||||
At runtime, allocate one of the registers in `desired_locations`. You are free to modify the register. The register is discarded at the end of the allocation scope.
|
||||
|
||||
### Pure `Use`
|
||||
|
||||
```c++
|
||||
Xbyak::Reg64 UseGpr(Argument& arg);
|
||||
Xbyak::Xmm UseXmm(Argument& arg);
|
||||
OpArg UseOpArg(Argument& arg);
|
||||
void Use(Argument& arg, HostLoc host_loc);
|
||||
```
|
||||
|
||||
At runtime, the value corresponding to `arg` will be placed a register. The actual register is determined by
|
||||
which one of the above functions is called. `UseGpr` places it in an unused GPR, `UseXmm` places it
|
||||
in an unused XMM register, `UseOpArg` might be in a register or might be a memory location, and `Use` allows
|
||||
you to specify a specific register (GPR or XMM) to use.
|
||||
|
||||
This register **must not** have it's value changed.
|
||||
|
||||
### `UseScratch`
|
||||
|
||||
```c++
|
||||
Xbyak::Reg64 UseScratchGpr(Argument& arg);
|
||||
Xbyak::Xmm UseScratchXmm(Argument& arg);
|
||||
void UseScratch(Argument& arg, HostLoc host_loc);
|
||||
```
|
||||
|
||||
At runtime, the value corresponding to `arg` will be placed a register. The actual register is determined by
|
||||
which one of the above functions is called. `UseScratchGpr` places it in an unused GPR, `UseScratchXmm` places it
|
||||
in an unused XMM register, and `UseScratch` allows you to specify a specific register (GPR or XMM) to use.
|
||||
|
||||
The return value is the register allocated to you.
|
||||
|
||||
You are free to modify the value in the register. The register is discarded at the end of the allocation scope.
|
||||
|
||||
### `Define` as register
|
||||
|
||||
A `Define` is the defintion of a value. This is the only time when a value may be set.
|
||||
|
||||
```c++
|
||||
void DefineValue(IR::Inst* inst, const Xbyak::Reg& reg);
|
||||
```
|
||||
|
||||
By calling `DefineValue`, you are stating that you wish to define the value for `inst`, and you have written the
|
||||
value to the specified register `reg`.
|
||||
|
||||
### `Define`ing as an alias of a different value
|
||||
|
||||
Adding a `Define` to an existing value.
|
||||
|
||||
```c++
|
||||
void DefineValue(IR::Inst* inst, Argument& arg);
|
||||
```
|
||||
|
||||
You are declaring that the value for `inst` is the same as the value for `arg`. No host machine instructions are
|
||||
emitted.
|
||||
|
||||
## When to use each?
|
||||
|
||||
* Prefer `Use` to `UseScratch` where possible.
|
||||
* Prefer the `OpArg` variants where possible.
|
||||
* Prefer to **not** use the specific `HostLoc` variants where possible.
|
||||
|
||||
# Return Stack Buffer Optimization (x64 Backend)
|
||||
|
||||
One of the optimizations that dynarmic does is block-linking. Block-linking is done when
|
||||
the destination address of a jump is available at JIT-time. Instead of returning to the
|
||||
dispatcher at the end of a block we can perform block-linking: just jump directly to the
|
||||
next block. This is beneficial because returning to the dispatcher can often be quite
|
||||
expensive.
|
||||
|
||||
What should we do in cases when we can't predict the destination address? The eponymous
|
||||
example is when executing a return statement at the end of a function; the return address
|
||||
is not statically known at compile time.
|
||||
|
||||
We deal with this by using a return stack buffer: When we execute a call instruction,
|
||||
we push our prediction onto the RSB. When we execute a return instruction, we pop a
|
||||
prediction off the RSB. If the prediction is a hit, we immediately jump to the relevant
|
||||
compiled block. Otherwise, we return to the dispatcher.
|
||||
|
||||
This is the essential idea behind this optimization.
|
||||
|
||||
## `UniqueHash`
|
||||
|
||||
One complication dynarmic has is that a compiled block is not uniquely identifiable by
|
||||
the PC alone, but bits in the FPSCR and CPSR are also relevant. We resolve this by
|
||||
computing a 64-bit `UniqueHash` that is guaranteed to uniquely identify a block.
|
||||
|
||||
```c++
|
||||
u64 LocationDescriptor::UniqueHash() const {
|
||||
// This value MUST BE UNIQUE.
|
||||
// This calculation has to match up with EmitX64::EmitTerminalPopRSBHint
|
||||
u64 pc_u64 = u64(arm_pc) << 32;
|
||||
u64 fpscr_u64 = u64(fpscr.Value());
|
||||
u64 t_u64 = cpsr.T() ? 1 : 0;
|
||||
u64 e_u64 = cpsr.E() ? 2 : 0;
|
||||
return pc_u64 | fpscr_u64 | t_u64 | e_u64;
|
||||
}
|
||||
```
|
||||
|
||||
## Our implementation isn't actually a stack
|
||||
|
||||
Dynarmic's RSB isn't actually a stack. It was implemented as a ring buffer because
|
||||
that showed better performance in tests.
|
||||
|
||||
### RSB Structure
|
||||
|
||||
The RSB is implemented as a ring buffer. `rsb_ptr` is the index of the insertion
|
||||
point. Each element in `rsb_location_descriptors` is a `UniqueHash` and they
|
||||
each correspond to an element in `rsb_codeptrs`. `rsb_codeptrs` contains the
|
||||
host addresses for the corresponding the compiled blocks.
|
||||
|
||||
`RSBSize` was chosen by performance testing. Note that this is bigger than the
|
||||
size of the real RSB in hardware (which has 3 entries). Larger RSBs than 8
|
||||
showed degraded performance.
|
||||
|
||||
```c++
|
||||
struct JitState {
|
||||
// ...
|
||||
|
||||
static constexpr size_t RSBSize = 8; // MUST be a power of 2.
|
||||
u32 rsb_ptr = 0;
|
||||
std::array<u64, RSBSize> rsb_location_descriptors;
|
||||
std::array<u64, RSBSize> rsb_codeptrs;
|
||||
void ResetRSB();
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### RSB Push
|
||||
|
||||
We insert our prediction at the insertion point iff the RSB doesn't already
|
||||
contain a prediction with the same `UniqueHash`.
|
||||
|
||||
```c++
|
||||
void EmitX64::EmitPushRSB(IR::Block&, IR::Inst* inst) {
|
||||
using namespace Xbyak::util;
|
||||
|
||||
ASSERT(inst->GetArg(0).IsImmediate());
|
||||
u64 imm64 = inst->GetArg(0).GetU64();
|
||||
|
||||
Xbyak::Reg64 code_ptr_reg = reg_alloc.ScratchGpr(code, {HostLoc::RCX});
|
||||
Xbyak::Reg64 loc_desc_reg = reg_alloc.ScratchGpr(code);
|
||||
Xbyak::Reg32 index_reg = reg_alloc.ScratchGpr(code).cvt32();
|
||||
u64 code_ptr = unique_hash_to_code_ptr.find(imm64) != unique_hash_to_code_ptr.end()
|
||||
? u64(unique_hash_to_code_ptr[imm64])
|
||||
: u64(code->GetReturnFromRunCodeAddress());
|
||||
|
||||
code->mov(index_reg, dword[code.ABI_JIT_PTR + offsetof(JitState, rsb_ptr)]);
|
||||
code->add(index_reg, 1);
|
||||
code->and_(index_reg, u32(JitState::RSBSize - 1));
|
||||
|
||||
code->mov(loc_desc_reg, u64(imm64));
|
||||
CodePtr patch_location = code->getCurr<CodePtr>();
|
||||
patch_unique_hash_locations[imm64].emplace_back(patch_location);
|
||||
code->mov(code_ptr_reg, u64(code_ptr)); // This line has to match up with EmitX64::Patch.
|
||||
code->EnsurePatchLocationSize(patch_location, 10);
|
||||
|
||||
Xbyak::Label label;
|
||||
for (size_t i = 0; i < JitState::RSBSize; ++i) {
|
||||
code->cmp(loc_desc_reg, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_location_descriptors) + i * sizeof(u64)]);
|
||||
code->je(label, code->T_SHORT);
|
||||
}
|
||||
|
||||
code->mov(dword[code.ABI_JIT_PTR + offsetof(JitState, rsb_ptr)], index_reg);
|
||||
code->mov(qword[code.ABI_JIT_PTR + index_reg.cvt64() * 8 + offsetof(JitState, rsb_location_descriptors)], loc_desc_reg);
|
||||
code->mov(qword[code.ABI_JIT_PTR + index_reg.cvt64() * 8 + offsetof(JitState, rsb_codeptrs)], code_ptr_reg);
|
||||
code->L(label);
|
||||
}
|
||||
```
|
||||
|
||||
In pseudocode:
|
||||
|
||||
```c++
|
||||
for (i := 0 .. RSBSize-1)
|
||||
if (rsb_location_descriptors[i] == imm64)
|
||||
goto label;
|
||||
rsb_ptr++;
|
||||
rsb_ptr %= RSBSize;
|
||||
rsb_location_desciptors[rsb_ptr] = imm64; //< The UniqueHash
|
||||
rsb_codeptr[rsb_ptr] = /* codeptr corresponding to the UniqueHash */;
|
||||
label:
|
||||
```
|
||||
|
||||
## RSB Pop
|
||||
|
||||
To check if a predicition is in the RSB, we linearly scan the RSB.
|
||||
|
||||
```c++
|
||||
void EmitX64::EmitTerminalPopRSBHint(IR::Term::PopRSBHint, IR::LocationDescriptor initial_location) {
|
||||
using namespace Xbyak::util;
|
||||
|
||||
// This calculation has to match up with IREmitter::PushRSB
|
||||
code->mov(ecx, MJitStateReg(Arm::Reg::PC));
|
||||
code->shl(rcx, 32);
|
||||
code->mov(ebx, dword[code.ABI_JIT_PTR + offsetof(JitState, FPSCR_mode)]);
|
||||
code->or_(ebx, dword[code.ABI_JIT_PTR + offsetof(JitState, CPSR_et)]);
|
||||
code->or_(rbx, rcx);
|
||||
|
||||
code->mov(rax, u64(code->GetReturnFromRunCodeAddress()));
|
||||
for (size_t i = 0; i < JitState::RSBSize; ++i) {
|
||||
code->cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_location_descriptors) + i * sizeof(u64)]);
|
||||
code->cmove(rax, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_codeptrs) + i * sizeof(u64)]);
|
||||
}
|
||||
|
||||
code->jmp(rax);
|
||||
}
|
||||
```
|
||||
|
||||
In pseudocode:
|
||||
|
||||
```c++
|
||||
rbx := ComputeUniqueHash()
|
||||
rax := ReturnToDispatch
|
||||
for (i := 0 .. RSBSize-1)
|
||||
if (rbx == rsb_location_descriptors[i])
|
||||
rax = rsb_codeptrs[i]
|
||||
goto rax
|
||||
```
|
||||
|
||||
# Fast memory (Fastmem)
|
||||
|
||||
The main way of accessing memory in JITed programs is via an invoked function, say "Read()" and "Write()". On our translator, such functions usually take a sizable amounts of code space (push + call + pop). Trash the i-cache (due to an indirect call) and overall make code emission more bloated.
|
||||
|
||||
The solution? Delegate invalid accesses to a dedicated arena, similar to a swap. The main idea behind such mechanism is to allow the OS to transmit page faults from invalid accesses into the JIT translator directly, bypassing address space calls, while this sacrifices i-cache coherency, it allows for smaller code-size and "faster" throguhput.
|
||||
|
||||
Many kernels however, do not support fast signal dispatching (Solaris, OpenBSD, FreeBSD). Only Linux and Windows support relatively "fast" signal dispatching. Hence this feature is better suited for them only.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
In x86_64 for example, when a page fault occurs, the CPU will transmit via control registers and the stack (see `IRETQ`) the appropriate arguments for a page fault handler, the OS then will transform that into something that can be sent into userspace.
|
||||
|
||||
Most modern OSes implement kernel-page-table-isolation, which means a set of system calls will invoke a context switch (not often used syscalls), whereas others are handled by the same process address space (the smaller kernel portion, often used syscalls) without needing a context switch. This effect can be negated on systems with PCID (up to 4096 unique IDs).
|
||||
|
||||
Signal dispatching takes a performance hit from reloading `%cr3` - but Linux does something more clever to avoid reloads: VDSO will take care of the entire thing in the same address space. Making dispatching as costly as an indirect call - without the hazards of increased code size.
|
||||
|
||||
The main downside from this is the constant i-cache trashing and pipeline hazards introduced by the VDSO signal handlers. However on most benchmarks fastmem does perform faster than without (Linux only). This also abuses the fact of continous address space emulation by using an arena - which can then be potentially transparently mapped into a hugepage, reducing TLB walk times.
|
||||
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
@@ -49,7 +49,7 @@ Important API Changes in v6.x Series
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Design documentation can be found at [docs/Design.md](docs/Design.md).
|
||||
Design documentation can be found at [./Design.md](./Design.md).
|
||||
|
||||
|
||||
Usage Example
|
||||
@@ -119,10 +119,10 @@
|
||||
"package": "VulkanUtilityLibraries",
|
||||
"repo": "scripts/VulkanUtilityHeaders",
|
||||
"tag": "%VERSION%",
|
||||
"git_version": "1.4.328",
|
||||
"git_version": "1.4.335",
|
||||
"artifact": "VulkanUtilityHeaders.tar.zst",
|
||||
"git_host": "git.crueter.xyz",
|
||||
"hash": "9922217b39faf73cd4fc1510f2fdba14a49aa5c0d77f9ee24ee0512cef16b234d0cabc83c1fec861fa5df1d43e7f086ca9b6501753899119f39c5ca530cb0dae"
|
||||
"hash": "16dac0e6586702580c4279e4cd37ffe3cf909c93eb31b5069da7af36436d47b270a9cbaac953bb66c22ed12ed67ffa096688599267f307dfb62be1bc09f79833"
|
||||
},
|
||||
"spirv-tools": {
|
||||
"package": "SPIRV-Tools",
|
||||
|
||||
@@ -217,7 +217,6 @@ else()
|
||||
--disable-ffmpeg
|
||||
--disable-ffprobe
|
||||
--disable-network
|
||||
--disable-postproc
|
||||
--disable-swresample
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=vp8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
let
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11";
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
|
||||
pkgs = import nixpkgs { config = {}; overlays = []; };
|
||||
in
|
||||
pkgs.mkShellNoCC {
|
||||
|
||||
@@ -18,6 +18,7 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
id("com.github.triplet.play") version "3.8.6"
|
||||
id("idea")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,8 @@ plugins {
|
||||
*/
|
||||
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
|
||||
|
||||
val edenDir = project(":Eden").projectDir
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
@@ -36,7 +39,6 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -241,11 +243,17 @@ android {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("../../../CMakeLists.txt")
|
||||
path = file("${edenDir}/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// Inclusion to exclude build/ dir from non-Android
|
||||
excludeDirs.add(file("${edenDir}/build"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||
delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
|
||||
@@ -346,7 +354,7 @@ fun getGitVersion(): String {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val artifactsDir = layout.projectDirectory.dir("../../../artifacts")
|
||||
val artifactsDir = layout.projectDirectory.dir("${edenDir}/artifacts")
|
||||
val outputsDir = layout.buildDirectory.dir("outputs").get()
|
||||
|
||||
android.applicationVariants.forEach { variant ->
|
||||
|
||||
@@ -29,6 +29,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
|
||||
<application
|
||||
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
||||
|
||||
@@ -639,6 +639,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
|
||||
companion object {
|
||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||
const val EXTRA_OVERLAY_GAMELESS_EDIT_MODE = "overlayGamelessEditMode"
|
||||
|
||||
fun stopForegroundService(activity: Activity) {
|
||||
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||
@@ -652,6 +653,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
activity.startActivity(launcher)
|
||||
}
|
||||
|
||||
fun launchForOverlayEdit(context: Context): Intent {
|
||||
return Intent(context, EmulationActivity::class.java).apply {
|
||||
putExtra(EXTRA_OVERLAY_GAMELESS_EDIT_MODE, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
|
||||
if (view == null) {
|
||||
return true
|
||||
|
||||
@@ -143,8 +143,21 @@ class ReleaseAdapter(
|
||||
binding.containerDownloads.removeAllViews()
|
||||
|
||||
release.artifacts.forEach { artifact ->
|
||||
val alreadyInstalled = try {
|
||||
// Prefer fast check via ViewModel list; fallback to helper if needed
|
||||
driverViewModel.driverData.any {
|
||||
File(it.first).name.equals(artifact.name, ignoreCase = true)
|
||||
} || GpuDriverHelper.isDriverZipInstalledByName(artifact.name)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
val button = MaterialButton(binding.root.context).apply {
|
||||
text = artifact.name
|
||||
text = if (alreadyInstalled) {
|
||||
context.getString(R.string.installed_label, artifact.name)
|
||||
} else {
|
||||
artifact.name
|
||||
}
|
||||
setTextAppearance(
|
||||
com.google.android.material.R.style.TextAppearance_Material3_LabelLarge
|
||||
)
|
||||
@@ -154,7 +167,7 @@ class ReleaseAdapter(
|
||||
com.google.android.material.R.color.m3_button_background_color_selector
|
||||
)
|
||||
)
|
||||
setIconResource(R.drawable.ic_import)
|
||||
setIconResource(if (alreadyInstalled) R.drawable.ic_check else R.drawable.ic_import)
|
||||
iconTint = ColorStateList.valueOf(
|
||||
MaterialColors.getColor(
|
||||
this,
|
||||
@@ -167,7 +180,22 @@ class ReleaseAdapter(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
isEnabled = !alreadyInstalled
|
||||
setOnClickListener {
|
||||
// Double-check just before starting (race-proof)
|
||||
if (GpuDriverHelper.isDriverZipInstalledByName(artifact.name)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.driver_already_installed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
// Update UI to reflect installed state
|
||||
this.isEnabled = false
|
||||
this.text = context.getString(R.string.installed_label, artifact.name)
|
||||
this.setIconResource(R.drawable.ic_check)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val dialogBinding =
|
||||
DialogProgressBinding.inflate(LayoutInflater.from(context))
|
||||
dialogBinding.progressBar.isIndeterminate = true
|
||||
@@ -233,6 +261,10 @@ class ReleaseAdapter(
|
||||
driverViewModel.onDriverAdded(Pair(driverPath, driverData))
|
||||
|
||||
progressDialog.dismiss()
|
||||
// Update button to installed state
|
||||
this@apply.isEnabled = false
|
||||
this@apply.text = context.getString(R.string.installed_label, artifact.name)
|
||||
this@apply.setIconResource(R.drawable.ic_check)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(
|
||||
|
||||
@@ -14,7 +14,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
|
||||
CORE_SYNC_CORE_SPEED("sync_core_speed"),
|
||||
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
|
||||
USE_FAST_CPU_TIME("use_fast_cpu_time"),
|
||||
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
|
||||
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
||||
CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"),
|
||||
@@ -23,11 +22,11 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"),
|
||||
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_FAST_GPU("use_fast_gpu_time"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
|
||||
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
|
||||
@@ -41,6 +40,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
DPAD_SLIDE("dpad_slide"),
|
||||
HAPTIC_FEEDBACK("haptic_feedback"),
|
||||
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
||||
OVERLAY_SNAP_TO_GRID("overlay_snap_to_grid"),
|
||||
TOUCHSCREEN("touchscreen"),
|
||||
AIRPLANE_MODE("airplane_mode"),
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -6,8 +9,7 @@ package org.yuzu.yuzu_emu.features.settings.model
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class ByteSetting(override val key: String) : AbstractByteSetting {
|
||||
AUDIO_VOLUME("volume"),
|
||||
RENDERER_DYNA_STATE("dyna_state");
|
||||
AUDIO_VOLUME("volume"),;
|
||||
|
||||
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_SCREEN_LAYOUT("screen_layout"),
|
||||
RENDERER_ASPECT_RATIO("aspect_ratio"),
|
||||
RENDERER_OPTIMIZE_SPIRV_OUTPUT("optimize_spirv_output"),
|
||||
|
||||
RENDERER_DYNA_STATE("dyna_state"),
|
||||
DMA_ACCURACY("dma_accuracy"),
|
||||
AUDIO_OUTPUT_ENGINE("output_engine"),
|
||||
MAX_ANISOTROPY("max_anisotropy"),
|
||||
@@ -41,7 +43,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
SOC_OVERLAY_POSITION("soc_overlay_position"),
|
||||
MEMORY_LAYOUT("memory_layout_mode"),
|
||||
FSR_SHARPENING_SLIDER("fsr_sharpening_slider"),
|
||||
RENDERER_SAMPLE_SHADING_FRACTION("sample_shading_fraction"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading_fraction"),
|
||||
FAST_CPU_TIME("fast_cpu_time"),
|
||||
CPU_TICKS("cpu_ticks"),
|
||||
FAST_GPU_TIME("fast_gpu_time"),
|
||||
@@ -62,6 +64,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
|
||||
MY_PAGE_APPLET("my_page_applet_mode"),
|
||||
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
||||
OVERLAY_GRID_SIZE("overlay_grid_size"),
|
||||
DEBUG_KNOBS("debug_knobs")
|
||||
;
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ object Settings {
|
||||
SECTION_INPUT_PLAYER_SEVEN,
|
||||
SECTION_INPUT_PLAYER_EIGHT,
|
||||
SECTION_APP_SETTINGS(R.string.app_settings),
|
||||
SECTION_CUSTOM_PATHS(R.string.preferences_custom_paths),
|
||||
SECTION_DEBUG(R.string.preferences_debug),
|
||||
SECTION_EDEN_VEIL(R.string.eden_veil),
|
||||
SECTION_APPLETS(R.string.applets_menu);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* A settings item that launches an intent when clicked.
|
||||
*/
|
||||
class LaunchableSetting(
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val launchIntent: (android.content.Context) -> Intent
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = SettingsItem.TYPE_LAUNCHABLE
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
class PathSetting(
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
@DrawableRes val iconId: Int = 0,
|
||||
val pathType: PathType,
|
||||
val defaultPathGetter: () -> String,
|
||||
val currentPathGetter: () -> String,
|
||||
val pathSetter: (String) -> Unit
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
|
||||
override val type = TYPE_PATH
|
||||
|
||||
enum class PathType {
|
||||
SAVE_DATA,
|
||||
NAND,
|
||||
SDMC
|
||||
}
|
||||
|
||||
fun getCurrentPath(): String = currentPathGetter()
|
||||
|
||||
fun getDefaultPath(): String = defaultPathGetter()
|
||||
|
||||
fun setPath(path: String) = pathSetter(path)
|
||||
|
||||
fun isUsingDefaultPath(): Boolean = getCurrentPath() == getDefaultPath()
|
||||
|
||||
companion object {
|
||||
const val TYPE_PATH = 14
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,8 @@ abstract class SettingsItem(
|
||||
const val TYPE_INPUT_PROFILE = 10
|
||||
const val TYPE_STRING_INPUT = 11
|
||||
const val TYPE_SPINBOX = 12
|
||||
const val TYPE_LAUNCHABLE = 13
|
||||
const val TYPE_PATH = 14
|
||||
|
||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||
|
||||
@@ -132,7 +134,7 @@ abstract class SettingsItem(
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
ByteSetting.RENDERER_DYNA_STATE,
|
||||
IntSetting.RENDERER_DYNA_STATE,
|
||||
titleId = R.string.dyna_state,
|
||||
descriptionId = R.string.dyna_state_description,
|
||||
choicesId = R.array.dynaStateEntries,
|
||||
@@ -160,16 +162,9 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.descriptor_indexing_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_SAMPLE_SHADING,
|
||||
titleId = R.string.sample_shading,
|
||||
descriptionId = R.string.sample_shading_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_SAMPLE_SHADING_FRACTION,
|
||||
IntSetting.RENDERER_SAMPLE_SHADING,
|
||||
titleId = R.string.sample_shading_fraction,
|
||||
descriptionId = R.string.sample_shading_fraction_description,
|
||||
units = "%"
|
||||
@@ -311,6 +306,7 @@ abstract class SettingsItem(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ACCURACY,
|
||||
titleId = R.string.renderer_accuracy,
|
||||
descriptionId = R.string.renderer_accuracy_description,
|
||||
choicesId = R.array.rendererAccuracyNames,
|
||||
valuesId = R.array.rendererAccuracyValues
|
||||
)
|
||||
@@ -371,6 +367,30 @@ abstract class SettingsItem(
|
||||
warningMessage = R.string.warning_resolution
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY,
|
||||
titleId = R.string.show_input_overlay,
|
||||
descriptionId = R.string.show_input_overlay_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID,
|
||||
titleId = R.string.overlay_snap_to_grid,
|
||||
descriptionId = R.string.overlay_snap_to_grid_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.OVERLAY_GRID_SIZE,
|
||||
titleId = R.string.overlay_grid_size,
|
||||
descriptionId = R.string.overlay_grid_size_description,
|
||||
min = 16,
|
||||
max = 128,
|
||||
units = "px"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,
|
||||
@@ -430,7 +450,7 @@ abstract class SettingsItem(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_FRAMETIME,
|
||||
R.string.show_frametime,
|
||||
descriptionId = R.string.show_frametime_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
@@ -451,7 +471,7 @@ abstract class SettingsItem(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_BAT_TEMPERATURE,
|
||||
R.string.show_bat_temperature,
|
||||
descriptionId = R.string.show_bat_temperature_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
@@ -481,21 +501,21 @@ abstract class SettingsItem(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_SOC_OVERLAY,
|
||||
R.string.enable_soc_overlay,
|
||||
descriptionId = R.string.soc_overlay_options_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SOC_OVERLAY_BACKGROUND,
|
||||
R.string.perf_overlay_background,
|
||||
descriptionId = R.string.perf_overlay_background_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.SOC_OVERLAY_POSITION,
|
||||
titleId = R.string.overlay_position,
|
||||
descriptionId = R.string.overlay_position_description,
|
||||
descriptionId = 0,
|
||||
choicesId = R.array.statsPosition,
|
||||
valuesId = R.array.staticThemeValues
|
||||
)
|
||||
@@ -505,28 +525,28 @@ abstract class SettingsItem(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_DEVICE_MODEL,
|
||||
titleId = R.string.show_device_model,
|
||||
descriptionId = R.string.show_device_model_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_GPU_MODEL,
|
||||
titleId = R.string.show_gpu_model,
|
||||
descriptionId = R.string.show_gpu_model_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_SOC_MODEL,
|
||||
titleId = R.string.show_soc_model,
|
||||
descriptionId = R.string.show_soc_model_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_FW_VERSION,
|
||||
titleId = R.string.show_fw_version,
|
||||
descriptionId = R.string.show_fw_version_description
|
||||
descriptionId = 0
|
||||
)
|
||||
)
|
||||
|
||||
@@ -626,13 +646,6 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FAST_GPU,
|
||||
titleId = R.string.use_fast_gpu_time,
|
||||
descriptionId = R.string.use_fast_gpu_time_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FAST_GPU_TIME,
|
||||
@@ -642,13 +655,6 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.gpuValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_FAST_CPU_TIME,
|
||||
titleId = R.string.use_fast_cpu_time,
|
||||
descriptionId = R.string.use_fast_cpu_time_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FAST_CPU_TIME,
|
||||
@@ -766,6 +772,14 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.renderer_debug_description
|
||||
)
|
||||
)
|
||||
// BCn texture patching debug override
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_PATCH_OLD_QCOM_DRIVERS,
|
||||
titleId = R.string.patch_old_qcom_drivers,
|
||||
descriptionId = R.string.patch_old_qcom_drivers_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_AUTO_STUB,
|
||||
|
||||
@@ -93,6 +93,13 @@ class SettingsAdapter(
|
||||
StringInputViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_LAUNCHABLE -> {
|
||||
LaunchableViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_PATH -> {
|
||||
PathViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
else -> {
|
||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||
}
|
||||
@@ -209,6 +216,11 @@ class SettingsAdapter(
|
||||
fragment.view?.findNavController()?.navigate(action)
|
||||
}
|
||||
|
||||
fun onLaunchableClick(item: LaunchableSetting) {
|
||||
val intent = item.launchIntent(context)
|
||||
fragment.requireActivity().startActivity(intent)
|
||||
}
|
||||
|
||||
fun onInputProfileClick(item: InputProfileSetting, position: Int) {
|
||||
InputProfileDialogFragment.newInstance(
|
||||
settingsViewModel,
|
||||
@@ -442,6 +454,18 @@ class SettingsAdapter(
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
fun onPathClick(item: PathSetting, position: Int) {
|
||||
settingsViewModel.clickedItem = item
|
||||
settingsViewModel.setPathSettingPosition(position)
|
||||
settingsViewModel.setShouldShowPathPicker(true)
|
||||
}
|
||||
|
||||
fun onPathReset(item: PathSetting, position: Int) {
|
||||
settingsViewModel.clickedItem = item
|
||||
settingsViewModel.setPathSettingPosition(position)
|
||||
settingsViewModel.setShouldShowPathResetDialog(true)
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
||||
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||
return oldItem.setting.key == newItem.setting.key
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings as AndroidSettings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
@@ -19,14 +25,19 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.PathUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.io.File
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
private lateinit var presenter: SettingsFragmentPresenter
|
||||
@@ -39,6 +50,20 @@ class SettingsFragment : Fragment() {
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||
|
||||
private val requestAllFilesPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (hasAllFilesPermission()) {
|
||||
showPathPickerDialog()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.all_files_permission_required,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
@@ -134,6 +159,24 @@ class SettingsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
settingsViewModel.shouldShowPathPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowPathPicker(false) }
|
||||
) {
|
||||
if (it) {
|
||||
handlePathPickerRequest()
|
||||
}
|
||||
}
|
||||
|
||||
settingsViewModel.shouldShowPathResetDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowPathResetDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
showPathResetDialog()
|
||||
}
|
||||
}
|
||||
|
||||
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
|
||||
binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
|
||||
binding.toolbarSettings.setOnMenuItemClickListener {
|
||||
@@ -184,4 +227,199 @@ class SettingsFragment : Fragment() {
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAllFilesPermission(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestAllFilesPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val intent = Intent(AndroidSettings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
intent.data = "package:${requireContext().packageName}".toUri()
|
||||
requestAllFilesPermissionLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePathPickerRequest() {
|
||||
if (!hasAllFilesPermission()) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.all_files_permission_required)
|
||||
.setMessage(R.string.all_files_permission_required)
|
||||
.setPositiveButton(R.string.grant_permission) { _, _ ->
|
||||
requestAllFilesPermission()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
showPathPickerDialog()
|
||||
}
|
||||
|
||||
private fun showPathPickerDialog() {
|
||||
directoryPickerLauncher.launch(null)
|
||||
}
|
||||
|
||||
private val directoryPickerLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
val pathSetting = settingsViewModel.clickedItem as? PathSetting ?: return@registerForActivityResult
|
||||
val rawPath = PathUtil.getPathFromUri(uri)
|
||||
if (rawPath != null) {
|
||||
handleSelectedPath(pathSetting, rawPath)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.invalid_directory,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSelectedPath(pathSetting: PathSetting, path: String) {
|
||||
if (!PathUtil.validateDirectory(path)) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.invalid_directory,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
if (pathSetting.pathType == PathSetting.PathType.SAVE_DATA) {
|
||||
val oldPath = pathSetting.getCurrentPath()
|
||||
if (oldPath != path) {
|
||||
promptSaveMigration(pathSetting, oldPath, path)
|
||||
}
|
||||
} else {
|
||||
setPathAndNotify(pathSetting, path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun promptSaveMigration(pathSetting: PathSetting, fromPath: String, toPath: String) {
|
||||
val sourceSavePath = "$fromPath/user/save"
|
||||
val destSavePath = "$toPath/user/save"
|
||||
val sourceSaveDir = File(sourceSavePath)
|
||||
val destSaveDir = File(destSavePath)
|
||||
|
||||
val sourceHasSaves = PathUtil.hasContent(sourceSavePath)
|
||||
val destHasSaves = PathUtil.hasContent(destSavePath)
|
||||
|
||||
if (!sourceHasSaves) {
|
||||
setPathAndNotify(pathSetting, toPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (destHasSaves) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.migrate_save_data)
|
||||
.setMessage(R.string.destination_has_saves)
|
||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||
migrateSaveData(pathSetting, sourceSaveDir, destSaveDir, toPath)
|
||||
}
|
||||
.setNegativeButton(R.string.skip_migration) { _, _ ->
|
||||
setPathAndNotify(pathSetting, toPath)
|
||||
}
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.migrate_save_data)
|
||||
.setMessage(R.string.migrate_save_data_question)
|
||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||
migrateSaveData(pathSetting, sourceSaveDir, destSaveDir, toPath)
|
||||
}
|
||||
.setNegativeButton(R.string.skip_migration) { _, _ ->
|
||||
setPathAndNotify(pathSetting, toPath)
|
||||
}
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateSaveData(
|
||||
pathSetting: PathSetting,
|
||||
sourceDir: File,
|
||||
destDir: File,
|
||||
newPath: String
|
||||
) {
|
||||
Thread {
|
||||
val success = PathUtil.copyDirectory(sourceDir, destDir, overwrite = true)
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
if (success) {
|
||||
setPathAndNotify(pathSetting, newPath)
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.save_migration_complete,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.save_migration_failed,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun setPathAndNotify(pathSetting: PathSetting, path: String) {
|
||||
pathSetting.setPath(path)
|
||||
NativeConfig.saveGlobalConfig()
|
||||
|
||||
NativeConfig.reloadGlobalConfig()
|
||||
|
||||
val messageResId = if (pathSetting.pathType == PathSetting.PathType.SAVE_DATA) {
|
||||
R.string.save_directory_set
|
||||
} else {
|
||||
R.string.path_set
|
||||
}
|
||||
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
messageResId,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
val position = settingsViewModel.pathSettingPosition.value
|
||||
if (position >= 0) {
|
||||
settingsAdapter?.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPathResetDialog() {
|
||||
val pathSetting = settingsViewModel.clickedItem as? PathSetting ?: return
|
||||
|
||||
if (pathSetting.isUsingDefaultPath()) {
|
||||
return
|
||||
}
|
||||
|
||||
val currentPath = pathSetting.getCurrentPath()
|
||||
val defaultPath = pathSetting.getDefaultPath()
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.reset_to_nand)
|
||||
.setMessage(R.string.migrate_save_data_question)
|
||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
||||
val sourceSaveDir = File(currentPath, "user/save")
|
||||
val destSaveDir = File(defaultPath, "user/save")
|
||||
|
||||
if (sourceSaveDir.exists() && sourceSaveDir.listFiles()?.isNotEmpty() == true) {
|
||||
migrateSaveData(pathSetting, sourceSaveDir, destSaveDir, defaultPath)
|
||||
} else {
|
||||
setPathAndNotify(pathSetting, defaultPath)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
// just dismiss
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
||||
@@ -28,6 +29,7 @@ import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
@@ -107,8 +109,8 @@ class SettingsFragmentPresenter(
|
||||
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
||||
MenuTag.SECTION_APP_SETTINGS -> addThemeSettings(sl)
|
||||
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
MenuTag.SECTION_EDEN_VEIL -> addEdenVeilSettings(sl)
|
||||
MenuTag.SECTION_APPLETS -> addAppletSettings(sl)
|
||||
MenuTag.SECTION_CUSTOM_PATHS -> addCustomPathsSettings(sl)
|
||||
}
|
||||
settingsList = sl
|
||||
adapter.submitList(settingsList) {
|
||||
@@ -179,14 +181,6 @@ class SettingsFragmentPresenter(
|
||||
menuKey = MenuTag.SECTION_DEBUG
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.eden_veil,
|
||||
descriptionId = R.string.eden_veil_description,
|
||||
iconId = R.drawable.ic_eden_veil,
|
||||
menuKey = MenuTag.SECTION_EDEN_VEIL
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.applets_menu,
|
||||
@@ -195,6 +189,16 @@ class SettingsFragmentPresenter(
|
||||
menuKey = MenuTag.SECTION_APPLETS
|
||||
)
|
||||
)
|
||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.preferences_custom_paths,
|
||||
descriptionId = R.string.preferences_custom_paths_description,
|
||||
iconId = R.drawable.ic_folder_open,
|
||||
menuKey = MenuTag.SECTION_CUSTOM_PATHS
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.reset_to_default,
|
||||
@@ -217,30 +221,61 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||
add(LongSetting.CUSTOM_RTC.key)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(IntSetting.FAST_CPU_TIME.key)
|
||||
add(BooleanSetting.USE_LRU_CACHE.key)
|
||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||
|
||||
add(IntSetting.MEMORY_LAYOUT.key)
|
||||
add(BooleanSetting.USE_CUSTOM_CPU_TICKS.key)
|
||||
add(IntSetting.CPU_TICKS.key)
|
||||
|
||||
add(HeaderSetting(R.string.network))
|
||||
add(StringSetting.WEB_TOKEN.key)
|
||||
add(StringSetting.WEB_USERNAME.key)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(crueter): sub-submenus?
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.backend))
|
||||
// add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||
|
||||
add(HeaderSetting(R.string.processing))
|
||||
|
||||
add(IntSetting.RENDERER_VSYNC.key)
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_SHADER_BACKEND.key)
|
||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||
add(IntSetting.FSR_SHARPENING_SLIDER.key)
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
add(IntSetting.RENDERER_OPTIMIZE_SPIRV_OUTPUT.key)
|
||||
|
||||
add(HeaderSetting(R.string.advanced))
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.DMA_ACCURACY.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||
add(IntSetting.RENDERER_ASTC_RECOMPRESSION.key)
|
||||
|
||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
|
||||
add(HeaderSetting(R.string.extensions))
|
||||
|
||||
add(IntSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
|
||||
add(HeaderSetting(R.string.display))
|
||||
|
||||
@@ -272,6 +307,19 @@ class SettingsFragmentPresenter(
|
||||
|
||||
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(BooleanSetting.SHOW_INPUT_OVERLAY.key)
|
||||
add(BooleanSetting.OVERLAY_SNAP_TO_GRID.key)
|
||||
add(IntSetting.OVERLAY_GRID_SIZE.key)
|
||||
add(
|
||||
LaunchableSetting(
|
||||
titleId = R.string.edit_overlay_layout,
|
||||
descriptionId = R.string.edit_overlay_layout_description,
|
||||
launchIntent = { context ->
|
||||
EmulationActivity.launchForOverlayEdit(context)
|
||||
}
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.input_overlay_behavior))
|
||||
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
|
||||
@@ -450,42 +498,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEdenVeilSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.veil_extensions))
|
||||
add(ByteSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(BooleanSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING_FRACTION.key)
|
||||
|
||||
add(HeaderSetting(R.string.veil_renderer))
|
||||
add(IntSetting.DMA_ACCURACY.key)
|
||||
add(BooleanSetting.BUFFER_REORDER_DISABLE.key)
|
||||
add(BooleanSetting.RENDERER_FAST_GPU.key)
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(IntSetting.RENDERER_SHADER_BACKEND.key)
|
||||
add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||
add(IntSetting.RENDERER_ASTC_RECOMPRESSION.key)
|
||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||
add(IntSetting.RENDERER_OPTIMIZE_SPIRV_OUTPUT.key)
|
||||
|
||||
add(HeaderSetting(R.string.veil_misc))
|
||||
add(BooleanSetting.USE_FAST_CPU_TIME.key)
|
||||
add(IntSetting.FAST_CPU_TIME.key)
|
||||
add(BooleanSetting.USE_CUSTOM_CPU_TICKS.key)
|
||||
add(IntSetting.CPU_TICKS.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.CPUOPT_UNSAFE_HOST_MMU.key)
|
||||
add(BooleanSetting.USE_LRU_CACHE.key)
|
||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||
add(IntSetting.MEMORY_LAYOUT.key)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAppletSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(IntSetting.SWKBD_APPLET.key)
|
||||
@@ -1113,14 +1125,16 @@ class SettingsFragmentPresenter(
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
staticThemeColor,
|
||||
titleId = R.string.static_theme_color,
|
||||
choicesId = R.array.staticThemeNames,
|
||||
valuesId = R.array.staticThemeValues
|
||||
if (IntSetting.THEME.getInt() != 1) {
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
staticThemeColor,
|
||||
titleId = R.string.static_theme_color,
|
||||
choicesId = R.array.staticThemeNames,
|
||||
valuesId = R.array.staticThemeValues
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
@@ -1159,18 +1173,65 @@ class SettingsFragmentPresenter(
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.gpu))
|
||||
|
||||
add(IntSetting.RENDERER_BACKEND.key)
|
||||
add(BooleanSetting.RENDERER_DEBUG.key)
|
||||
add(BooleanSetting.RENDERER_PATCH_OLD_QCOM_DRIVERS.key)
|
||||
add(BooleanSetting.BUFFER_REORDER_DISABLE.key)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
|
||||
add(IntSetting.CPU_BACKEND.key)
|
||||
add(IntSetting.CPU_ACCURACY.key)
|
||||
add(BooleanSetting.USE_AUTO_STUB.key)
|
||||
add(SettingsItem.FASTMEM_COMBINED)
|
||||
add(BooleanSetting.CPUOPT_UNSAFE_HOST_MMU.key)
|
||||
|
||||
add(HeaderSetting(R.string.log))
|
||||
|
||||
add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key)
|
||||
|
||||
add(HeaderSetting(R.string.general))
|
||||
|
||||
add(IntSetting.DEBUG_KNOBS.key)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCustomPathsSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(
|
||||
PathSetting(
|
||||
titleId = R.string.custom_save_directory,
|
||||
descriptionId = R.string.custom_save_directory_description,
|
||||
iconId = R.drawable.ic_save,
|
||||
pathType = PathSetting.PathType.SAVE_DATA,
|
||||
defaultPathGetter = { NativeConfig.getDefaultSaveDir() },
|
||||
currentPathGetter = { NativeConfig.getSaveDir() },
|
||||
pathSetter = { path -> NativeConfig.setSaveDir(path) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
PathSetting(
|
||||
titleId = R.string.custom_nand_directory,
|
||||
descriptionId = R.string.custom_nand_directory_description,
|
||||
iconId = R.drawable.ic_folder_open,
|
||||
pathType = PathSetting.PathType.NAND,
|
||||
defaultPathGetter = { DirectoryInitialization.userDirectory + "/nand" },
|
||||
currentPathGetter = { NativeConfig.getNandDir() },
|
||||
pathSetter = { path -> NativeConfig.setNandDir(path) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
PathSetting(
|
||||
titleId = R.string.custom_sdmc_directory,
|
||||
descriptionId = R.string.custom_sdmc_directory_description,
|
||||
iconId = R.drawable.ic_folder_open,
|
||||
pathType = PathSetting.PathType.SDMC,
|
||||
defaultPathGetter = { DirectoryInitialization.userDirectory + "/sdmc" },
|
||||
currentPathGetter = { NativeConfig.getSdmcDir() },
|
||||
pathSetter = { path -> NativeConfig.setSdmcDir(path) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,16 @@ class SettingsViewModel : ViewModel() {
|
||||
|
||||
private val _shouldRecreateForLanguageChange = MutableStateFlow(false)
|
||||
val shouldRecreateForLanguageChange = _shouldRecreateForLanguageChange.asStateFlow()
|
||||
|
||||
private val _shouldShowPathPicker = MutableStateFlow(false)
|
||||
val shouldShowPathPicker = _shouldShowPathPicker.asStateFlow()
|
||||
|
||||
private val _shouldShowPathResetDialog = MutableStateFlow(false)
|
||||
val shouldShowPathResetDialog = _shouldShowPathResetDialog.asStateFlow()
|
||||
|
||||
private val _pathSettingPosition = MutableStateFlow(-1)
|
||||
val pathSettingPosition = _pathSettingPosition.asStateFlow()
|
||||
|
||||
fun setShouldRecreate(value: Boolean) {
|
||||
_shouldRecreate.value = value
|
||||
}
|
||||
@@ -112,6 +122,18 @@ class SettingsViewModel : ViewModel() {
|
||||
_shouldRecreateForLanguageChange.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowPathPicker(value: Boolean) {
|
||||
_shouldShowPathPicker.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowPathResetDialog(value: Boolean) {
|
||||
_shouldShowPathResetDialog.value = value
|
||||
}
|
||||
|
||||
fun setPathSettingPosition(value: Int) {
|
||||
_pathSettingPosition.value = value
|
||||
}
|
||||
|
||||
fun getCurrentDeviceParams(defaultParams: ParamPackage): ParamPackage =
|
||||
try {
|
||||
InputHandler.registeredControllers[currentDevice]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.LaunchableSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class LaunchableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: LaunchableSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as LaunchableSetting
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.text = ""
|
||||
binding.textSettingValue.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
0, 0, R.drawable.ic_arrow_forward, 0
|
||||
)
|
||||
|
||||
binding.buttonClear.setVisible(false)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
adapter.onLaunchableClick(setting)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
// no-op
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.PathUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class PathViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
||||
private lateinit var setting: PathSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as PathSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
setting.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
|
||||
val currentPath = setting.getCurrentPath()
|
||||
val displayPath = PathUtil.truncatePathForDisplay(currentPath)
|
||||
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.text = if (setting.isUsingDefaultPath()) {
|
||||
binding.root.context.getString(R.string.default_string)
|
||||
} else {
|
||||
displayPath
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(!setting.isUsingDefaultPath())
|
||||
binding.buttonClear.text = binding.root.context.getString(R.string.reset_to_default)
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onPathReset(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
setStyle(true, binding)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
adapter.onPathClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -201,6 +201,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
super.onCreate(savedInstanceState)
|
||||
updateOrientation()
|
||||
|
||||
if (args.overlayGamelessEditMode) {
|
||||
return
|
||||
}
|
||||
|
||||
val intent = requireActivity().intent
|
||||
val intentUri: Uri? = intent.data
|
||||
intentGame = null
|
||||
@@ -558,6 +562,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
return
|
||||
}
|
||||
|
||||
if (args.overlayGamelessEditMode) {
|
||||
setupOverlayGamelessEditMode()
|
||||
return
|
||||
}
|
||||
|
||||
if (game == null) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Game not yet initialized in onViewCreated - will be set up by async intent handler"
|
||||
@@ -568,6 +577,39 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
completeViewSetup()
|
||||
}
|
||||
|
||||
|
||||
private fun setupOverlayGamelessEditMode() {
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.refreshControls(gameless = true)
|
||||
}
|
||||
|
||||
binding.doneControlConfig.setOnClickListener {
|
||||
finishOverlayGamelessEditMode()
|
||||
}
|
||||
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
binding.loadingIndicator.visibility = View.GONE
|
||||
|
||||
// in gameless edit mode, back = done
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
viewLifecycleOwner,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finishOverlayGamelessEditMode()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun finishOverlayGamelessEditMode() {
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
NativeConfig.saveGlobalConfig()
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
private fun completeViewSetup() {
|
||||
if (_binding == null || game == null) {
|
||||
return
|
||||
@@ -900,9 +942,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
b.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
||||
}
|
||||
} else {
|
||||
b.surfaceInputOverlay.setVisible(
|
||||
val shouldShowOverlay = if (args.overlayGamelessEditMode) {
|
||||
true
|
||||
} else {
|
||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
||||
)
|
||||
}
|
||||
b.surfaceInputOverlay.setVisible(shouldShowOverlay)
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
b.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
@@ -1531,6 +1576,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
|
||||
findItem(R.id.menu_show_overlay).isChecked =
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
findItem(R.id.menu_snap_to_grid).isChecked =
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()
|
||||
findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
|
||||
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
|
||||
}
|
||||
@@ -1559,6 +1606,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_snap_to_grid -> {
|
||||
it.isChecked = !it.isChecked
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID.setBoolean(it.isChecked)
|
||||
binding.surfaceInputOverlay.invalidate()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_adjust_overlay -> {
|
||||
adjustOverlay()
|
||||
true
|
||||
@@ -1935,13 +1989,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
|
||||
handler.postDelayed({
|
||||
if (isOverlayVisible) {
|
||||
if (isOverlayVisible && isAdded && _binding != null) {
|
||||
hideOverlay()
|
||||
}
|
||||
}, seconds * 1000L)
|
||||
}
|
||||
|
||||
fun handleScreenTap(isLongTap: Boolean) {
|
||||
if (binding.surfaceInputOverlay.isGamelessMode()) {
|
||||
return
|
||||
}
|
||||
|
||||
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
|
||||
val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
|
||||
|
||||
@@ -1963,6 +2021,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun initializeOverlayAutoHide() {
|
||||
if (binding.surfaceInputOverlay.isGamelessMode()) {
|
||||
return
|
||||
}
|
||||
|
||||
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
|
||||
val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
|
||||
val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -31,6 +31,7 @@ import org.yuzu.yuzu_emu.model.TaskState
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.BufferedOutputStream
|
||||
@@ -99,11 +100,11 @@ class InstallableFragment : Fragment() {
|
||||
},
|
||||
export = {
|
||||
val oldSaveDataFolder = File(
|
||||
"${DirectoryInitialization.userDirectory}/nand" +
|
||||
NativeConfig.getSaveDir() +
|
||||
NativeLibrary.getDefaultProfileSaveDataRoot(false)
|
||||
)
|
||||
val futureSaveDataFolder = File(
|
||||
"${DirectoryInitialization.userDirectory}/nand" +
|
||||
NativeConfig.getSaveDir() +
|
||||
NativeLibrary.getDefaultProfileSaveDataRoot(true)
|
||||
)
|
||||
if (!oldSaveDataFolder.exists() && !futureSaveDataFolder.exists()) {
|
||||
@@ -213,7 +214,7 @@ class InstallableFragment : Fragment() {
|
||||
}
|
||||
|
||||
val internalSaveFolder = File(
|
||||
"${DirectoryInitialization.userDirectory}/nand$baseSaveDir"
|
||||
"${NativeConfig.getSaveDir()}$baseSaveDir"
|
||||
)
|
||||
internalSaveFolder.deleteRecursively()
|
||||
internalSaveFolder.mkdir()
|
||||
@@ -290,7 +291,7 @@ class InstallableFragment : Fragment() {
|
||||
cacheSaveDir.mkdir()
|
||||
|
||||
val oldSaveDataFolder = File(
|
||||
"${DirectoryInitialization.userDirectory}/nand" +
|
||||
NativeConfig.getSaveDir() +
|
||||
NativeLibrary.getDefaultProfileSaveDataRoot(false)
|
||||
)
|
||||
if (oldSaveDataFolder.exists()) {
|
||||
@@ -298,7 +299,7 @@ class InstallableFragment : Fragment() {
|
||||
}
|
||||
|
||||
val futureSaveDataFolder = File(
|
||||
"${DirectoryInitialization.userDirectory}/nand" +
|
||||
NativeConfig.getSaveDir() +
|
||||
NativeLibrary.getDefaultProfileSaveDataRoot(true)
|
||||
)
|
||||
if (futureSaveDataFolder.exists()) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,6 +18,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -57,8 +61,7 @@ class Game(
|
||||
}.zip"
|
||||
|
||||
val saveDir: String
|
||||
get() = DirectoryInitialization.userDirectory + "/nand" +
|
||||
NativeLibrary.getSavePath(programId)
|
||||
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId)
|
||||
|
||||
val addonDir: String
|
||||
get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/"
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -50,6 +52,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
|
||||
|
||||
private var inEditMode = false
|
||||
private var gamelessMode = false
|
||||
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||
@@ -60,6 +63,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
private var hasMoved = false
|
||||
private val moveThreshold = 20f
|
||||
|
||||
private val gridPaint = Paint().apply {
|
||||
color = Color.argb(60, 255, 255, 255)
|
||||
strokeWidth = 1f
|
||||
style = Paint.Style.STROKE
|
||||
}
|
||||
|
||||
private lateinit var windowInsets: WindowInsets
|
||||
|
||||
var layout = OverlayLayout.Landscape
|
||||
@@ -91,6 +100,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
|
||||
// Draw grid when in edit mode and snap-to-grid is enabled
|
||||
if (inEditMode && BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
drawGrid(canvas)
|
||||
}
|
||||
|
||||
for (button in overlayButtons) {
|
||||
button.draw(canvas)
|
||||
}
|
||||
@@ -102,6 +117,26 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawGrid(canvas: Canvas) {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
val width = canvas.width
|
||||
val height = canvas.height
|
||||
|
||||
// Draw vertical lines
|
||||
var x = 0
|
||||
while (x <= width) {
|
||||
canvas.drawLine(x.toFloat(), 0f, x.toFloat(), height.toFloat(), gridPaint)
|
||||
x += gridSize
|
||||
}
|
||||
|
||||
// Draw horizontal lines
|
||||
var y = 0
|
||||
while (y <= height) {
|
||||
canvas.drawLine(0f, y.toFloat(), width.toFloat(), y.toFloat(), gridPaint)
|
||||
y += gridSize
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
if (inEditMode) {
|
||||
return onTouchWhileEditing(event)
|
||||
@@ -668,14 +703,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshControls() {
|
||||
fun refreshControls(gameless: Boolean = false) {
|
||||
// Store gameless mode if set to true
|
||||
if (gameless) {
|
||||
gamelessMode = true
|
||||
}
|
||||
|
||||
// Remove all the overlay buttons from the HashSet.
|
||||
overlayButtons.clear()
|
||||
overlayDpads.clear()
|
||||
overlayJoysticks.clear()
|
||||
|
||||
// Add all the enabled overlay items back to the HashSet.
|
||||
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
|
||||
if (gamelessMode || BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
|
||||
addOverlayControls(layout)
|
||||
}
|
||||
invalidate()
|
||||
@@ -712,9 +752,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
if (!editMode) {
|
||||
scaleDialog?.dismiss()
|
||||
scaleDialog = null
|
||||
gamelessMode = false
|
||||
}
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun isGamelessMode(): Boolean = gamelessMode
|
||||
|
||||
private fun showScaleDialog(
|
||||
button: InputOverlayDrawableButton?,
|
||||
dpad: InputOverlayDrawableDpad?,
|
||||
@@ -867,6 +912,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Increase this number every time there is a breaking change to every overlay layout
|
||||
const val OVERLAY_VERSION = 1
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -11,6 +14,8 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||
|
||||
/**
|
||||
@@ -121,11 +126,23 @@ class InputOverlayDrawableButton(
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
setBounds(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
width + controlPositionX,
|
||||
height + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
width + finalX,
|
||||
height + finalY
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
previousTouchY = fingerPositionY
|
||||
@@ -134,6 +151,11 @@ class InputOverlayDrawableButton(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
defaultStateBitmap.setBounds(left, top, right, bottom)
|
||||
pressedStateBitmap.setBounds(left, top, right, bottom)
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@@ -229,11 +231,23 @@ class InputOverlayDrawableDpad(
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
setBounds(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
width + controlPositionX,
|
||||
height + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
width + finalX,
|
||||
height + finalY
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
previousTouchY = fingerPositionY
|
||||
@@ -242,6 +256,11 @@ class InputOverlayDrawableDpad(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
fun setPosition(x: Int, y: Int) {
|
||||
controlPositionX = x
|
||||
controlPositionY = y
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@@ -213,25 +214,37 @@ class InputOverlayDrawableJoystick(
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
bounds = Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
virtBounds = Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
setInnerBounds()
|
||||
bounds = Rect(
|
||||
Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
@@ -242,6 +255,11 @@ class InputOverlayDrawableJoystick(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
private fun setInnerBounds() {
|
||||
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
|
||||
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
|
||||
|
||||
@@ -186,6 +186,10 @@ class GamesFragment : Fragment() {
|
||||
val currentViewType = getCurrentViewType()
|
||||
val savedViewType = if (isLandscape || currentViewType != GameAdapter.VIEW_TYPE_CAROUSEL) currentViewType else GameAdapter.VIEW_TYPE_GRID
|
||||
|
||||
//This prevents Grid/List views from reusing scaled or otherwise modified ViewHolders left over from the carousel.
|
||||
adapter = null
|
||||
recycledViewPool.clear()
|
||||
|
||||
gameAdapter.setViewType(savedViewType)
|
||||
currentFilter = preferences.getInt(PREF_SORT_TYPE, View.NO_ID)
|
||||
|
||||
|
||||
@@ -456,7 +456,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
|
||||
|
||||
val firmwarePath =
|
||||
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
|
||||
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
@@ -499,7 +499,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
fun uninstallFirmware() {
|
||||
val firmwarePath =
|
||||
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
|
||||
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.firmware_uninstalling
|
||||
|
||||
@@ -238,4 +238,18 @@ object GpuDriverHelper {
|
||||
driverStorageDirectory.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a driver zip with the given filename is already present and valid in the
|
||||
* internal driver storage directory. Validation requires a readable meta.json with a name.
|
||||
*/
|
||||
fun isDriverZipInstalledByName(fileName: String): Boolean {
|
||||
// Normalize separators in case upstream sent a path
|
||||
val baseName = fileName.substringAfterLast('/')
|
||||
.substringAfterLast('\\')
|
||||
val candidate = File("$driverStoragePath$baseName")
|
||||
if (!candidate.exists() || candidate.length() == 0L) return false
|
||||
val metadata = getMetadataFromZip(candidate)
|
||||
return metadata.name != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -183,4 +186,22 @@ object NativeConfig {
|
||||
*/
|
||||
@Synchronized
|
||||
external fun saveControlPlayerValues()
|
||||
|
||||
/**
|
||||
* Directory paths getters and setters
|
||||
*/
|
||||
@Synchronized
|
||||
external fun getSaveDir(): String
|
||||
@Synchronized
|
||||
external fun getDefaultSaveDir(): String
|
||||
@Synchronized
|
||||
external fun setSaveDir(path: String)
|
||||
@Synchronized
|
||||
external fun getNandDir(): String
|
||||
@Synchronized
|
||||
external fun setNandDir(path: String)
|
||||
@Synchronized
|
||||
external fun getSdmcDir(): String
|
||||
@Synchronized
|
||||
external fun setSdmcDir(path: String)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import java.io.File
|
||||
|
||||
object PathUtil {
|
||||
|
||||
/**
|
||||
* Converts a content:// URI from the Storage Access Framework to a real filesystem path.
|
||||
*/
|
||||
fun getPathFromUri(uri: Uri): String? {
|
||||
val docId = try {
|
||||
DocumentsContract.getTreeDocumentId(uri)
|
||||
} catch (_: Exception) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (docId.startsWith("primary:")) {
|
||||
val relativePath = docId.substringAfter(":")
|
||||
val primaryStoragePath = android.os.Environment.getExternalStorageDirectory().absolutePath
|
||||
return "$primaryStoragePath/$relativePath"
|
||||
}
|
||||
|
||||
// external SD cards and other volumes)
|
||||
val storageIdString = docId.substringBefore(":")
|
||||
val removablePath = getRemovableStoragePath(storageIdString)
|
||||
if (removablePath != null) {
|
||||
return "$removablePath/${docId.substringAfter(":")}"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a path is a valid, writable directory.
|
||||
* Creates the directory if it doesn't exist.
|
||||
*/
|
||||
fun validateDirectory(path: String): Boolean {
|
||||
val dir = File(path)
|
||||
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return dir.isDirectory && dir.canWrite()
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a directory recursively from source to destination.
|
||||
*/
|
||||
fun copyDirectory(source: File, destination: File, overwrite: Boolean = true): Boolean {
|
||||
return try {
|
||||
source.copyRecursively(destination, overwrite)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory has any content.
|
||||
*/
|
||||
fun hasContent(path: String): Boolean {
|
||||
val dir = File(path)
|
||||
return dir.exists() && dir.listFiles()?.isNotEmpty() == true
|
||||
}
|
||||
|
||||
|
||||
fun truncatePathForDisplay(path: String, maxLength: Int = 40): String {
|
||||
return if (path.length > maxLength) {
|
||||
"...${path.takeLast(maxLength - 3)}"
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
// This really shouldn't be necessary, but the Android API seemingly
|
||||
// doesn't have a way of doing this?
|
||||
// Apparently, on certain devices the mount location can vary, so add
|
||||
// extra cases here if we discover any new ones.
|
||||
fun getRemovableStoragePath(idString: String): String? {
|
||||
var pathFile: File
|
||||
|
||||
pathFile = File("/mnt/media_rw/$idString");
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.content.Context
|
||||
@@ -55,6 +52,24 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
private val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
private val carouselAdapterObserver = object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
if (!pendingScrollAfterReload) return
|
||||
doOnNextLayout {
|
||||
refreshView()
|
||||
pendingScrollAfterReload = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isCarouselMode: Boolean
|
||||
get() {
|
||||
val lm = layoutManager as? LinearLayoutManager
|
||||
return lm != null &&
|
||||
lm.orientation == RecyclerView.HORIZONTAL &&
|
||||
lm !is androidx.recyclerview.widget.GridLayoutManager
|
||||
}
|
||||
|
||||
var flingMultiplier: Float = 1f
|
||||
|
||||
var pendingScrollAfterReload: Boolean = false
|
||||
@@ -70,6 +85,18 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
setChildrenDrawingOrderEnabled(true)
|
||||
}
|
||||
|
||||
override fun setAdapter(adapter: Adapter<*>?) {
|
||||
val oldAdapter = this.adapter as? GameAdapter
|
||||
|
||||
if (oldAdapter !== adapter) {
|
||||
oldAdapter?.unregisterAdapterDataObserver(carouselAdapterObserver)
|
||||
}
|
||||
|
||||
super.setAdapter(adapter)
|
||||
|
||||
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
|
||||
}
|
||||
|
||||
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
|
||||
return paddingStart + (width - paddingStart - paddingEnd) / 2
|
||||
}
|
||||
@@ -79,14 +106,14 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun getLayoutManagerCenter(layoutManager: RecyclerView.LayoutManager): Int {
|
||||
return if (layoutManager is LinearLayoutManager) {
|
||||
calculateCenter(
|
||||
if (isCarouselMode) {
|
||||
return calculateCenter(
|
||||
layoutManager.width,
|
||||
layoutManager.paddingStart,
|
||||
layoutManager.paddingEnd
|
||||
)
|
||||
} else {
|
||||
width / 2
|
||||
return width / 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +122,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun restoreScrollState(position: Int = 0, attempts: Int = 0) {
|
||||
if (!isCarouselMode) return
|
||||
|
||||
val lm = layoutManager as? LinearLayoutManager ?: return
|
||||
if (lm.findLastVisibleItemPosition() == RecyclerView.NO_POSITION && attempts < 10) {
|
||||
post { restoreScrollState(position, attempts + 1) }
|
||||
@@ -104,6 +133,10 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun getClosestChildPosition(fullRange: Boolean = false): Int {
|
||||
if (!isCarouselMode) {
|
||||
return RecyclerView.NO_POSITION
|
||||
}
|
||||
|
||||
val lm = layoutManager as? LinearLayoutManager ?: return RecyclerView.NO_POSITION
|
||||
var minDistance = Int.MAX_VALUE
|
||||
var closestPosition = RecyclerView.NO_POSITION
|
||||
@@ -203,15 +236,22 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun refreshView() {
|
||||
updateChildScalesAndAlpha()
|
||||
focusCenteredCard()
|
||||
if (isCarouselMode) {
|
||||
updateChildScalesAndAlpha()
|
||||
focusCenteredCard()
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyInsetsReady(newBottomInset: Int) {
|
||||
if (bottomInset != newBottomInset) {
|
||||
bottomInset = newBottomInset
|
||||
}
|
||||
setupCarousel(true)
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
} else {
|
||||
setupCarousel(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyLaidOut(fallBackBottomInset: Int) {
|
||||
@@ -221,7 +261,10 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
if (gameAdapter.cardSize != newCardSize) {
|
||||
gameAdapter.setCardSize(newCardSize)
|
||||
}
|
||||
setupCarousel(true)
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun cardSize(bottomInset: Int): Int {
|
||||
@@ -252,17 +295,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
internalFlingMultiplier
|
||||
).coerceIn(1f, 5f)
|
||||
|
||||
gameAdapter .registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
if (pendingScrollAfterReload) {
|
||||
doOnNextLayout {
|
||||
refreshView()
|
||||
pendingScrollAfterReload = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Detach SnapHelper during setup
|
||||
pagerSnapHelper?.attachToRecyclerView(null)
|
||||
|
||||
@@ -321,22 +353,27 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
override fun onScrollStateChanged(state: Int) {
|
||||
super.onScrollStateChanged(state)
|
||||
if (state == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
if (state == RecyclerView.SCROLL_STATE_IDLE && isCarouselMode) {
|
||||
focusCenteredCard()
|
||||
}
|
||||
}
|
||||
|
||||
override fun scrollToPosition(position: Int) {
|
||||
if (isCarouselMode) {
|
||||
(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(position, overlapPx)
|
||||
doOnNextLayout {
|
||||
refreshView()
|
||||
}
|
||||
} else {
|
||||
super.scrollToPosition(position)
|
||||
(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(position, overlapPx)
|
||||
doOnNextLayout {
|
||||
refreshView()
|
||||
}
|
||||
}
|
||||
|
||||
private var lastFocusSearchTime: Long = 0
|
||||
override fun focusSearch(focused: View, direction: Int): View? {
|
||||
if (layoutManager !is LinearLayoutManager) return super.focusSearch(focused, direction)
|
||||
if (!isCarouselMode) {
|
||||
return super.focusSearch(focused, direction)
|
||||
}
|
||||
val vh = findContainingViewHolder(focused) ?: return super.focusSearch(focused, direction)
|
||||
val itemCount = adapter?.itemCount ?: return super.focusSearch(focused, direction)
|
||||
val position = vh.bindingAdapterPosition
|
||||
@@ -371,6 +408,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
focused
|
||||
}
|
||||
}
|
||||
// Prevent focus from escaping to external UI elements when forced snapping was removed
|
||||
View.FOCUS_DOWN -> focused
|
||||
else -> super.focusSearch(focused, direction)
|
||||
}
|
||||
}
|
||||
@@ -434,7 +473,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
// NEEDED: fixes center snapping, but introduces ghost movement
|
||||
override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
|
||||
if (layoutManager !is LinearLayoutManager) return null
|
||||
if (!isCarouselMode) return null
|
||||
return layoutManager.findViewByPosition(getClosestChildPosition())
|
||||
}
|
||||
|
||||
@@ -443,12 +482,10 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
layoutManager: RecyclerView.LayoutManager,
|
||||
targetView: View
|
||||
): IntArray? {
|
||||
if (layoutManager !is LinearLayoutManager) {
|
||||
return super.calculateDistanceToFinalSnap(
|
||||
layoutManager,
|
||||
targetView
|
||||
)
|
||||
if (!isCarouselMode) {
|
||||
return super.calculateDistanceToFinalSnap(layoutManager, targetView)
|
||||
}
|
||||
|
||||
val out = IntArray(2)
|
||||
out[0] = getChildDistanceToCenter(targetView).toInt()
|
||||
out[1] = 0
|
||||
@@ -461,8 +498,11 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
velocityX: Int,
|
||||
velocityY: Int
|
||||
): Int {
|
||||
if (layoutManager !is LinearLayoutManager) return RecyclerView.NO_POSITION
|
||||
if (!isCarouselMode) return RecyclerView.NO_POSITION
|
||||
|
||||
val closestPosition = this@CarouselRecyclerView.getClosestChildPosition()
|
||||
if (closestPosition == RecyclerView.NO_POSITION) return RecyclerView.NO_POSITION
|
||||
|
||||
val internalMaxFling = resources.getInteger(R.integer.carousel_max_fling_count)
|
||||
val maxFling = preferences.getInt(CAROUSEL_MAX_FLING_COUNT, internalMaxFling).coerceIn(
|
||||
1,
|
||||
|
||||
@@ -35,6 +35,14 @@ class GradientBorderCardView @JvmOverloads constructor(
|
||||
updateThemeState()
|
||||
}
|
||||
|
||||
private fun updateBorderState() {
|
||||
val shouldShow = isPressed || isFocused || isSelected
|
||||
if (showGradientBorder != shouldShow) {
|
||||
showGradientBorder = shouldShow
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateThemeState() {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val themeIndex = try {
|
||||
@@ -43,6 +51,7 @@ class GradientBorderCardView @JvmOverloads constructor(
|
||||
0 // Default to Eden theme if error
|
||||
}
|
||||
isEdenTheme = themeIndex == 0
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
@@ -93,27 +102,22 @@ class GradientBorderCardView @JvmOverloads constructor(
|
||||
|
||||
override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
|
||||
showGradientBorder = gainFocus
|
||||
invalidate()
|
||||
updateBorderState()
|
||||
}
|
||||
|
||||
override fun setSelected(selected: Boolean) {
|
||||
super.setSelected(selected)
|
||||
showGradientBorder = selected
|
||||
invalidate()
|
||||
updateBorderState()
|
||||
}
|
||||
|
||||
override fun setPressed(pressed: Boolean) {
|
||||
super.setPressed(pressed)
|
||||
if (pressed) {
|
||||
showGradientBorder = true
|
||||
invalidate()
|
||||
}
|
||||
updateBorderState()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
if (showGradientBorder && !isPressed) {
|
||||
if (showGradientBorder) {
|
||||
canvas.drawPath(borderPath, borderPaint)
|
||||
}
|
||||
}
|
||||
@@ -121,6 +125,5 @@ class GradientBorderCardView @JvmOverloads constructor(
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
updateThemeState()
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <common/fs/path_util.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <input_common/main.h>
|
||||
#include "android_config.h"
|
||||
@@ -68,6 +69,24 @@ void AndroidConfig::ReadPathValues() {
|
||||
}
|
||||
EndArray();
|
||||
|
||||
const auto nand_dir_setting = ReadStringSetting(std::string("nand_directory"));
|
||||
if (!nand_dir_setting.empty()) {
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::NANDDir, nand_dir_setting);
|
||||
}
|
||||
|
||||
const auto sdmc_dir_setting = ReadStringSetting(std::string("sdmc_directory"));
|
||||
if (!sdmc_dir_setting.empty()) {
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::SDMCDir, sdmc_dir_setting);
|
||||
}
|
||||
|
||||
const auto save_dir_setting = ReadStringSetting(std::string("save_directory"));
|
||||
if (save_dir_setting.empty()) {
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::SaveDir,
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir));
|
||||
} else {
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::SaveDir, save_dir_setting);
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
@@ -222,6 +241,26 @@ void AndroidConfig::SavePathValues() {
|
||||
}
|
||||
EndArray();
|
||||
|
||||
// Save custom NAND directory
|
||||
const auto nand_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir);
|
||||
WriteStringSetting(std::string("nand_directory"), nand_path,
|
||||
std::make_optional(std::string("")));
|
||||
|
||||
// Save custom SDMC directory
|
||||
const auto sdmc_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
||||
WriteStringSetting(std::string("sdmc_directory"), sdmc_path,
|
||||
std::make_optional(std::string("")));
|
||||
|
||||
// Save custom save directory
|
||||
const auto save_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::SaveDir);
|
||||
if (save_path == nand_path) {
|
||||
WriteStringSetting(std::string("save_directory"), std::string(""),
|
||||
std::make_optional(std::string("")));
|
||||
} else {
|
||||
WriteStringSetting(std::string("save_directory"), save_path,
|
||||
std::make_optional(std::string("")));
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,10 @@ namespace AndroidSettings {
|
||||
|
||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<s32> overlay_grid_size{linkage, 32, "overlay_grid_size",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<s32> lock_drawer{linkage, false, "lock_drawer",
|
||||
|
||||
@@ -1166,11 +1166,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerInit(JNIEnv* env, jobject obj) {
|
||||
// for some reason the full user directory isnt initialized in Android, so we need to create it
|
||||
const auto play_time_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::PlayTimeDir);
|
||||
if (!Common::FS::IsDir(play_time_dir)) {
|
||||
if (!Common::FS::CreateDir(play_time_dir)) {
|
||||
LOG_WARNING(Frontend, "Failed to create play time directory");
|
||||
}
|
||||
}
|
||||
if (!Common::FS::IsDir(play_time_dir) && !Common::FS::CreateDir(play_time_dir))
|
||||
LOG_WARNING(Frontend, "Failed to create play time directory");
|
||||
|
||||
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||
}
|
||||
@@ -1183,13 +1180,16 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerStart(JNIEnv* env, job
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerStop(JNIEnv* env, jobject obj) {
|
||||
play_time_manager->Stop();
|
||||
if (play_time_manager)
|
||||
play_time_manager->Stop();
|
||||
}
|
||||
|
||||
jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetPlayTime(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
return play_time_manager->GetPlayTime(program_id);
|
||||
jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetPlayTime(JNIEnv* env, jobject obj, jstring jprogramId) {
|
||||
if (play_time_manager) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
return play_time_manager->GetPlayTime(program_id);
|
||||
}
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(JNIEnv* env,
|
||||
@@ -1199,17 +1199,17 @@ jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(JNI
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerResetProgramPlayTime(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
if (play_time_manager) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
play_time_manager->ResetProgramPlayTime(program_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerSetPlayTime(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId, jlong playTimeSeconds) {
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
if (play_time_manager) {
|
||||
play_time_manager->SetPlayTime(program_id, static_cast<u64>(playTimeSeconds));
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
play_time_manager->SetPlayTime(program_id, u64(playTimeSeconds));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1389,12 +1389,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
|
||||
const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
|
||||
ASSERT(user_id);
|
||||
|
||||
const auto nandDir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
|
||||
auto vfsNandDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(nandDir),
|
||||
const auto saveDir = Common::FS::GetEdenPath(Common::FS::EdenPath::SaveDir);
|
||||
auto vfsSaveDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(saveDir),
|
||||
FileSys::OpenMode::Read);
|
||||
|
||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||
{}, vfsNandDir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Account, program_id,
|
||||
{}, vfsSaveDir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Account, program_id,
|
||||
user_id->AsU128(), 0);
|
||||
return Common::Android::ToJString(env, user_save_data_path);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
|
||||
#include <jni.h>
|
||||
#include <common/fs/path_util.h>
|
||||
|
||||
#include "android_config.h"
|
||||
#include "android_settings.h"
|
||||
@@ -545,4 +546,39 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveControlPlayerValues(JNIEnv*
|
||||
}
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getSaveDir(JNIEnv* env, jobject obj) {
|
||||
return Common::Android::ToJString(env,
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::SaveDir));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDefaultSaveDir(JNIEnv* env, jobject obj) {
|
||||
return Common::Android::ToJString(env,
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setSaveDir(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto path = Common::Android::GetJString(env, jpath);
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::SaveDir, path);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getNandDir(JNIEnv* env, jobject obj) {
|
||||
return Common::Android::ToJString(env,
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setNandDir(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto path = Common::Android::GetJString(env, jpath);
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::NANDDir, path);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getSdmcDir(JNIEnv* env, jobject obj) {
|
||||
return Common::Android::ToJString(env,
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setSdmcDir(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto path = Common::Android::GetJString(env, jpath);
|
||||
Common::FS::SetEdenPath(Common::FS::EdenPath::SDMCDir, path);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
|
Before Width: | Height: | Size: 631 KiB After Width: | Height: | Size: 672 KiB |
|
After Width: | Height: | Size: 102 KiB |
@@ -1,39 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:pathData="M317.52,255.37C318.06,255.37 318.6,255.38 319.65,255.41C322.37,255.43 324.58,255.42 326.79,255.41C349.49,255.43 372.2,255.43 394.91,255.46C400.46,255.47 406.02,255.65 411.58,255.6C413.68,255.59 414.71,256.12 414.54,258.43C414.35,261.26 414.33,264.1 414.37,266.94C414.4,269.64 413.52,270.71 410.51,270.7C377.29,270.59 344.07,270.65 310.85,270.66C295.82,270.66 280.79,270.66 265.75,270.66C264.61,270.66 263.47,270.66 262.06,270.66C262.06,272.3 262.06,273.7 262.06,275.5C312.31,275.5 362.6,275.5 413.71,275.5C412.65,281.11 411.74,286.21 410.66,291.28C410.55,291.83 409.53,292.38 408.84,292.56C407.95,292.78 406.96,292.62 406.01,292.62C359.34,292.62 312.66,292.62 265.99,292.62C264.75,292.62 263.51,292.62 262.03,292.62C262.03,294.2 262.03,295.49 262.03,297.18C310.9,297.18 359.72,297.18 409.36,297.18C408.27,300.87 407.4,304.03 406.39,307.15C404.52,312.95 404.48,312.94 398.53,312.94C354.38,312.94 310.22,312.95 266.07,312.92C263.76,312.91 261.86,313.09 262.84,316.08C263.07,316.81 264.11,317.46 264.92,317.77C265.65,318.06 266.58,317.84 267.42,317.84C310.84,317.84 354.26,317.84 397.67,317.84C399.01,317.84 400.34,317.84 402.55,317.84C400.14,322.79 398.2,327.17 395.86,331.32C395.42,332.11 393.44,332.31 392.17,332.31C373.14,332.37 354.11,332.35 335.08,332.35C313.54,332.35 291.98,332.36 270.43,332.35C268.66,332.35 266.89,332.26 264.63,332.33C262.52,332.45 260.88,332.45 259.24,332.45C258.67,327.67 257.69,322.9 257.58,318.11C257.21,300.69 257.06,283.26 257.01,265.84C257,262.52 257.85,259.2 258.72,255.69C260.68,255.49 262.21,255.48 264.04,255.51C279.33,255.53 294.33,255.5 309.62,255.47C312.44,255.44 314.98,255.4 317.52,255.37Z"
|
||||
android:fillColor="#BF42F6"/>
|
||||
<path
|
||||
android:pathData="M196.08,256.6C196.08,254.63 196.08,252.67 196.08,250.27C194.45,250.27 192.91,250.27 191.37,250.27C161.1,250.29 130.82,250.28 100.54,250.37C97.92,250.38 96.9,249.67 97.32,246.97C97.78,243.97 98.17,240.93 98.27,237.9C98.34,235.6 99.36,235.17 101.4,235.17C132.61,235.1 163.82,234.94 195.04,234.86C197.66,234.86 199.69,234.63 199.14,230.9C165.95,230.9 132.78,230.9 99.13,230.9C100.08,226.41 100.73,222.27 101.94,218.31C102.23,217.39 104.41,216.53 105.72,216.53C137.99,216.47 170.27,216.52 202.54,216.6C204.76,216.61 206.52,216.34 206.71,213.17C172.35,213.17 138.07,213.17 102.94,213.17C105.27,206.84 107.26,201.13 109.56,195.55C109.89,194.76 111.78,194.23 112.96,194.22C129.67,194.14 146.39,194.08 163.1,194.23C166.69,194.27 168.59,192.48 170.67,189.3C151.01,189.3 131.87,189.3 111.88,189.3C114.84,183.98 117.32,179.27 120.09,174.74C120.55,174 122.27,173.74 123.4,173.74C144.22,173.68 165.04,173.54 185.85,173.82C190.74,173.88 194.5,172.04 198.31,169.21C173.73,169.21 149.15,169.21 123.5,169.21C125.26,166.62 126.46,164.76 127.76,162.97C133.8,154.66 133.81,154.67 144.13,154.67C171.78,154.66 199.43,154.67 227.08,154.67C228.19,154.67 229.29,154.67 231.08,154.67C228.38,151.86 226.32,149.93 222.7,149.94C196.21,150.06 169.72,150.01 143.22,150.01C142.01,150.01 140.79,150.01 139.58,150.01C139.41,149.71 139.24,149.41 139.08,149.12C141.68,146.49 144.22,143.8 146.91,141.27C154.7,133.92 154.72,133.79 165.43,133.98C181.21,134.26 196.1,138.61 210.47,144.78C214.32,146.44 217.65,147.31 221.76,145.43C224.98,143.95 228.71,143.61 232.67,143.03C229.67,148.57 233.27,151.22 236.42,154.19C240.32,157.87 244.05,161.72 247.81,165.54C248.6,166.35 249.19,167.35 250.32,168.84C248.78,168.93 247.82,169.03 246.87,169.03C240.24,169.04 233.61,169.23 227,168.97C207.93,168.24 191.92,175.26 178.31,188.15C173.45,192.76 169.11,197.93 164.57,202.87C164.1,203.38 163.83,204.06 163.06,205.34C186.14,197.23 207.86,186.73 232.14,185.87C232.34,186.18 232.53,186.5 232.73,186.82C229.85,190.18 227.16,193.72 224.07,196.87C211.77,209.42 204.42,224.53 201.03,241.6C200.07,246.39 200.14,251.4 199.32,256.52C197.97,256.68 197.03,256.64 196.08,256.6ZM209.68,194.22C212.31,194.23 214.96,194.01 217.56,194.29C221.34,194.69 223.62,192.8 226.08,189.17C219.87,190.63 214.42,191.92 208.98,193.24C208.98,193.24 209.12,193.87 209.68,194.22Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M195.81,256.61C197.03,256.64 197.97,256.68 199.2,256.74C199.58,258.84 199.68,260.93 199.79,263.02C200.15,263.15 200.52,263.28 200.89,263.41C202.45,261.11 204.02,258.8 205.97,256.3C207.64,256.04 208.92,255.97 210.44,255.9C211.39,255.81 212.11,255.66 212.84,255.65C221.05,255.54 229.27,255.45 237.79,255.4C240.14,255.48 242.19,255.51 244.24,255.54C243.48,260.08 242.67,264.61 241.96,269.16C238.76,289.76 238.29,310.44 239.8,331.49C238.12,331.99 236.72,332.2 235.33,332.4C197.17,332.39 159.01,332.31 120.85,332.45C117.22,332.46 115.21,331.39 113.93,328.08C112.65,324.8 111,321.67 109.23,317.84C113.61,317.84 117.25,317.84 120.89,317.84C157.58,317.84 194.27,317.85 230.96,317.81C232.33,317.8 234.26,318.64 234.58,316.04C234.9,313.41 233.71,312.89 231.33,312.9C204.94,312.97 178.55,312.94 152.17,312.95C138.29,312.95 124.42,312.9 110.54,313.03C108.13,313.05 106.9,312.32 106.24,310.03C105.12,306.12 103.81,302.26 102.24,297.34C146.69,297.16 190.42,297.32 234.45,297.24C234.54,295.65 234.62,294.35 234.7,292.8C233.41,292.73 232.39,292.63 231.38,292.63C191.74,292.61 152.11,292.59 112.48,292.59C109.43,292.59 106.38,292.78 103.34,292.7C102.57,292.68 101.25,292.09 101.13,291.56C99.99,286.28 99.05,280.95 97.99,275.27C103.72,275.27 108.74,275.24 113.75,275.28C115.11,275.29 116.47,275.55 117.83,275.55C155.88,275.57 193.94,275.57 231.99,275.56C236.12,275.56 236.28,275.39 236.25,270.67C190.27,270.67 144.28,270.67 97.69,270.67C97.5,266.15 97.2,262.01 97.29,257.87C97.3,257.33 99.29,256.41 100.37,256.4C110.67,256.26 120.97,256.28 131.28,256.27C143.26,256.26 155.24,256.22 167.23,256.28C176.66,256.33 186.1,256.51 195.81,256.61Z"
|
||||
android:fillColor="#BF43F5"/>
|
||||
<path
|
||||
android:pathData="M235.35,332.69C236.72,332.2 238.12,331.99 239.8,331.78C241.62,343.91 242.64,356.16 244.79,368.19C247.54,383.59 251.45,398.75 257.46,413.28C257.66,413.75 257.8,414.25 257.96,414.73C240.7,421.84 173.22,399.36 158.05,381.11C172.31,381.11 186.11,381.11 199.9,381.11C213.75,381.11 227.59,381.11 241.47,381.11C241.67,377.83 240.89,376.4 237.49,376.42C210.37,376.55 183.25,376.46 156.13,376.54C153.11,376.55 150.66,375.97 148.51,373.71C143.83,368.81 138.99,364.07 134.22,359.27C134.38,358.97 134.53,358.67 134.69,358.38C168.96,358.38 203.24,358.38 237.91,358.38C237.59,356.49 237.35,355.09 237.06,353.42C235.66,353.42 234.41,353.42 233.17,353.42C204.05,353.42 174.93,353.43 145.82,353.41C141.61,353.41 137.4,353.12 133.21,353.29C130.69,353.4 129.04,352.57 127.7,350.52C124.9,346.24 122.01,342.03 118.68,337.09C158.03,337.09 196.51,337.09 235.37,337.09C235.37,335.43 235.37,334.21 235.35,332.69Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M259.22,332.74C260.88,332.45 262.52,332.45 264.42,332.44C264.74,333.79 264.81,335.15 264.9,336.86C307.33,336.86 349.65,336.86 392.94,336.86C390.82,340.23 389.17,342.95 387.42,345.59C382.22,353.42 382.21,353.42 372.98,353.42C339.55,353.42 306.12,353.42 272.68,353.42C271.45,353.42 270.21,353.42 269.01,353.42C268.6,357.97 268.9,358.32 272.97,358.32C306.4,358.33 339.83,358.33 373.27,358.33C374.49,358.33 375.71,358.33 377.95,358.33C375.2,361.41 372.88,363.64 370.99,366.18C364.84,374.47 357,377.15 346.54,376.75C324.07,375.9 301.55,376.49 279.05,376.49C277.81,376.49 276.57,376.49 275.34,376.49C275.16,380.42 275.74,381.1 279.15,381.1C302.59,381.11 326.04,381.1 349.48,381.11C350.69,381.11 351.89,381.11 353.09,381.11C353.19,381.39 353.29,381.68 353.39,381.97C351.01,383.75 348.69,385.61 346.25,387.3C330.27,398.36 312.8,406.17 293.96,410.9C291.74,411.45 289.36,411.83 287.12,411.64C285.84,411.52 284.3,410.33 283.54,409.19C277.07,399.49 273.01,388.7 269.22,377.75C264.17,363.21 261.34,348.2 259.22,332.74Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M326.78,255.15C324.58,255.42 322.37,255.43 319.85,255.39C321.63,245.65 321.41,236 319.06,226.36C315.84,213.14 308.31,203.06 296.96,195.73C296.52,195.45 296.09,195.16 295.7,194.28C307.61,194.28 319.52,194.28 331.43,194.28C331.48,193.93 331.53,193.59 331.57,193.25C326.8,191.44 322.07,189.34 316.8,189.29C306.29,189.2 295.77,189.33 285.27,189.21C283.16,189.19 281.07,188.5 279.01,187.57C291.5,182.55 304.29,183.1 317.13,185.13C330.14,187.18 341.45,193.94 354.27,199.15C353.23,197.19 352.63,196.05 351.78,194.42C353.03,194.32 353.99,194.18 354.94,194.18C369.55,194.16 384.16,194.21 398.77,194.12C400.99,194.11 402.21,194.7 402.93,196.91C404.51,201.76 406.34,206.52 408.3,211.96C378.96,211.96 350.45,211.96 321.79,211.96C321.89,215.04 323.22,215.9 325.8,215.89C345.56,215.82 365.32,215.82 385.08,215.87C392.43,215.89 399.77,216.08 407.11,216.34C408.02,216.37 409.48,217.14 409.67,217.85C410.73,221.77 411.47,225.78 412.41,230.2C383.7,230.2 355.5,230.2 327.33,230.2C326.95,234.48 327.15,234.7 330.87,234.7C357.35,234.74 383.84,234.81 410.32,234.8C412.27,234.8 413.19,235.26 413.31,237.37C413.52,240.82 413.96,244.26 414.41,247.69C414.69,249.87 413.98,250.71 411.68,250.63C407.17,250.48 402.65,250.56 398.13,250.56C375.85,250.56 353.57,250.56 331.29,250.56C327.03,250.56 327.03,250.57 326.78,255.15Z"
|
||||
android:fillColor="#FF43C4"/>
|
||||
<path
|
||||
android:pathData="M227.85,100.36C256.3,95.58 282.98,98.91 297.89,104.06C295.79,108.97 293.7,113.87 291.61,118.76C290.5,121.37 289.2,123.91 288.3,126.59C287.56,128.8 286.27,129.35 284.07,129.34C269.05,129.26 254.03,129.29 238.21,129.29C240.88,132.05 242.88,134.26 246.57,134.22C257.92,134.12 269.27,134.18 280.62,134.18C281.77,134.18 282.91,134.18 285.21,134.18C282.72,138.33 281.09,142.24 278.43,145.21C274.25,149.87 269.29,153.82 264.85,158.26C263.14,159.97 262.34,159.9 260.7,158.1C253.98,150.75 247.23,143.42 240.16,136.4C232.38,128.67 222.49,125.46 211.78,124.23C202.25,123.13 193.21,124.88 184.27,127.97C182.14,128.71 179.81,129.15 177.55,129.24C172.62,129.42 167.68,129.29 162.59,128.89C181.97,114.22 203.7,104.88 227.85,100.36ZM264.4,152.93C265.08,152.12 265.76,151.3 266.85,150.01C264.04,150.01 261.99,150.01 259.33,150.01C260.37,151.48 260.92,152.63 261.81,153.37C262.25,153.73 263.32,153.32 264.4,152.93Z"
|
||||
android:fillColor="#FF42C3"/>
|
||||
<path
|
||||
android:pathData="M338.22,180.32C330,173.61 320.35,171.22 310.4,169.77C298.87,168.1 287.56,168.74 276.35,173.62C276.99,172.43 277.52,171.16 278.31,170.08C288.95,155.53 302.06,143.72 317.99,135.27C320.4,133.99 323.68,134.06 326.56,134.01C335.71,133.86 344.86,133.9 354,134.01C355.37,134.02 357.07,134.45 358.04,135.32C362.81,139.6 367.39,144.09 372.01,148.53C372.28,148.78 372.38,149.19 372.81,150.01C371.31,150.01 370.2,150.01 369.09,150.01C348.07,150.01 327.04,150.08 306.02,149.93C302.48,149.9 300.52,151.7 297.87,154.67C299.78,154.67 300.97,154.67 302.15,154.67C326.02,154.67 349.88,154.71 373.74,154.6C376.46,154.59 378.32,155.41 379.86,157.57C382.39,161.15 385.05,164.63 388.18,168.84C367.16,168.84 347,168.84 326.84,168.84C326.8,169.1 326.77,169.35 326.73,169.61C331.21,171.27 335.15,173.92 340.45,173.81C356.21,173.5 371.98,173.75 387.75,173.65C390.3,173.63 391.87,174.45 393.01,176.69C395.03,180.7 397.23,184.61 399.61,189.04C398.04,189.14 397.06,189.26 396.07,189.26C380.83,189.27 365.59,189.23 350.34,189.31C348.17,189.32 346.58,188.76 345.1,187.13C343,184.79 340.64,182.68 338.22,180.32Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M263.75,255.47C262.21,255.48 260.68,255.49 258.86,255.49C260.26,237.4 261.8,219.3 267.17,201.8C267.96,199.21 268.84,198.07 271.41,199.91C273.97,201.74 276.92,203.14 279.13,205.31C294.14,220.1 308.29,235.58 317.44,255.16C314.98,255.4 312.44,255.44 309.6,255.22C308.77,252.11 307.31,250.9 304.14,250.95C292.11,251.14 280.07,251.03 268.03,251.04C266.7,251.04 265.38,251.04 263.75,251.04C263.75,252.81 263.75,254.14 263.75,255.47ZM265.23,234.42C275.39,234.42 285.55,234.42 295.71,234.42C295.83,234.04 295.95,233.66 296.07,233.28C294.82,232.66 293.59,231.54 292.34,231.52C283.86,231.36 275.38,231.45 266.9,231.42C264.98,231.41 264.11,232.09 265.23,234.42ZM272.28,213.13C270.05,212.98 268.06,213.13 268.44,216.24C272.26,216.24 276,216.24 279.86,216.24C278.35,212.79 275.57,213.04 272.28,213.13Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M244.37,255.35C242.19,255.51 240.14,255.48 237.87,255.14C237.72,253.74 237.8,252.65 237.92,251.1C230.12,251.1 222.74,251.09 215.35,251.1C211.33,251.1 210.79,251.62 210.2,255.9C208.92,255.97 207.64,256.04 206.08,256.11C209.18,249.49 212.07,242.57 216.01,236.32C223.02,225.18 230.96,214.72 241.59,206.63C246.5,202.9 251.86,200.11 257.81,198.49C258.58,198.28 259.39,198.25 261.09,198.02C254,217.03 248.24,235.81 244.37,255.35ZM226.88,230.34C223.86,230.04 223.17,232.3 221.91,234.84C228.63,234.84 234.89,234.75 241.15,234.88C244,234.94 244.78,233.68 244.4,230.4C238.69,230.4 233.05,230.4 226.88,230.34ZM249.9,214.76C249.15,214.31 248.42,213.55 247.63,213.47C245.66,213.28 243.65,213.49 241.66,213.37C239.1,213.21 237.69,214.56 236.27,217.12C240.49,217.12 244.21,217.2 247.93,217.03C248.58,217 249.17,215.9 249.9,214.76Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M315.52,109.68C327.71,114.67 338.96,120.8 349.32,129.08C328.46,127.7 308.6,131.4 289.08,139.83C289.67,136.65 290.71,134.68 293.44,133.84C297.43,132.61 301.38,131.27 305.32,129.43C301.9,129.43 298.47,129.43 294.12,129.43C298.11,121.6 301.71,114.44 305.48,107.37C305.75,106.86 307.39,106.71 308.22,106.96C310.62,107.69 312.94,108.7 315.52,109.68Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
</vector>
|
||||
@@ -1,39 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:pathData="M346.71,255.31C347.5,255.31 348.29,255.32 349.83,255.37C353.84,255.39 357.09,255.38 360.33,255.37C393.73,255.39 427.12,255.4 460.51,255.45C468.68,255.46 476.86,255.73 485.03,255.65C488.12,255.63 489.63,256.4 489.39,259.81C489.1,263.97 489.08,268.15 489.13,272.32C489.18,276.29 487.88,277.86 483.45,277.85C434.6,277.69 385.75,277.78 336.9,277.79C314.79,277.8 292.68,277.79 270.58,277.79C268.9,277.79 267.22,277.79 265.15,277.79C265.15,280.2 265.15,282.26 265.15,284.91C339.05,284.91 412.99,284.91 488.16,284.91C486.61,293.16 485.26,300.67 483.68,308.12C483.51,308.93 482.02,309.74 481,309.99C479.69,310.32 478.23,310.09 476.84,310.09C408.2,310.09 339.56,310.09 270.92,310.09C269.1,310.09 267.27,310.09 265.1,310.09C265.1,312.41 265.1,314.32 265.1,316.79C336.97,316.79 408.77,316.79 481.77,316.79C480.16,322.22 478.88,326.87 477.4,331.45C474.64,339.99 474.59,339.98 465.83,339.98C400.9,339.98 335.98,339.99 271.05,339.94C267.64,339.93 264.86,340.19 266.29,344.59C266.64,345.66 268.16,346.61 269.36,347.08C270.43,347.5 271.8,347.17 273.04,347.17C336.88,347.17 400.73,347.17 464.58,347.17C466.54,347.17 468.5,347.17 471.75,347.17C468.21,354.46 465.35,360.89 461.91,367C461.26,368.16 458.35,368.45 456.48,368.46C428.5,368.54 400.52,368.52 372.54,368.52C340.85,368.52 309.15,368.52 277.46,368.51C274.85,368.51 272.24,368.39 268.93,368.49C265.82,368.66 263.41,368.67 261.01,368.67C260.16,361.64 258.72,354.62 258.57,347.57C258.02,321.95 257.79,296.33 257.72,270.71C257.71,265.83 258.95,260.94 260.24,255.78C263.12,255.48 265.37,255.47 268.05,255.52C290.54,255.54 312.61,255.5 335.09,255.46C339.23,255.41 342.97,255.36 346.71,255.31Z"
|
||||
android:fillColor="#BF42F6"/>
|
||||
<path
|
||||
android:pathData="M168.12,257.11C168.12,254.22 168.12,251.34 168.12,247.81C165.72,247.81 163.45,247.81 161.19,247.81C116.67,247.84 72.15,247.82 27.63,247.96C23.77,247.97 22.27,246.93 22.88,242.96C23.56,238.54 24.13,234.07 24.27,229.61C24.38,226.23 25.88,225.61 28.88,225.6C74.78,225.49 120.68,225.26 166.58,225.15C170.45,225.14 173.43,224.81 172.62,219.32C123.81,219.32 75.02,219.32 25.55,219.32C26.94,212.72 27.9,206.64 29.68,200.81C30.1,199.46 33.31,198.19 35.24,198.19C82.7,198.1 130.16,198.18 177.61,198.3C180.89,198.3 183.47,197.92 183.75,193.25C133.22,193.25 82.8,193.25 31.15,193.25C34.58,183.94 37.49,175.54 40.89,167.34C41.37,166.17 44.15,165.4 45.87,165.39C70.46,165.26 95.04,165.18 119.62,165.4C124.9,165.45 127.69,162.82 130.75,158.15C101.83,158.15 73.69,158.15 44.3,158.15C48.65,150.32 52.3,143.4 56.37,136.74C57.04,135.65 59.57,135.27 61.24,135.26C91.85,135.18 122.47,134.97 153.07,135.38C160.26,135.47 165.79,132.76 171.4,128.6C135.25,128.6 99.1,128.6 61.39,128.6C63.98,124.8 65.74,122.06 67.65,119.43C76.53,107.21 76.55,107.22 91.72,107.22C132.38,107.21 173.04,107.21 213.7,107.21C215.33,107.21 216.96,107.21 219.59,107.21C215.62,103.09 212.58,100.25 207.27,100.27C168.31,100.44 129.35,100.36 90.39,100.36C88.6,100.36 86.82,100.36 85.03,100.36C84.78,99.92 84.53,99.49 84.29,99.05C88.12,95.2 91.85,91.24 95.8,87.51C107.26,76.71 107.3,76.52 123.05,76.8C146.26,77.21 168.15,83.6 189.28,92.68C194.94,95.11 199.84,96.4 205.89,93.63C210.61,91.46 216.1,90.96 221.92,90.11C217.51,98.24 222.81,102.15 227.44,106.52C233.17,111.93 238.66,117.59 244.19,123.21C245.36,124.39 246.23,125.87 247.88,128.06C245.62,128.19 244.21,128.34 242.8,128.34C233.06,128.35 223.31,128.63 213.59,128.25C185.54,127.18 161.99,137.5 141.99,156.46C134.83,163.24 128.46,170.84 121.77,178.11C121.09,178.85 120.69,179.85 119.56,181.74C153.5,169.81 185.44,154.36 221.15,153.1C221.44,153.57 221.73,154.03 222.01,154.5C217.78,159.44 213.82,164.64 209.28,169.28C191.19,187.74 180.38,209.95 175.39,235.05C173.99,242.11 174.08,249.46 172.89,256.99C170.89,257.24 169.51,257.18 168.12,257.11ZM188.12,165.39C191.98,165.39 195.88,165.07 199.7,165.48C205.27,166.07 208.62,163.3 212.23,157.96C203.1,160.11 195.09,162 187.09,163.95C187.08,163.95 187.3,164.87 188.12,165.39Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M167.72,257.14C169.51,257.18 170.89,257.24 172.7,257.32C173.27,260.41 173.42,263.49 173.57,266.56C174.11,266.75 174.65,266.95 175.19,267.14C177.49,263.75 179.79,260.35 182.66,256.68C185.11,256.3 187,256.2 189.23,256.08C190.64,255.95 191.7,255.74 192.76,255.73C204.84,255.56 216.92,255.43 229.45,255.36C232.92,255.47 235.93,255.51 238.94,255.56C237.82,262.23 236.63,268.9 235.59,275.59C230.88,305.88 230.19,336.29 232.4,367.24C229.94,367.99 227.89,368.29 225.84,368.59C169.72,368.57 113.6,368.46 57.49,368.65C52.15,368.67 49.19,367.1 47.3,362.24C45.43,357.42 43.01,352.8 40.4,347.17C46.84,347.17 52.19,347.17 57.54,347.17C111.5,347.17 165.45,347.18 219.4,347.13C221.43,347.13 224.27,348.35 224.73,344.53C225.2,340.67 223.46,339.9 219.95,339.91C181.15,340.02 142.34,339.97 103.54,339.98C83.14,339.98 62.73,339.92 42.33,340.11C38.78,340.14 36.96,339.06 36,335.69C34.36,329.94 32.42,324.27 30.12,317.03C95.49,316.77 159.8,317 224.55,316.88C224.68,314.54 224.79,312.64 224.92,310.35C223.02,310.25 221.52,310.11 220.02,310.11C161.74,310.07 103.46,310.05 45.17,310.05C40.69,310.05 36.21,310.32 31.74,310.21C30.6,310.18 28.66,309.31 28.49,308.53C26.81,300.76 25.43,292.93 23.86,284.57C32.3,284.57 39.67,284.54 47.05,284.59C49.04,284.6 51.04,284.99 53.04,284.99C109,285.01 164.97,285.01 220.93,285C227.01,285 227.24,284.75 227.2,277.81C159.57,277.81 91.94,277.81 23.43,277.81C23.15,271.16 22.71,265.07 22.84,258.98C22.86,258.2 25.78,256.84 27.37,256.82C42.52,256.62 57.67,256.64 72.82,256.63C90.44,256.61 108.07,256.55 125.69,256.64C139.57,256.71 153.44,256.98 167.72,257.14Z"
|
||||
android:fillColor="#BF43F5"/>
|
||||
<path
|
||||
android:pathData="M225.87,369.02C227.89,368.29 229.94,367.99 232.42,367.67C235.09,385.52 236.59,403.52 239.75,421.23C243.8,443.87 249.54,466.17 258.38,487.52C258.67,488.22 258.88,488.96 259.13,489.67C233.74,500.12 134.5,467.06 112.2,440.21C133.17,440.21 153.45,440.21 173.74,440.21C194.1,440.21 214.46,440.21 234.87,440.21C235.17,435.4 234.01,433.3 229.01,433.32C189.13,433.52 149.25,433.38 109.36,433.5C104.92,433.52 101.33,432.66 98.16,429.33C91.29,422.13 84.17,415.17 77.15,408.11C77.38,407.67 77.61,407.23 77.84,406.79C128.24,406.79 178.65,406.79 229.63,406.79C229.16,404.01 228.81,401.96 228.39,399.5C226.32,399.5 224.49,399.5 222.66,399.5C179.84,399.5 137.02,399.51 94.2,399.48C88.02,399.48 81.83,399.06 75.66,399.31C71.96,399.47 69.52,398.25 67.56,395.24C63.45,388.95 59.19,382.75 54.3,375.48C112.17,375.48 168.75,375.48 225.9,375.48C225.9,373.05 225.9,371.25 225.87,369.02Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M260.97,369.08C263.41,368.67 265.82,368.66 268.61,368.65C269.1,370.64 269.19,372.63 269.32,375.15C331.73,375.15 393.96,375.15 457.62,375.15C454.49,380.1 452.08,384.1 449.5,387.99C441.86,399.51 441.83,399.49 428.27,399.49C379.1,399.5 329.94,399.5 280.77,399.5C278.95,399.5 277.13,399.5 275.36,399.5C274.76,406.19 275.21,406.71 281.19,406.71C330.36,406.72 379.52,406.71 428.69,406.72C430.48,406.72 432.28,406.72 435.58,406.72C431.53,411.25 428.13,414.53 425.35,418.27C416.3,430.46 404.77,434.4 389.38,433.81C356.34,432.56 323.22,433.43 290.13,433.43C288.31,433.43 286.48,433.43 284.68,433.43C284.41,439.2 285.27,440.2 290.28,440.21C324.76,440.22 359.23,440.21 393.71,440.21C395.48,440.21 397.25,440.21 399.01,440.21C399.16,440.64 399.31,441.06 399.46,441.49C395.96,444.11 392.55,446.85 388.96,449.33C365.45,465.59 339.77,477.07 312.06,484.02C308.8,484.84 305.3,485.4 301.99,485.11C300.12,484.95 297.85,483.2 296.73,481.51C287.23,467.26 281.26,451.38 275.67,435.29C268.26,413.89 264.08,391.82 260.97,369.08Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M360.32,254.99C357.09,255.38 353.84,255.39 350.13,255.34C352.74,241.02 352.42,226.82 348.97,212.64C344.23,193.2 333.16,178.39 316.47,167.6C315.83,167.19 315.2,166.76 314.61,165.46C332.13,165.46 349.64,165.46 367.16,165.46C367.23,164.96 367.3,164.46 367.37,163.96C360.35,161.29 353.4,158.21 345.64,158.14C330.19,158 314.73,158.19 299.27,158.02C296.18,157.99 293.1,156.98 290.07,155.61C308.44,148.22 327.24,149.03 346.13,152.01C365.26,155.03 381.9,164.97 400.74,172.63C399.23,169.75 398.34,168.07 397.08,165.68C398.93,165.53 400.33,165.32 401.74,165.32C423.23,165.3 444.71,165.37 466.2,165.24C469.46,165.22 471.26,166.09 472.32,169.34C474.64,176.47 477.33,183.47 480.21,191.47C437.06,191.47 395.13,191.47 352.99,191.47C353.13,196.01 355.09,197.26 358.88,197.25C387.94,197.15 417,197.14 446.06,197.22C456.86,197.26 467.67,197.53 478.46,197.91C479.79,197.96 481.95,199.1 482.22,200.13C483.77,205.9 484.86,211.8 486.24,218.3C444.03,218.3 402.55,218.3 361.13,218.3C360.58,224.59 360.87,224.91 366.33,224.91C405.28,224.98 444.23,225.07 483.18,225.06C486.04,225.06 487.39,225.73 487.58,228.83C487.88,233.91 488.53,238.97 489.19,244.02C489.6,247.21 488.55,248.46 485.18,248.34C478.55,248.12 471.9,248.24 465.26,248.24C432.49,248.23 399.72,248.24 366.95,248.24C360.69,248.24 360.69,248.25 360.32,254.99Z"
|
||||
android:fillColor="#FF43C4"/>
|
||||
<path
|
||||
android:pathData="M214.84,27.35C256.67,20.33 295.91,25.23 317.84,32.8C314.75,40.02 311.68,47.22 308.61,54.41C306.97,58.24 305.06,61.99 303.74,65.93C302.65,69.18 300.74,69.98 297.52,69.97C275.43,69.85 253.34,69.9 230.07,69.9C234.01,73.95 236.94,77.2 242.37,77.15C259.06,76.99 275.76,77.09 292.45,77.1C294.13,77.1 295.81,77.1 299.19,77.1C295.53,83.19 293.14,88.94 289.22,93.31C283.08,100.17 275.78,105.97 269.24,112.5C266.73,115.01 265.56,114.91 263.14,112.27C253.27,101.45 243.33,90.67 232.95,80.36C221.49,68.98 206.96,64.27 191.21,62.45C177.19,60.83 163.89,63.42 150.75,67.96C147.61,69.04 144.19,69.69 140.87,69.82C133.62,70.09 126.36,69.9 118.87,69.31C147.37,47.73 179.32,34 214.84,27.35ZM268.58,104.66C269.59,103.46 270.59,102.27 272.19,100.38C268.06,100.38 265.05,100.38 261.13,100.38C262.66,102.53 263.47,104.22 264.79,105.31C265.43,105.84 267,105.24 268.58,104.66Z"
|
||||
android:fillColor="#FF42C3"/>
|
||||
<path
|
||||
android:pathData="M377.15,144.94C365.06,135.07 350.86,131.56 336.23,129.43C319.28,126.96 302.65,127.92 286.16,135.08C287.11,133.34 287.88,131.47 289.04,129.88C304.69,108.48 323.97,91.12 347.4,78.68C350.95,76.8 355.77,76.91 360.01,76.84C373.45,76.62 386.9,76.67 400.35,76.83C402.37,76.86 404.87,77.49 406.3,78.77C413.31,85.06 420.04,91.67 426.84,98.19C427.23,98.56 427.39,99.17 428.02,100.36C425.81,100.36 424.18,100.36 422.55,100.36C391.63,100.36 360.71,100.48 329.8,100.25C324.58,100.21 321.7,102.85 317.82,107.22C320.62,107.22 322.36,107.22 324.11,107.22C359.2,107.21 394.29,107.28 429.39,107.13C433.39,107.11 436.12,108.31 438.38,111.49C442.11,116.74 446.02,121.86 450.61,128.06C419.7,128.06 390.05,128.06 360.41,128.06C360.36,128.43 360.3,128.81 360.25,129.19C366.83,131.63 372.64,135.53 380.43,135.37C403.61,134.91 426.8,135.29 449.99,135.13C453.74,135.1 456.04,136.3 457.71,139.61C460.69,145.49 463.92,151.25 467.42,157.76C465.12,157.91 463.67,158.09 462.22,158.09C439.81,158.1 417.39,158.05 394.98,158.16C391.79,158.18 389.44,157.36 387.27,154.95C384.18,151.51 380.71,148.41 377.15,144.94Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M267.63,255.46C265.37,255.47 263.12,255.48 260.45,255.48C262.5,228.88 264.77,202.27 272.66,176.53C273.83,172.72 275.12,171.05 278.9,173.76C282.66,176.45 287.01,178.5 290.25,181.69C312.32,203.43 333.13,226.21 346.6,255C342.97,255.36 339.23,255.41 335.05,255.09C333.83,250.51 331.69,248.73 327.03,248.81C309.33,249.09 291.63,248.93 273.93,248.93C271.98,248.93 270.02,248.93 267.63,248.93C267.63,251.54 267.63,253.5 267.63,255.46ZM269.8,224.5C284.75,224.5 299.69,224.5 314.64,224.5C314.81,223.95 314.98,223.38 315.15,222.83C313.33,221.92 311.52,220.26 309.68,220.23C297.21,219.99 284.74,220.13 272.27,220.09C269.44,220.08 268.16,221.08 269.8,224.5ZM280.18,193.19C276.89,192.97 273.97,193.18 274.53,197.77C280.15,197.77 285.65,197.77 291.32,197.77C289.11,192.69 285.02,193.05 280.18,193.19Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M239.14,255.27C235.93,255.51 232.92,255.47 229.57,254.97C229.36,252.92 229.48,251.32 229.65,249.02C218.18,249.02 207.32,249.02 196.46,249.03C190.55,249.03 189.75,249.79 188.89,256.1C187,256.2 185.11,256.3 182.83,256.4C187.38,246.66 191.64,236.49 197.43,227.29C207.73,210.92 219.41,195.53 235.05,183.63C242.26,178.14 250.14,174.04 258.89,171.65C260.02,171.35 261.23,171.31 263.73,170.96C253.3,198.93 244.82,226.55 239.14,255.27ZM213.42,218.5C208.98,218.06 207.96,221.38 206.1,225.12C215.98,225.12 225.19,224.99 234.4,225.18C238.59,225.27 239.74,223.41 239.18,218.58C230.78,218.58 222.49,218.58 213.42,218.5ZM247.27,195.58C246.16,194.92 245.09,193.8 243.93,193.69C241.03,193.41 238.07,193.72 235.15,193.54C231.39,193.32 229.31,195.29 227.22,199.05C233.42,199.05 238.9,199.18 244.37,198.93C245.32,198.89 246.2,197.27 247.27,195.58Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M343.76,41.06C361.68,48.39 378.24,57.41 393.47,69.59C362.8,67.56 333.58,72.99 304.89,85.39C305.74,80.72 307.29,77.83 311.3,76.58C317.16,74.77 322.97,72.82 328.77,70.1C323.73,70.1 318.69,70.1 312.29,70.1C318.16,58.59 323.45,48.06 329,37.66C329.4,36.92 331.81,36.69 333.03,37.06C336.57,38.13 339.97,39.62 343.76,41.06Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
</vector>
|
||||
@@ -1,39 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:pathData="M304.46,255.4C304.89,255.4 305.31,255.4 306.14,255.43C308.29,255.44 310.04,255.44 311.78,255.43C329.71,255.44 347.63,255.44 365.56,255.47C369.95,255.48 374.33,255.62 378.72,255.58C380.38,255.57 381.19,255.99 381.06,257.81C380.9,260.05 380.89,262.29 380.92,264.53C380.95,266.66 380.25,267.51 377.87,267.5C351.65,267.41 325.42,267.46 299.2,267.47C287.33,267.47 275.46,267.47 263.59,267.47C262.69,267.47 261.79,267.47 260.68,267.47C260.68,268.76 260.68,269.87 260.68,271.29C300.35,271.29 340.05,271.29 380.4,271.29C379.57,275.72 378.85,279.75 378,283.75C377.91,284.18 377.1,284.62 376.56,284.76C375.85,284.93 375.07,284.81 374.33,284.81C337.48,284.81 300.63,284.81 263.78,284.81C262.8,284.81 261.82,284.81 260.65,284.81C260.65,286.05 260.65,287.08 260.65,288.4C299.24,288.4 337.78,288.4 376.97,288.4C376.11,291.32 375.42,293.81 374.62,296.27C373.14,300.86 373.12,300.85 368.42,300.85C333.56,300.85 298.7,300.86 263.85,300.83C262.02,300.83 260.52,300.96 261.29,303.33C261.48,303.9 262.3,304.41 262.94,304.66C263.52,304.89 264.25,304.71 264.91,304.71C299.19,304.71 333.46,304.71 367.74,304.71C368.79,304.71 369.85,304.71 371.59,304.71C369.69,308.63 368.15,312.08 366.31,315.36C365.96,315.98 364.4,316.14 363.39,316.14C348.37,316.19 333.35,316.17 318.33,316.17C301.32,316.17 284.3,316.17 267.29,316.17C265.89,316.17 264.49,316.1 262.71,316.16C261.04,316.25 259.75,316.25 258.46,316.25C258,312.48 257.23,308.71 257.15,304.93C256.85,291.18 256.73,277.42 256.69,263.66C256.69,261.04 257.35,258.42 258.05,255.65C259.59,255.49 260.8,255.48 262.24,255.51C274.31,255.52 286.16,255.5 298.23,255.48C300.45,255.45 302.46,255.42 304.46,255.4Z"
|
||||
android:fillColor="#BF42F6"/>
|
||||
<path
|
||||
android:pathData="M208.59,256.37C208.59,254.82 208.59,253.26 208.59,251.37C207.3,251.37 206.09,251.37 204.87,251.37C180.97,251.39 157.07,251.38 133.17,251.45C131.1,251.46 130.29,250.9 130.62,248.77C130.99,246.4 131.29,244 131.37,241.6C131.43,239.79 132.23,239.45 133.84,239.45C158.48,239.39 183.12,239.26 207.77,239.21C209.84,239.2 211.44,239.02 211,236.08C184.8,236.08 158.61,236.08 132.05,236.08C132.8,232.53 133.32,229.27 134.27,226.14C134.49,225.41 136.22,224.73 137.26,224.73C162.73,224.69 188.21,224.73 213.69,224.79C215.45,224.79 216.83,224.59 216.98,222.08C189.85,222.08 162.79,222.08 135.06,222.08C136.9,217.08 138.46,212.57 140.29,208.17C140.55,207.54 142.04,207.13 142.96,207.12C156.16,207.06 169.36,207.01 182.55,207.13C185.39,207.16 186.89,205.74 188.53,203.24C173.01,203.24 157.9,203.24 142.12,203.24C144.45,199.04 146.41,195.32 148.6,191.75C148.96,191.16 150.32,190.96 151.21,190.95C167.65,190.91 184.08,190.8 200.51,191.01C204.37,191.06 207.34,189.61 210.35,187.38C190.94,187.38 171.54,187.38 151.29,187.38C152.68,185.33 153.63,183.86 154.66,182.45C159.42,175.89 159.43,175.9 167.58,175.9C189.4,175.89 211.23,175.89 233.06,175.89C233.94,175.89 234.81,175.89 236.22,175.89C234.09,173.68 232.46,172.15 229.61,172.17C208.69,172.26 187.78,172.21 166.86,172.21C165.9,172.21 164.94,172.21 163.98,172.21C163.85,171.98 163.72,171.75 163.59,171.51C165.64,169.44 167.65,167.32 169.77,165.32C175.92,159.52 175.94,159.41 184.39,159.57C196.85,159.79 208.61,163.22 219.95,168.09C222.99,169.4 225.62,170.09 228.87,168.6C231.4,167.44 234.35,167.17 237.47,166.71C235.1,171.08 237.95,173.17 240.44,175.52C243.51,178.42 246.46,181.46 249.43,184.48C250.05,185.12 250.52,185.91 251.41,187.09C250.2,187.15 249.44,187.23 248.68,187.24C243.45,187.24 238.22,187.39 233,187.19C217.94,186.61 205.3,192.15 194.56,202.33C190.72,205.97 187.3,210.05 183.71,213.95C183.34,214.35 183.13,214.89 182.52,215.9C200.74,209.5 217.89,201.2 237.06,200.53C237.21,200.78 237.37,201.03 237.52,201.28C235.25,203.93 233.13,206.73 230.69,209.21C220.98,219.12 215.17,231.05 212.49,244.52C211.74,248.31 211.79,252.26 211.15,256.3C210.08,256.43 209.34,256.4 208.59,256.37ZM219.33,207.12C221.4,207.13 223.49,206.96 225.55,207.17C228.53,207.49 230.34,206 232.27,203.13C227.37,204.29 223.07,205.3 218.77,206.35C218.77,206.35 218.89,206.85 219.33,207.12Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M208.38,256.38C209.34,256.4 210.08,256.43 211.05,256.48C211.35,258.14 211.43,259.79 211.51,261.44C211.81,261.54 212.1,261.64 212.39,261.75C213.62,259.93 214.86,258.11 216.4,256.13C217.71,255.93 218.73,255.87 219.92,255.81C220.68,255.74 221.25,255.63 221.82,255.62C228.3,255.53 234.79,255.46 241.52,255.42C243.38,255.48 244.99,255.51 246.61,255.53C246.01,259.12 245.37,262.7 244.81,266.29C242.28,282.55 241.91,298.87 243.1,315.49C241.78,315.89 240.68,316.05 239.58,316.21C209.45,316.2 179.32,316.14 149.2,316.25C146.34,316.26 144.74,315.41 143.73,312.8C142.73,310.21 141.43,307.74 140.02,304.71C143.48,304.71 146.35,304.71 149.23,304.71C178.19,304.71 207.16,304.72 236.12,304.69C237.21,304.69 238.73,305.34 238.98,303.29C239.23,301.22 238.3,300.81 236.42,300.82C215.59,300.87 194.75,300.85 173.92,300.85C162.97,300.85 152.01,300.82 141.06,300.92C139.16,300.94 138.18,300.36 137.66,298.55C136.78,295.46 135.74,292.42 134.51,288.53C169.6,288.39 204.12,288.52 238.88,288.45C238.95,287.2 239.01,286.17 239.08,284.95C238.06,284.89 237.26,284.82 236.45,284.81C205.16,284.8 173.88,284.78 142.59,284.78C140.18,284.78 137.78,284.93 135.37,284.87C134.76,284.85 133.72,284.39 133.63,283.97C132.73,279.8 131.99,275.6 131.15,271.1C135.68,271.1 139.63,271.09 143.59,271.11C144.67,271.12 145.74,271.33 146.81,271.33C176.85,271.34 206.9,271.34 236.94,271.34C240.2,271.34 240.33,271.2 240.3,267.48C204,267.48 167.69,267.48 130.92,267.48C130.76,263.91 130.53,260.64 130.6,257.37C130.61,256.95 132.18,256.22 133.03,256.21C141.16,256.1 149.3,256.11 157.43,256.11C166.89,256.1 176.35,256.07 185.81,256.11C193.26,256.15 200.71,256.3 208.38,256.38Z"
|
||||
android:fillColor="#BF43F5"/>
|
||||
<path
|
||||
android:pathData="M239.59,316.44C240.68,316.05 241.78,315.89 243.11,315.72C244.54,325.3 245.35,334.96 247.05,344.47C249.22,356.62 252.3,368.59 257.05,380.06C257.2,380.44 257.32,380.83 257.45,381.21C243.82,386.82 190.54,369.07 178.57,354.66C189.83,354.66 200.72,354.66 211.61,354.66C222.54,354.66 233.47,354.66 244.42,354.66C244.59,352.08 243.97,350.95 241.28,350.96C219.87,351.07 198.46,350.99 177.05,351.06C174.66,351.07 172.73,350.61 171.03,348.82C167.34,344.95 163.52,341.22 159.75,337.42C159.88,337.19 160,336.95 160.12,336.72C187.18,336.72 214.24,336.72 241.61,336.72C241.36,335.23 241.17,334.13 240.95,332.8C239.83,332.8 238.85,332.8 237.87,332.8C214.88,332.8 191.9,332.81 168.91,332.8C165.59,332.8 162.26,332.57 158.96,332.7C156.97,332.79 155.66,332.13 154.6,330.52C152.4,327.14 150.11,323.81 147.49,319.91C178.55,319.91 208.93,319.91 239.61,319.91C239.61,318.61 239.61,317.64 239.59,316.44Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M258.44,316.48C259.75,316.25 261.04,316.25 262.54,316.24C262.8,317.31 262.85,318.38 262.92,319.73C296.42,319.73 329.83,319.73 364.01,319.73C362.33,322.39 361.03,324.54 359.64,326.62C355.54,332.81 355.53,332.8 348.25,332.8C321.86,332.8 295.46,332.8 269.07,332.8C268.09,332.8 267.11,332.8 266.16,332.8C265.84,336.39 266.08,336.68 269.29,336.68C295.68,336.68 322.08,336.68 348.47,336.68C349.44,336.68 350.4,336.68 352.17,336.68C350,339.11 348.17,340.87 346.68,342.88C341.82,349.43 335.64,351.54 327.37,351.23C309.64,350.55 291.86,351.02 274.09,351.02C273.11,351.02 272.13,351.02 271.17,351.02C271.02,354.12 271.48,354.66 274.17,354.66C292.68,354.66 311.19,354.66 329.7,354.66C330.65,354.66 331.6,354.66 332.54,354.66C332.62,354.89 332.7,355.12 332.78,355.35C330.9,356.75 329.07,358.22 327.14,359.56C314.53,368.29 300.74,374.45 285.87,378.18C284.11,378.62 282.23,378.92 280.46,378.76C279.45,378.68 278.23,377.74 277.63,376.83C272.53,369.18 269.33,360.66 266.33,352.02C262.35,340.53 260.11,328.68 258.44,316.48Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M311.77,255.22C310.04,255.44 308.29,255.44 306.3,255.42C307.7,247.72 307.53,240.1 305.68,232.49C303.13,222.06 297.19,214.1 288.23,208.31C287.89,208.09 287.55,207.86 287.23,207.16C296.64,207.16 306.04,207.16 315.44,207.16C315.48,206.9 315.52,206.63 315.56,206.36C311.79,204.93 308.05,203.27 303.89,203.23C295.6,203.16 287.3,203.26 279,203.17C277.34,203.15 275.68,202.61 274.06,201.88C283.92,197.91 294.01,198.34 304.15,199.94C314.42,201.56 323.36,206.9 333.47,211.01C332.66,209.46 332.18,208.57 331.51,207.28C332.5,207.2 333.25,207.09 334.01,207.09C345.54,207.08 357.08,207.11 368.61,207.04C370.36,207.03 371.33,207.5 371.9,209.25C373.14,213.07 374.59,216.83 376.13,221.13C352.97,221.13 330.46,221.13 307.83,221.13C307.91,223.56 308.96,224.24 311,224.23C326.6,224.18 342.2,224.17 357.8,224.21C363.6,224.23 369.4,224.38 375.2,224.59C375.91,224.61 377.07,225.22 377.21,225.77C378.05,228.87 378.63,232.04 379.37,235.53C356.71,235.53 334.45,235.53 312.2,235.53C311.91,238.9 312.07,239.08 315,239.08C335.91,239.11 356.82,239.16 377.73,239.16C379.27,239.16 379.99,239.52 380.09,241.18C380.25,243.91 380.6,246.63 380.95,249.34C381.17,251.05 380.61,251.72 378.8,251.66C375.24,251.54 371.67,251.6 368.11,251.6C350.51,251.6 332.92,251.6 315.33,251.6C311.97,251.6 311.97,251.61 311.77,255.22Z"
|
||||
android:fillColor="#FF43C4"/>
|
||||
<path
|
||||
android:pathData="M233.67,133.02C256.13,129.25 277.19,131.88 288.96,135.95C287.31,139.82 285.66,143.69 284.01,147.55C283.13,149.6 282.1,151.61 281.39,153.73C280.81,155.48 279.79,155.91 278.06,155.9C266.2,155.83 254.34,155.87 241.85,155.87C243.96,158.04 245.54,159.78 248.45,159.75C257.41,159.67 266.37,159.72 275.33,159.73C276.24,159.73 277.14,159.73 278.95,159.73C276.99,163 275.7,166.08 273.6,168.43C270.3,172.11 266.39,175.23 262.88,178.73C261.53,180.08 260.9,180.03 259.6,178.61C254.3,172.8 248.97,167.01 243.39,161.48C237.24,155.37 229.44,152.84 220.98,151.86C213.46,150.99 206.32,152.38 199.27,154.82C197.58,155.4 195.74,155.75 193.96,155.82C190.07,155.97 186.17,155.86 182.15,155.55C197.45,143.96 214.61,136.59 233.67,133.02ZM262.52,174.52C263.06,173.88 263.6,173.24 264.46,172.22C262.24,172.22 260.62,172.22 258.52,172.22C259.34,173.38 259.78,174.29 260.48,174.87C260.83,175.15 261.67,174.84 262.52,174.52Z"
|
||||
android:fillColor="#FF42C3"/>
|
||||
<path
|
||||
android:pathData="M320.81,196.15C314.32,190.85 306.7,188.96 298.84,187.82C289.74,186.5 280.81,187.01 271.96,190.85C272.47,189.92 272.88,188.91 273.51,188.06C281.91,176.57 292.26,167.25 304.84,160.58C306.74,159.57 309.33,159.62 311.6,159.59C318.82,159.47 326.04,159.5 333.26,159.59C334.35,159.6 335.69,159.93 336.45,160.63C340.22,164 343.83,167.55 347.48,171.05C347.69,171.25 347.78,171.57 348.11,172.21C346.93,172.21 346.05,172.21 345.18,172.21C328.58,172.21 311.98,172.28 295.39,172.15C292.59,172.13 291.04,173.55 288.95,175.9C290.46,175.9 291.4,175.9 292.33,175.9C311.17,175.9 330.01,175.93 348.85,175.85C351,175.84 352.47,176.48 353.68,178.19C355.68,181.01 357.78,183.76 360.24,187.08C343.65,187.08 327.73,187.08 311.82,187.08C311.79,187.29 311.76,187.49 311.74,187.69C315.27,189 318.39,191.09 322.57,191.01C335.01,190.76 347.46,190.96 359.91,190.88C361.92,190.87 363.16,191.51 364.06,193.28C365.66,196.45 367.39,199.54 369.27,203.03C368.03,203.11 367.26,203.21 366.48,203.21C354.44,203.21 342.41,203.18 330.38,203.24C328.66,203.25 327.4,202.81 326.24,201.52C324.58,199.67 322.72,198.01 320.81,196.15Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M262.01,255.48C260.8,255.48 259.59,255.49 258.15,255.49C259.26,241.21 260.47,226.92 264.71,213.11C265.34,211.06 266.03,210.16 268.06,211.62C270.08,213.06 272.41,214.16 274.15,215.88C286.01,227.55 297.17,239.78 304.4,255.23C302.46,255.42 300.45,255.45 298.21,255.28C297.55,252.82 296.4,251.87 293.9,251.91C284.4,252.06 274.9,251.97 265.39,251.98C264.34,251.98 263.3,251.98 262.01,251.98C262.01,253.37 262.01,254.43 262.01,255.48ZM263.18,238.86C271.2,238.86 279.22,238.86 287.25,238.86C287.34,238.56 287.43,238.26 287.52,237.96C286.55,237.47 285.57,236.58 284.58,236.57C277.89,236.44 271.2,236.51 264.5,236.49C262.98,236.48 262.3,237.02 263.18,238.86ZM268.75,222.05C266.99,221.93 265.42,222.05 265.71,224.51C268.73,224.51 271.68,224.51 274.73,224.51C273.54,221.78 271.35,221.98 268.75,222.05Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M246.72,255.38C244.99,255.51 243.38,255.48 241.58,255.22C241.46,254.11 241.53,253.25 241.62,252.02C235.47,252.02 229.64,252.02 223.81,252.02C220.63,252.03 220.2,252.43 219.74,255.82C218.73,255.87 217.71,255.93 216.49,255.98C218.93,250.76 221.21,245.3 224.33,240.35C229.86,231.57 236.13,223.3 244.52,216.92C248.39,213.97 252.62,211.77 257.32,210.49C257.93,210.32 258.57,210.3 259.92,210.12C254.32,225.13 249.77,239.96 246.72,255.38ZM232.91,235.63C230.52,235.4 229.98,237.18 228.98,239.19C234.28,239.19 239.23,239.12 244.17,239.22C246.42,239.27 247.04,238.27 246.74,235.68C242.23,235.68 237.78,235.68 232.91,235.63ZM251.08,223.33C250.49,222.98 249.91,222.38 249.29,222.32C247.73,222.17 246.14,222.34 244.57,222.24C242.56,222.12 241.44,223.18 240.32,225.2C243.65,225.2 246.59,225.26 249.52,225.13C250.03,225.11 250.51,224.24 251.08,223.33Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M302.88,140.38C312.51,144.32 321.39,149.16 329.57,155.7C313.1,154.61 297.42,157.52 282.02,164.18C282.47,161.67 283.3,160.12 285.45,159.45C288.6,158.48 291.72,157.43 294.83,155.97C292.13,155.97 289.42,155.97 285.99,155.97C289.14,149.79 291.98,144.13 294.96,138.55C295.17,138.15 296.47,138.03 297.12,138.23C299.02,138.8 300.85,139.61 302.88,140.38Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 256 KiB |
@@ -1,39 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="512dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:pathData="M317.52,168.37C318.06,168.37 318.6,168.38 319.65,168.41C322.37,168.43 324.58,168.42 326.79,168.41C349.49,168.43 372.2,168.43 394.91,168.46C400.46,168.47 406.02,168.65 411.58,168.6C413.68,168.59 414.71,169.12 414.54,171.43C414.35,174.26 414.33,177.1 414.37,179.94C414.4,182.64 413.52,183.71 410.51,183.7C377.29,183.59 344.07,183.65 310.85,183.66C295.82,183.66 280.79,183.66 265.75,183.66C264.61,183.66 263.47,183.66 262.06,183.66C262.06,185.29 262.06,186.7 262.06,188.5C312.31,188.5 362.6,188.5 413.71,188.5C412.65,194.11 411.74,199.21 410.66,204.28C410.55,204.83 409.53,205.38 408.84,205.56C407.95,205.78 406.96,205.62 406.01,205.62C359.34,205.62 312.66,205.62 265.99,205.62C264.75,205.62 263.51,205.62 262.03,205.62C262.03,207.2 262.03,208.49 262.03,210.18C310.9,210.18 359.72,210.18 409.36,210.18C408.27,213.87 407.4,217.03 406.39,220.15C404.52,225.95 404.48,225.94 398.53,225.94C354.38,225.94 310.22,225.95 266.07,225.92C263.76,225.91 261.86,226.09 262.84,229.08C263.07,229.81 264.11,230.46 264.92,230.77C265.65,231.06 266.58,230.84 267.42,230.84C310.84,230.84 354.26,230.84 397.67,230.84C399.01,230.84 400.34,230.84 402.55,230.84C400.14,235.79 398.2,240.17 395.86,244.32C395.42,245.11 393.44,245.31 392.17,245.31C373.14,245.37 354.11,245.35 335.08,245.35C313.54,245.35 291.98,245.35 270.43,245.35C268.66,245.35 266.89,245.26 264.63,245.33C262.52,245.45 260.88,245.45 259.24,245.46C258.67,240.67 257.69,235.9 257.58,231.11C257.21,213.69 257.06,196.26 257.01,178.84C257,175.52 257.85,172.2 258.72,168.69C260.68,168.49 262.21,168.48 264.04,168.51C279.33,168.53 294.33,168.5 309.62,168.47C312.44,168.44 314.98,168.4 317.52,168.37Z"
|
||||
android:fillColor="#BF42F6"/>
|
||||
<path
|
||||
android:pathData="M196.08,169.6C196.08,167.63 196.08,165.67 196.08,163.27C194.45,163.27 192.91,163.27 191.37,163.27C161.1,163.29 130.82,163.28 100.54,163.37C97.92,163.38 96.9,162.67 97.32,159.97C97.78,156.97 98.17,153.93 98.27,150.9C98.34,148.6 99.36,148.17 101.4,148.17C132.61,148.1 163.82,147.94 195.04,147.86C197.66,147.86 199.69,147.63 199.14,143.9C165.95,143.9 132.78,143.9 99.13,143.9C100.08,139.41 100.73,135.27 101.94,131.31C102.23,130.39 104.41,129.53 105.72,129.53C137.99,129.47 170.27,129.52 202.54,129.6C204.76,129.61 206.52,129.34 206.71,126.17C172.35,126.17 138.07,126.17 102.94,126.17C105.27,119.84 107.26,114.13 109.56,108.55C109.89,107.76 111.78,107.23 112.96,107.22C129.67,107.14 146.39,107.08 163.1,107.23C166.69,107.27 168.59,105.48 170.67,102.31C151.01,102.31 131.87,102.31 111.88,102.31C114.84,96.98 117.32,92.27 120.09,87.75C120.55,87 122.27,86.74 123.4,86.74C144.22,86.68 165.04,86.54 185.85,86.82C190.74,86.88 194.5,85.04 198.31,82.21C173.73,82.21 149.15,82.21 123.5,82.21C125.26,79.62 126.46,77.76 127.76,75.97C133.8,67.66 133.81,67.67 144.13,67.67C171.78,67.67 199.43,67.67 227.08,67.67C228.19,67.67 229.29,67.67 231.08,67.67C228.38,64.86 226.32,62.93 222.7,62.94C196.21,63.06 169.72,63.01 143.22,63.01C142.01,63.01 140.79,63.01 139.58,63.01C139.41,62.71 139.24,62.41 139.08,62.12C141.68,59.5 144.22,56.8 146.91,54.27C154.7,46.92 154.72,46.79 165.43,46.98C181.21,47.26 196.1,51.61 210.47,57.78C214.32,59.44 217.65,60.31 221.76,58.43C224.98,56.95 228.71,56.61 232.67,56.03C229.67,61.57 233.27,64.22 236.42,67.19C240.32,70.87 244.05,74.72 247.81,78.54C248.6,79.35 249.19,80.35 250.32,81.84C248.78,81.93 247.82,82.03 246.87,82.03C240.24,82.04 233.61,82.23 227,81.97C207.93,81.24 191.92,88.26 178.31,101.15C173.45,105.76 169.11,110.93 164.57,115.87C164.1,116.38 163.83,117.06 163.06,118.34C186.14,110.23 207.86,99.73 232.14,98.87C232.34,99.18 232.53,99.5 232.73,99.82C229.85,103.18 227.16,106.72 224.07,109.87C211.77,122.42 204.42,137.53 201.03,154.6C200.07,159.39 200.14,164.4 199.32,169.51C197.97,169.68 197.03,169.64 196.08,169.6ZM209.68,107.22C212.31,107.23 214.96,107.01 217.56,107.29C221.34,107.69 223.62,105.8 226.08,102.17C219.87,103.64 214.42,104.92 208.98,106.24C208.98,106.24 209.12,106.87 209.68,107.22Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M195.81,169.61C197.03,169.64 197.97,169.68 199.2,169.74C199.58,171.84 199.68,173.93 199.79,176.02C200.15,176.15 200.52,176.28 200.89,176.41C202.45,174.11 204.02,171.8 205.97,169.3C207.64,169.04 208.92,168.97 210.44,168.9C211.39,168.81 212.11,168.66 212.84,168.65C221.05,168.54 229.27,168.45 237.79,168.4C240.14,168.48 242.19,168.51 244.24,168.54C243.48,173.08 242.67,177.61 241.96,182.16C238.76,202.76 238.29,223.44 239.8,244.49C238.12,244.99 236.72,245.2 235.33,245.4C197.17,245.39 159.01,245.31 120.85,245.45C117.22,245.46 115.21,244.38 113.93,241.08C112.65,237.8 111,234.67 109.23,230.84C113.61,230.84 117.25,230.84 120.89,230.84C157.58,230.84 194.27,230.85 230.96,230.81C232.33,230.8 234.26,231.63 234.58,229.04C234.9,226.41 233.71,225.89 231.33,225.9C204.94,225.97 178.55,225.94 152.17,225.95C138.29,225.95 124.42,225.9 110.54,226.03C108.13,226.05 106.9,225.32 106.24,223.03C105.12,219.12 103.81,215.26 102.24,210.34C146.69,210.16 190.42,210.32 234.45,210.24C234.54,208.65 234.62,207.35 234.7,205.8C233.41,205.73 232.39,205.63 231.38,205.63C191.74,205.61 152.11,205.59 112.48,205.59C109.43,205.59 106.38,205.78 103.34,205.7C102.57,205.68 101.25,205.09 101.13,204.56C99.99,199.28 99.05,193.95 97.99,188.26C103.72,188.26 108.74,188.24 113.75,188.28C115.11,188.29 116.47,188.55 117.83,188.55C155.88,188.57 193.94,188.57 231.99,188.56C236.12,188.56 236.28,188.39 236.25,183.67C190.27,183.67 144.28,183.67 97.69,183.67C97.5,179.15 97.2,175.01 97.29,170.87C97.3,170.33 99.29,169.41 100.37,169.4C110.67,169.26 120.97,169.28 131.28,169.27C143.26,169.26 155.24,169.22 167.23,169.28C176.66,169.33 186.1,169.51 195.81,169.61Z"
|
||||
android:fillColor="#BF43F5"/>
|
||||
<path
|
||||
android:pathData="M235.35,245.69C236.72,245.2 238.12,244.99 239.8,244.78C241.62,256.91 242.64,269.16 244.79,281.19C247.54,296.59 251.45,311.75 257.46,326.28C257.66,326.75 257.8,327.25 257.96,327.73C240.7,334.84 173.22,312.36 158.05,294.11C172.31,294.11 186.11,294.11 199.9,294.11C213.75,294.11 227.59,294.11 241.47,294.11C241.67,290.83 240.89,289.4 237.49,289.42C210.37,289.55 183.25,289.46 156.13,289.54C153.11,289.55 150.66,288.97 148.51,286.71C143.83,281.81 138.99,277.07 134.22,272.27C134.38,271.97 134.53,271.67 134.69,271.38C168.96,271.38 203.24,271.38 237.91,271.38C237.59,269.49 237.35,268.09 237.06,266.42C235.66,266.42 234.41,266.42 233.17,266.42C204.05,266.42 174.93,266.43 145.82,266.41C141.61,266.41 137.4,266.12 133.21,266.29C130.69,266.4 129.04,265.57 127.7,263.52C124.9,259.24 122.01,255.03 118.68,250.09C158.03,250.09 196.51,250.09 235.37,250.09C235.37,248.43 235.37,247.21 235.35,245.69Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M259.22,245.74C260.88,245.45 262.52,245.45 264.42,245.44C264.74,246.79 264.81,248.15 264.9,249.86C307.33,249.86 349.65,249.86 392.94,249.86C390.82,253.23 389.17,255.95 387.42,258.59C382.22,266.42 382.21,266.42 372.98,266.42C339.55,266.42 306.12,266.42 272.68,266.42C271.45,266.42 270.21,266.42 269.01,266.42C268.6,270.97 268.9,271.32 272.97,271.32C306.4,271.33 339.83,271.33 373.27,271.33C374.49,271.33 375.71,271.33 377.95,271.33C375.2,274.41 372.88,276.64 370.99,279.18C364.84,287.47 357,290.15 346.54,289.75C324.07,288.9 301.55,289.49 279.05,289.49C277.81,289.49 276.57,289.49 275.34,289.49C275.16,293.42 275.74,294.1 279.15,294.1C302.59,294.11 326.04,294.1 349.48,294.11C350.69,294.11 351.89,294.11 353.09,294.11C353.19,294.39 353.29,294.68 353.39,294.97C351.01,296.75 348.69,298.61 346.25,300.3C330.27,311.36 312.8,319.17 293.96,323.9C291.74,324.45 289.36,324.83 287.12,324.64C285.84,324.52 284.3,323.33 283.54,322.19C277.07,312.49 273.01,301.7 269.22,290.75C264.17,276.21 261.34,261.2 259.22,245.74Z"
|
||||
android:fillColor="#985DED"/>
|
||||
<path
|
||||
android:pathData="M326.78,168.15C324.58,168.42 322.37,168.43 319.85,168.39C321.63,158.65 321.41,149 319.06,139.36C315.84,126.14 308.31,116.06 296.96,108.73C296.52,108.45 296.09,108.16 295.7,107.28C307.61,107.28 319.52,107.28 331.43,107.28C331.48,106.93 331.53,106.59 331.57,106.25C326.8,104.44 322.07,102.34 316.8,102.29C306.29,102.2 295.77,102.33 285.27,102.21C283.16,102.19 281.07,101.5 279.01,100.57C291.5,95.55 304.29,96.1 317.13,98.13C330.14,100.18 341.45,106.94 354.27,112.15C353.23,110.19 352.63,109.05 351.78,107.42C353.03,107.32 353.99,107.18 354.94,107.18C369.55,107.16 384.16,107.21 398.77,107.12C400.99,107.11 402.21,107.7 402.93,109.91C404.51,114.76 406.34,119.52 408.3,124.96C378.96,124.96 350.45,124.96 321.79,124.96C321.89,128.04 323.22,128.9 325.8,128.89C345.56,128.82 365.32,128.82 385.08,128.87C392.43,128.89 399.77,129.08 407.11,129.34C408.02,129.37 409.48,130.14 409.67,130.85C410.73,134.77 411.47,138.78 412.41,143.2C383.7,143.2 355.5,143.2 327.33,143.2C326.95,147.48 327.15,147.7 330.87,147.7C357.35,147.74 383.84,147.81 410.32,147.8C412.27,147.8 413.19,148.26 413.31,150.37C413.52,153.82 413.96,157.26 414.41,160.69C414.69,162.87 413.98,163.71 411.68,163.63C407.17,163.48 402.65,163.56 398.13,163.56C375.85,163.56 353.57,163.56 331.29,163.56C327.03,163.56 327.03,163.57 326.78,168.15Z"
|
||||
android:fillColor="#FF43C4"/>
|
||||
<path
|
||||
android:pathData="M227.85,13.36C256.3,8.58 282.98,11.91 297.89,17.06C295.79,21.98 293.7,26.87 291.61,31.76C290.5,34.37 289.2,36.91 288.3,39.59C287.56,41.8 286.27,42.35 284.07,42.34C269.05,42.26 254.03,42.3 238.21,42.3C240.88,45.05 242.88,47.26 246.57,47.22C257.92,47.11 269.27,47.18 280.62,47.19C281.77,47.19 282.91,47.19 285.21,47.19C282.72,51.33 281.09,55.24 278.43,58.21C274.25,62.87 269.29,66.82 264.85,71.26C263.14,72.97 262.34,72.9 260.7,71.1C253.98,63.75 247.23,56.42 240.16,49.4C232.38,41.67 222.49,38.46 211.78,37.23C202.25,36.13 193.21,37.88 184.27,40.97C182.14,41.71 179.81,42.15 177.55,42.24C172.62,42.42 167.68,42.29 162.59,41.89C181.97,27.22 203.7,17.88 227.85,13.36ZM264.4,65.93C265.08,65.12 265.76,64.31 266.85,63.01C264.04,63.01 261.99,63.01 259.33,63.01C260.37,64.48 260.92,65.63 261.81,66.37C262.25,66.73 263.32,66.32 264.4,65.93Z"
|
||||
android:fillColor="#FF42C3"/>
|
||||
<path
|
||||
android:pathData="M338.22,93.32C330,86.61 320.35,84.22 310.4,82.77C298.87,81.1 287.56,81.75 276.35,86.62C276.99,85.43 277.52,84.16 278.31,83.08C288.95,68.53 302.06,56.72 317.99,48.27C320.4,46.99 323.68,47.06 326.56,47.01C335.71,46.86 344.86,46.9 354,47.01C355.37,47.02 357.07,47.45 358.04,48.33C362.81,52.6 367.39,57.09 372.01,61.53C372.28,61.78 372.38,62.19 372.81,63.01C371.31,63.01 370.2,63.01 369.09,63.01C348.07,63.01 327.04,63.08 306.02,62.93C302.48,62.9 300.52,64.7 297.87,67.67C299.78,67.67 300.97,67.67 302.15,67.67C326.02,67.67 349.88,67.71 373.74,67.6C376.46,67.59 378.32,68.41 379.86,70.57C382.39,74.14 385.05,77.63 388.18,81.84C367.16,81.84 347,81.84 326.84,81.84C326.8,82.1 326.77,82.35 326.73,82.61C331.21,84.27 335.15,86.92 340.45,86.81C356.21,86.5 371.98,86.75 387.75,86.65C390.3,86.63 391.87,87.45 393.01,89.69C395.03,93.7 397.23,97.61 399.61,102.04C398.04,102.14 397.06,102.26 396.07,102.26C380.83,102.27 365.59,102.23 350.34,102.31C348.17,102.32 346.58,101.76 345.1,100.13C343,97.79 340.64,95.68 338.22,93.32Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M263.75,168.47C262.21,168.48 260.68,168.49 258.86,168.49C260.26,150.4 261.8,132.3 267.17,114.8C267.96,112.21 268.84,111.07 271.41,112.92C273.97,114.75 276.92,116.14 279.13,118.31C294.14,133.1 308.29,148.58 317.44,168.16C314.98,168.4 312.44,168.44 309.6,168.22C308.77,165.11 307.31,163.9 304.14,163.95C292.11,164.14 280.07,164.03 268.03,164.04C266.7,164.04 265.38,164.04 263.75,164.04C263.75,165.81 263.75,167.14 263.75,168.47ZM265.23,147.42C275.39,147.42 285.55,147.42 295.71,147.42C295.83,147.04 295.95,146.66 296.07,146.28C294.82,145.66 293.59,144.54 292.34,144.52C283.86,144.36 275.38,144.45 266.9,144.42C264.98,144.41 264.11,145.09 265.23,147.42ZM272.28,126.13C270.05,125.98 268.06,126.13 268.44,129.24C272.26,129.24 276,129.24 279.86,129.24C278.35,125.79 275.57,126.04 272.28,126.13Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M244.37,168.35C242.19,168.51 240.14,168.48 237.87,168.14C237.72,166.74 237.8,165.65 237.92,164.1C230.12,164.1 222.74,164.09 215.35,164.1C211.33,164.1 210.79,164.62 210.2,168.9C208.92,168.97 207.64,169.04 206.08,169.11C209.18,162.49 212.07,155.57 216.01,149.32C223.02,138.18 230.96,127.72 241.59,119.63C246.5,115.89 251.86,113.11 257.81,111.49C258.58,111.28 259.39,111.25 261.09,111.02C254,130.03 248.24,148.81 244.37,168.35ZM226.88,143.34C223.86,143.04 223.17,145.3 221.91,147.84C228.63,147.84 234.89,147.75 241.15,147.88C244,147.94 244.78,146.68 244.4,143.4C238.69,143.4 233.05,143.4 226.88,143.34ZM249.9,127.76C249.15,127.31 248.42,126.55 247.63,126.47C245.66,126.28 243.65,126.49 241.66,126.37C239.1,126.21 237.69,127.56 236.27,130.12C240.49,130.12 244.21,130.2 247.93,130.03C248.58,130 249.17,128.9 249.9,127.76Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
<path
|
||||
android:pathData="M315.52,22.68C327.71,27.67 338.96,33.8 349.32,42.08C328.46,40.7 308.6,44.4 289.08,52.83C289.67,49.65 290.71,47.68 293.44,46.84C297.43,45.61 301.38,44.27 305.32,42.43C301.9,42.43 298.47,42.43 294.12,42.43C298.11,34.6 301.71,27.44 305.48,20.37C305.75,19.86 307.39,19.71 308.22,19.96C310.62,20.69 312.94,21.7 315.52,22.68Z"
|
||||
android:fillColor="#FF44C4"/>
|
||||
</vector>
|
||||
@@ -48,7 +48,7 @@
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:src="@drawable/ic_yuzu_title" />
|
||||
android:src="@drawable/ic_yuzu_named" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -44,12 +44,11 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_logo"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="150dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="28dp"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_marginVertical="24dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/ic_yuzu_title" />
|
||||
android:src="@drawable/ic_yuzu_named" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/EdenCard"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/linear_layout_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
@@ -21,15 +21,17 @@
|
||||
android:id="@+id/logo_image"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginVertical="48dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/ic_yuzu_full" />
|
||||
android:src="@drawable/ic_yuzu" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/home_settings_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false" />
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
android:id="@+id/menu_edit_overlay"
|
||||
android:title="@string/emulation_touch_overlay_edit" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_snap_to_grid"
|
||||
android:title="@string/emulation_snap_to_grid"
|
||||
android:checkable="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_adjust_overlay"
|
||||
android:title="@string/emulation_control_adjust" />
|
||||
|
||||