diff options
43 files changed, 879 insertions, 847 deletions
diff --git a/.gitmodules b/.gitmodules index fdddb0d3a..b72a2ec8c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 61baabb03..be8b0b5e8 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -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() diff --git a/externals/SDL b/externals/SDL -Subproject 031912c4b6c5db80b443f04aa56fec3e4e64515 +Subproject cc016b0046d563287f0aa9f09b958b5e70d4369 diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers -Subproject ed857118e243fdc0f3a100f00ac9919e874cfe6 +Subproject df60f0316899460eeaaefa06d2dd7e4e300c160 diff --git a/externals/VulkanMemoryAllocator b/externals/VulkanMemoryAllocator -Subproject 9b0fc3e7b02afe97895eb3e945fe800c3a7485a +Subproject 2f382df218d7e8516dee3b3caccb819a62b571a diff --git a/externals/cpp-httplib b/externals/cpp-httplib -Subproject 6d963fbe8d415399d65e94db7910bbd22fe3741 +Subproject a609330e4c6374f741d3b369269f7848255e195 diff --git a/externals/cpp-jwt b/externals/cpp-jwt -Subproject e12ef06218596b52d9b5d6e1639484866a8e706 +Subproject 10ef5735d842b31025f1257ae78899f50a40fb1 diff --git a/externals/ffmpeg/ffmpeg b/externals/ffmpeg/ffmpeg -Subproject 6b6b9e593dd4d3aaf75f48d40a13ef03bdef9fd +Subproject 9c1294eaddb88cb0e044c675ccae059a85fc9c6 diff --git a/externals/inih/inih b/externals/inih/inih -Subproject 1e80a47dffbda813604f0913e2ad68c7054c14e +Subproject 9cecf0643da0846e77f64d10a126d9f48b9e05e diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 6757b59da..1d50c9f8c 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -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 diff --git a/externals/libusb/libusb b/externals/libusb/libusb -Subproject c6a35c56016ea2ab2f19115d2ea1e85e0edae15 +Subproject c060e9ce30ac2e3ffb49d94209c4dae77b6642f diff --git a/externals/opus b/externals/opus new file mode 160000 +Subproject 101a71e03bbf860aaafb7090a0e440675cb2766 diff --git a/externals/opus/CMakeLists.txt b/externals/opus/CMakeLists.txt deleted file mode 100644 index d9a03423d..000000000 --- a/externals/opus/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/externals/opus/opus b/externals/opus/opus deleted file mode 160000 -Subproject ad8fe90db79b7d2a135e3dfd2ed6631b0c5662a diff --git a/externals/vcpkg b/externals/vcpkg -Subproject cbf56573a987527b39272e88cbdd11389b78c6e +Subproject ef2eef17340f3fbd679327d286fad06dd6e838e diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 115f72710..e2c5b6acd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -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)      /** @@ -294,11 +287,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).       */      external fun isRunning(): Boolean diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index f9f88a1d2..0c82cdba8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -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 { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 598a9d42b..07bd78bf7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -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 diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 6527c64ab..b43978fce 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -12,15 +12,14 @@ import kotlinx.serialization.Serializable  @Serializable  class Game(      val title: String, -    val description: String, -    val regions: String,      val path: String, -    val gameId: String, -    val company: String, +    val programId: String, +    val developer: String, +    val version: String,      val isHomebrew: Boolean  ) : 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      } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index 6e09fa81d..8512ed17c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -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 diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index eafcf9e42..738275297 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt @@ -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 diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 5ee74a52c..8c3268e9c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt @@ -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)          } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 9001ca9ab..e6aca6b44 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -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) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt index 9fe99fab1..654d62f52 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt @@ -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, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt new file mode 100644 index 000000000..0f3542ac6 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt @@ -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() +} diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index e15d1480b..1c36661f5 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -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}) diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp new file mode 100644 index 000000000..24d9df702 --- /dev/null +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -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" diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 598f4e8bf..686b73588 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -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,416 @@  #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() = default; - -    static EmulationSession& GetInstance() { -        return s_instance; -    } - -    const Core::System& System() const { -        return m_system; -    } +EmulationSession::EmulationSession() { +    m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); +} -    Core::System& System() { -        return m_system; -    } +EmulationSession& EmulationSession::GetInstance() { +    return s_instance; +} -    const EmuWindow_Android& Window() const { -        return *m_window; -    } +const Core::System& EmulationSession::System() const { +    return m_system; +} -    EmuWindow_Android& Window() { -        return *m_window; -    } +Core::System& EmulationSession::System() { +    return m_system; +} -    ANativeWindow* NativeWindow() const { -        return m_native_window; -    } +const EmuWindow_Android& EmulationSession::Window() const { +    return *m_window; +} -    void SetNativeWindow(ANativeWindow* native_window) { -        m_native_window = native_window; -    } +EmuWindow_Android& EmulationSession::Window() { +    return *m_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; -            } +ANativeWindow* EmulationSession::NativeWindow() const { +    return m_native_window; +} -            using namespace Common::Literals; -            [[maybe_unused]] std::vector<u8> buffer(1_MiB); +void EmulationSession::SetNativeWindow(ANativeWindow* native_window) { +    m_native_window = native_window; +} -            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 (!dest->Resize(src->GetSize())) { +            return false;          } -        if (nsp->GetStatus() != Loader::ResultStatus::Success) { -            return InstallError; -        } +        using namespace Common::Literals; +        [[maybe_unused]] std::vector<u8> buffer(1_MiB); -        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; +        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; +    }; -    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(); -        } +    enum InstallResult { +        Success = 0, +        SuccessFileOverwritten = 1, +        InstallError = 2, +        ErrorBaseGame = 3, +        ErrorFilenameExtension = 4, +    }; -        // 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); -        } +    m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); +    m_system.GetFileSystemController().CreateFactories(*m_vfs); -        // 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); +    [[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;          } - -        m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); -#endif +    } else { +        return ErrorFilenameExtension;      } -    bool IsRunning() const { -        return m_is_running; +    if (!nsp) { +        return InstallError;      } -    bool IsPaused() const { -        return m_is_running && m_is_paused; +    if (nsp->GetStatus() != Loader::ResultStatus::Success) { +        return InstallError;      } -    const Core::PerfStatsResults& PerfStats() const { -        std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); -        return m_perf_stats; -    } +    jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, +                                                                                        copy_func); -    void SurfaceChanged() { -        if (!IsRunning()) { -            return; -        } -        m_window->OnSurfaceChanged(m_native_window); +    switch (res) { +    case FileSys::InstallResult::Success: +        return Success; +    case FileSys::InstallResult::OverwriteExisting: +        return SuccessFileOverwritten; +    case FileSys::InstallResult::ErrorBaseInstall: +        return ErrorBaseGame; +    default: +        return InstallError;      } +} -    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; -        } +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{}; -        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()); -                } -            } -        } +    // 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();      } -    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(); }); +    // 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); +    } -        return Core::SystemResultStatus::Success; +    // 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);      } -    void ShutdownEmulation() { -        std::scoped_lock lock(m_mutex); +    m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); +#endif +} -        m_is_running = false; +bool EmulationSession::IsRunning() const { +    return m_is_running; +} -        // Unload user input. -        m_system.HIDCore().UnloadInputDevices(); +bool EmulationSession::IsPaused() const { +    return m_is_running && m_is_paused; +} -        // 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; -        } +const Core::PerfStatsResults& EmulationSession::PerfStats() const { +    std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); +    return m_perf_stats; +} -        // Tear down the render window. -        m_window.reset(); +void EmulationSession::SurfaceChanged() { +    if (!IsRunning()) { +        return;      } +    m_window->OnSurfaceChanged(m_native_window); +} -    void PauseEmulation() { -        std::scoped_lock lock(m_mutex); -        m_system.Pause(); -        m_is_paused = true; +void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { +    const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); +    if (!file) { +        return;      } -    void UnPauseEmulation() { -        std::scoped_lock lock(m_mutex); -        m_system.Run(); -        m_is_paused = false; +    auto loader = Loader::GetLoader(m_system, file); +    if (!loader) { +        return;      } -    void HaltEmulation() { -        std::scoped_lock lock(m_mutex); -        m_is_running = false; -        m_cv.notify_one(); +    const auto file_type = loader->GetFileType(); +    if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { +        return;      } -    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); +    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()); +            }          } +    } +} -        void(m_system.Run()); - -        if (m_system.DebuggerEnabled()) { -            m_system.InitializeDebugger(); -        } +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); + +    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(); }); -        OnEmulationStarted(); +    return Core::SystemResultStatus::Success; +} -        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(); -            } -        } -    } +void EmulationSession::ShutdownEmulation() { +    std::scoped_lock lock(m_mutex); -    std::string GetRomTitle(const std::string& path) { -        return GetRomMetadata(path).title; -    } +    m_is_running = false; -    std::vector<u8> GetRomIcon(const std::string& path) { -        return GetRomMetadata(path).icon; -    } +    // Unload user input. +    m_system.HIDCore().UnloadInputDevices(); -    bool GetIsHomebrew(const std::string& path) { -        return GetRomMetadata(path).isHomebrew; +    // 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;      } -    void ResetRomMetadata() { -        m_rom_metadata_cache.clear(); -    } +    // Tear down the render window. +    m_window.reset(); +} -    bool IsHandheldOnly() { -        jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); +void EmulationSession::PauseEmulation() { +    std::scoped_lock lock(m_mutex); +    m_system.Pause(); +    m_is_paused = true; +} -        if (npad_style_set.fullkey == 1) { -            return false; -        } +void EmulationSession::UnPauseEmulation() { +    std::scoped_lock lock(m_mutex); +    m_system.Run(); +    m_is_paused = false; +} -        if (npad_style_set.handheld == 0) { -            return false; -        } +void EmulationSession::HaltEmulation() { +    std::scoped_lock lock(m_mutex); +    m_is_running = false; +    m_cv.notify_one(); +} -        return !Settings::IsDockedMode(); +void EmulationSession::RunEmulation() { +    { +        std::scoped_lock lock(m_mutex); +        m_is_running = true;      } -    void SetDeviceType([[maybe_unused]] int index, int type) { -        jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); -        controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); +    // 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 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); +    void(m_system.Run()); -            if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { -                handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); -                controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); -                handheld->Disconnect(); -            } -        } +    if (m_system.DebuggerEnabled()) { +        m_system.InitializeDebugger(); +    } -        // 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); +    OnEmulationStarted(); -            if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { -                player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); -                controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); -                player1->Disconnect(); +    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;              }          } - -        if (!controller->IsConnected()) { -            controller->Connect(); +        { +            // Refresh performance stats. +            std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); +            m_perf_stats = m_system.GetAndResetPerfStats();          }      } +} + +bool EmulationSession::IsHandheldOnly() { +    jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); -    void OnGamepadDisconnectEvent([[maybe_unused]] int index) { -        jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); -        controller->Disconnect(); +    if (npad_style_set.fullkey == 1) { +        return false;      } -    SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { -        return m_software_keyboard; +    if (npad_style_set.handheld == 0) { +        return false;      } -private: -    struct RomMetadata { -        std::string title; -        std::vector<u8> icon; -        bool isHomebrew; -    }; +    return !Settings::IsDockedMode(); +} -    RomMetadata GetRomMetadata(const std::string& path) { -        if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { -            return search->second; -        } +void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { +    jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); +    controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); +} -        return CacheRomMetadata(path); -    } +void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { +    jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); -    RomMetadata CacheRomMetadata(const std::string& path) { -        jconst file = Core::GetGameFileFromPath(m_vfs, path); -        jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); +    // 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); -        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 (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { +            handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); +            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); +            handheld->Disconnect();          } - -        m_rom_metadata_cache[path] = entry; - -        return entry;      } -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)); -    } +    // 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); -    static void OnEmulationStarted() { -        JNIEnv* env = IDCache::GetEnvForThread(); -        env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), -                                  IDCache::GetOnEmulationStarted()); +        if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { +            player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); +            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); +            player1->Disconnect(); +        }      } -    static void OnEmulationStopped(Core::SystemResultStatus result) { -        JNIEnv* env = IDCache::GetEnvForThread(); -        env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), -                                  IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); +    if (!controller->IsConnected()) { +        controller->Connect();      } +} -private: -    static EmulationSession s_instance; - -    // Frontend management -    std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; - -    // 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; +void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { +    jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); +    controller->Disconnect(); +} -    // GPU driver parameters -    std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; +SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { +    return m_software_keyboard; +} -    // Synchronization -    std::condition_variable_any m_cv; -    mutable std::mutex m_perf_stats_mutex; -    mutable std::mutex m_mutex; -}; +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)); +} -/*static*/ EmulationSession EmulationSession::s_instance; +void EmulationSession::OnEmulationStarted() { +    JNIEnv* env = IDCache::GetEnvForThread(); +    env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted()); +} -} // Anonymous namespace +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 +558,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,46 +663,6 @@ 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) {      // Create the default config.ini.      Config{}; diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h new file mode 100644 index 000000000..b1db87e41 --- /dev/null +++ b/src/android/app/src/main/jni/native.h @@ -0,0 +1,84 @@ +// 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); +    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; +}; diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml index 1f5de219b..6340171ec 100644 --- a/src/android/app/src/main/res/layout/card_game.xml +++ b/src/android/app/src/main/res/layout/card_game.xml @@ -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:textAlignment="center" -                android:textSize="14sp" -                android:singleLine="true" -                android:marqueeRepeatLimit="marquee_forever"                  android:ellipsize="none" +                android:marqueeRepeatLimit="marquee_forever"                  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" +                android:singleLine="true" +                android:textAlignment="center" +                android:textSize="14sp" +                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> diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp index 298a79bac..1dd826a4a 100644 --- a/src/common/fs/fs_android.cpp +++ b/src/common/fs/fs_android.cpp @@ -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 diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index b441c2a12..2c9234313 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h @@ -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 diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 0c4c88cde..c3a81f9a9 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -401,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; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 4c7aba3f5..72c481798 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -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 diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index ff067c8d9..242ea6665 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -31,6 +31,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 +703,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); diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp index 7d733eb54..96b225d5f 100644 --- a/src/core/hle/service/caps/caps_manager.cpp +++ b/src/core/hle/service/caps/caps_manager.cpp @@ -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, diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index 44d85117f..e20c70c7b 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h @@ -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; diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 1ba2b7972..eab023568 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -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); diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index e85625ee4..296b07b00 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -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 diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 89e71f506..21912e95f 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -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;  }; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 81ef98f61..821f44f1a 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -147,6 +147,9 @@ bool Swapchain::AcquireNextImage() {      case VK_ERROR_OUT_OF_DATE_KHR:          is_outdated = true;          break; +    case VK_ERROR_SURFACE_LOST_KHR: +        vk::Check(result); +        break;      default:          LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));          break; @@ -180,6 +183,9 @@ void Swapchain::Present(VkSemaphore render_semaphore) {      case VK_ERROR_OUT_OF_DATE_KHR:          is_outdated = true;          break; +    case VK_ERROR_SURFACE_LOST_KHR: +        vk::Check(result); +        break;      default:          LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result));          break; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7cc11ae3b..0df163029 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -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 diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 270a40c5f..f9c6efe4f 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -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;  | 
