Compare commits
45 Commits
android-11
...
android-11
Author | SHA1 | Date | |
---|---|---|---|
d2df38c8c3 | |||
7e284809de | |||
324c93e4aa | |||
133788d0d4 | |||
e8cb8b2668 | |||
361dbdddcc | |||
ab3e3c11af | |||
db5c24eb66 | |||
f7755df2af | |||
c60204e255 | |||
5e69769356 | |||
22cac3a5e3 | |||
e867768316 | |||
07276cf62a | |||
f04bc172ae | |||
585b6e9d46 | |||
a9e29a3972 | |||
1e61c3e1e7 | |||
79d3cef8db | |||
3e0da4f698 | |||
789c16305d | |||
1836e62d33 | |||
0bbbe80f75 | |||
70be45c992 | |||
9b3c64f4a4 | |||
eec3d356b6 | |||
2c1d850b46 | |||
2581590023 | |||
8427b9d49d | |||
0bb1c7c804 | |||
f26dddf3b5 | |||
a065dcdcd9 | |||
d6bd16b2c0 | |||
a49b146ccc | |||
fd9e157184 | |||
6cbd4020e8 | |||
3558b236cd | |||
48e82c4138 | |||
9eb70aea1d | |||
0460fbacc9 | |||
c73297e840 | |||
633d869ff4 | |||
e03f86cc54 | |||
a0a3566977 | |||
d3997bad9b |
@ -19,6 +19,7 @@ cmake .. \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DYUZU_CRASH_DUMPS=ON \
|
||||
-DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
|
||||
-DYUZU_USE_BUNDLED_FFMPEG=ON \
|
||||
-GNinja
|
||||
|
@ -23,6 +23,7 @@ cmake .. \
|
||||
-DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
|
||||
-DYUZU_USE_BUNDLED_FFMPEG=ON \
|
||||
-DYUZU_ENABLE_LTO=ON \
|
||||
-DYUZU_CRASH_DUMPS=ON \
|
||||
-GNinja
|
||||
|
||||
ninja
|
||||
|
@ -17,7 +17,6 @@ cmake .. \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_CCACHE=ON \
|
||||
-DYUZU_CRASH_DUMPS=ON \
|
||||
-DYUZU_USE_BUNDLED_SDL2=OFF \
|
||||
-DYUZU_USE_EXTERNAL_SDL2=OFF \
|
||||
-DYUZU_TESTS=OFF \
|
||||
|
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -32,7 +32,7 @@
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "opus"]
|
||||
path = externals/opus/opus
|
||||
path = externals/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
[submodule "SDL"]
|
||||
path = externals/SDL
|
||||
@ -58,3 +58,6 @@
|
||||
[submodule "VulkanMemoryAllocator"]
|
||||
path = externals/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "breakpad"]
|
||||
path = externals/breakpad
|
||||
url = https://github.com/yuzu-emu/breakpad.git
|
||||
|
@ -52,7 +52,7 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
|
||||
|
||||
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
|
||||
|
||||
@ -139,9 +139,6 @@ if (YUZU_USE_BUNDLED_VCPKG)
|
||||
if (YUZU_TESTS)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
|
||||
endif()
|
||||
if (YUZU_CRASH_DUMPS)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
|
||||
endif()
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
|
||||
endif()
|
||||
@ -551,6 +548,18 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
||||
find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS})
|
||||
endif()
|
||||
|
||||
if (WIN32 AND YUZU_CRASH_DUMPS)
|
||||
set(BREAKPAD_VER "breakpad-c89f9dd")
|
||||
download_bundled_external("breakpad/" ${BREAKPAD_VER} BREAKPAD_PREFIX)
|
||||
|
||||
set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include")
|
||||
set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib")
|
||||
|
||||
add_library(libbreakpad_client INTERFACE IMPORTED)
|
||||
target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}")
|
||||
target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
# Prefer the -pthread flag on Linux.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
@ -570,13 +579,6 @@ elseif (WIN32)
|
||||
# PSAPI is the Process Status API
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
||||
endif()
|
||||
|
||||
if (YUZU_CRASH_DUMPS)
|
||||
find_library(DBGHELP_LIBRARY dbghelp)
|
||||
if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
|
||||
message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
|
||||
endif()
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
set(PLATFORM_LIBRARIES rt)
|
||||
endif()
|
||||
|
@ -1,6 +1,5 @@
|
||||
| Pull Request | Commit | Title | Author | Merged? |
|
||||
|----|----|----|----|----|
|
||||
| [11910](https://github.com/yuzu-emu/yuzu//pull/11910) | [`8427b9d49`](https://github.com/yuzu-emu/yuzu//pull/11910/files) | renderer_vulkan: ensure exception on surface loss | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
|
||||
|
||||
End of merge log. You can find the original README.md below the break.
|
||||
|
106
externals/CMakeLists.txt
vendored
106
externals/CMakeLists.txt
vendored
@ -134,6 +134,10 @@ endif()
|
||||
|
||||
# Opus
|
||||
if (NOT TARGET Opus::opus)
|
||||
set(OPUS_BUILD_TESTING OFF)
|
||||
set(OPUS_BUILD_PROGRAMS OFF)
|
||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
|
||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
|
||||
add_subdirectory(opus)
|
||||
endif()
|
||||
|
||||
@ -189,3 +193,105 @@ if (ANDROID)
|
||||
add_subdirectory(libadrenotools)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Breakpad
|
||||
# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
|
||||
if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
|
||||
set(BREAKPAD_WIN32_DEFINES
|
||||
NOMINMAX
|
||||
UNICODE
|
||||
WIN32_LEAN_AND_MEAN
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_CRT_SECURE_NO_DEPRECATE
|
||||
_CRT_NONSTDC_NO_DEPRECATE
|
||||
)
|
||||
|
||||
# libbreakpad
|
||||
add_library(libbreakpad STATIC)
|
||||
file(GLOB_RECURSE LIBBREAKPAD_SOURCES breakpad/src/processor/*.cc)
|
||||
file(GLOB_RECURSE LIBDISASM_SOURCES breakpad/src/third_party/libdisasm/*.c)
|
||||
list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "_unittest|_selftest|synth_minidump|/tests|/testdata|/solaris|microdump_stackwalk|minidump_dump|minidump_stackwalk")
|
||||
if (WIN32)
|
||||
list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/mac|/android")
|
||||
target_compile_definitions(libbreakpad PRIVATE ${BREAKPAD_WIN32_DEFINES})
|
||||
target_include_directories(libbreakpad PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
|
||||
elseif (APPLE)
|
||||
list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/windows|/android")
|
||||
else()
|
||||
list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/mac|/windows|/android")
|
||||
endif()
|
||||
target_sources(libbreakpad PRIVATE ${LIBBREAKPAD_SOURCES} ${LIBDISASM_SOURCES})
|
||||
target_include_directories(libbreakpad
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/third_party/libdisasm
|
||||
)
|
||||
|
||||
# libbreakpad_client
|
||||
add_library(libbreakpad_client STATIC)
|
||||
file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc)
|
||||
|
||||
if (WIN32)
|
||||
file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc)
|
||||
list(FILTER LIBBREAKPAD_COMMON_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc")
|
||||
target_include_directories(libbreakpad_client PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
|
||||
target_compile_definitions(libbreakpad_client PRIVATE ${BREAKPAD_WIN32_DEFINES})
|
||||
elseif (APPLE)
|
||||
target_compile_definitions(libbreakpad_client PRIVATE HAVE_MACH_O_NLIST_H)
|
||||
file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/mac/*.cc breakpad/src/common/mac/*.cc)
|
||||
list(APPEND LIBBREAKPAD_CLIENT_SOURCES breakpad/src/common/mac/MachIPC.mm)
|
||||
else()
|
||||
target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H)
|
||||
file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/linux/*.cc breakpad/src/common/linux/*.cc)
|
||||
endif()
|
||||
list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES})
|
||||
list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/sender|/tests|/unittests|/testcases|_unittest|_test")
|
||||
target_sources(libbreakpad_client PRIVATE ${LIBBREAKPAD_CLIENT_SOURCES})
|
||||
target_include_directories(libbreakpad_client PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(libbreakpad_client PRIVATE wininet.lib)
|
||||
elseif (APPLE)
|
||||
find_library(CoreFoundation_FRAMEWORK CoreFoundation)
|
||||
target_link_libraries(libbreakpad_client PRIVATE ${CoreFoundation_FRAMEWORK})
|
||||
else()
|
||||
find_library(PTHREAD_LIBRARIES pthread)
|
||||
target_compile_definitions(libbreakpad_client PRIVATE HAVE_GETCONTEXT=1)
|
||||
if (PTHREAD_LIBRARIES)
|
||||
target_link_libraries(libbreakpad_client PRIVATE ${PTHREAD_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Host tools for symbol processing
|
||||
if (LINUX)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
add_executable(minidump_stackwalk breakpad/src/processor/minidump_stackwalk.cc)
|
||||
target_link_libraries(minidump_stackwalk PRIVATE libbreakpad libbreakpad_client)
|
||||
|
||||
add_executable(dump_syms
|
||||
breakpad/src/common/dwarf_cfi_to_module.cc
|
||||
breakpad/src/common/dwarf_cu_to_module.cc
|
||||
breakpad/src/common/dwarf_line_to_module.cc
|
||||
breakpad/src/common/dwarf_range_list_handler.cc
|
||||
breakpad/src/common/language.cc
|
||||
breakpad/src/common/module.cc
|
||||
breakpad/src/common/path_helper.cc
|
||||
breakpad/src/common/stabs_reader.cc
|
||||
breakpad/src/common/stabs_to_module.cc
|
||||
breakpad/src/common/dwarf/bytereader.cc
|
||||
breakpad/src/common/dwarf/dwarf2diehandler.cc
|
||||
breakpad/src/common/dwarf/dwarf2reader.cc
|
||||
breakpad/src/common/dwarf/elf_reader.cc
|
||||
breakpad/src/common/linux/crc32.cc
|
||||
breakpad/src/common/linux/dump_symbols.cc
|
||||
breakpad/src/common/linux/elf_symbols_to_module.cc
|
||||
breakpad/src/common/linux/elfutils.cc
|
||||
breakpad/src/common/linux/file_id.cc
|
||||
breakpad/src/common/linux/linux_libc_support.cc
|
||||
breakpad/src/common/linux/memory_mapped_file.cc
|
||||
breakpad/src/common/linux/safe_readlink.cc
|
||||
breakpad/src/tools/linux/dump_syms/dump_syms.cc)
|
||||
target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
|
||||
endif()
|
||||
endif()
|
||||
|
2
externals/SDL
vendored
2
externals/SDL
vendored
Submodule externals/SDL updated: 031912c4b6...cc016b0046
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: ed857118e2...df60f03168
2
externals/VulkanMemoryAllocator
vendored
2
externals/VulkanMemoryAllocator
vendored
Submodule externals/VulkanMemoryAllocator updated: 9b0fc3e7b0...2f382df218
1
externals/breakpad
vendored
Submodule
1
externals/breakpad
vendored
Submodule
Submodule externals/breakpad added at c89f9dddc7
2
externals/cpp-httplib
vendored
2
externals/cpp-httplib
vendored
Submodule externals/cpp-httplib updated: 6d963fbe8d...a609330e4c
2
externals/cpp-jwt
vendored
2
externals/cpp-jwt
vendored
Submodule externals/cpp-jwt updated: e12ef06218...10ef5735d8
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 7da378033a...0df09e2f6b
2
externals/ffmpeg/ffmpeg
vendored
2
externals/ffmpeg/ffmpeg
vendored
Submodule externals/ffmpeg/ffmpeg updated: 6b6b9e593d...9c1294eadd
2
externals/inih/inih
vendored
2
externals/inih/inih
vendored
Submodule externals/inih/inih updated: 1e80a47dff...9cecf0643d
7
externals/libusb/CMakeLists.txt
vendored
7
externals/libusb/CMakeLists.txt
vendored
@ -49,11 +49,6 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
|
||||
|
||||
set(LIBUSB_INCLUDE_DIRS "${LIBUSB_SRC_DIR}/libusb" CACHE PATH "libusb headers path" FORCE)
|
||||
|
||||
# MINGW: causes "externals/libusb/libusb/libusb/os/windows_winusb.c:1427:2: error: conversion to non-scalar type requested", so cannot statically link it for now.
|
||||
if (NOT MINGW)
|
||||
set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}")
|
||||
endif()
|
||||
|
||||
make_directory("${LIBUSB_PREFIX}")
|
||||
|
||||
add_custom_command(
|
||||
@ -146,8 +141,6 @@ else() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_include_directories(usb BEFORE PRIVATE libusb/msvc)
|
||||
endif()
|
||||
|
||||
# Works around other libraries providing their own definition of USB GUIDs (e.g. SDL2)
|
||||
target_compile_definitions(usb PRIVATE "-DGUID_DEVINTERFACE_USB_DEVICE=(GUID){ 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}")
|
||||
else()
|
||||
target_include_directories(usb
|
||||
# turns out other projects also have "config.h", so make sure the
|
||||
|
2
externals/libusb/libusb
vendored
2
externals/libusb/libusb
vendored
Submodule externals/libusb/libusb updated: c6a35c5601...c060e9ce30
1
externals/opus
vendored
Submodule
1
externals/opus
vendored
Submodule
Submodule externals/opus added at 101a71e03b
259
externals/opus/CMakeLists.txt
vendored
259
externals/opus/CMakeLists.txt
vendored
@ -1,259 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
project(opus)
|
||||
|
||||
option(OPUS_STACK_PROTECTOR "Use stack protection" OFF)
|
||||
option(OPUS_USE_ALLOCA "Use alloca for stack arrays (on non-C99 compilers)" OFF)
|
||||
option(OPUS_CUSTOM_MODES "Enable non-Opus modes, e.g. 44.1 kHz & 2^n frames" OFF)
|
||||
option(OPUS_FIXED_POINT "Compile as fixed-point (for machines without a fast enough FPU)" OFF)
|
||||
option(OPUS_ENABLE_FLOAT_API "Compile with the floating point API (for machines with float library" ON)
|
||||
|
||||
include(opus/opus_functions.cmake)
|
||||
|
||||
if(OPUS_STACK_PROTECTOR)
|
||||
if(NOT MSVC) # GC on by default on MSVC
|
||||
check_and_set_flag(STACK_PROTECTION_STRONG -fstack-protector-strong)
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
check_and_set_flag(BUFFER_SECURITY_CHECK /GS-)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(opus
|
||||
# CELT sources
|
||||
opus/celt/bands.c
|
||||
opus/celt/celt.c
|
||||
opus/celt/celt_decoder.c
|
||||
opus/celt/celt_encoder.c
|
||||
opus/celt/celt_lpc.c
|
||||
opus/celt/cwrs.c
|
||||
opus/celt/entcode.c
|
||||
opus/celt/entdec.c
|
||||
opus/celt/entenc.c
|
||||
opus/celt/kiss_fft.c
|
||||
opus/celt/laplace.c
|
||||
opus/celt/mathops.c
|
||||
opus/celt/mdct.c
|
||||
opus/celt/modes.c
|
||||
opus/celt/pitch.c
|
||||
opus/celt/quant_bands.c
|
||||
opus/celt/rate.c
|
||||
opus/celt/vq.c
|
||||
|
||||
# SILK sources
|
||||
opus/silk/A2NLSF.c
|
||||
opus/silk/CNG.c
|
||||
opus/silk/HP_variable_cutoff.c
|
||||
opus/silk/LPC_analysis_filter.c
|
||||
opus/silk/LPC_fit.c
|
||||
opus/silk/LPC_inv_pred_gain.c
|
||||
opus/silk/LP_variable_cutoff.c
|
||||
opus/silk/NLSF2A.c
|
||||
opus/silk/NLSF_VQ.c
|
||||
opus/silk/NLSF_VQ_weights_laroia.c
|
||||
opus/silk/NLSF_decode.c
|
||||
opus/silk/NLSF_del_dec_quant.c
|
||||
opus/silk/NLSF_encode.c
|
||||
opus/silk/NLSF_stabilize.c
|
||||
opus/silk/NLSF_unpack.c
|
||||
opus/silk/NSQ.c
|
||||
opus/silk/NSQ_del_dec.c
|
||||
opus/silk/PLC.c
|
||||
opus/silk/VAD.c
|
||||
opus/silk/VQ_WMat_EC.c
|
||||
opus/silk/ana_filt_bank_1.c
|
||||
opus/silk/biquad_alt.c
|
||||
opus/silk/bwexpander.c
|
||||
opus/silk/bwexpander_32.c
|
||||
opus/silk/check_control_input.c
|
||||
opus/silk/code_signs.c
|
||||
opus/silk/control_SNR.c
|
||||
opus/silk/control_audio_bandwidth.c
|
||||
opus/silk/control_codec.c
|
||||
opus/silk/dec_API.c
|
||||
opus/silk/decode_core.c
|
||||
opus/silk/decode_frame.c
|
||||
opus/silk/decode_indices.c
|
||||
opus/silk/decode_parameters.c
|
||||
opus/silk/decode_pitch.c
|
||||
opus/silk/decode_pulses.c
|
||||
opus/silk/decoder_set_fs.c
|
||||
opus/silk/enc_API.c
|
||||
opus/silk/encode_indices.c
|
||||
opus/silk/encode_pulses.c
|
||||
opus/silk/gain_quant.c
|
||||
opus/silk/init_decoder.c
|
||||
opus/silk/init_encoder.c
|
||||
opus/silk/inner_prod_aligned.c
|
||||
opus/silk/interpolate.c
|
||||
opus/silk/lin2log.c
|
||||
opus/silk/log2lin.c
|
||||
opus/silk/pitch_est_tables.c
|
||||
opus/silk/process_NLSFs.c
|
||||
opus/silk/quant_LTP_gains.c
|
||||
opus/silk/resampler.c
|
||||
opus/silk/resampler_down2.c
|
||||
opus/silk/resampler_down2_3.c
|
||||
opus/silk/resampler_private_AR2.c
|
||||
opus/silk/resampler_private_IIR_FIR.c
|
||||
opus/silk/resampler_private_down_FIR.c
|
||||
opus/silk/resampler_private_up2_HQ.c
|
||||
opus/silk/resampler_rom.c
|
||||
opus/silk/shell_coder.c
|
||||
opus/silk/sigm_Q15.c
|
||||
opus/silk/sort.c
|
||||
opus/silk/stereo_LR_to_MS.c
|
||||
opus/silk/stereo_MS_to_LR.c
|
||||
opus/silk/stereo_decode_pred.c
|
||||
opus/silk/stereo_encode_pred.c
|
||||
opus/silk/stereo_find_predictor.c
|
||||
opus/silk/stereo_quant_pred.c
|
||||
opus/silk/sum_sqr_shift.c
|
||||
opus/silk/table_LSF_cos.c
|
||||
opus/silk/tables_LTP.c
|
||||
opus/silk/tables_NLSF_CB_NB_MB.c
|
||||
opus/silk/tables_NLSF_CB_WB.c
|
||||
opus/silk/tables_gain.c
|
||||
opus/silk/tables_other.c
|
||||
opus/silk/tables_pitch_lag.c
|
||||
opus/silk/tables_pulses_per_block.c
|
||||
|
||||
# Opus sources
|
||||
opus/src/analysis.c
|
||||
opus/src/mapping_matrix.c
|
||||
opus/src/mlp.c
|
||||
opus/src/mlp_data.c
|
||||
opus/src/opus.c
|
||||
opus/src/opus_decoder.c
|
||||
opus/src/opus_encoder.c
|
||||
opus/src/opus_multistream.c
|
||||
opus/src/opus_multistream_decoder.c
|
||||
opus/src/opus_multistream_encoder.c
|
||||
opus/src/opus_projection_decoder.c
|
||||
opus/src/opus_projection_encoder.c
|
||||
opus/src/repacketizer.c
|
||||
)
|
||||
|
||||
if (DEBUG)
|
||||
target_sources(opus PRIVATE opus/silk/debug.c)
|
||||
endif()
|
||||
|
||||
if (OPUS_FIXED_POINT)
|
||||
target_sources(opus PRIVATE
|
||||
opus/silk/fixed/LTP_analysis_filter_FIX.c
|
||||
opus/silk/fixed/LTP_scale_ctrl_FIX.c
|
||||
opus/silk/fixed/apply_sine_window_FIX.c
|
||||
opus/silk/fixed/autocorr_FIX.c
|
||||
opus/silk/fixed/burg_modified_FIX.c
|
||||
opus/silk/fixed/corrMatrix_FIX.c
|
||||
opus/silk/fixed/encode_frame_FIX.c
|
||||
opus/silk/fixed/find_LPC_FIX.c
|
||||
opus/silk/fixed/find_LTP_FIX.c
|
||||
opus/silk/fixed/find_pitch_lags_FIX.c
|
||||
opus/silk/fixed/find_pred_coefs_FIX.c
|
||||
opus/silk/fixed/k2a_FIX.c
|
||||
opus/silk/fixed/k2a_Q16_FIX.c
|
||||
opus/silk/fixed/noise_shape_analysis_FIX.c
|
||||
opus/silk/fixed/pitch_analysis_core_FIX.c
|
||||
opus/silk/fixed/prefilter_FIX.c
|
||||
opus/silk/fixed/process_gains_FIX.c
|
||||
opus/silk/fixed/regularize_correlations_FIX.c
|
||||
opus/silk/fixed/residual_energy16_FIX.c
|
||||
opus/silk/fixed/residual_energy_FIX.c
|
||||
opus/silk/fixed/schur64_FIX.c
|
||||
opus/silk/fixed/schur_FIX.c
|
||||
opus/silk/fixed/solve_LS_FIX.c
|
||||
opus/silk/fixed/vector_ops_FIX.c
|
||||
opus/silk/fixed/warped_autocorrelation_FIX.c
|
||||
)
|
||||
else()
|
||||
target_sources(opus PRIVATE
|
||||
opus/silk/float/LPC_analysis_filter_FLP.c
|
||||
opus/silk/float/LPC_inv_pred_gain_FLP.c
|
||||
opus/silk/float/LTP_analysis_filter_FLP.c
|
||||
opus/silk/float/LTP_scale_ctrl_FLP.c
|
||||
opus/silk/float/apply_sine_window_FLP.c
|
||||
opus/silk/float/autocorrelation_FLP.c
|
||||
opus/silk/float/burg_modified_FLP.c
|
||||
opus/silk/float/bwexpander_FLP.c
|
||||
opus/silk/float/corrMatrix_FLP.c
|
||||
opus/silk/float/encode_frame_FLP.c
|
||||
opus/silk/float/energy_FLP.c
|
||||
opus/silk/float/find_LPC_FLP.c
|
||||
opus/silk/float/find_LTP_FLP.c
|
||||
opus/silk/float/find_pitch_lags_FLP.c
|
||||
opus/silk/float/find_pred_coefs_FLP.c
|
||||
opus/silk/float/inner_product_FLP.c
|
||||
opus/silk/float/k2a_FLP.c
|
||||
opus/silk/float/noise_shape_analysis_FLP.c
|
||||
opus/silk/float/pitch_analysis_core_FLP.c
|
||||
opus/silk/float/process_gains_FLP.c
|
||||
opus/silk/float/regularize_correlations_FLP.c
|
||||
opus/silk/float/residual_energy_FLP.c
|
||||
opus/silk/float/scale_copy_vector_FLP.c
|
||||
opus/silk/float/scale_vector_FLP.c
|
||||
opus/silk/float/schur_FLP.c
|
||||
opus/silk/float/sort_FLP.c
|
||||
opus/silk/float/warped_autocorrelation_FLP.c
|
||||
opus/silk/float/wrappers_FLP.c
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(opus PRIVATE OPUS_BUILD ENABLE_HARDENING)
|
||||
|
||||
if(NOT MSVC)
|
||||
if(MINGW)
|
||||
target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=0)
|
||||
else()
|
||||
target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=2)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# It is strongly recommended to uncomment one of these VAR_ARRAYS: Use C99
|
||||
# variable-length arrays for stack allocation USE_ALLOCA: Use alloca() for stack
|
||||
# allocation If none is defined, then the fallback is a non-threadsafe global
|
||||
# array
|
||||
if(OPUS_USE_ALLOCA OR MSVC)
|
||||
target_compile_definitions(opus PRIVATE USE_ALLOCA)
|
||||
else()
|
||||
target_compile_definitions(opus PRIVATE VAR_ARRAYS)
|
||||
endif()
|
||||
|
||||
if(OPUS_CUSTOM_MODES)
|
||||
target_compile_definitions(opus PRIVATE CUSTOM_MODES)
|
||||
endif()
|
||||
|
||||
if(NOT OPUS_ENABLE_FLOAT_API)
|
||||
target_compile_definitions(opus PRIVATE DISABLE_FLOAT_API)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(opus
|
||||
PUBLIC
|
||||
-DOPUS_VERSION="\\"1.3.1\\""
|
||||
|
||||
PRIVATE
|
||||
# Use C99 intrinsics to speed up float-to-int conversion
|
||||
HAVE_LRINTF
|
||||
)
|
||||
|
||||
if (FIXED_POINT)
|
||||
target_compile_definitions(opus PRIVATE -DFIXED_POINT=1 -DDISABLE_FLOAT_API)
|
||||
endif()
|
||||
|
||||
target_include_directories(opus
|
||||
PUBLIC
|
||||
opus/include
|
||||
|
||||
PRIVATE
|
||||
opus/celt
|
||||
opus/silk
|
||||
opus/silk/fixed
|
||||
opus/silk/float
|
||||
opus/src
|
||||
)
|
||||
|
||||
add_library(Opus::opus ALIAS opus)
|
1
externals/opus/opus
vendored
1
externals/opus/opus
vendored
Submodule externals/opus/opus deleted from ad8fe90db7
2
externals/vcpkg
vendored
2
externals/vcpkg
vendored
Submodule externals/vcpkg updated: cbf56573a9...ef2eef1734
@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||
@ -68,7 +69,7 @@ object NativeLibrary {
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun openContentUri(path: String?, openmode: String?): Int {
|
||||
return if (isNativePath(path!!)) {
|
||||
return if (DocumentsTree.isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openmode)
|
||||
@ -78,7 +79,7 @@ object NativeLibrary {
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getSize(path: String?): Long {
|
||||
return if (isNativePath(path!!)) {
|
||||
return if (DocumentsTree.isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
@ -88,23 +89,41 @@ object NativeLibrary {
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun exists(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
return if (DocumentsTree.isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
FileUtil.exists(path, suppressLog = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun isDirectory(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
return if (DocumentsTree.isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getParentDirectory(path: String): String =
|
||||
if (DocumentsTree.isNativePath(path)) {
|
||||
YuzuApplication.documentsTree!!.getParentDirectory(path)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getFilename(path: String): String =
|
||||
if (DocumentsTree.isNativePath(path)) {
|
||||
YuzuApplication.documentsTree!!.getFilename(path)
|
||||
} else {
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if pro controller isn't available and handheld is
|
||||
*/
|
||||
@ -215,32 +234,6 @@ object NativeLibrary {
|
||||
|
||||
external fun initGameIni(gameID: String?)
|
||||
|
||||
/**
|
||||
* Gets the embedded icon within the given ROM.
|
||||
*
|
||||
* @param filename the file path to the ROM.
|
||||
* @return a byte array containing the JPEG data for the icon.
|
||||
*/
|
||||
external fun getIcon(filename: String): ByteArray
|
||||
|
||||
/**
|
||||
* Gets the embedded title of the given ISO/ROM.
|
||||
*
|
||||
* @param filename The file path to the ISO/ROM.
|
||||
* @return the embedded title of the ISO/ROM.
|
||||
*/
|
||||
external fun getTitle(filename: String): String
|
||||
|
||||
external fun getDescription(filename: String): String
|
||||
|
||||
external fun getGameId(filename: String): String
|
||||
|
||||
external fun getRegions(filename: String): String
|
||||
|
||||
external fun getCompany(filename: String): String
|
||||
|
||||
external fun isHomebrew(filename: String): Boolean
|
||||
|
||||
external fun setAppDirectory(directory: String)
|
||||
|
||||
/**
|
||||
@ -259,7 +252,7 @@ object NativeLibrary {
|
||||
|
||||
external fun reloadKeys(): Boolean
|
||||
|
||||
external fun initializeEmulation()
|
||||
external fun initializeSystem()
|
||||
|
||||
external fun defaultCPUCore(): Int
|
||||
|
||||
@ -293,11 +286,6 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun stopEmulation()
|
||||
|
||||
/**
|
||||
* Resets the in-memory ROM metadata cache.
|
||||
*/
|
||||
external fun resetRomMetadata()
|
||||
|
||||
/**
|
||||
* Returns true if emulation is running (or is paused).
|
||||
*/
|
||||
@ -517,6 +505,36 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun initializeEmptyUserDirectory()
|
||||
|
||||
/**
|
||||
* Gets the launch path for a given applet. It is the caller's responsibility to also
|
||||
* set the system's current applet ID before trying to launch the nca given by this function.
|
||||
*
|
||||
* @param id The applet entry ID
|
||||
* @return The applet's launch path
|
||||
*/
|
||||
external fun getAppletLaunchPath(id: Long): String
|
||||
|
||||
/**
|
||||
* Sets the system's current applet ID before launching.
|
||||
*
|
||||
* @param appletId One of the ids in the Service::AM::Applets::AppletId enum
|
||||
*/
|
||||
external fun setCurrentAppletId(appletId: Int)
|
||||
|
||||
/**
|
||||
* Sets the cabinet mode for launching the cabinet applet.
|
||||
*
|
||||
* @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode
|
||||
*/
|
||||
external fun setCabinetMode(cabinetMode: Int)
|
||||
|
||||
/**
|
||||
* Checks whether NAND contents are available and valid.
|
||||
*
|
||||
* @return 'true' if firmware is available
|
||||
*/
|
||||
external fun isFirmwareAvailable(): Boolean
|
||||
|
||||
/**
|
||||
* Button type for use in onTouchEvent
|
||||
*/
|
||||
|
@ -45,7 +45,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
@ -57,17 +56,16 @@ import kotlin.math.roundToInt
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
||||
private var controllerMappingHelper: ControllerMappingHelper? = null
|
||||
|
||||
var isActivityRecreated = false
|
||||
private lateinit var nfcReader: NfcReader
|
||||
private lateinit var inputHandler: InputHandler
|
||||
|
||||
private val gyro = FloatArray(3)
|
||||
private val accel = FloatArray(3)
|
||||
private var motionTimestamp: Long = 0
|
||||
private var flipMotionOrientation: Boolean = false
|
||||
|
||||
private var controllerIds = InputHandler.getGameControllerIds()
|
||||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
@ -95,8 +93,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
|
||||
isActivityRecreated = savedInstanceState != null
|
||||
|
||||
controllerMappingHelper = ControllerMappingHelper()
|
||||
|
||||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||
enableFullscreenImmersive()
|
||||
|
||||
@ -105,8 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
inputHandler = InputHandler()
|
||||
inputHandler.initialize()
|
||||
InputHandler.initialize()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||
@ -162,6 +157,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
InputHandler.updateControllerIds()
|
||||
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
@ -195,7 +191,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
return inputHandler.dispatchKeyEvent(event)
|
||||
return InputHandler.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
@ -210,7 +206,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
return true
|
||||
}
|
||||
|
||||
return inputHandler.dispatchGenericMotionEvent(event)
|
||||
return InputHandler.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
|
@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding
|
||||
import org.yuzu.yuzu_emu.model.Applet
|
||||
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
|
||||
class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
|
||||
RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
|
||||
View.OnClickListener {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): AppletAdapter.AppletViewHolder {
|
||||
CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
.apply { root.setOnClickListener(this@AppletAdapter) }
|
||||
.also { return AppletViewHolder(it) }
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
|
||||
holder.bind(applets[position])
|
||||
|
||||
override fun getItemCount(): Int = applets.size
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val applet = (view.tag as AppletViewHolder).applet
|
||||
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
|
||||
if (appletPath.isEmpty()) {
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
R.string.applets_error_applet,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
if (applet.appletInfo == AppletInfo.Cabinet) {
|
||||
view.findNavController()
|
||||
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
|
||||
return
|
||||
}
|
||||
|
||||
NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
|
||||
val appletGame = Game(
|
||||
title = YuzuApplication.appContext.getString(applet.titleId),
|
||||
path = appletPath
|
||||
)
|
||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
||||
view.findNavController().navigate(action)
|
||||
}
|
||||
|
||||
inner class AppletViewHolder(val binding: CardAppletOptionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
lateinit var applet: Applet
|
||||
|
||||
init {
|
||||
itemView.tag = this
|
||||
}
|
||||
|
||||
fun bind(applet: Applet) {
|
||||
this.applet = applet
|
||||
|
||||
binding.title.setText(applet.titleId)
|
||||
binding.description.setText(applet.descriptionId)
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.context.resources,
|
||||
applet.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.databinding.DialogListItemBinding
|
||||
import org.yuzu.yuzu_emu.model.CabinetMode
|
||||
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
|
||||
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
|
||||
class CabinetLauncherDialogAdapter(val fragment: Fragment) :
|
||||
RecyclerView.Adapter<CabinetModeViewHolder>(),
|
||||
View.OnClickListener {
|
||||
private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
|
||||
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
.apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
|
||||
.also { return CabinetModeViewHolder(it) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = cabinetModes.size
|
||||
|
||||
override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
|
||||
holder.bind(cabinetModes[position])
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val mode = (view.tag as CabinetModeViewHolder).cabinetMode
|
||||
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
|
||||
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
|
||||
NativeLibrary.setCabinetMode(mode.id)
|
||||
val appletGame = Game(
|
||||
title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
|
||||
path = appletPath
|
||||
)
|
||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
||||
fragment.findNavController().navigate(action)
|
||||
}
|
||||
|
||||
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
lateinit var cabinetMode: CabinetMode
|
||||
|
||||
init {
|
||||
itemView.tag = this
|
||||
}
|
||||
|
||||
fun bind(cabinetMode: CabinetMode) {
|
||||
this.cabinetMode = cabinetMode
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.context.resources,
|
||||
cabinetMode.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
binding.title.setText(cabinetMode.titleId)
|
||||
}
|
||||
}
|
||||
}
|
@ -147,7 +147,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.gameId == newItem.gameId
|
||||
return oldItem.programId == newItem.programId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
|
@ -0,0 +1,113 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.AppletAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentAppletLauncherBinding
|
||||
import org.yuzu.yuzu_emu.model.Applet
|
||||
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
|
||||
class AppletLauncherFragment : Fragment() {
|
||||
private var _binding: FragmentAppletLauncherBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentAppletLauncherBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarApplets.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
}
|
||||
|
||||
val applets = listOf(
|
||||
Applet(
|
||||
R.string.album_applet,
|
||||
R.string.album_applet_description,
|
||||
R.drawable.ic_album,
|
||||
AppletInfo.PhotoViewer
|
||||
),
|
||||
Applet(
|
||||
R.string.cabinet_applet,
|
||||
R.string.cabinet_applet_description,
|
||||
R.drawable.ic_nfc,
|
||||
AppletInfo.Cabinet
|
||||
),
|
||||
Applet(
|
||||
R.string.mii_edit_applet,
|
||||
R.string.mii_edit_applet_description,
|
||||
R.drawable.ic_mii,
|
||||
AppletInfo.MiiEdit
|
||||
)
|
||||
)
|
||||
|
||||
binding.listApplets.apply {
|
||||
layoutManager = GridLayoutManager(
|
||||
requireContext(),
|
||||
resources.getInteger(R.integer.grid_columns)
|
||||
)
|
||||
adapter = AppletAdapter(requireActivity(), applets)
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
val leftInsets = barInsets.left + cutoutInsets.left
|
||||
val rightInsets = barInsets.right + cutoutInsets.right
|
||||
|
||||
val mlpAppBar = binding.toolbarApplets.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpAppBar.leftMargin = leftInsets
|
||||
mlpAppBar.rightMargin = rightInsets
|
||||
binding.toolbarApplets.layoutParams = mlpAppBar
|
||||
|
||||
val mlpListApplets =
|
||||
binding.listApplets.layoutParams as ViewGroup.MarginLayoutParams
|
||||
mlpListApplets.leftMargin = leftInsets
|
||||
mlpListApplets.rightMargin = rightInsets
|
||||
binding.listApplets.layoutParams = mlpListApplets
|
||||
|
||||
binding.listApplets.updatePadding(bottom = barInsets.bottom)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.DialogListBinding
|
||||
|
||||
class CabinetLauncherDialogFragment : DialogFragment() {
|
||||
private lateinit var binding: DialogListBinding
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
binding = DialogListBinding.inflate(layoutInflater)
|
||||
binding.dialogList.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = CabinetLauncherDialogAdapter(this@CabinetLauncherDialogFragment)
|
||||
}
|
||||
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.cabinet_launcher)
|
||||
.setView(binding.root)
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return binding.root
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
@ -25,6 +26,7 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@ -156,6 +158,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.showFpsText.setTextColor(Color.YELLOW)
|
||||
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
|
||||
|
||||
binding.drawerLayout.addDrawerListener(object : DrawerListener {
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
binding.surfaceInputOverlay.dispatchTouchEvent(
|
||||
MotionEvent.obtain(
|
||||
SystemClock.uptimeMillis(),
|
||||
SystemClock.uptimeMillis() + 100,
|
||||
MotionEvent.ACTION_UP,
|
||||
0f,
|
||||
0f,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
// No op
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
// No op
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
// No op
|
||||
}
|
||||
})
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
|
||||
game.title
|
||||
|
@ -30,6 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||
@ -131,6 +132,20 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.applets,
|
||||
R.string.applets_description,
|
||||
R.drawable.ic_applet,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
|
||||
},
|
||||
{ NativeLibrary.isFirmwareAvailable() },
|
||||
R.string.applets_error_firmware,
|
||||
R.string.applets_error_description
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
|
@ -8,6 +8,7 @@ import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@ -32,7 +33,9 @@ class MessageDialogFragment : DialogFragment() {
|
||||
if (titleId != 0) dialog.setTitle(titleId)
|
||||
if (titleString.isNotEmpty()) dialog.setTitle(titleString)
|
||||
|
||||
if (descriptionId != 0) dialog.setMessage(descriptionId)
|
||||
if (descriptionId != 0) {
|
||||
dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
|
||||
}
|
||||
if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
|
||||
|
||||
if (helpLinkId != 0) {
|
||||
|
@ -0,0 +1,55 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
||||
data class Applet(
|
||||
@StringRes val titleId: Int,
|
||||
@StringRes val descriptionId: Int,
|
||||
@DrawableRes val iconId: Int,
|
||||
val appletInfo: AppletInfo,
|
||||
val cabinetMode: CabinetMode = CabinetMode.None
|
||||
)
|
||||
|
||||
// Combination of Common::AM::Applets::AppletId enum and the entry id
|
||||
enum class AppletInfo(val appletId: Int, val entryId: Long = 0) {
|
||||
None(0x00),
|
||||
Application(0x01),
|
||||
OverlayDisplay(0x02),
|
||||
QLaunch(0x03),
|
||||
Starter(0x04),
|
||||
Auth(0x0A),
|
||||
Cabinet(0x0B, 0x0100000000001002),
|
||||
Controller(0x0C),
|
||||
DataErase(0x0D),
|
||||
Error(0x0E),
|
||||
NetConnect(0x0F),
|
||||
ProfileSelect(0x10),
|
||||
SoftwareKeyboard(0x11),
|
||||
MiiEdit(0x12, 0x0100000000001009),
|
||||
Web(0x13),
|
||||
Shop(0x14),
|
||||
PhotoViewer(0x015, 0x010000000000100D),
|
||||
Settings(0x16),
|
||||
OfflineWeb(0x17),
|
||||
LoginShare(0x18),
|
||||
WebAuth(0x19),
|
||||
MyPage(0x1A)
|
||||
}
|
||||
|
||||
// Matches enum in Service::NFP::CabinetMode with extra metadata
|
||||
enum class CabinetMode(
|
||||
val id: Int,
|
||||
@StringRes val titleId: Int = 0,
|
||||
@DrawableRes val iconId: Int = 0
|
||||
) {
|
||||
None(-1),
|
||||
StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit),
|
||||
StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh),
|
||||
StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore),
|
||||
StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear)
|
||||
}
|
@ -11,16 +11,15 @@ import kotlinx.serialization.Serializable
|
||||
@Parcelize
|
||||
@Serializable
|
||||
class Game(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val regions: String,
|
||||
val title: String = "",
|
||||
val path: String,
|
||||
val gameId: String,
|
||||
val company: String,
|
||||
val isHomebrew: Boolean
|
||||
val programId: String = "",
|
||||
val developer: String = "",
|
||||
val version: String = "",
|
||||
val isHomebrew: Boolean = false
|
||||
) : Parcelable {
|
||||
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
|
||||
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
|
||||
val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
|
||||
val keyLastPlayedTime get() = "${programId}_LastPlayed"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Game) {
|
||||
@ -32,11 +31,9 @@ class Game(
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = title.hashCode()
|
||||
result = 31 * result + description.hashCode()
|
||||
result = 31 * result + regions.hashCode()
|
||||
result = 31 * result + path.hashCode()
|
||||
result = 31 * result + gameId.hashCode()
|
||||
result = 31 * result + company.hashCode()
|
||||
result = 31 * result + programId.hashCode()
|
||||
result = 31 * result + developer.hashCode()
|
||||
result = 31 * result + isHomebrew.hashCode()
|
||||
return result
|
||||
}
|
||||
|
@ -14,15 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.MissingFieldException
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import org.yuzu.yuzu_emu.utils.GameMetadata
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GamesViewModel : ViewModel() {
|
||||
val games: StateFlow<List<Game>> get() = _games
|
||||
private val _games = MutableStateFlow(emptyList<Game>())
|
||||
@ -49,26 +47,34 @@ class GamesViewModel : ViewModel() {
|
||||
// Retrieve list of cached games
|
||||
val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
.getStringSet(GameHelper.KEY_GAMES, emptySet())
|
||||
if (storedGames!!.isNotEmpty()) {
|
||||
val deserializedGames = mutableSetOf<Game>()
|
||||
storedGames.forEach {
|
||||
val game: Game
|
||||
try {
|
||||
game = Json.decodeFromString(it)
|
||||
} catch (e: MissingFieldException) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val gameExists =
|
||||
DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
|
||||
?.exists()
|
||||
if (gameExists == true) {
|
||||
deserializedGames.add(game)
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (storedGames!!.isNotEmpty()) {
|
||||
val deserializedGames = mutableSetOf<Game>()
|
||||
storedGames.forEach {
|
||||
val game: Game
|
||||
try {
|
||||
game = Json.decodeFromString(it)
|
||||
} catch (e: Exception) {
|
||||
// We don't care about any errors related to parsing the game cache
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val gameExists =
|
||||
DocumentFile.fromSingleUri(
|
||||
YuzuApplication.appContext,
|
||||
Uri.parse(game.path)
|
||||
)?.exists()
|
||||
if (gameExists == true) {
|
||||
deserializedGames.add(game)
|
||||
}
|
||||
}
|
||||
setGames(deserializedGames.toList())
|
||||
}
|
||||
reloadGames(false)
|
||||
}
|
||||
setGames(deserializedGames.toList())
|
||||
}
|
||||
reloadGames(false)
|
||||
}
|
||||
|
||||
fun setGames(games: List<Game>) {
|
||||
@ -106,7 +112,7 @@ class GamesViewModel : ViewModel() {
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
NativeLibrary.resetRomMetadata()
|
||||
GameMetadata.resetMetadata()
|
||||
setGames(GameHelper.getGames())
|
||||
_isReloading.value = false
|
||||
|
||||
|
@ -403,6 +403,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
} else {
|
||||
firmwarePath.deleteRecursively()
|
||||
cacheFirmwareDir.copyRecursively(firmwarePath, true)
|
||||
NativeLibrary.initializeSystem()
|
||||
getString(R.string.save_file_imported_success)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -648,7 +649,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
|
||||
// Reinitialize relevant data
|
||||
NativeLibrary.initializeEmulation()
|
||||
NativeLibrary.initializeSystem()
|
||||
gamesViewModel.reloadGames(false)
|
||||
|
||||
return@newInstance getString(R.string.user_data_import_success)
|
||||
|
@ -1,70 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
||||
/**
|
||||
* Some controllers have incorrect mappings. This class has special-case fixes for them.
|
||||
*/
|
||||
class ControllerMappingHelper {
|
||||
/**
|
||||
* Some controllers report extra button presses that can be ignored.
|
||||
*/
|
||||
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
|
||||
return if (isDualShock4(inputDevice)) {
|
||||
// The two analog triggers generate analog motion events as well as a keycode.
|
||||
// We always prefer to use the analog values, so throw away the button press
|
||||
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale an axis to be zero-centered with a proper range.
|
||||
*/
|
||||
fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
|
||||
if (isDualShock4(inputDevice)) {
|
||||
// Android doesn't have correct mappings for this controller's triggers. It reports them
|
||||
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
|
||||
// Scale them to properly zero-centered with a range of [0.0, 1.0].
|
||||
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
|
||||
return (value + 1) / 2.0f
|
||||
}
|
||||
} else if (isXboxOneWireless(inputDevice)) {
|
||||
// Same as the DualShock 4, the mappings are missing.
|
||||
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
|
||||
return (value + 1) / 2.0f
|
||||
}
|
||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
||||
// This axis is stuck at ~.5. Ignore it.
|
||||
return 0.0f
|
||||
}
|
||||
} else if (isMogaPro2Hid(inputDevice)) {
|
||||
// This controller has a broken axis that reports a constant value. Ignore it.
|
||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
||||
return 0.0f
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Sony DualShock 4 controller
|
||||
private fun isDualShock4(inputDevice: InputDevice): Boolean {
|
||||
return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
|
||||
}
|
||||
|
||||
// Microsoft Xbox One controller
|
||||
private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
|
||||
return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
|
||||
}
|
||||
|
||||
// Moga Pro 2 HID
|
||||
private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
|
||||
return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ object DirectoryInitialization {
|
||||
fun start() {
|
||||
if (!areDirectoriesReady) {
|
||||
initializeInternalStorage()
|
||||
NativeLibrary.initializeEmulation()
|
||||
NativeLibrary.initializeSystem()
|
||||
areDirectoriesReady = true
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,23 @@ class DocumentsTree {
|
||||
return node != null && node.isDirectory
|
||||
}
|
||||
|
||||
fun getParentDirectory(filepath: String): String {
|
||||
val node = resolvePath(filepath)!!
|
||||
val parentNode = node.parent
|
||||
if (parentNode != null && parentNode.isDirectory) {
|
||||
return parentNode.uri!!.toString()
|
||||
}
|
||||
return node.uri!!.toString()
|
||||
}
|
||||
|
||||
fun getFilename(filepath: String): String {
|
||||
val node = resolvePath(filepath)
|
||||
if (node != null) {
|
||||
return node.name!!
|
||||
}
|
||||
return filepath
|
||||
}
|
||||
|
||||
private fun resolvePath(filepath: String): DocumentsNode? {
|
||||
val tokens = StringTokenizer(filepath, File.separator, false)
|
||||
var iterator = root
|
||||
|
@ -144,7 +144,7 @@ object FileUtil {
|
||||
* @param path Native content uri path
|
||||
* @return bool
|
||||
*/
|
||||
fun exists(path: String?): Boolean {
|
||||
fun exists(path: String?, suppressLog: Boolean = false): Boolean {
|
||||
var c: Cursor? = null
|
||||
try {
|
||||
val mUri = Uri.parse(path)
|
||||
@ -152,7 +152,9 @@ object FileUtil {
|
||||
c = context.contentResolver.query(mUri, columns, null, null, null)
|
||||
return c!!.count > 0
|
||||
} catch (e: Exception) {
|
||||
Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
|
||||
if (!suppressLog) {
|
||||
Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(c)
|
||||
}
|
||||
|
@ -71,27 +71,26 @@ object GameHelper {
|
||||
|
||||
fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
|
||||
val filePath = uri.toString()
|
||||
var name = NativeLibrary.getTitle(filePath)
|
||||
var name = GameMetadata.getTitle(filePath)
|
||||
|
||||
// If the game's title field is empty, use the filename.
|
||||
if (name.isEmpty()) {
|
||||
name = FileUtil.getFilename(uri)
|
||||
}
|
||||
var gameId = NativeLibrary.getGameId(filePath)
|
||||
var programId = GameMetadata.getProgramId(filePath)
|
||||
|
||||
// If the game's ID field is empty, use the filename without extension.
|
||||
if (gameId.isEmpty()) {
|
||||
gameId = name.substring(0, name.lastIndexOf("."))
|
||||
if (programId.isEmpty()) {
|
||||
programId = name.substring(0, name.lastIndexOf("."))
|
||||
}
|
||||
|
||||
val newGame = Game(
|
||||
name,
|
||||
NativeLibrary.getDescription(filePath).replace("\n", " "),
|
||||
NativeLibrary.getRegions(filePath),
|
||||
filePath,
|
||||
gameId,
|
||||
NativeLibrary.getCompany(filePath),
|
||||
NativeLibrary.isHomebrew(filePath)
|
||||
programId,
|
||||
GameMetadata.getDeveloper(filePath),
|
||||
GameMetadata.getVersion(filePath),
|
||||
GameMetadata.getIsHomebrew(filePath)
|
||||
)
|
||||
|
||||
if (addedToLibrary) {
|
||||
|
@ -18,7 +18,6 @@ import coil.key.Keyer
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.Options
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
@ -36,7 +35,7 @@ class GameIconFetcher(
|
||||
}
|
||||
|
||||
private fun decodeGameIcon(uri: String): Bitmap? {
|
||||
val data = NativeLibrary.getIcon(uri)
|
||||
val data = GameMetadata.getIcon(uri)
|
||||
return BitmapFactory.decodeByteArray(
|
||||
data,
|
||||
0,
|
||||
|
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
object GameMetadata {
|
||||
external fun getTitle(path: String): String
|
||||
|
||||
external fun getProgramId(path: String): String
|
||||
|
||||
external fun getDeveloper(path: String): String
|
||||
|
||||
external fun getVersion(path: String): String
|
||||
|
||||
external fun getIcon(path: String): ByteArray
|
||||
|
||||
external fun getIsHomebrew(path: String): Boolean
|
||||
|
||||
external fun resetMetadata()
|
||||
}
|
@ -3,17 +3,24 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
class InputHandler {
|
||||
object InputHandler {
|
||||
private var controllerIds = getGameControllerIds()
|
||||
|
||||
fun initialize() {
|
||||
// Connect first controller
|
||||
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
|
||||
}
|
||||
|
||||
fun updateControllerIds() {
|
||||
controllerIds = getGameControllerIds()
|
||||
}
|
||||
|
||||
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
val button: Int = when (event.device.vendorId) {
|
||||
0x045E -> getInputXboxButtonKey(event.keyCode)
|
||||
@ -35,7 +42,7 @@ class InputHandler {
|
||||
}
|
||||
|
||||
return NativeLibrary.onGamePadButtonEvent(
|
||||
getPlayerNumber(event.device.controllerNumber),
|
||||
getPlayerNumber(event.device.controllerNumber, event.deviceId),
|
||||
button,
|
||||
action
|
||||
)
|
||||
@ -58,9 +65,14 @@ class InputHandler {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getPlayerNumber(index: Int): Int {
|
||||
private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
|
||||
var deviceIndex = index
|
||||
if (deviceId != -1) {
|
||||
deviceIndex = controllerIds[deviceId]!!
|
||||
}
|
||||
|
||||
// TODO: Joycons are handled as different controllers. Find a way to merge them.
|
||||
return when (index) {
|
||||
return when (deviceIndex) {
|
||||
2 -> NativeLibrary.Player2Device
|
||||
3 -> NativeLibrary.Player3Device
|
||||
4 -> NativeLibrary.Player4Device
|
||||
@ -238,7 +250,7 @@ class InputHandler {
|
||||
}
|
||||
|
||||
private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||
|
||||
when (axis) {
|
||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||
@ -297,7 +309,7 @@ class InputHandler {
|
||||
|
||||
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
||||
// Joycon support is half dead. Right joystick doesn't work
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||
|
||||
when (axis) {
|
||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||
@ -325,7 +337,7 @@ class InputHandler {
|
||||
}
|
||||
|
||||
private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||
|
||||
when (axis) {
|
||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||
@ -362,4 +374,33 @@ class InputHandler {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getGameControllerIds(): Map<Int, Int> {
|
||||
val gameControllerDeviceIds = mutableMapOf<Int, Int>()
|
||||
val deviceIds = InputDevice.getDeviceIds()
|
||||
var controllerSlot = 1
|
||||
deviceIds.forEach { deviceId ->
|
||||
InputDevice.getDevice(deviceId)?.apply {
|
||||
// Don't over-assign controllers
|
||||
if (controllerSlot >= 8) {
|
||||
return gameControllerDeviceIds
|
||||
}
|
||||
|
||||
// Verify that the device has gamepad buttons, control sticks, or both.
|
||||
if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
||||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||
) {
|
||||
// This device is a game controller. Store its device ID.
|
||||
if (deviceId and id and vendorId and productId != 0) {
|
||||
// Additionally filter out devices that have no ID
|
||||
gameControllerDeviceIds
|
||||
.takeIf { !it.contains(deviceId) }
|
||||
?.put(deviceId, controllerSlot)
|
||||
controllerSlot++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return gameControllerDeviceIds
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,10 @@ add_library(yuzu-android SHARED
|
||||
id_cache.cpp
|
||||
id_cache.h
|
||||
native.cpp
|
||||
native.h
|
||||
native_config.cpp
|
||||
uisettings.cpp
|
||||
game_metadata.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
112
src/android/app/src/main/jni/game_metadata.cpp
Normal file
112
src/android/app/src/main/jni/game_metadata.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <core/core.h>
|
||||
#include <core/file_sys/patch_manager.h>
|
||||
#include <core/loader/nro.h>
|
||||
#include <jni.h>
|
||||
#include "core/loader/loader.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "native.h"
|
||||
|
||||
struct RomMetadata {
|
||||
std::string title;
|
||||
u64 programId;
|
||||
std::string developer;
|
||||
std::string version;
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file =
|
||||
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
EmulationSession::GetInstance().System().GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
FileSys::NACP nacp;
|
||||
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
entry.developer = nacp.GetDeveloperName();
|
||||
} else {
|
||||
entry.developer = "";
|
||||
}
|
||||
|
||||
entry.version = "1.0.0";
|
||||
}
|
||||
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path) {
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version);
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon;
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
|
||||
return m_rom_metadata_cache.clear();
|
||||
}
|
||||
|
||||
} // extern "C"
|
@ -33,7 +33,6 @@
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
@ -48,514 +47,419 @@
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "jni/applets/software_keyboard.h"
|
||||
#include "jni/config.h"
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
#include "jni/id_cache.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "jni/native.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#define jconst [[maybe_unused]] const auto
|
||||
#define jauto [[maybe_unused]] auto
|
||||
|
||||
namespace {
|
||||
static EmulationSession s_instance;
|
||||
|
||||
class EmulationSession final {
|
||||
public:
|
||||
EmulationSession() {
|
||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
}
|
||||
EmulationSession::EmulationSession() {
|
||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
}
|
||||
|
||||
~EmulationSession() = default;
|
||||
EmulationSession& EmulationSession::GetInstance() {
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
static EmulationSession& GetInstance() {
|
||||
return s_instance;
|
||||
}
|
||||
const Core::System& EmulationSession::System() const {
|
||||
return m_system;
|
||||
}
|
||||
|
||||
const Core::System& System() const {
|
||||
return m_system;
|
||||
}
|
||||
Core::System& EmulationSession::System() {
|
||||
return m_system;
|
||||
}
|
||||
|
||||
Core::System& System() {
|
||||
return m_system;
|
||||
}
|
||||
const EmuWindow_Android& EmulationSession::Window() const {
|
||||
return *m_window;
|
||||
}
|
||||
|
||||
const EmuWindow_Android& Window() const {
|
||||
return *m_window;
|
||||
}
|
||||
EmuWindow_Android& EmulationSession::Window() {
|
||||
return *m_window;
|
||||
}
|
||||
|
||||
EmuWindow_Android& Window() {
|
||||
return *m_window;
|
||||
}
|
||||
ANativeWindow* EmulationSession::NativeWindow() const {
|
||||
return m_native_window;
|
||||
}
|
||||
|
||||
ANativeWindow* NativeWindow() const {
|
||||
return m_native_window;
|
||||
}
|
||||
void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
void SetNativeWindow(ANativeWindow* native_window) {
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
int InstallFileToNand(std::string filename, std::string file_extension) {
|
||||
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
|
||||
std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!dest->Resize(src->GetSize())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
[[maybe_unused]] std::vector<u8> buffer(1_MiB);
|
||||
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||
jconst read = src->Read(buffer.data(), buffer.size(), i);
|
||||
dest->Write(buffer.data(), read, i);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
enum InstallResult {
|
||||
Success = 0,
|
||||
SuccessFileOverwritten = 1,
|
||||
InstallError = 2,
|
||||
ErrorBaseGame = 3,
|
||||
ErrorFilenameExtension = 4,
|
||||
};
|
||||
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
|
||||
if (file_extension == "nsp") {
|
||||
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
if (nsp->IsExtractedType()) {
|
||||
return InstallError;
|
||||
}
|
||||
} else {
|
||||
return ErrorFilenameExtension;
|
||||
int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
|
||||
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
|
||||
std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nsp) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
*nsp, true, copy_func);
|
||||
|
||||
switch (res) {
|
||||
case FileSys::InstallResult::Success:
|
||||
return Success;
|
||||
case FileSys::InstallResult::OverwriteExisting:
|
||||
return SuccessFileOverwritten;
|
||||
case FileSys::InstallResult::ErrorBaseInstall:
|
||||
return ErrorBaseGame;
|
||||
default:
|
||||
return InstallError;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||
const std::string& custom_driver_name,
|
||||
const std::string& file_redirect_dir) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
void* handle{};
|
||||
const char* file_redirect_dir_{};
|
||||
int featureFlags{};
|
||||
|
||||
// Enable driver file redirection when renderer debugging is enabled.
|
||||
if (Settings::values.renderer_debug && file_redirect_dir.size()) {
|
||||
featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
|
||||
file_redirect_dir_ = file_redirect_dir.c_str();
|
||||
}
|
||||
|
||||
// Try to load a custom driver.
|
||||
if (custom_driver_name.size()) {
|
||||
handle = adrenotools_open_libvulkan(
|
||||
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
|
||||
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
|
||||
}
|
||||
|
||||
// Try to load the system driver.
|
||||
if (!handle) {
|
||||
handle =
|
||||
adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
|
||||
nullptr, nullptr, file_redirect_dir_, nullptr);
|
||||
}
|
||||
|
||||
m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsRunning() const {
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
bool IsPaused() const {
|
||||
return m_is_running && m_is_paused;
|
||||
}
|
||||
|
||||
const Core::PerfStatsResults& PerfStats() const {
|
||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||
return m_perf_stats;
|
||||
}
|
||||
|
||||
void SurfaceChanged() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
m_window->OnSurfaceChanged(m_native_window);
|
||||
}
|
||||
|
||||
void ConfigureFilesystemProvider(const std::string& filepath) {
|
||||
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
if (!loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
m_manual_provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Create the render window.
|
||||
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
|
||||
m_vulkan_library);
|
||||
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
|
||||
// Initialize system.
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
m_software_keyboard = android_keyboard.get();
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetAppletFrontendSet({
|
||||
nullptr, // Amiibo Settings
|
||||
nullptr, // Controller Selector
|
||||
nullptr, // Error Display
|
||||
nullptr, // Mii Editor
|
||||
nullptr, // Parental Controls
|
||||
nullptr, // Photo Viewer
|
||||
nullptr, // Profile Selector
|
||||
std::move(android_keyboard), // Software Keyboard
|
||||
nullptr, // Web Browser
|
||||
});
|
||||
|
||||
// Initialize filesystem.
|
||||
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
m_manual_provider.get());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
|
||||
// Initialize account manager
|
||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
||||
|
||||
// Load the ROM.
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
return m_load_result;
|
||||
}
|
||||
|
||||
// Complete initialization.
|
||||
m_system.GPU().Start();
|
||||
m_system.GetCpuManager().OnGpuReady();
|
||||
m_system.RegisterExitCallback([&] { HaltEmulation(); });
|
||||
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
void ShutdownEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
m_is_running = false;
|
||||
|
||||
// Unload user input.
|
||||
m_system.HIDCore().UnloadInputDevices();
|
||||
|
||||
// Shutdown the main emulated process
|
||||
if (m_load_result == Core::SystemResultStatus::Success) {
|
||||
m_system.DetachDebugger();
|
||||
m_system.ShutdownMainProcess();
|
||||
m_detached_tasks.WaitForAllTasks();
|
||||
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
|
||||
m_window.reset();
|
||||
OnEmulationStopped(Core::SystemResultStatus::Success);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tear down the render window.
|
||||
m_window.reset();
|
||||
}
|
||||
|
||||
void PauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Pause();
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void UnPauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Run();
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void HaltEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = false;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
void RunEmulation() {
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = true;
|
||||
}
|
||||
|
||||
// Load the disk shader cache.
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
m_system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||
LoadDiskCacheProgress);
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
}
|
||||
|
||||
void(m_system.Run());
|
||||
|
||||
if (m_system.DebuggerEnabled()) {
|
||||
m_system.InitializeDebugger();
|
||||
}
|
||||
|
||||
OnEmulationStarted();
|
||||
|
||||
while (true) {
|
||||
{
|
||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||
if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Refresh performance stats.
|
||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||
m_perf_stats = m_system.GetAndResetPerfStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetRomTitle(const std::string& path) {
|
||||
return GetRomMetadata(path).title;
|
||||
}
|
||||
|
||||
std::vector<u8> GetRomIcon(const std::string& path) {
|
||||
return GetRomMetadata(path).icon;
|
||||
}
|
||||
|
||||
bool GetIsHomebrew(const std::string& path) {
|
||||
return GetRomMetadata(path).isHomebrew;
|
||||
}
|
||||
|
||||
void ResetRomMetadata() {
|
||||
m_rom_metadata_cache.clear();
|
||||
}
|
||||
|
||||
bool IsHandheldOnly() {
|
||||
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
if (!dest->Resize(src->GetSize())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (npad_style_set.handheld == 0) {
|
||||
return false;
|
||||
using namespace Common::Literals;
|
||||
[[maybe_unused]] std::vector<u8> buffer(1_MiB);
|
||||
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||
jconst read = src->Read(buffer.data(), buffer.size(), i);
|
||||
dest->Write(buffer.data(), read, i);
|
||||
}
|
||||
|
||||
return !Settings::IsDockedMode();
|
||||
}
|
||||
|
||||
void SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
jauto handheld =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
|
||||
handheld->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||
jauto player1 =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
player1->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!controller->IsConnected()) {
|
||||
controller->Connect();
|
||||
}
|
||||
}
|
||||
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
|
||||
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
|
||||
return m_software_keyboard;
|
||||
}
|
||||
|
||||
private:
|
||||
struct RomMetadata {
|
||||
std::string title;
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
return true;
|
||||
};
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path) {
|
||||
if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
enum InstallResult {
|
||||
Success = 0,
|
||||
SuccessFileOverwritten = 1,
|
||||
InstallError = 2,
|
||||
ErrorBaseGame = 3,
|
||||
ErrorFilenameExtension = 4,
|
||||
};
|
||||
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
|
||||
if (file_extension == "nsp") {
|
||||
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
if (nsp->IsExtractedType()) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
return CacheRomMetadata(path);
|
||||
} else {
|
||||
return ErrorFilenameExtension;
|
||||
}
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
jconst file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
if (!nsp) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadIcon(entry.icon);
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
|
||||
return InstallError;
|
||||
}
|
||||
|
||||
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
|
||||
copy_func);
|
||||
|
||||
switch (res) {
|
||||
case FileSys::InstallResult::Success:
|
||||
return Success;
|
||||
case FileSys::InstallResult::OverwriteExisting:
|
||||
return SuccessFileOverwritten;
|
||||
case FileSys::InstallResult::ErrorBaseInstall:
|
||||
return ErrorBaseGame;
|
||||
default:
|
||||
return InstallError;
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
|
||||
const std::string& custom_driver_dir,
|
||||
const std::string& custom_driver_name,
|
||||
const std::string& file_redirect_dir) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
void* handle{};
|
||||
const char* file_redirect_dir_{};
|
||||
int featureFlags{};
|
||||
|
||||
// Enable driver file redirection when renderer debugging is enabled.
|
||||
if (Settings::values.renderer_debug && file_redirect_dir.size()) {
|
||||
featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
|
||||
file_redirect_dir_ = file_redirect_dir.c_str();
|
||||
}
|
||||
|
||||
// Try to load a custom driver.
|
||||
if (custom_driver_name.size()) {
|
||||
handle = adrenotools_open_libvulkan(
|
||||
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
|
||||
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
|
||||
}
|
||||
|
||||
// Try to load the system driver.
|
||||
if (!handle) {
|
||||
handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
|
||||
nullptr, nullptr, file_redirect_dir_, nullptr);
|
||||
}
|
||||
|
||||
m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool EmulationSession::IsRunning() const {
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
bool EmulationSession::IsPaused() const {
|
||||
return m_is_running && m_is_paused;
|
||||
}
|
||||
|
||||
const Core::PerfStatsResults& EmulationSession::PerfStats() const {
|
||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||
return m_perf_stats;
|
||||
}
|
||||
|
||||
void EmulationSession::SurfaceChanged() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
m_window->OnSurfaceChanged(m_native_window);
|
||||
}
|
||||
|
||||
void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
|
||||
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
if (!loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
m_manual_provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
void EmulationSession::InitializeSystem() {
|
||||
// Initialize filesystem.
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
m_manual_provider.get());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
}
|
||||
|
||||
return entry;
|
||||
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Create the render window.
|
||||
m_window =
|
||||
std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
|
||||
|
||||
// Initialize system.
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
m_software_keyboard = android_keyboard.get();
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetAppletFrontendSet({
|
||||
nullptr, // Amiibo Settings
|
||||
nullptr, // Controller Selector
|
||||
nullptr, // Error Display
|
||||
nullptr, // Mii Editor
|
||||
nullptr, // Parental Controls
|
||||
nullptr, // Photo Viewer
|
||||
nullptr, // Profile Selector
|
||||
std::move(android_keyboard), // Software Keyboard
|
||||
nullptr, // Web Browser
|
||||
});
|
||||
|
||||
// Initialize filesystem.
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
|
||||
// Initialize account manager
|
||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
||||
|
||||
// Load the ROM.
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
return m_load_result;
|
||||
}
|
||||
|
||||
private:
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
|
||||
IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
|
||||
static_cast<jint>(progress), static_cast<jint>(max));
|
||||
// Complete initialization.
|
||||
m_system.GPU().Start();
|
||||
m_system.GetCpuManager().OnGpuReady();
|
||||
m_system.RegisterExitCallback([&] { HaltEmulation(); });
|
||||
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
void EmulationSession::ShutdownEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
m_is_running = false;
|
||||
|
||||
// Unload user input.
|
||||
m_system.HIDCore().UnloadInputDevices();
|
||||
|
||||
// Shutdown the main emulated process
|
||||
if (m_load_result == Core::SystemResultStatus::Success) {
|
||||
m_system.DetachDebugger();
|
||||
m_system.ShutdownMainProcess();
|
||||
m_detached_tasks.WaitForAllTasks();
|
||||
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
|
||||
m_window.reset();
|
||||
OnEmulationStopped(Core::SystemResultStatus::Success);
|
||||
return;
|
||||
}
|
||||
|
||||
static void OnEmulationStarted() {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetOnEmulationStarted());
|
||||
// Tear down the render window.
|
||||
m_window.reset();
|
||||
}
|
||||
|
||||
void EmulationSession::PauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Pause();
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void EmulationSession::UnPauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Run();
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void EmulationSession::HaltEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = false;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
void EmulationSession::RunEmulation() {
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = true;
|
||||
}
|
||||
|
||||
static void OnEmulationStopped(Core::SystemResultStatus result) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
|
||||
// Load the disk shader cache.
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
static EmulationSession s_instance;
|
||||
void(m_system.Run());
|
||||
|
||||
// Frontend management
|
||||
std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
if (m_system.DebuggerEnabled()) {
|
||||
m_system.InitializeDebugger();
|
||||
}
|
||||
|
||||
// Window management
|
||||
std::unique_ptr<EmuWindow_Android> m_window;
|
||||
ANativeWindow* m_native_window{};
|
||||
OnEmulationStarted();
|
||||
|
||||
// Core emulation
|
||||
Core::System m_system;
|
||||
InputCommon::InputSubsystem m_input_subsystem;
|
||||
Common::DetachedTasks m_detached_tasks;
|
||||
Core::PerfStatsResults m_perf_stats{};
|
||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||
std::atomic<bool> m_is_running = false;
|
||||
std::atomic<bool> m_is_paused = false;
|
||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||
while (true) {
|
||||
{
|
||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||
if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Refresh performance stats.
|
||||
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
|
||||
m_perf_stats = m_system.GetAndResetPerfStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU driver parameters
|
||||
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
|
||||
bool EmulationSession::IsHandheldOnly() {
|
||||
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
|
||||
// Synchronization
|
||||
std::condition_variable_any m_cv;
|
||||
mutable std::mutex m_perf_stats_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
};
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static*/ EmulationSession EmulationSession::s_instance;
|
||||
if (npad_style_set.handheld == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
return !Settings::IsDockedMode();
|
||||
}
|
||||
|
||||
void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
|
||||
handheld->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||
jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
player1->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!controller->IsConnected()) {
|
||||
controller->Connect();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
|
||||
SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
|
||||
return m_software_keyboard;
|
||||
}
|
||||
|
||||
void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
|
||||
int max) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
|
||||
IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
|
||||
static_cast<jint>(progress), static_cast<jint>(max));
|
||||
}
|
||||
|
||||
void EmulationSession::OnEmulationStarted() {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted());
|
||||
}
|
||||
|
||||
void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(),
|
||||
static_cast<jint>(result));
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
Common::Log::Initialize();
|
||||
@ -657,10 +561,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().ResetRomMetadata();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
||||
}
|
||||
@ -766,51 +666,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
|
||||
}
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
return env->NewStringUTF(title.c_str());
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz) {
|
||||
// Create the default config.ini.
|
||||
Config{};
|
||||
// Initialize the emulated system.
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
EmulationSession::GetInstance().InitializeSystem();
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
|
||||
@ -898,4 +759,49 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
||||
}
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
|
||||
jlong jid) {
|
||||
auto bis_system =
|
||||
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
||||
if (!bis_system) {
|
||||
return ToJString(env, "");
|
||||
}
|
||||
|
||||
auto applet_nca =
|
||||
bis_system->GetEntry(static_cast<u64>(jid), FileSys::ContentRecordType::Program);
|
||||
if (!applet_nca) {
|
||||
return ToJString(env, "");
|
||||
}
|
||||
|
||||
return ToJString(env, applet_nca->GetFullPath());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jclass clazz,
|
||||
jint jappletId) {
|
||||
EmulationSession::GetInstance().System().GetAppletManager().SetCurrentAppletId(
|
||||
static_cast<Service::AM::Applets::AppletId>(jappletId));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz,
|
||||
jint jcabinetMode) {
|
||||
EmulationSession::GetInstance().System().GetAppletManager().SetCabinetMode(
|
||||
static_cast<Service::NFP::CabinetMode>(jcabinetMode));
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) {
|
||||
auto bis_system =
|
||||
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
|
||||
if (!bis_system) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query an applet to see if it's available
|
||||
auto applet_nca =
|
||||
bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program);
|
||||
if (!applet_nca) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
85
src/android/app/src/main/jni/native.h
Normal file
85
src/android/app/src/main/jni/native.h
Normal file
@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include "common/detached_tasks.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "jni/applets/software_keyboard.h"
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
class EmulationSession final {
|
||||
public:
|
||||
explicit EmulationSession();
|
||||
~EmulationSession() = default;
|
||||
|
||||
static EmulationSession& GetInstance();
|
||||
const Core::System& System() const;
|
||||
Core::System& System();
|
||||
|
||||
const EmuWindow_Android& Window() const;
|
||||
EmuWindow_Android& Window();
|
||||
ANativeWindow* NativeWindow() const;
|
||||
void SetNativeWindow(ANativeWindow* native_window);
|
||||
void SurfaceChanged();
|
||||
|
||||
int InstallFileToNand(std::string filename, std::string file_extension);
|
||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||
const std::string& custom_driver_name,
|
||||
const std::string& file_redirect_dir);
|
||||
|
||||
bool IsRunning() const;
|
||||
bool IsPaused() const;
|
||||
void PauseEmulation();
|
||||
void UnPauseEmulation();
|
||||
void HaltEmulation();
|
||||
void RunEmulation();
|
||||
void ShutdownEmulation();
|
||||
|
||||
const Core::PerfStatsResults& PerfStats() const;
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void InitializeSystem();
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
|
||||
|
||||
bool IsHandheldOnly();
|
||||
void SetDeviceType([[maybe_unused]] int index, int type);
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index);
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index);
|
||||
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
|
||||
|
||||
private:
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||
static void OnEmulationStarted();
|
||||
static void OnEmulationStopped(Core::SystemResultStatus result);
|
||||
|
||||
private:
|
||||
// Window management
|
||||
std::unique_ptr<EmuWindow_Android> m_window;
|
||||
ANativeWindow* m_native_window{};
|
||||
|
||||
// Core emulation
|
||||
Core::System m_system;
|
||||
InputCommon::InputSubsystem m_input_subsystem;
|
||||
Common::DetachedTasks m_detached_tasks;
|
||||
Core::PerfStatsResults m_perf_stats{};
|
||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||
std::atomic<bool> m_is_running = false;
|
||||
std::atomic<bool> m_is_paused = false;
|
||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||
|
||||
// GPU driver parameters
|
||||
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
|
||||
|
||||
// Synchronization
|
||||
std::condition_variable_any m_cv;
|
||||
mutable std::mutex m_perf_stats_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
};
|
9
src/android/app/src/main/res/drawable/ic_album.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_album.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
|
||||
</vector>
|
9
src/android/app/src/main/res/drawable/ic_applet.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_applet.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M17,16l-4,-4V8.82C14.16,8.4 15,7.3 15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6c0,1.3 0.84,2.4 2,2.82V12l-4,4H3v5h5v-3.05l4,-4.2 4,4.2V21h5v-5h-4z" />
|
||||
</vector>
|
9
src/android/app/src/main/res/drawable/ic_edit.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_edit.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
|
||||
</vector>
|
18
src/android/app/src/main/res/drawable/ic_mii.xml
Normal file
18
src/android/app/src/main/res/drawable/ic_mii.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M9,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M20.77,8.58l-0.92,2.01c0.09,0.46 0.15,0.93 0.15,1.41 0,4.41 -3.59,8 -8,8s-8,-3.59 -8,-8c0,-0.05 0.01,-0.1 0,-0.14 2.6,-0.98 4.69,-2.99 5.74,-5.55C11.58,8.56 14.37,10 17.5,10c0.45,0 0.89,-0.04 1.33,-0.1l-0.6,-1.32 -0.88,-1.93 -1.93,-0.88 -2.79,-1.27 2.79,-1.27 0.71,-0.32C14.87,2.33 13.47,2 12,2 6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-1.47 -0.33,-2.87 -0.9,-4.13l-0.33,0.71z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M15,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M20.6,5.6L19.5,8l-1.1,-2.4L16,4.5l2.4,-1.1L19.5,1l1.1,2.4L23,4.5z" />
|
||||
</vector>
|
9
src/android/app/src/main/res/drawable/ic_refresh.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_refresh.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||
</vector>
|
9
src/android/app/src/main/res/drawable/ic_restore.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_restore.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
|
||||
</vector>
|
57
src/android/app/src/main/res/layout/card_applet_option.xml
Normal file
57
src/android/app/src/main/res/layout/card_applet_option.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?attr/materialCardViewOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:padding="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/applets" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/description"
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/applets_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -1,63 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:id="@+id/card_game"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:transitionName="card_game"
|
||||
android:layout_gravity="center"
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="12dp">
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
android:id="@+id/card_game_art"
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/image_game_screen"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="150dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_game_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:src="@drawable/default_icon" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.ExtraSmall"
|
||||
tools:src="@drawable/default_icon" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:id="@+id/text_game_title"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="none"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textSize="14sp"
|
||||
android:singleLine="true"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:ellipsize="none"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="@+id/card_game_art"
|
||||
app:layout_constraintStart_toStartOf="@+id/card_game_art"
|
||||
app:layout_constraintTop_toBottomOf="@+id/card_game_art"
|
||||
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
|
||||
app:layout_constraintStart_toStartOf="@+id/image_game_screen"
|
||||
app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
|
||||
tools:text="The Legend of Zelda: Skyward Sword" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
15
src/android/app/src/main/res/layout/dialog_list.xml
Normal file
15
src/android/app/src/main/res/layout/dialog_list.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/dialog_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="false"
|
||||
android:paddingVertical="12dp"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
30
src/android/app/src/main/res/layout/dialog_list_item.xml
Normal file
30
src/android/app/src/main/res/layout/dialog_list_item.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
tools:src="@drawable/ic_nfc" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="List option" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_applets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_applets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_applets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/applets" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_applets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -25,6 +25,9 @@
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
|
||||
app:destination="@id/driverManagerFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
|
||||
app:destination="@id/appletLauncherFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
@ -102,5 +105,17 @@
|
||||
android:id="@+id/driverManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
|
||||
android:label="DriverManagerFragment" />
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
|
||||
android:label="AppletLauncherFragment" >
|
||||
<action
|
||||
android:id="@+id/action_appletLauncherFragment_to_cabinetLauncherDialogFragment"
|
||||
app:destination="@id/cabinetLauncherDialogFragment" />
|
||||
</fragment>
|
||||
<dialog
|
||||
android:id="@+id/cabinetLauncherDialogFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
|
||||
android:label="CabinetLauncherDialogFragment" />
|
||||
|
||||
</navigation>
|
||||
|
@ -124,6 +124,24 @@
|
||||
<string name="share_save_file">Share save file</string>
|
||||
<string name="export_save_failed">Failed to export save</string>
|
||||
|
||||
<!-- Applet launcher strings -->
|
||||
<string name="applets">Applet launcher</string>
|
||||
<string name="applets_description">Launch system applets using installed firmware</string>
|
||||
<string name="applets_error_firmware">Firmware not installed</string>
|
||||
<string name="applets_error_applet">Applet not available</string>
|
||||
<string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and <a href="https://yuzu-emu.org/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.]]></string>
|
||||
<string name="album_applet">Album</string>
|
||||
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string>
|
||||
<string name="mii_edit_applet">Mii edit</string>
|
||||
<string name="mii_edit_applet_description">View and edit Miis with the system editor</string>
|
||||
<string name="cabinet_applet">Cabinet</string>
|
||||
<string name="cabinet_applet_description">Edit and delete data stored on amiibo</string>
|
||||
<string name="cabinet_launcher">Cabinet launcher</string>
|
||||
<string name="cabinet_nickname_and_owner">Nickname and owner settings</string>
|
||||
<string name="cabinet_game_data_eraser">Game data eraser</string>
|
||||
<string name="cabinet_restorer">Restorer</string>
|
||||
<string name="cabinet_formatter">Formatter</string>
|
||||
|
||||
<!-- About screen strings -->
|
||||
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
|
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/fs/fs_android.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Common::FS::Android {
|
||||
|
||||
@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) {
|
||||
env->GetJavaVM(&g_jvm);
|
||||
native_library = clazz;
|
||||
|
||||
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(JMethodID, JMethodName, Signature)
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(JMethodID, JMethodName, Signature)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
|
||||
F(JMethodID, JMethodName, Signature)
|
||||
#define F(JMethodID, JMethodName, Signature) \
|
||||
JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
|
||||
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
#undef FH
|
||||
}
|
||||
|
||||
void UnRegisterCallbacks() {
|
||||
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
|
||||
#define F(JMethodID) JMethodID = nullptr;
|
||||
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
#undef FH
|
||||
}
|
||||
|
||||
bool IsContentUri(const std::string& path) {
|
||||
@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
#undef F
|
||||
#undef FR
|
||||
|
||||
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(FunctionName, JMethodID, Caller)
|
||||
#define F(FunctionName, JMethodID, Caller) \
|
||||
std::string FunctionName(const std::string& filepath) { \
|
||||
if (JMethodID == nullptr) { \
|
||||
return 0; \
|
||||
} \
|
||||
auto env = GetEnvForThread(); \
|
||||
jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
|
||||
jstring j_return = \
|
||||
static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \
|
||||
if (!j_return) { \
|
||||
return {}; \
|
||||
} \
|
||||
const jchar* jchars = env->GetStringChars(j_return, nullptr); \
|
||||
const jsize length = env->GetStringLength(j_return); \
|
||||
const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \
|
||||
const std::string converted_string = Common::UTF16ToUTF8(string_view); \
|
||||
env->ReleaseStringChars(j_return, jchars); \
|
||||
return converted_string; \
|
||||
}
|
||||
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
|
||||
#undef F
|
||||
#undef FH
|
||||
|
||||
} // namespace Common::FS::Android
|
||||
|
@ -17,19 +17,28 @@
|
||||
"(Ljava/lang/String;)Z") \
|
||||
V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
|
||||
|
||||
#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \
|
||||
V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \
|
||||
"(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \
|
||||
"(Ljava/lang/String;)Ljava/lang/String;")
|
||||
|
||||
namespace Common::FS::Android {
|
||||
|
||||
static JavaVM* g_jvm = nullptr;
|
||||
static jclass native_library = nullptr;
|
||||
|
||||
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
|
||||
#define F(JMethodID) static jmethodID JMethodID = nullptr;
|
||||
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
#undef FH
|
||||
|
||||
enum class OpenMode {
|
||||
Read,
|
||||
@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
#undef F
|
||||
#undef FR
|
||||
|
||||
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName)
|
||||
#define F(FunctionName) std::string FunctionName(const std::string& filepath);
|
||||
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
|
||||
#undef F
|
||||
#undef FH
|
||||
|
||||
} // namespace Common::FS::Android
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define AMIIBO_DIR "amiibo"
|
||||
#define CACHE_DIR "cache"
|
||||
#define CONFIG_DIR "config"
|
||||
#define CRASH_DUMPS_DIR "crash_dumps"
|
||||
#define DUMP_DIR "dump"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
|
@ -119,6 +119,7 @@ public:
|
||||
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
|
||||
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
|
||||
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
|
||||
GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR);
|
||||
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
|
||||
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
|
||||
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
|
||||
@ -400,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
|
||||
}
|
||||
|
||||
std::string_view GetParentPath(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
if (path[0] != '/') {
|
||||
std::string path_string{path};
|
||||
return FS::Android::GetParentDirectory(path_string);
|
||||
}
|
||||
#endif
|
||||
const auto name_bck_index = path.rfind('\\');
|
||||
const auto name_fwd_index = path.rfind('/');
|
||||
std::size_t name_index;
|
||||
|
@ -15,6 +15,7 @@ enum class YuzuPath {
|
||||
AmiiboDir, // Where Amiibo backups are stored.
|
||||
CacheDir, // Where cached filesystem data is stored.
|
||||
ConfigDir, // Where config files are stored.
|
||||
CrashDumpsDir, // Where crash dumps are stored.
|
||||
DumpDir, // Where dumped data is stored.
|
||||
KeysDir, // Where key files are stored.
|
||||
LoadDir, // Where cheat/mod files are stored.
|
||||
|
@ -505,7 +505,6 @@ struct Values {
|
||||
linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
|
||||
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
|
||||
Category::Debugging};
|
||||
Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||
|
||||
// Miscellaneous
|
||||
|
@ -14,6 +14,10 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <common/fs/fs_android.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
/// Make a string lowercase
|
||||
@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
||||
if (full_path.empty())
|
||||
return false;
|
||||
|
||||
#ifdef ANDROID
|
||||
if (full_path[0] != '/') {
|
||||
*_pPath = Common::FS::Android::GetParentDirectory(full_path);
|
||||
*_pFilename = Common::FS::Android::GetFilename(full_path);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::size_t dir_end = full_path.find_last_of("/"
|
||||
// windows needs the : included for something like just "C:" to be considered a directory
|
||||
#ifdef _WIN32
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "core/hle/service/am/applets/applet_cabinet.h"
|
||||
#include "core/hle/service/am/applets/applet_mii_edit_types.h"
|
||||
#include "core/hle/service/am/applets/applet_profile_select.h"
|
||||
#include "core/hle/service/am/applets/applet_software_keyboard_types.h"
|
||||
#include "core/hle/service/am/applets/applet_web_browser.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/am/idle.h"
|
||||
@ -31,6 +32,7 @@
|
||||
#include "core/hle/service/apm/apm_controller.h"
|
||||
#include "core/hle/service/apm/apm_interface.h"
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
#include "core/hle/service/caps/caps_su.h"
|
||||
#include "core/hle/service/caps/caps_types.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
@ -702,9 +704,17 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(HLERequestContext& c
|
||||
void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>();
|
||||
const auto report_option = rp.PopEnum<Capture::AlbumReportOption>();
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option);
|
||||
LOG_INFO(Service_AM, "called, report_option={}", report_option);
|
||||
|
||||
const auto screenshot_service =
|
||||
system.ServiceManager().GetService<Service::Capture::IScreenShotApplicationService>(
|
||||
"caps:su");
|
||||
|
||||
if (screenshot_service) {
|
||||
screenshot_service->CaptureAndSaveScreenshot(report_option);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1562,7 +1572,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
|
||||
{16, nullptr, "GetMainAppletStorageId"},
|
||||
{17, nullptr, "GetCallerAppletIdentityInfoStack"},
|
||||
{18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"},
|
||||
{19, nullptr, "GetDesirableKeyboardLayout"},
|
||||
{19, &ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout, "GetDesirableKeyboardLayout"},
|
||||
{20, nullptr, "PopExtraStorage"},
|
||||
{25, nullptr, "GetPopExtraStorageEvent"},
|
||||
{30, nullptr, "UnpopInData"},
|
||||
@ -1581,7 +1591,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
|
||||
{120, nullptr, "GetLaunchStorageInfoForDebug"},
|
||||
{130, nullptr, "GetGpuErrorDetectedSystemEvent"},
|
||||
{140, nullptr, "SetApplicationMemoryReservation"},
|
||||
{150, nullptr, "ShouldSetGpuTimeSliceManually"},
|
||||
{150, &ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually, "ShouldSetGpuTimeSliceManually"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
@ -1596,6 +1606,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
|
||||
case Applets::AppletId::PhotoViewer:
|
||||
PushInShowAlbum();
|
||||
break;
|
||||
case Applets::AppletId::SoftwareKeyboard:
|
||||
PushInShowSoftwareKeyboard();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1672,6 +1685,14 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
|
||||
rb.PushRaw(applet_info);
|
||||
}
|
||||
|
||||
void ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0);
|
||||
}
|
||||
|
||||
void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
|
||||
const Service::Account::ProfileManager manager{};
|
||||
bool is_empty{true};
|
||||
@ -1691,6 +1712,14 @@ void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext&
|
||||
rb.Push(user_count);
|
||||
}
|
||||
|
||||
void ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
|
||||
void ILibraryAppletSelfAccessor::PushInShowAlbum() {
|
||||
const Applets::CommonArguments arguments{
|
||||
.arguments_version = Applets::CommonArgumentVersion::Version3,
|
||||
@ -1759,6 +1788,61 @@ void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
|
||||
queue_data.emplace_back(std::move(argument_data));
|
||||
}
|
||||
|
||||
void ILibraryAppletSelfAccessor::PushInShowSoftwareKeyboard() {
|
||||
const Applets::CommonArguments arguments{
|
||||
.arguments_version = Applets::CommonArgumentVersion::Version3,
|
||||
.size = Applets::CommonArgumentSize::Version3,
|
||||
.library_version = static_cast<u32>(Applets::SwkbdAppletVersion::Version524301),
|
||||
.theme_color = Applets::ThemeColor::BasicBlack,
|
||||
.play_startup_sound = true,
|
||||
.system_tick = system.CoreTiming().GetClockTicks(),
|
||||
};
|
||||
|
||||
std::vector<char16_t> initial_string(0);
|
||||
|
||||
const Applets::SwkbdConfigCommon swkbd_config{
|
||||
.type = Applets::SwkbdType::Qwerty,
|
||||
.ok_text{},
|
||||
.left_optional_symbol_key{},
|
||||
.right_optional_symbol_key{},
|
||||
.use_prediction = false,
|
||||
.key_disable_flags{},
|
||||
.initial_cursor_position = Applets::SwkbdInitialCursorPosition::Start,
|
||||
.header_text{},
|
||||
.sub_text{},
|
||||
.guide_text{},
|
||||
.max_text_length = 500,
|
||||
.min_text_length = 0,
|
||||
.password_mode = Applets::SwkbdPasswordMode::Disabled,
|
||||
.text_draw_type = Applets::SwkbdTextDrawType::Box,
|
||||
.enable_return_button = true,
|
||||
.use_utf8 = false,
|
||||
.use_blur_background = true,
|
||||
.initial_string_offset{},
|
||||
.initial_string_length = static_cast<u32>(initial_string.size()),
|
||||
.user_dictionary_offset{},
|
||||
.user_dictionary_entries{},
|
||||
.use_text_check = false,
|
||||
};
|
||||
|
||||
Applets::SwkbdConfigNew swkbd_config_new{};
|
||||
|
||||
std::vector<u8> argument_data(sizeof(arguments));
|
||||
std::vector<u8> swkbd_data(sizeof(swkbd_config) + sizeof(swkbd_config_new));
|
||||
std::vector<u8> work_buffer(swkbd_config.initial_string_length * sizeof(char16_t));
|
||||
|
||||
std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
|
||||
std::memcpy(swkbd_data.data(), &swkbd_config, sizeof(swkbd_config));
|
||||
std::memcpy(swkbd_data.data() + sizeof(swkbd_config), &swkbd_config_new,
|
||||
sizeof(Applets::SwkbdConfigNew));
|
||||
std::memcpy(work_buffer.data(), initial_string.data(),
|
||||
swkbd_config.initial_string_length * sizeof(char16_t));
|
||||
|
||||
queue_data.emplace_back(std::move(argument_data));
|
||||
queue_data.emplace_back(std::move(swkbd_data));
|
||||
queue_data.emplace_back(std::move(work_buffer));
|
||||
}
|
||||
|
||||
IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
|
||||
: ServiceFramework{system_, "IAppletCommonFunctions"} {
|
||||
// clang-format off
|
||||
|
@ -347,11 +347,14 @@ private:
|
||||
void GetLibraryAppletInfo(HLERequestContext& ctx);
|
||||
void ExitProcessAndReturn(HLERequestContext& ctx);
|
||||
void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
|
||||
void GetDesirableKeyboardLayout(HLERequestContext& ctx);
|
||||
void GetMainAppletAvailableUsers(HLERequestContext& ctx);
|
||||
void ShouldSetGpuTimeSliceManually(HLERequestContext& ctx);
|
||||
|
||||
void PushInShowAlbum();
|
||||
void PushInShowCabinetData();
|
||||
void PushInShowMiiEditData();
|
||||
void PushInShowSoftwareKeyboard();
|
||||
|
||||
std::deque<std::vector<u8>> queue_data;
|
||||
};
|
||||
|
@ -228,12 +228,14 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
|
||||
|
||||
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
|
||||
const ScreenShotAttribute& attribute,
|
||||
std::span<const u8> image_data, u64 aruid) {
|
||||
return SaveScreenShot(out_entry, attribute, {}, image_data, aruid);
|
||||
AlbumReportOption report_option, std::span<const u8> image_data,
|
||||
u64 aruid) {
|
||||
return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
|
||||
}
|
||||
|
||||
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
|
||||
const ScreenShotAttribute& attribute,
|
||||
AlbumReportOption report_option,
|
||||
const ApplicationData& app_data, std::span<const u8> image_data,
|
||||
u64 aruid) {
|
||||
const u64 title_id = system.GetApplicationProcessProgramID();
|
||||
@ -407,10 +409,14 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static void PNGToMemory(void* context, void* png, int len) {
|
||||
void AlbumManager::FlipVerticallyOnWrite(bool flip) {
|
||||
stbi_flip_vertically_on_write(flip);
|
||||
}
|
||||
|
||||
static void PNGToMemory(void* context, void* data, int len) {
|
||||
std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context);
|
||||
png_image->reserve(len);
|
||||
std::memcpy(png_image->data(), png, len);
|
||||
unsigned char* png = static_cast<unsigned char*>(data);
|
||||
png_image->insert(png_image->end(), png, png + len);
|
||||
}
|
||||
|
||||
Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image,
|
||||
|
@ -59,14 +59,17 @@ public:
|
||||
const ScreenShotDecodeOption& decoder_options) const;
|
||||
|
||||
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
|
||||
std::span<const u8> image_data, u64 aruid);
|
||||
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
|
||||
const ApplicationData& app_data, std::span<const u8> image_data,
|
||||
AlbumReportOption report_option, std::span<const u8> image_data,
|
||||
u64 aruid);
|
||||
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
|
||||
AlbumReportOption report_option, const ApplicationData& app_data,
|
||||
std::span<const u8> image_data, u64 aruid);
|
||||
Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
|
||||
const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
|
||||
std::span<const u8> image_data);
|
||||
|
||||
void FlipVerticallyOnWrite(bool flip);
|
||||
|
||||
private:
|
||||
static constexpr std::size_t NandAlbumFileLimit = 1000;
|
||||
static constexpr std::size_t SdAlbumFileLimit = 10000;
|
||||
|
@ -34,7 +34,7 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
ScreenShotAttribute attribute{};
|
||||
u32 report_option{};
|
||||
AlbumReportOption report_option{};
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u64 applet_resource_user_id{};
|
||||
};
|
||||
@ -49,13 +49,16 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
|
||||
parameters.applet_resource_user_id);
|
||||
|
||||
ApplicationAlbumEntry entry{};
|
||||
const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
|
||||
parameters.applet_resource_user_id);
|
||||
manager->FlipVerticallyOnWrite(false);
|
||||
const auto result =
|
||||
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
|
||||
image_data_buffer, parameters.applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 10};
|
||||
rb.Push(result);
|
||||
rb.PushRaw(entry);
|
||||
}
|
||||
|
||||
void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
@ -83,6 +86,7 @@ void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
|
||||
image_data_buffer.size(), thumbnail_image_data_buffer.size());
|
||||
|
||||
ApplicationAlbumEntry entry{};
|
||||
manager->FlipVerticallyOnWrite(false);
|
||||
const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
|
||||
parameters.file_id, image_data_buffer);
|
||||
|
||||
|
@ -2,10 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/caps/caps_manager.h"
|
||||
#include "core/hle/service/caps/caps_su.h"
|
||||
#include "core/hle/service/caps/caps_types.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Service::Capture {
|
||||
|
||||
@ -58,8 +60,10 @@ void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
|
||||
parameters.applet_resource_user_id);
|
||||
|
||||
ApplicationAlbumEntry entry{};
|
||||
const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
|
||||
parameters.applet_resource_user_id);
|
||||
manager->FlipVerticallyOnWrite(false);
|
||||
const auto result =
|
||||
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
|
||||
image_data_buffer, parameters.applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 10};
|
||||
rb.Push(result);
|
||||
@ -88,13 +92,43 @@ void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
|
||||
ApplicationAlbumEntry entry{};
|
||||
ApplicationData app_data{};
|
||||
std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
|
||||
manager->FlipVerticallyOnWrite(false);
|
||||
const auto result =
|
||||
manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer,
|
||||
parameters.applet_resource_user_id);
|
||||
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, app_data,
|
||||
image_data_buffer, parameters.applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 10};
|
||||
rb.Push(result);
|
||||
rb.PushRaw(entry);
|
||||
}
|
||||
|
||||
void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption report_option) {
|
||||
auto& renderer = system.Renderer();
|
||||
Layout::FramebufferLayout layout =
|
||||
Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
|
||||
|
||||
const Capture::ScreenShotAttribute attribute{
|
||||
.unknown_0{},
|
||||
.orientation = Capture::AlbumImageOrientation::None,
|
||||
.unknown_1{},
|
||||
.unknown_2{},
|
||||
};
|
||||
|
||||
renderer.RequestScreenshot(
|
||||
image_data.data(),
|
||||
[attribute, report_option, this](bool invert_y) {
|
||||
// Convert from BGRA to RGBA
|
||||
for (std::size_t i = 0; i < image_data.size(); i += bytes_per_pixel) {
|
||||
const u8 temp = image_data[i];
|
||||
image_data[i] = image_data[i + 2];
|
||||
image_data[i + 2] = temp;
|
||||
}
|
||||
|
||||
Capture::ApplicationAlbumEntry entry{};
|
||||
manager->FlipVerticallyOnWrite(invert_y);
|
||||
manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
|
||||
},
|
||||
layout);
|
||||
}
|
||||
|
||||
} // namespace Service::Capture
|
||||
|
@ -10,6 +10,7 @@ class System;
|
||||
}
|
||||
|
||||
namespace Service::Capture {
|
||||
enum class AlbumReportOption : s32;
|
||||
class AlbumManager;
|
||||
|
||||
class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
|
||||
@ -18,11 +19,19 @@ public:
|
||||
std::shared_ptr<AlbumManager> album_manager);
|
||||
~IScreenShotApplicationService() override;
|
||||
|
||||
void CaptureAndSaveScreenshot(AlbumReportOption report_option);
|
||||
|
||||
private:
|
||||
static constexpr std::size_t screenshot_width = 1280;
|
||||
static constexpr std::size_t screenshot_height = 720;
|
||||
static constexpr std::size_t bytes_per_pixel = 4;
|
||||
|
||||
void SetShimLibraryVersion(HLERequestContext& ctx);
|
||||
void SaveScreenShotEx0(HLERequestContext& ctx);
|
||||
void SaveScreenShotEx1(HLERequestContext& ctx);
|
||||
|
||||
std::array<u8, screenshot_width * screenshot_height * bytes_per_pixel> image_data;
|
||||
|
||||
std::shared_ptr<AlbumManager> manager;
|
||||
};
|
||||
|
||||
|
@ -227,14 +227,14 @@ add_executable(yuzu
|
||||
yuzu.rc
|
||||
)
|
||||
|
||||
if (WIN32 AND YUZU_CRASH_DUMPS)
|
||||
if (YUZU_CRASH_DUMPS)
|
||||
target_sources(yuzu PRIVATE
|
||||
mini_dump.cpp
|
||||
mini_dump.h
|
||||
breakpad.cpp
|
||||
breakpad.h
|
||||
)
|
||||
|
||||
target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
|
||||
target_link_libraries(yuzu PRIVATE libbreakpad_client)
|
||||
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
|
77
src/yuzu/breakpad.cpp
Normal file
77
src/yuzu/breakpad.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <client/windows/handler/exception_handler.h>
|
||||
#elif defined(__linux__)
|
||||
#include <client/linux/handler/exception_handler.h>
|
||||
#else
|
||||
#error Minidump creation not supported on this platform
|
||||
#endif
|
||||
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "yuzu/breakpad.h"
|
||||
|
||||
namespace Breakpad {
|
||||
|
||||
static void PruneDumpDirectory(const std::filesystem::path& dump_path) {
|
||||
// Code in this function should be exception-safe.
|
||||
struct Entry {
|
||||
std::filesystem::path path;
|
||||
std::filesystem::file_time_type last_write_time;
|
||||
};
|
||||
std::vector<Entry> existing_dumps;
|
||||
|
||||
// Get existing entries.
|
||||
std::error_code ec;
|
||||
std::filesystem::directory_iterator dir(dump_path, ec);
|
||||
for (auto& entry : dir) {
|
||||
if (entry.is_regular_file()) {
|
||||
existing_dumps.push_back(Entry{
|
||||
.path = entry.path(),
|
||||
.last_write_time = entry.last_write_time(ec),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort descending by creation date.
|
||||
std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) {
|
||||
return a.last_write_time > b.last_write_time;
|
||||
});
|
||||
|
||||
// Delete older dumps.
|
||||
for (size_t i = 5; i < existing_dumps.size(); i++) {
|
||||
std::filesystem::remove(existing_dumps[i].path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
|
||||
bool succeeded) {
|
||||
// Prevent time- and space-consuming core dumps from being generated, as we have
|
||||
// already generated a minidump and a core file will not be useful anyway.
|
||||
_exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void InstallCrashHandler() {
|
||||
// Write crash dumps to profile directory.
|
||||
const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir);
|
||||
PruneDumpDirectory(dump_path);
|
||||
|
||||
#if defined(_WIN32)
|
||||
// TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API.
|
||||
static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr,
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL};
|
||||
#elif defined(__linux__)
|
||||
static google_breakpad::MinidumpDescriptor descriptor{dump_path};
|
||||
static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback,
|
||||
nullptr, true, -1};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Breakpad
|
10
src/yuzu/breakpad.h
Normal file
10
src/yuzu/breakpad.h
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Breakpad {
|
||||
|
||||
void InstallCrashHandler();
|
||||
|
||||
}
|
@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
|
||||
|
||||
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
|
||||
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
|
||||
|
||||
connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
|
||||
if (crash_dump_warning_shown) {
|
||||
return;
|
||||
}
|
||||
QMessageBox::warning(this, tr("Restart Required"),
|
||||
tr("yuzu is required to restart in order to apply this setting."),
|
||||
QMessageBox::Ok, QMessageBox::Ok);
|
||||
crash_dump_warning_shown = true;
|
||||
});
|
||||
}
|
||||
|
||||
ConfigureDebug::~ConfigureDebug() = default;
|
||||
@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() {
|
||||
ui->disable_web_applet->setEnabled(false);
|
||||
ui->disable_web_applet->setText(tr("Web applet not compiled"));
|
||||
#endif
|
||||
|
||||
#ifdef YUZU_DBGHELP
|
||||
ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
|
||||
#else
|
||||
ui->create_crash_dumps->setEnabled(false);
|
||||
ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConfigureDebug::ApplyConfiguration() {
|
||||
@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
|
||||
Settings::values.reporting_services = ui->reporting_services->isChecked();
|
||||
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
|
||||
Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
|
||||
Settings::values.quest_flag = ui->quest_flag->isChecked();
|
||||
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
|
||||
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
|
||||
|
@ -471,13 +471,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="create_crash_dumps">
|
||||
<property name="text">
|
||||
<string>Create Minidump After Crash</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="dump_audio_commands">
|
||||
<property name="toolTip">
|
||||
|
@ -159,8 +159,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "yuzu/util/clickable_label.h"
|
||||
#include "yuzu/vk_device_info.h"
|
||||
|
||||
#ifdef YUZU_DBGHELP
|
||||
#include "yuzu/mini_dump.h"
|
||||
#ifdef YUZU_CRASH_DUMPS
|
||||
#include "yuzu/breakpad.h"
|
||||
#endif
|
||||
|
||||
using namespace Common::Literals;
|
||||
@ -1072,7 +1072,7 @@ void GMainWindow::InitializeWidgets() {
|
||||
});
|
||||
volume_popup->layout()->addWidget(volume_slider);
|
||||
|
||||
volume_button = new QPushButton();
|
||||
volume_button = new VolumeButton();
|
||||
volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
|
||||
volume_button->setFocusPolicy(Qt::NoFocus);
|
||||
volume_button->setCheckable(true);
|
||||
@ -1103,6 +1103,8 @@ void GMainWindow::InitializeWidgets() {
|
||||
context_menu.exec(volume_button->mapToGlobal(menu_location));
|
||||
volume_button->repaint();
|
||||
});
|
||||
connect(volume_button, &VolumeButton::VolumeChanged, this, &GMainWindow::UpdateVolumeUI);
|
||||
|
||||
statusBar()->insertPermanentWidget(0, volume_button);
|
||||
|
||||
// setup AA button
|
||||
@ -2906,7 +2908,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||
|
||||
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
||||
// Determine full paths for icon and shortcut
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
|
||||
const char* home = std::getenv("HOME");
|
||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||
@ -2963,7 +2965,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||
|
||||
QImage icon_data =
|
||||
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
|
||||
// Convert and write the icon as a PNG
|
||||
if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
|
||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||
@ -4002,7 +4004,7 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
|
||||
const std::string& comment, const std::string& icon_path,
|
||||
const std::string& command, const std::string& arguments,
|
||||
const std::string& categories, const std::string& keywords) {
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
|
||||
// This desktop file template was writing referencing
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
|
||||
std::string shortcut_contents{};
|
||||
@ -5126,6 +5128,32 @@ void GMainWindow::changeEvent(QEvent* event) {
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void VolumeButton::wheelEvent(QWheelEvent* event) {
|
||||
|
||||
int num_degrees = event->angleDelta().y() / 8;
|
||||
int num_steps = (num_degrees / 15) * scroll_multiplier;
|
||||
// Stated in QT docs: Most mouse types work in steps of 15 degrees, in which case the delta
|
||||
// value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
|
||||
|
||||
if (num_steps > 0) {
|
||||
Settings::values.volume.SetValue(
|
||||
std::min(200, Settings::values.volume.GetValue() + num_steps));
|
||||
} else {
|
||||
Settings::values.volume.SetValue(
|
||||
std::max(0, Settings::values.volume.GetValue() + num_steps));
|
||||
}
|
||||
|
||||
scroll_multiplier = std::min(MaxMultiplier, scroll_multiplier * 2);
|
||||
scroll_timer.start(100); // reset the multiplier if no scroll event occurs within 100 ms
|
||||
|
||||
emit VolumeChanged();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void VolumeButton::ResetMultiplier() {
|
||||
scroll_multiplier = 1;
|
||||
}
|
||||
|
||||
#ifdef main
|
||||
#undef main
|
||||
#endif
|
||||
@ -5187,22 +5215,15 @@ int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef YUZU_DBGHELP
|
||||
PROCESS_INFORMATION pi;
|
||||
if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
|
||||
MiniDump::SpawnDebuggee(argv[0], pi)) {
|
||||
// Delete the config object so that it doesn't save when the program exits
|
||||
config.reset(nullptr);
|
||||
MiniDump::DebugDebuggee(pi);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (StartupChecks(argv[0], &has_broken_vulkan,
|
||||
Settings::values.perform_vulkan_check.GetValue())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef YUZU_CRASH_DUMPS
|
||||
Breakpad::InstallCrashHandler();
|
||||
#endif
|
||||
|
||||
Common::DetachedTasks detached_tasks;
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
@ -137,6 +138,28 @@ namespace VkDeviceInfo {
|
||||
class Record;
|
||||
}
|
||||
|
||||
class VolumeButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VolumeButton(QWidget* parent = nullptr) : QPushButton(parent), scroll_multiplier(1) {
|
||||
connect(&scroll_timer, &QTimer::timeout, this, &VolumeButton::ResetMultiplier);
|
||||
}
|
||||
|
||||
signals:
|
||||
void VolumeChanged();
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void ResetMultiplier();
|
||||
|
||||
private:
|
||||
int scroll_multiplier;
|
||||
QTimer scroll_timer;
|
||||
constexpr static int MaxMultiplier = 8;
|
||||
};
|
||||
|
||||
class GMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
@ -481,7 +504,7 @@ private:
|
||||
QPushButton* dock_status_button = nullptr;
|
||||
QPushButton* filter_status_button = nullptr;
|
||||
QPushButton* aa_status_button = nullptr;
|
||||
QPushButton* volume_button = nullptr;
|
||||
VolumeButton* volume_button = nullptr;
|
||||
QWidget* volume_popup = nullptr;
|
||||
QSlider* volume_slider = nullptr;
|
||||
QTimer status_bar_update_timer;
|
||||
|
@ -1,202 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <windows.h>
|
||||
#include "yuzu/mini_dump.h"
|
||||
#include "yuzu/startup_checks.h"
|
||||
|
||||
// dbghelp.h must be included after windows.h
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace MiniDump {
|
||||
|
||||
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
|
||||
EXCEPTION_POINTERS* pep) {
|
||||
char file_name[255];
|
||||
const std::time_t the_time = std::time(nullptr);
|
||||
std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
|
||||
|
||||
// Open the file
|
||||
HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
|
||||
fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the minidump
|
||||
const MINIDUMP_TYPE dump_type = MiniDumpNormal;
|
||||
|
||||
const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
|
||||
dump_type, (pep != 0) ? info : 0, 0, 0);
|
||||
|
||||
if (write_dump_status) {
|
||||
fmt::print(stderr, "MiniDump created: {}", file_name);
|
||||
} else {
|
||||
fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
|
||||
}
|
||||
|
||||
// Close the file
|
||||
CloseHandle(file_handle);
|
||||
}
|
||||
|
||||
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
|
||||
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
|
||||
|
||||
HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
|
||||
if (thread_handle == nullptr) {
|
||||
fmt::print(stderr, "OpenThread failed ({})", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get child process context
|
||||
CONTEXT context = {};
|
||||
context.ContextFlags = CONTEXT_ALL;
|
||||
if (!GetThreadContext(thread_handle, &context)) {
|
||||
fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Create exception pointers for minidump
|
||||
EXCEPTION_POINTERS ep;
|
||||
ep.ExceptionRecord = &record;
|
||||
ep.ContextRecord = &context;
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION info;
|
||||
info.ThreadId = deb_ev.dwThreadId;
|
||||
info.ExceptionPointers = &ep;
|
||||
info.ClientPointers = false;
|
||||
|
||||
CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
|
||||
|
||||
if (CloseHandle(thread_handle) == 0) {
|
||||
fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
|
||||
std::memset(&pi, 0, sizeof(pi));
|
||||
|
||||
// Don't debug if we are already being debugged
|
||||
if (IsDebuggerPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SpawnChild(arg0, &pi, 0)) {
|
||||
fmt::print(stderr, "warning: continuing without crash dumps");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool can_debug = DebugActiveProcess(pi.dwProcessId);
|
||||
if (!can_debug) {
|
||||
fmt::print(stderr,
|
||||
"warning: DebugActiveProcess failed ({}), continuing without crash dumps",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* ExceptionName(DWORD exception) {
|
||||
switch (exception) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return "EXCEPTION_ACCESS_VIOLATION";
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return "EXCEPTION_DATATYPE_MISALIGNMENT";
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return "EXCEPTION_BREAKPOINT";
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
return "EXCEPTION_SINGLE_STEP";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
return "EXCEPTION_FLT_DENORMAL_OPERAND";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
return "EXCEPTION_FLT_INEXACT_RESULT";
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
return "EXCEPTION_FLT_INVALID_OPERATION";
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
return "EXCEPTION_FLT_OVERFLOW";
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return "EXCEPTION_FLT_STACK_CHECK";
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
return "EXCEPTION_FLT_UNDERFLOW";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return "EXCEPTION_INT_OVERFLOW";
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return "EXCEPTION_PRIV_INSTRUCTION";
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return "EXCEPTION_IN_PAGE_ERROR";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return "EXCEPTION_ILLEGAL_INSTRUCTION";
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return "EXCEPTION_STACK_OVERFLOW";
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
return "EXCEPTION_INVALID_DISPOSITION";
|
||||
case EXCEPTION_GUARD_PAGE:
|
||||
return "EXCEPTION_GUARD_PAGE";
|
||||
case EXCEPTION_INVALID_HANDLE:
|
||||
return "EXCEPTION_INVALID_HANDLE";
|
||||
default:
|
||||
return "unknown exception type";
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDebuggee(PROCESS_INFORMATION& pi) {
|
||||
DEBUG_EVENT deb_ev = {};
|
||||
|
||||
while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
|
||||
const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
|
||||
if (!wait_success) {
|
||||
fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (deb_ev.dwDebugEventCode) {
|
||||
case OUTPUT_DEBUG_STRING_EVENT:
|
||||
case CREATE_PROCESS_DEBUG_EVENT:
|
||||
case CREATE_THREAD_DEBUG_EVENT:
|
||||
case EXIT_PROCESS_DEBUG_EVENT:
|
||||
case EXIT_THREAD_DEBUG_EVENT:
|
||||
case LOAD_DLL_DEBUG_EVENT:
|
||||
case RIP_EVENT:
|
||||
case UNLOAD_DLL_DEBUG_EVENT:
|
||||
// Continue on all other debug events
|
||||
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
|
||||
break;
|
||||
case EXCEPTION_DEBUG_EVENT:
|
||||
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
|
||||
|
||||
// We want to generate a crash dump if we are seeing the same exception again.
|
||||
if (!deb_ev.u.Exception.dwFirstChance) {
|
||||
fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
|
||||
record.ExceptionCode, ExceptionName(record.ExceptionCode));
|
||||
DumpFromDebugEvent(deb_ev, pi);
|
||||
}
|
||||
|
||||
// Continue without handling the exception.
|
||||
// Lets the debuggee use its own exception handler.
|
||||
// - If one does not exist, we will see the exception once more where we make a minidump
|
||||
// for. Then when it reaches here again, yuzu will probably crash.
|
||||
// - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
|
||||
// infinite loop of exceptions.
|
||||
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MiniDump
|
@ -1,19 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace MiniDump {
|
||||
|
||||
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
|
||||
EXCEPTION_POINTERS* pep);
|
||||
|
||||
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
|
||||
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
|
||||
void DebugDebuggee(PROCESS_INFORMATION& pi);
|
||||
|
||||
} // namespace MiniDump
|
@ -33,10 +33,6 @@
|
||||
"description": "Compile tests",
|
||||
"dependencies": [ "catch2" ]
|
||||
},
|
||||
"dbghelp": {
|
||||
"description": "Compile Windows crash dump (Minidump) support",
|
||||
"dependencies": [ "dbghelp" ]
|
||||
},
|
||||
"web-service": {
|
||||
"description": "Enable web services (telemetry, etc.)",
|
||||
"dependencies": [
|
||||
|
Reference in New Issue
Block a user