Merge remote-tracking branch 'origin/master' into ssl
This commit is contained in:
commit
98685d48e3
300 changed files with 24064 additions and 17224 deletions
|
@ -26,7 +26,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
|
|||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
|
||||
compileSdkVersion = "android-33"
|
||||
compileSdkVersion = "android-34"
|
||||
ndkVersion = "25.2.9519653"
|
||||
|
||||
buildFeatures {
|
||||
|
@ -51,7 +51,7 @@ android {
|
|||
// TODO If this is ever modified, change application_id in strings.xml
|
||||
applicationId = "org.yuzu.yuzu_emu"
|
||||
minSdk = 30
|
||||
targetSdk = 33
|
||||
targetSdk = 34
|
||||
versionName = getGitVersion()
|
||||
|
||||
// If you want to use autoVersion for the versionCode, create a property in local.properties
|
||||
|
|
|
@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
@ -69,7 +70,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
|
||||
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>
|
||||
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".features.DocumentProvider"
|
||||
|
|
|
@ -286,7 +286,7 @@ object NativeLibrary {
|
|||
/**
|
||||
* Unpauses emulation from a paused state.
|
||||
*/
|
||||
external fun unPauseEmulation()
|
||||
external fun unpauseEmulation()
|
||||
|
||||
/**
|
||||
* Pauses emulation.
|
||||
|
@ -313,6 +313,21 @@ object NativeLibrary {
|
|||
*/
|
||||
external fun isPaused(): Boolean
|
||||
|
||||
/**
|
||||
* Mutes emulation sound
|
||||
*/
|
||||
external fun muteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Unmutes emulation sound
|
||||
*/
|
||||
external fun unmuteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if emulation audio is muted.
|
||||
*/
|
||||
external fun isMuted(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the performance stats for the current game
|
||||
*/
|
||||
|
|
|
@ -27,13 +27,13 @@ import android.view.MotionEvent
|
|||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||
|
@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
|
|||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
@ -63,6 +65,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
|
@ -102,6 +106,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
inputHandler = InputHandler()
|
||||
inputHandler.initialize()
|
||||
|
||||
val memoryUtil = MemoryUtil(this)
|
||||
if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(
|
||||
R.string.device_memory_inadequate,
|
||||
memoryUtil.getDeviceRAM(),
|
||||
"8 ${getString(R.string.memory_gigabyte)}"
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
// Start a foreground service to prevent the app from getting killed in the background
|
||||
val startIntent = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(startIntent)
|
||||
|
@ -305,6 +322,41 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
}
|
||||
|
||||
if (NativeLibrary.isMuted()) {
|
||||
val unmuteIcon = Icon.createWithResource(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute
|
||||
)
|
||||
val unmutePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute,
|
||||
Intent(actionUnmute),
|
||||
pendingFlags
|
||||
)
|
||||
val unmuteRemoteAction = RemoteAction(
|
||||
unmuteIcon,
|
||||
getString(R.string.unmute),
|
||||
getString(R.string.unmute),
|
||||
unmutePendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(unmuteRemoteAction)
|
||||
} else {
|
||||
val muteIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_mute)
|
||||
val mutePendingIntent = PendingIntent.getBroadcast(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_mute,
|
||||
Intent(actionMute),
|
||||
pendingFlags
|
||||
)
|
||||
val muteRemoteAction = RemoteAction(
|
||||
muteIcon,
|
||||
getString(R.string.mute),
|
||||
getString(R.string.mute),
|
||||
mutePendingIntent
|
||||
)
|
||||
pictureInPictureActions.add(muteRemoteAction)
|
||||
}
|
||||
|
||||
return this.apply { setActions(pictureInPictureActions) }
|
||||
}
|
||||
|
||||
|
@ -322,10 +374,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private var pictureInPictureReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
if (intent.action == actionPlay) {
|
||||
if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation()
|
||||
if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation()
|
||||
} else if (intent.action == actionPause) {
|
||||
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
|
||||
}
|
||||
if (intent.action == actionUnmute) {
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
} else if (intent.action == actionMute) {
|
||||
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
|
||||
}
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +396,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
IntentFilter().apply {
|
||||
addAction(actionPause)
|
||||
addAction(actionPlay)
|
||||
addAction(actionMute)
|
||||
addAction(actionUnmute)
|
||||
}.also {
|
||||
registerReceiver(pictureInPictureReceiver, it)
|
||||
}
|
||||
|
@ -347,6 +406,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
// Always resume audio, since there is no UI button
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -714,7 +714,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
State.PAUSED -> {
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
NativeLibrary.unPauseEmulation()
|
||||
NativeLibrary.unpauseEmulation()
|
||||
}
|
||||
|
||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||
|
|
|
@ -68,79 +68,109 @@ class HomeSettingsFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() },
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() },
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) {
|
||||
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
},
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.supportsCustomDriverLoading()) {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() }
|
||||
)
|
||||
},
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() },
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) {
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!BuildConfig.PREMIUM) {
|
||||
optionsList.add(
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
||||
class LongMessageDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val titleId = requireArguments().getInt(TITLE)
|
||||
val description = requireArguments().getString(DESCRIPTION)
|
||||
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setPositiveButton(R.string.close, null)
|
||||
.setTitle(titleId)
|
||||
.setMessage(description)
|
||||
|
||||
if (helpLinkId != 0) {
|
||||
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
|
||||
openLink(getString(helpLinkId))
|
||||
}
|
||||
}
|
||||
|
||||
return dialog.show()
|
||||
}
|
||||
|
||||
private fun openLink(link: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "LongMessageDialogFragment"
|
||||
|
||||
private const val TITLE = "Title"
|
||||
private const val DESCRIPTION = "Description"
|
||||
private const val HELP_LINK = "Link"
|
||||
|
||||
fun newInstance(
|
||||
titleId: Int,
|
||||
description: String,
|
||||
helpLinkId: Int = 0
|
||||
): LongMessageDialogFragment {
|
||||
val dialog = LongMessageDialogFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.apply {
|
||||
putInt(TITLE, titleId)
|
||||
putString(DESCRIPTION, description)
|
||||
putInt(HELP_LINK, helpLinkId)
|
||||
}
|
||||
dialog.arguments = bundle
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
|||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
|
@ -128,10 +127,7 @@ class SearchFragment : Fragment() {
|
|||
|
||||
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
|
||||
|
||||
R.id.chip_retail -> baseList.filter {
|
||||
FileUtil.hasExtension(it.path, "xci") ||
|
||||
FileUtil.hasExtension(it.path, "nsp")
|
||||
}
|
||||
R.id.chip_retail -> baseList.filter { !it.isHomebrew }
|
||||
|
||||
else -> baseList
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class Game(
|
|||
|
||||
companion object {
|
||||
val extensions: Set<String> = HashSet(
|
||||
listOf(".xci", ".nsp", ".nca", ".nro")
|
||||
listOf("xci", "nsp", "nca", "nro")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
|
@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
|||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
|
@ -294,7 +296,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "keys")) {
|
||||
if (FileUtil.getExtension(result) != "keys") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_prod_keys_failure_extension_description
|
||||
|
@ -391,7 +393,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "bin")) {
|
||||
if (FileUtil.getExtension(result) != "bin") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_amiibo_keys_failure_extension_description
|
||||
|
@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
}
|
||||
|
||||
val installGameUpdate =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val installGameUpdate = registerForActivityResult(
|
||||
ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { documents: List<Uri> ->
|
||||
if (documents.isNotEmpty()) {
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
this@MainActivity,
|
||||
R.string.install_game_content
|
||||
) {
|
||||
val result = NativeLibrary.installFileToNand(it.toString())
|
||||
var installSuccess = 0
|
||||
var installOverwrite = 0
|
||||
var errorBaseGame = 0
|
||||
var errorExtension = 0
|
||||
var errorOther = 0
|
||||
var errorTotal = 0
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
documents.forEach {
|
||||
when (NativeLibrary.installFileToNand(it.toString())) {
|
||||
NativeLibrary.InstallFileToNandResult.Success -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_game_content_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
installSuccess += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_game_content_success_overwrite,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
installOverwrite += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
R.string.install_game_content_failure_base
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
errorBaseGame += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
R.string.install_game_content_failure_file_extension,
|
||||
R.string.install_game_content_help_link
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
errorExtension += 1
|
||||
}
|
||||
|
||||
else -> {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
R.string.install_game_content_failure_description,
|
||||
R.string.install_game_content_help_link
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
errorOther += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
val separator = System.getProperty("line.separator") ?: "\n"
|
||||
val installResult = StringBuilder()
|
||||
if (installSuccess > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_install,
|
||||
installSuccess
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (installOverwrite > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_overwrite,
|
||||
installOverwrite
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
errorTotal = errorBaseGame + errorExtension + errorOther
|
||||
if (errorTotal > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_failed_count,
|
||||
errorTotal
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
if (errorBaseGame > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_base)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (errorExtension > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_file_extension)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (errorOther > 0) {
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_description)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
LongMessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
installResult.toString().trim(),
|
||||
R.string.install_game_content_help_link
|
||||
).show(supportFragmentManager, LongMessageDialogFragment.TAG)
|
||||
} else {
|
||||
LongMessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_success,
|
||||
installResult.toString().trim()
|
||||
).show(supportFragmentManager, LongMessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
return@newInstance result
|
||||
return@newInstance installSuccess + installOverwrite + errorTotal
|
||||
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.Context
|
|||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
|
@ -185,19 +184,18 @@ object FileUtil {
|
|||
|
||||
/**
|
||||
* Get file display name from given path
|
||||
* @param path content uri path
|
||||
* @param uri content uri
|
||||
* @return String display name
|
||||
*/
|
||||
fun getFilename(context: Context, path: String): String {
|
||||
val resolver = context.contentResolver
|
||||
fun getFilename(uri: Uri): String {
|
||||
val resolver = YuzuApplication.appContext.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME
|
||||
)
|
||||
var filename = ""
|
||||
var c: Cursor? = null
|
||||
try {
|
||||
val mUri = Uri.parse(path)
|
||||
c = resolver.query(mUri, columns, null, null, null)
|
||||
c = resolver.query(uri, columns, null, null, null)
|
||||
c!!.moveToNext()
|
||||
filename = c.getString(0)
|
||||
} catch (e: Exception) {
|
||||
|
@ -326,25 +324,9 @@ object FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun hasExtension(path: String, extension: String): Boolean =
|
||||
path.substring(path.lastIndexOf(".") + 1).contains(extension)
|
||||
|
||||
fun hasExtension(uri: Uri, extension: String): Boolean {
|
||||
val fileName: String?
|
||||
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
|
||||
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor?.moveToFirst()
|
||||
|
||||
if (nameIndex == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
fileName = cursor.getString(nameIndex)
|
||||
cursor.close()
|
||||
|
||||
if (fileName == null) {
|
||||
return false
|
||||
}
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
|
||||
fun getExtension(uri: Uri): String {
|
||||
val fileName = getFilename(uri)
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
||||
.lowercase()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
|
|||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
@ -33,15 +32,9 @@ object GameHelper {
|
|||
val children = FileUtil.listFiles(context, gamesUri)
|
||||
for (file in children) {
|
||||
if (!file.isDirectory) {
|
||||
val filename = file.uri.toString()
|
||||
val extensionStart = filename.lastIndexOf('.')
|
||||
if (extensionStart > 0) {
|
||||
val fileExtension = filename.substring(extensionStart)
|
||||
|
||||
// Check that the file has an extension we care about before trying to read out of it.
|
||||
if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
|
||||
games.add(getGame(filename))
|
||||
}
|
||||
// Check that the file has an extension we care about before trying to read out of it.
|
||||
if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
|
||||
games.add(getGame(file.uri))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,21 +52,19 @@ object GameHelper {
|
|||
return games.toList()
|
||||
}
|
||||
|
||||
private fun getGame(filePath: String): Game {
|
||||
private fun getGame(uri: Uri): Game {
|
||||
val filePath = uri.toString()
|
||||
var name = NativeLibrary.getTitle(filePath)
|
||||
|
||||
// If the game's title field is empty, use the filename.
|
||||
if (name.isEmpty()) {
|
||||
name = filePath.substring(filePath.lastIndexOf("/") + 1)
|
||||
name = FileUtil.getFilename(uri)
|
||||
}
|
||||
var gameId = NativeLibrary.getGameId(filePath)
|
||||
|
||||
// If the game's ID field is empty, use the filename without extension.
|
||||
if (gameId.isEmpty()) {
|
||||
gameId = filePath.substring(
|
||||
filePath.lastIndexOf("/") + 1,
|
||||
filePath.lastIndexOf(".")
|
||||
)
|
||||
gameId = name.substring(0, name.lastIndexOf("."))
|
||||
}
|
||||
|
||||
val newGame = Game(
|
||||
|
|
|
@ -113,6 +113,8 @@ object GpuDriverHelper {
|
|||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
external fun supportsCustomDriverLoading(): Boolean
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val customDriverName: String?
|
||||
get() {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import java.util.Locale
|
||||
|
||||
class MemoryUtil(val context: Context) {
|
||||
|
||||
private val Long.floatForm: String
|
||||
get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
|
||||
|
||||
private fun bytesToSizeUnit(size: Long): String {
|
||||
return when {
|
||||
size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
|
||||
size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
|
||||
size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
|
||||
size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
|
||||
size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
|
||||
size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
|
||||
else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
|
||||
}
|
||||
}
|
||||
|
||||
private val totalMemory =
|
||||
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
|
||||
val memInfo = ActivityManager.MemoryInfo()
|
||||
getMemoryInfo(memInfo)
|
||||
memInfo.totalMem
|
||||
}
|
||||
|
||||
fun isLessThan(minimum: Int, size: Long): Boolean {
|
||||
return when (size) {
|
||||
Kb -> totalMemory < Mb && totalMemory < minimum
|
||||
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
|
||||
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
|
||||
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
|
||||
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
|
||||
Eb -> totalMemory / Eb < minimum
|
||||
else -> totalMemory < Kb && totalMemory < minimum
|
||||
}
|
||||
}
|
||||
|
||||
fun getDeviceRAM(): String {
|
||||
return bytesToSizeUnit(totalMemory)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Kb: Long = 1024
|
||||
const val Mb = Kb * 1024
|
||||
const val Gb = Mb * 1024
|
||||
const val Tb = Gb * 1024
|
||||
const val Pb = Tb * 1024
|
||||
const val Eb = Pb * 1024
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ add_library(yuzu-android SHARED
|
|||
id_cache.cpp
|
||||
id_cache.h
|
||||
native.cpp
|
||||
native.h
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <android/api-level.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <core/loader/nro.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
|
@ -59,6 +60,9 @@
|
|||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#define jconst [[maybe_unused]] const auto
|
||||
#define jauto [[maybe_unused]] auto
|
||||
|
||||
namespace {
|
||||
|
||||
class EmulationSession final {
|
||||
|
@ -98,8 +102,8 @@ public:
|
|||
}
|
||||
|
||||
int InstallFileToNand(std::string filename) {
|
||||
const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
|
||||
std::size_t block_size) {
|
||||
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
|
||||
std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
@ -108,10 +112,10 @@ public:
|
|||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
std::vector<u8> buffer(1_MiB);
|
||||
[[maybe_unused]] std::vector<u8> buffer(1_MiB);
|
||||
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||
const auto read = src->Read(buffer.data(), buffer.size(), i);
|
||||
jconst read = src->Read(buffer.data(), buffer.size(), i);
|
||||
dest->Write(buffer.data(), read, i);
|
||||
}
|
||||
return true;
|
||||
|
@ -128,14 +132,14 @@ public:
|
|||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
std::shared_ptr<FileSys::NSP> nsp;
|
||||
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
|
||||
if (filename.ends_with("nsp")) {
|
||||
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
if (nsp->IsExtractedType()) {
|
||||
return InstallError;
|
||||
}
|
||||
} else if (filename.ends_with("xci")) {
|
||||
const auto xci =
|
||||
jconst xci =
|
||||
std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
nsp = xci->GetSecurePartitionNSP();
|
||||
} else {
|
||||
|
@ -150,7 +154,7 @@ public:
|
|||
return InstallError;
|
||||
}
|
||||
|
||||
const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
*nsp, true, copy_func);
|
||||
|
||||
switch (res) {
|
||||
|
@ -233,10 +237,11 @@ public:
|
|||
m_system.SetFilesystem(m_vfs);
|
||||
|
||||
// Initialize system.
|
||||
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
m_software_keyboard = android_keyboard.get();
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetAppletFrontendSet({
|
||||
nullptr, // Amiibo Settings
|
||||
|
@ -330,7 +335,7 @@ public:
|
|||
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||
if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
|
@ -362,7 +367,7 @@ public:
|
|||
}
|
||||
|
||||
bool IsHandheldOnly() {
|
||||
const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
return false;
|
||||
|
@ -375,17 +380,17 @@ public:
|
|||
return !Settings::values.use_docked_mode.GetValue();
|
||||
}
|
||||
|
||||
void SetDeviceType(int index, int type) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void OnGamepadConnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
auto handheld =
|
||||
jauto handheld =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
|
@ -397,7 +402,8 @@ public:
|
|||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||
auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
jauto player1 =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
|
@ -411,8 +417,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void OnGamepadDisconnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
|
||||
|
@ -428,7 +434,7 @@ private:
|
|||
};
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path) {
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
|
@ -436,14 +442,14 @@ private:
|
|||
}
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
jconst file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadIcon(entry.icon);
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
jauto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
|
@ -514,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
|||
|
||||
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
|
||||
|
||||
const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
@ -526,83 +532,104 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
|||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jobject surf) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jobject surf) {
|
||||
EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_directory) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_directory) {
|
||||
Common::FS::SetAppDirectory(GetJString(env, j_directory));
|
||||
}
|
||||
|
||||
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_file) {
|
||||
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_file) {
|
||||
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
|
||||
}
|
||||
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
|
||||
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||
jstring custom_driver_name, jstring file_redirect_dir) {
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
|
||||
jstring hook_lib_dir,
|
||||
jstring custom_driver_dir,
|
||||
jstring custom_driver_name,
|
||||
jstring file_redirect_dir) {
|
||||
EmulationSession::GetInstance().InitializeGpuDriver(
|
||||
GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
|
||||
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||
constexpr auto KgslPath{"/dev/kgsl-3d0"};
|
||||
|
||||
return access(KgslPath, F_OK) == 0;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool SupportsCustomDriver() {
|
||||
return android_get_device_api_level() >= 28 && CheckKgslPresent();
|
||||
}
|
||||
|
||||
jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
|
||||
JNIEnv* env, jobject instance) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
// If the KGSL device exists custom drivers can be loaded using adrenotools
|
||||
return SupportsCustomDriver();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().UnPauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().PauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().ResetRomMetadata();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = true;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = false;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
|
||||
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_type) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
|
||||
|
@ -610,8 +637,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JN
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
|
@ -619,17 +645,16 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unu
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jint j_device,
|
||||
jint j_button, jint action) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_button,
|
||||
jint action) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
// Ensure gamepad is connected
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
|
@ -639,8 +664,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unus
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint stick_id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
|
@ -650,9 +674,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_un
|
|||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device,
|
||||
jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x,
|
||||
jfloat accel_y, jfloat accel_z) {
|
||||
JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
|
||||
jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
|
||||
j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||
|
@ -660,8 +683,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
|
||||
jbyteArray j_data) {
|
||||
jboolean isCopy{false};
|
||||
std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
|
||||
|
@ -673,108 +695,92 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNI
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnRemoveNfcTag();
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased(id);
|
||||
}
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
return env->NewStringUTF(title.c_str());
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
|
||||
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
|
||||
// Create the default config.ini.
|
||||
Config{};
|
||||
// Initialize the emulated system.
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file,
|
||||
[[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
|
||||
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
|
||||
Config{};
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
@ -788,8 +794,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JN
|
|||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key, jstring j_value) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
@ -803,20 +808,18 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEn
|
|||
env->ReleaseStringUTFChars(j_value, value.data());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(j_game_id, game_id.data());
|
||||
}
|
||||
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
|
||||
jdoubleArray j_stats = env->NewDoubleArray(4);
|
||||
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
const auto results = EmulationSession::GetInstance().PerfStats();
|
||||
jconst results = EmulationSession::GetInstance().PerfStats();
|
||||
|
||||
// Converting the structure into an array makes it easier to pass it to the frontend
|
||||
double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
|
||||
|
@ -828,11 +831,11 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]]
|
|||
return j_stats;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {}
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_path) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
|
||||
jstring j_path) {
|
||||
const std::string path = GetJString(env, j_path);
|
||||
|
||||
|
@ -843,8 +846,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unus
|
|||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
|
||||
LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
|
||||
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
||||
}
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
// Function calls from the Java side
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_device,
|
||||
jstring j_type);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
|
||||
JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jbyteArray j_data);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jfloat x, jfloat y,
|
||||
jboolean pressed);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
|
||||
jfloat x, jfloat y);
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_file);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetRegions(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetCompany(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_filename);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_directory);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeGpuDriver(
|
||||
JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||
jstring custom_driver_name, jstring file_redirect_dir);
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_SetSysDirectory(
|
||||
JNIEnv* env, jclass clazz, jstring path_);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetSysDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring path);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeEmulation(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
|
||||
jclass clazz);
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetProfiling(JNIEnv* env, jclass clazz,
|
||||
jboolean enable);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_WriteProfileResults(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_NotifyOrientationChange(
|
||||
JNIEnv* env, jclass clazz, jint layout_option, jint rotation);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2(
|
||||
JNIEnv* env, jclass clazz, jstring j_path);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jobject surf);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetUserSetting(
|
||||
JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key,
|
||||
jstring j_value);
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetUserSetting(
|
||||
JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key);
|
||||
|
||||
JNIEXPORT jdoubleArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_LogDeviceInfo(JNIEnv* env,
|
||||
jclass clazz);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardText(
|
||||
JNIEnv* env, jclass clazz, jstring j_text);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardInput(
|
||||
JNIEnv* env, jclass clazz, jint j_key_code);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
9
src/android/app/src/main/res/drawable/ic_pip_mute.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_mute.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,9v6h4l5,5V4l-5,5H7z" />
|
||||
</vector>
|
9
src/android/app/src/main/res/drawable/ic_pip_unmute.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_pip_unmute.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
|
||||
</vector>
|
|
@ -104,12 +104,14 @@
|
|||
<string name="share_log_missing">No log file found</string>
|
||||
<string name="install_game_content">Install game content</string>
|
||||
<string name="install_game_content_description">Install game updates or DLC</string>
|
||||
<string name="install_game_content_failure">Error installing file to NAND</string>
|
||||
<string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
|
||||
<string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
|
||||
<string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
|
||||
<string name="install_game_content_success">Game content installed successfully</string>
|
||||
<string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
|
||||
<string name="install_game_content_failure">Error installing file(s) to NAND</string>
|
||||
<string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
|
||||
<string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
|
||||
<string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
|
||||
<string name="install_game_content_failed_count">%1$d installation error(s)</string>
|
||||
<string name="install_game_content_success">Game content(s) installed successfully</string>
|
||||
<string name="install_game_content_success_install">%1$d installed successfully</string>
|
||||
<string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
|
||||
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
|
||||
|
||||
<!-- About screen strings -->
|
||||
|
@ -270,6 +272,7 @@
|
|||
<string name="fatal_error">Fatal Error</string>
|
||||
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
|
||||
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
|
||||
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
|
||||
|
||||
<!-- Region Names -->
|
||||
<string name="region_japan">Japan</string>
|
||||
|
@ -300,6 +303,15 @@
|
|||
<string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
|
||||
<string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
|
||||
|
||||
<!-- Memory Sizes -->
|
||||
<string name="memory_byte">Byte</string>
|
||||
<string name="memory_kilobyte">KB</string>
|
||||
<string name="memory_megabyte">MB</string>
|
||||
<string name="memory_gigabyte">GB</string>
|
||||
<string name="memory_terabyte">TB</string>
|
||||
<string name="memory_petabyte">PB</string>
|
||||
<string name="memory_exabyte">EB</string>
|
||||
|
||||
<!-- Renderer APIs -->
|
||||
<string name="renderer_vulkan">Vulkan</string>
|
||||
<string name="renderer_none">None</string>
|
||||
|
@ -387,6 +399,8 @@
|
|||
<string name="picture_in_picture_description">Minimize window when placed in the background</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="mute">Mute</string>
|
||||
<string name="unmute">Unmute</string>
|
||||
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">Licenses</string>
|
||||
|
|
|
@ -15,3 +15,6 @@ android.useAndroidX=true
|
|||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
# Android Gradle plugin 8.0.2
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include "audio_buffer.h"
|
||||
#include "audio_core/device/device_session.h"
|
||||
|
@ -48,7 +49,7 @@ public:
|
|||
*
|
||||
* @param out_buffers - The buffers which were registered.
|
||||
*/
|
||||
void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) {
|
||||
void RegisterBuffers(boost::container::static_vector<AudioBuffer, N>& out_buffers) {
|
||||
std::scoped_lock l{lock};
|
||||
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
|
||||
BufferAppendLimit - registered_count)};
|
||||
|
@ -162,7 +163,8 @@ public:
|
|||
* @param max_buffers - Maximum number of buffers to released.
|
||||
* @return The number of buffers released.
|
||||
*/
|
||||
u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) {
|
||||
u32 GetRegisteredAppendedBuffers(
|
||||
boost::container::static_vector<AudioBuffer, N>& buffers_flushed, u32 max_buffers) {
|
||||
std::scoped_lock l{lock};
|
||||
if (registered_count + appended_count == 0) {
|
||||
return 0;
|
||||
|
@ -270,7 +272,7 @@ public:
|
|||
*/
|
||||
bool FlushBuffers(u32& buffers_released) {
|
||||
std::scoped_lock l{lock};
|
||||
std::vector<AudioBuffer> buffers_flushed{};
|
||||
boost::container::static_vector<AudioBuffer, N> buffers_flushed{};
|
||||
|
||||
buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ void DeviceSession::ClearBuffers() {
|
|||
}
|
||||
}
|
||||
|
||||
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
|
||||
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
|
||||
for (const auto& buffer : buffers) {
|
||||
Sink::SinkBuffer new_buffer{
|
||||
.frames = buffer.size / (channel_count * sizeof(s16)),
|
||||
|
@ -88,13 +88,13 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
|
|||
.consumed = false,
|
||||
};
|
||||
|
||||
tmp_samples.resize_destructive(buffer.size / sizeof(s16));
|
||||
if (type == Sink::StreamType::In) {
|
||||
std::vector<s16> samples{};
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
} else {
|
||||
std::vector<s16> samples(buffer.size / sizeof(s16));
|
||||
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, tmp_samples.data(),
|
||||
buffer.size);
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -62,7 +63,7 @@ public:
|
|||
*
|
||||
* @param buffers - The buffers to play.
|
||||
*/
|
||||
void AppendBuffers(std::span<const AudioBuffer> buffers) const;
|
||||
void AppendBuffers(std::span<const AudioBuffer> buffers);
|
||||
|
||||
/**
|
||||
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
|
||||
|
@ -146,8 +147,8 @@ private:
|
|||
std::shared_ptr<Core::Timing::EventType> thread_event;
|
||||
/// Is this session initialised?
|
||||
bool initialized{};
|
||||
/// Buffer queue
|
||||
std::vector<AudioBuffer> buffer_queue{};
|
||||
/// Temporary sample buffer
|
||||
Common::ScratchBuffer<s16> tmp_samples{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/audio_event.h"
|
||||
#include "audio_core/audio_manager.h"
|
||||
#include "audio_core/in/audio_in_system.h"
|
||||
|
@ -89,7 +90,7 @@ Result System::Start() {
|
|||
session->Start();
|
||||
state = State::Started;
|
||||
|
||||
std::vector<AudioBuffer> buffers_to_flush{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
|
||||
buffers.RegisterBuffers(buffers_to_flush);
|
||||
session->AppendBuffers(buffers_to_flush);
|
||||
session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
|
||||
|
@ -134,7 +135,7 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
|
|||
|
||||
void System::RegisterBuffers() {
|
||||
if (state == State::Started) {
|
||||
std::vector<AudioBuffer> registered_buffers{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
|
||||
buffers.RegisterBuffers(registered_buffers);
|
||||
session->AppendBuffers(registered_buffers);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ Result System::Start() {
|
|||
session->Start();
|
||||
state = State::Started;
|
||||
|
||||
std::vector<AudioBuffer> buffers_to_flush{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
|
||||
buffers.RegisterBuffers(buffers_to_flush);
|
||||
session->AppendBuffers(buffers_to_flush);
|
||||
session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
|
||||
|
@ -134,7 +134,7 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
|
|||
|
||||
void System::RegisterBuffers() {
|
||||
if (state == State::Started) {
|
||||
std::vector<AudioBuffer> registered_buffers{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
|
||||
buffers.RegisterBuffers(registered_buffers);
|
||||
session->AppendBuffers(registered_buffers);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
|
||||
|
||||
|
@ -144,6 +143,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
|
|||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
|
||||
// 0.12 seconds (2304000 / 19200000)
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
@ -184,8 +184,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
|
|||
u64 max_time{max_process_time};
|
||||
if (index == 1 && command_buffer.applet_resource_user_id ==
|
||||
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
|
||||
max_time = max_process_time -
|
||||
Core::Timing::CyclesToNs(render_times_taken[0]).count();
|
||||
max_time = max_process_time - render_times_taken[0];
|
||||
if (render_times_taken[0] > max_process_time) {
|
||||
max_time = 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "audio_core/renderer/command/resample/resample.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
@ -27,6 +28,7 @@ constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
|||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
std::array<T, TempBufferSize> tmp_samples{};
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
|
@ -49,18 +51,17 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
const u64 size{channel_count * samples_to_decode};
|
||||
const u64 size_bytes{size * sizeof(T)};
|
||||
|
||||
std::vector<T> samples(size);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), size_bytes);
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
out_buffer[i] = samples[i * channel_count + req.target_channel];
|
||||
out_buffer[i] = tmp_samples[i * channel_count + req.target_channel];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
@ -73,17 +74,16 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
}
|
||||
|
||||
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
|
||||
std::vector<T> samples(samples_to_decode);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), samples_to_decode * sizeof(T));
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
|
||||
std::memcpy(out_buffer.data(), tmp_samples.data(), samples_to_decode * sizeof(s16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -101,6 +101,7 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
std::array<u8, TempBufferSize> wavebuffer{};
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
|
@ -138,9 +139,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
}
|
||||
|
||||
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
std::vector<u8> wavebuffer(size);
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
|
||||
wavebuffer.size());
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
|
@ -258,7 +257,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
std::vector<s16> temp_buffer(TempBufferSize, 0);
|
||||
std::array<s16, TempBufferSize> temp_buffer{};
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
|
||||
|
|
|
@ -44,8 +44,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2&
|
|||
|
||||
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state, bool enabled,
|
||||
std::vector<std::span<const s32>> input_buffers,
|
||||
std::vector<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
std::span<std::span<const s32>> input_buffers,
|
||||
std::span<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
if (enabled) {
|
||||
auto state_00{state.unk_00};
|
||||
auto state_04{state.unk_04};
|
||||
|
@ -124,8 +124,8 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
|
|||
}
|
||||
|
||||
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -51,7 +51,7 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
|
|||
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
|
||||
state.delay_lines[channel].sample_count = sample_count.to_int_floor();
|
||||
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
|
||||
if (state.delay_lines[channel].buffer.size() == 0) {
|
||||
if (state.delay_lines[channel].sample_count == 0) {
|
||||
state.delay_lines[channel].buffer.push_back(0);
|
||||
}
|
||||
state.delay_lines[channel].buffer_pos = 0;
|
||||
|
@ -74,8 +74,8 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
|
|||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
std::span<std::span<const s32>> inputs, std::span<std::span<s32>> outputs,
|
||||
const u32 sample_count) {
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
|
@ -153,8 +153,8 @@ static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::St
|
|||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
const bool enabled, std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
|
||||
if (!IsChannelCountValid(params.channel_count)) {
|
||||
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
|
||||
|
@ -208,8 +208,8 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce
|
|||
}
|
||||
|
||||
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -408,8 +408,8 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
|
|||
}
|
||||
|
||||
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -47,8 +47,8 @@ static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersio
|
|||
*/
|
||||
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const bool enabled,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count,
|
||||
LightLimiterInfo::StatisticsInternal* statistics) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
|
@ -147,8 +147,8 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP
|
|||
}
|
||||
|
||||
void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
@ -190,8 +190,8 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP
|
|||
}
|
||||
|
||||
void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -250,8 +250,8 @@ static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine&
|
|||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
static constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
@ -369,8 +369,8 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever
|
|||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
const bool enabled, std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
|
@ -412,8 +412,8 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
|
|||
}
|
||||
|
||||
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
|
@ -18,20 +17,18 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
|
|||
auto base{entry_address.translated_address};
|
||||
if (state == PerformanceState::Start) {
|
||||
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
|
||||
*start_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*start_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
} else if (state == PerformanceState::Stop) {
|
||||
auto processed_time_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
|
||||
auto entry_count_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
|
||||
|
||||
*processed_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*processed_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
(*entry_count_ptr)++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
|
|||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
std::vector<s16> output(processor.sample_count);
|
||||
std::array<s16, TargetSampleCount * MaxChannels> output{};
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
@ -33,7 +33,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
|
|||
}
|
||||
|
||||
processor.memory->WriteBlockUnsafe(address + pos, output.data(),
|
||||
output.size() * sizeof(s16));
|
||||
processor.sample_count * sizeof(s16));
|
||||
pos += static_cast<u32>(processor.sample_count * sizeof(s16));
|
||||
if (pos >= size) {
|
||||
pos = 0;
|
||||
|
|
|
@ -33,8 +33,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
|||
.consumed{false},
|
||||
};
|
||||
|
||||
std::vector<s16> samples(out_buffer.frames * input_count);
|
||||
|
||||
std::array<s16, TargetSampleCount * MaxChannels> samples{};
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
const auto offset{inputs[channel] * out_buffer.frames};
|
||||
|
||||
|
@ -45,7 +44,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
|||
}
|
||||
|
||||
out_buffer.tag = reinterpret_cast<u64>(samples.data());
|
||||
stream->AppendBuffer(out_buffer, samples);
|
||||
stream->AppendBuffer(out_buffer, {samples.data(), out_buffer.frames * input_count});
|
||||
|
||||
if (stream->IsPaused()) {
|
||||
stream->Start();
|
||||
|
|
|
@ -125,10 +125,10 @@ bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<s32> sorted_results{node_states.GetSortedResuls()};
|
||||
const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
|
||||
auto sorted_results{node_states.GetSortedResuls()};
|
||||
const auto result_size{std::min(count, static_cast<s32>(sorted_results.second))};
|
||||
for (s32 i = 0; i < result_size; i++) {
|
||||
sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
|
||||
sorted_mix_infos[i] = &mix_infos[sorted_results.first[i]];
|
||||
}
|
||||
|
||||
CalcMixBufferOffset();
|
||||
|
|
|
@ -134,8 +134,8 @@ u32 NodeStates::GetNodeCount() const {
|
|||
return node_count;
|
||||
}
|
||||
|
||||
std::vector<s32> NodeStates::GetSortedResuls() const {
|
||||
return {results.rbegin(), results.rbegin() + result_pos};
|
||||
std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls() const {
|
||||
return {results.rbegin(), result_pos};
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
|
|
@ -175,7 +175,7 @@ public:
|
|||
*
|
||||
* @return Vector of nodes in reverse order.
|
||||
*/
|
||||
std::vector<s32> GetSortedResuls() const;
|
||||
std::pair<std::span<u32>::reverse_iterator, size_t> GetSortedResuls() const;
|
||||
|
||||
private:
|
||||
/// Number of nodes in the graph
|
||||
|
|
|
@ -444,6 +444,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
|
|||
std::scoped_lock l{lock};
|
||||
|
||||
const auto start_time{core.CoreTiming().GetClockTicks()};
|
||||
std::memset(output.data(), 0, output.size());
|
||||
|
||||
InfoUpdater info_updater(input, output, process_handle, behavior);
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
|
||||
: SinkStream{system_, type_} {}
|
||||
~NullSinkStreamImpl() override {}
|
||||
void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
|
||||
void AppendBuffer(SinkBuffer&, std::span<s16>) override {}
|
||||
std::vector<s16> ReleaseBuffer(u64) override {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
|
||||
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
||||
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
|
||||
if (type == StreamType::In) {
|
||||
queue.enqueue(buffer);
|
||||
queued_buffers++;
|
||||
|
@ -67,15 +66,16 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
static_cast<s16>(std::clamp(right_sample, min, max));
|
||||
}
|
||||
|
||||
samples.resize(samples.size() / system_channels * device_channels);
|
||||
samples = samples.subspan(0, samples.size() / system_channels * device_channels);
|
||||
|
||||
} else if (system_channels == 2 && device_channels == 6) {
|
||||
// We need moar samples! Not all games will provide 6 channel audio.
|
||||
// TODO: Implement some upmixing here. Currently just passthrough, with other
|
||||
// channels left as silence.
|
||||
std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
|
||||
auto new_size = samples.size() / system_channels * device_channels;
|
||||
tmp_samples.resize_destructive(new_size);
|
||||
|
||||
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
|
||||
for (u32 read_index = 0, write_index = 0; read_index < new_size;
|
||||
read_index += system_channels, write_index += device_channels) {
|
||||
const auto left_sample{static_cast<s16>(std::clamp(
|
||||
static_cast<s32>(
|
||||
|
@ -83,7 +83,7 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
volume),
|
||||
min, max))};
|
||||
|
||||
new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
|
||||
tmp_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
|
||||
|
||||
const auto right_sample{static_cast<s16>(std::clamp(
|
||||
static_cast<s32>(
|
||||
|
@ -91,9 +91,9 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
volume),
|
||||
min, max))};
|
||||
|
||||
new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
|
||||
tmp_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
|
||||
}
|
||||
samples = std::move(new_samples);
|
||||
samples = std::span<s16>(tmp_samples);
|
||||
|
||||
} else if (volume != 1.0f) {
|
||||
for (u32 i = 0; i < samples.size(); i++) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "common/polyfill_thread.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/ring_buffer.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -170,7 +171,7 @@ public:
|
|||
* @param buffer - Audio buffer information to be queued.
|
||||
* @param samples - The s16 samples to be queue for playback.
|
||||
*/
|
||||
virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
|
||||
virtual void AppendBuffer(SinkBuffer& buffer, std::span<s16> samples);
|
||||
|
||||
/**
|
||||
* Release a buffer. Audio In only, will fill a buffer with recorded samples.
|
||||
|
@ -255,6 +256,8 @@ private:
|
|||
/// Signalled when ring buffer entries are consumed
|
||||
std::condition_variable_any release_cv;
|
||||
std::mutex release_mutex;
|
||||
/// Temporary buffer for appending samples when upmixing
|
||||
Common::ScratchBuffer<s16> tmp_samples{};
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
|
|
@ -172,6 +172,8 @@ if(ARCHITECTURE_x86_64)
|
|||
x64/cpu_wait.h
|
||||
x64/native_clock.cpp
|
||||
x64/native_clock.h
|
||||
x64/rdtsc.cpp
|
||||
x64/rdtsc.h
|
||||
x64/xbyak_abi.h
|
||||
x64/xbyak_util.h
|
||||
)
|
||||
|
|
|
@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
|
|||
|
||||
if (True(filter & DirEntryFilter::File) &&
|
||||
entry.status().type() == fs::file_type::regular) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
|
|||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
|
||||
if (True(filter & DirEntryFilter::File) &&
|
||||
entry.status().type() == fs::file_type::regular) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -605,6 +605,12 @@ fs::file_type GetEntryType(const fs::path& path) {
|
|||
}
|
||||
|
||||
u64 GetSize(const fs::path& path) {
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::GetSize(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
const auto file_size = fs::file_size(path, ec);
|
||||
|
|
|
@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
|
|||
* @returns A boolean value.
|
||||
* Return true to indicate whether the callback is successful, false otherwise.
|
||||
*/
|
||||
using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>;
|
||||
using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>;
|
||||
|
||||
} // namespace Common::FS
|
||||
|
|
|
@ -75,8 +75,10 @@ enum class DriverResult {
|
|||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
InvalidParameters,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Delayed,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
|
@ -86,7 +88,7 @@ enum class NfcState {
|
|||
NewAmiibo,
|
||||
WaitingForAmiibo,
|
||||
AmiiboRemoved,
|
||||
NotAnAmiibo,
|
||||
InvalidTagType,
|
||||
NotSupported,
|
||||
WrongDeviceState,
|
||||
WriteFailed,
|
||||
|
@ -218,8 +220,22 @@ struct CameraStatus {
|
|||
};
|
||||
|
||||
struct NfcStatus {
|
||||
NfcState state{};
|
||||
std::vector<u8> data{};
|
||||
NfcState state{NfcState::Unknown};
|
||||
u8 uuid_length;
|
||||
u8 protocol;
|
||||
u8 tag_type;
|
||||
std::array<u8, 10> uuid;
|
||||
};
|
||||
|
||||
struct MifareData {
|
||||
u8 command;
|
||||
u8 sector;
|
||||
std::array<u8, 0x6> key;
|
||||
std::array<u8, 0x10> data;
|
||||
};
|
||||
|
||||
struct MifareRequest {
|
||||
std::array<MifareData, 0x10> data;
|
||||
};
|
||||
|
||||
// List of buttons to be passed to Qt that can be translated
|
||||
|
@ -294,7 +310,7 @@ struct CallbackStatus {
|
|||
BatteryStatus battery_status{};
|
||||
VibrationStatus vibration_status{};
|
||||
CameraFormat camera_status{CameraFormat::None};
|
||||
NfcState nfc_status{NfcState::Unknown};
|
||||
NfcStatus nfc_status{};
|
||||
std::vector<u8> raw_data{};
|
||||
};
|
||||
|
||||
|
@ -356,9 +372,30 @@ public:
|
|||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState StartNfcPolling() {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState StopNfcPolling() {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request,
|
||||
[[maybe_unused]] MifareRequest& out_data) {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
};
|
||||
|
||||
/// An abstract class template for a factory that can create input devices.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
return push_count;
|
||||
}
|
||||
|
||||
std::size_t Push(const std::vector<T>& input) {
|
||||
std::size_t Push(const std::span<T> input) {
|
||||
return Push(input.data(), input.size());
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "common/concepts.h"
|
||||
#include "common/make_unique_for_overwrite.h"
|
||||
|
||||
namespace Common {
|
||||
|
@ -16,6 +19,12 @@ namespace Common {
|
|||
template <typename T>
|
||||
class ScratchBuffer {
|
||||
public:
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
using value_type = T;
|
||||
using element_type = T;
|
||||
using iterator_category = std::contiguous_iterator_tag;
|
||||
|
||||
ScratchBuffer() = default;
|
||||
|
||||
explicit ScratchBuffer(size_t initial_capacity)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <version>
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
#include <string_view>
|
||||
|
||||
|
@ -25,9 +28,19 @@ std::string GetTimeZoneString() {
|
|||
if (time_zone_index == 0) { // Auto
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
|
||||
const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
|
||||
std::string_view current_zone_name = current_zone->name();
|
||||
location_name = current_zone_name;
|
||||
try {
|
||||
const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
|
||||
std::string_view current_zone_name = current_zone->name();
|
||||
location_name = current_zone_name;
|
||||
} catch (std::runtime_error& runtime_error) {
|
||||
// VCRUNTIME will throw a runtime_error if the operating system's selected time zone
|
||||
// cannot be found
|
||||
location_name = Common::TimeZone::FindSystemTimeZone();
|
||||
LOG_WARNING(Common,
|
||||
"Error occurred when trying to determine system time zone:\n{}\nFalling "
|
||||
"back to hour offset \"{}\"",
|
||||
runtime_error.what(), location_name);
|
||||
}
|
||||
#else
|
||||
location_name = Common::TimeZone::FindSystemTimeZone();
|
||||
#endif
|
||||
|
|
|
@ -483,6 +483,7 @@ struct Values {
|
|||
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
|
||||
"astc_recompression"};
|
||||
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
|
||||
SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
|
||||
|
||||
SwitchableSetting<u8> bg_red{0, "bg_red"};
|
||||
SwitchableSetting<u8> bg_green{0, "bg_green"};
|
||||
|
@ -524,9 +525,16 @@ struct Values {
|
|||
Setting<bool> tas_loop{false, "tas_loop"};
|
||||
|
||||
Setting<bool> mouse_panning{false, "mouse_panning"};
|
||||
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_deadzone_x_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
|
||||
Setting<u8, true> mouse_panning_deadzone_y_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
|
||||
Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
|
||||
Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
|
||||
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
||||
Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
|
||||
|
||||
|
|
|
@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
|
|||
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
|
||||
static constexpr s64 Multiplier = 100;
|
||||
// Convert Windows epoch to Unix epoch.
|
||||
static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL;
|
||||
static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
|
||||
|
||||
FILETIME filetime;
|
||||
GetSystemTimePreciseAsFileTime(&filetime);
|
||||
return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
|
||||
static_cast<s64>(filetime.dwLowDateTime)) -
|
||||
WindowsEpochToUnixEpochNS;
|
||||
static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ void AppendCPUInfo(FieldCollection& fc) {
|
|||
add_field("CPU_Extension_x64_GFNI", caps.gfni);
|
||||
add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
|
||||
add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
|
||||
add_field("CPU_Extension_x64_MONITORX", caps.monitorx);
|
||||
add_field("CPU_Extension_x64_MOVBE", caps.movbe);
|
||||
add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
|
||||
add_field("CPU_Extension_x64_POPCNT", caps.popcnt);
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
is_set = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSet() {
|
||||
[[nodiscard]] bool IsSet() const {
|
||||
return is_set;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,88 +2,75 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
class StandardWallClock final : public WallClock {
|
||||
public:
|
||||
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
|
||||
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
|
||||
start_time{SteadyClock::Now()} {}
|
||||
explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override {
|
||||
std::chrono::nanoseconds GetTimeNS() const override {
|
||||
return SteadyClock::Now() - start_time;
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
|
||||
std::chrono::microseconds GetTimeUS() const override {
|
||||
return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
|
||||
std::chrono::milliseconds GetTimeMS() const override {
|
||||
return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
|
||||
}
|
||||
|
||||
u64 GetClockCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetCNTPCT() const override {
|
||||
return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
u64 GetCPUCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetGPUTick() const override {
|
||||
return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
void Pause([[maybe_unused]] bool is_paused) override {
|
||||
// Do nothing in this clock type.
|
||||
u64 GetHostTicksNow() const override {
|
||||
return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
|
||||
}
|
||||
|
||||
u64 GetHostTicksElapsed() const override {
|
||||
return static_cast<u64>(GetTimeNS().count());
|
||||
}
|
||||
|
||||
bool IsNative() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
SteadyClock::time_point start_time;
|
||||
};
|
||||
|
||||
std::unique_ptr<WallClock> CreateOptimalClock() {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
const auto& caps = GetCPUCaps();
|
||||
u64 rtsc_frequency = 0;
|
||||
if (caps.invariant_tsc) {
|
||||
rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
|
||||
}
|
||||
|
||||
// Fallback to StandardWallClock if the hardware TSC does not have the precision greater than:
|
||||
// - A nanosecond
|
||||
// - The emulated CPU frequency
|
||||
// - The emulated clock counter frequency (CNTFRQ)
|
||||
if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency ||
|
||||
rtsc_frequency <= emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
|
||||
emulated_clock_frequency);
|
||||
if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
|
||||
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
|
||||
} else {
|
||||
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
|
||||
rtsc_frequency);
|
||||
// Fallback to StandardWallClock if the hardware TSC
|
||||
// - Is not invariant
|
||||
// - Is not more precise than GPUTickFreq
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
return std::make_unique<StandardWallClock>();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock() {
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
|
@ -12,50 +13,82 @@ namespace Common {
|
|||
|
||||
class WallClock {
|
||||
public:
|
||||
static constexpr u64 NS_RATIO = 1'000'000'000;
|
||||
static constexpr u64 US_RATIO = 1'000'000;
|
||||
static constexpr u64 MS_RATIO = 1'000;
|
||||
static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
|
||||
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
|
||||
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
|
||||
|
||||
virtual ~WallClock() = default;
|
||||
|
||||
/// Returns current wall time in nanoseconds
|
||||
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
|
||||
/// @returns The time in nanoseconds since the construction of this clock.
|
||||
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
|
||||
|
||||
/// Returns current wall time in microseconds
|
||||
[[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
|
||||
/// @returns The time in microseconds since the construction of this clock.
|
||||
virtual std::chrono::microseconds GetTimeUS() const = 0;
|
||||
|
||||
/// Returns current wall time in milliseconds
|
||||
[[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
|
||||
/// @returns The time in milliseconds since the construction of this clock.
|
||||
virtual std::chrono::milliseconds GetTimeMS() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated clock cycles
|
||||
[[nodiscard]] virtual u64 GetClockCycles() = 0;
|
||||
/// @returns The guest CNTPCT ticks since the construction of this clock.
|
||||
virtual u64 GetCNTPCT() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated cpu cycles
|
||||
[[nodiscard]] virtual u64 GetCPUCycles() = 0;
|
||||
/// @returns The guest GPU ticks since the construction of this clock.
|
||||
virtual u64 GetGPUTick() const = 0;
|
||||
|
||||
virtual void Pause(bool is_paused) = 0;
|
||||
/// @returns The raw host timer ticks since an indeterminate epoch.
|
||||
virtual u64 GetHostTicksNow() const = 0;
|
||||
|
||||
/// Tells if the wall clock, uses the host CPU's hardware clock
|
||||
[[nodiscard]] bool IsNative() const {
|
||||
return is_native;
|
||||
/// @returns The raw host timer ticks since the construction of this clock.
|
||||
virtual u64 GetHostTicksElapsed() const = 0;
|
||||
|
||||
/// @returns Whether the clock directly uses the host's hardware clock.
|
||||
virtual bool IsNative() const = 0;
|
||||
|
||||
static inline u64 NSToCNTPCT(u64 ns) {
|
||||
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 NSToGPUTick(u64 ns) {
|
||||
return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
// Cycle Timing
|
||||
|
||||
static inline u64 CPUTickToNS(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToUS(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToCNTPCT(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToGPUTick(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_)
|
||||
: emulated_cpu_frequency{emulated_cpu_frequency_},
|
||||
emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {}
|
||||
using NsRatio = std::nano;
|
||||
using UsRatio = std::micro;
|
||||
using MsRatio = std::milli;
|
||||
|
||||
u64 emulated_cpu_frequency;
|
||||
u64 emulated_clock_frequency;
|
||||
using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
|
||||
using NsToMsRatio = std::ratio_divide<std::nano, std::milli>;
|
||||
using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
|
||||
using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>;
|
||||
|
||||
private:
|
||||
bool is_native;
|
||||
// Cycle Timing
|
||||
|
||||
using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>;
|
||||
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
|
||||
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
|
||||
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateOptimalClock();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock();
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
@ -167,6 +168,7 @@ static CPUCaps Detect() {
|
|||
__cpuid(cpu_id, 0x80000001);
|
||||
caps.lzcnt = Common::Bit<5>(cpu_id[2]);
|
||||
caps.fma4 = Common::Bit<16>(cpu_id[2]);
|
||||
caps.monitorx = Common::Bit<29>(cpu_id[2]);
|
||||
}
|
||||
|
||||
if (max_ex_fn >= 0x80000007) {
|
||||
|
@ -187,6 +189,8 @@ static CPUCaps Detect() {
|
|||
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
|
||||
caps.tsc_crystal_ratio_numerator /
|
||||
caps.tsc_crystal_ratio_denominator;
|
||||
} else {
|
||||
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ struct CPUCaps {
|
|||
bool gfni : 1;
|
||||
bool invariant_tsc : 1;
|
||||
bool lzcnt : 1;
|
||||
bool monitorx : 1;
|
||||
bool movbe : 1;
|
||||
bool pclmulqdq : 1;
|
||||
bool popcnt : 1;
|
||||
|
|
|
@ -9,58 +9,64 @@
|
|||
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/cpu_wait.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace {
|
||||
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
// At 1 GHz, 100K cycles is 100us
|
||||
// At 2 GHz, 100K cycles is 50us
|
||||
// At 4 GHz, 100K cycles is 25us
|
||||
constexpr auto PauseCycles = 100'000U;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
__forceinline static void TPAUSE() {
|
||||
static constexpr auto RequestC02State = 0U;
|
||||
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
|
||||
}
|
||||
|
||||
__forceinline static void TPAUSE() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
// At 1 GHz, 100K cycles is 100us
|
||||
// At 2 GHz, 100K cycles is 50us
|
||||
// At 4 GHz, 100K cycles is 25us
|
||||
static constexpr auto PauseCycles = 100'000;
|
||||
_tpause(0, FencedRDTSC() + PauseCycles);
|
||||
__forceinline static void MWAITX() {
|
||||
static constexpr auto EnableWaitTimeFlag = 1U << 1;
|
||||
static constexpr auto RequestC1State = 0U;
|
||||
|
||||
// monitor_var should be aligned to a cache line.
|
||||
alignas(64) u64 monitor_var{};
|
||||
_mm_monitorx(&monitor_var, 0, 0);
|
||||
_mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
|
||||
}
|
||||
#else
|
||||
static u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
|
||||
static void TPAUSE() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
// At 1 GHz, 100K cycles is 100us
|
||||
// At 2 GHz, 100K cycles is 50us
|
||||
// At 4 GHz, 100K cycles is 25us
|
||||
static constexpr auto PauseCycles = 100'000;
|
||||
static constexpr auto RequestC02State = 0U;
|
||||
const auto tsc = FencedRDTSC() + PauseCycles;
|
||||
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
|
||||
const auto edx = static_cast<u32>(tsc >> 32);
|
||||
asm volatile("tpause %0" : : "r"(0), "d"(edx), "a"(eax));
|
||||
asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
|
||||
}
|
||||
|
||||
static void MWAITX() {
|
||||
static constexpr auto EnableWaitTimeFlag = 1U << 1;
|
||||
static constexpr auto RequestC1State = 0U;
|
||||
|
||||
// monitor_var should be aligned to a cache line.
|
||||
alignas(64) u64 monitor_var{};
|
||||
asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
|
||||
asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
|
||||
}
|
||||
#endif
|
||||
|
||||
void MicroSleep() {
|
||||
static const bool has_waitpkg = GetCPUCaps().waitpkg;
|
||||
static const bool has_monitorx = GetCPUCaps().monitorx;
|
||||
|
||||
if (has_waitpkg) {
|
||||
TPAUSE();
|
||||
} else if (has_monitorx) {
|
||||
MWAITX();
|
||||
} else {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
|
|
@ -1,164 +1,50 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "common/atomic_ops.h"
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace Common {
|
||||
NativeClock::NativeClock(u64 rdtsc_frequency_)
|
||||
: start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
|
||||
ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
|
||||
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
|
||||
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
|
||||
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
|
||||
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <u64 Nearest>
|
||||
static u64 RoundToNearest(u64 value) {
|
||||
const auto mod = value % Nearest;
|
||||
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
|
||||
}
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
FencedRDTSC();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
FencedRDTSC();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 250 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{250});
|
||||
const auto end_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return RoundToNearest<1000>(tsc_freq);
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
|
||||
}
|
||||
|
||||
namespace X64 {
|
||||
NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
u64 rtsc_frequency_)
|
||||
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
|
||||
rtsc_frequency_} {
|
||||
// Thread to re-adjust the RDTSC frequency after 10 seconds has elapsed.
|
||||
time_sync_thread = std::jthread{[this](std::stop_token token) {
|
||||
// Get the current time.
|
||||
const auto start_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 10 seconds.
|
||||
if (!Common::StoppableTimedWait(token, std::chrono::seconds{10})) {
|
||||
return;
|
||||
}
|
||||
const auto end_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
rtsc_frequency = tsc_freq;
|
||||
CalculateAndSetFactors();
|
||||
}};
|
||||
|
||||
time_point.inner.last_measure = FencedRDTSC();
|
||||
time_point.inner.accumulated_ticks = 0U;
|
||||
CalculateAndSetFactors();
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
TimePoint new_time_point{};
|
||||
TimePoint current_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
const u64 current_measure = FencedRDTSC();
|
||||
u64 diff = current_measure - current_time_point.inner.last_measure;
|
||||
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
|
||||
new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
|
||||
? current_measure
|
||||
: current_time_point.inner.last_measure;
|
||||
new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
|
||||
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
|
||||
current_time_point.pack, current_time_point.pack));
|
||||
return new_time_point.inner.accumulated_ticks;
|
||||
u64 NativeClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
|
||||
}
|
||||
|
||||
void NativeClock::Pause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
TimePoint current_time_point{};
|
||||
TimePoint new_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
new_time_point.pack = current_time_point.pack;
|
||||
new_time_point.inner.last_measure = FencedRDTSC();
|
||||
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
|
||||
current_time_point.pack, current_time_point.pack));
|
||||
}
|
||||
u64 NativeClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksNow() const {
|
||||
return FencedRDTSC();
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksElapsed() const {
|
||||
return FencedRDTSC() - start_ticks;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
|
||||
bool NativeClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 NativeClock::GetClockCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyHigh(rtsc_value, clock_rtsc_factor);
|
||||
}
|
||||
|
||||
u64 NativeClock::GetCPUCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
|
||||
}
|
||||
|
||||
void NativeClock::CalculateAndSetFactors() {
|
||||
ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
|
||||
us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
|
||||
ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency);
|
||||
clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
|
||||
cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
|
|
@ -3,58 +3,39 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace X64 {
|
||||
class NativeClock final : public WallClock {
|
||||
public:
|
||||
explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
u64 rtsc_frequency_);
|
||||
explicit NativeClock(u64 rdtsc_frequency_);
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override;
|
||||
std::chrono::nanoseconds GetTimeNS() const override;
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override;
|
||||
std::chrono::microseconds GetTimeUS() const override;
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override;
|
||||
std::chrono::milliseconds GetTimeMS() const override;
|
||||
|
||||
u64 GetClockCycles() override;
|
||||
u64 GetCNTPCT() const override;
|
||||
|
||||
u64 GetCPUCycles() override;
|
||||
u64 GetGPUTick() const override;
|
||||
|
||||
void Pause(bool is_paused) override;
|
||||
u64 GetHostTicksNow() const override;
|
||||
|
||||
u64 GetHostTicksElapsed() const override;
|
||||
|
||||
bool IsNative() const override;
|
||||
|
||||
private:
|
||||
u64 GetRTSC();
|
||||
u64 start_ticks;
|
||||
u64 rdtsc_frequency;
|
||||
|
||||
void CalculateAndSetFactors();
|
||||
|
||||
union alignas(16) TimePoint {
|
||||
TimePoint() : pack{} {}
|
||||
u128 pack{};
|
||||
struct Inner {
|
||||
u64 last_measure{};
|
||||
u64 accumulated_ticks{};
|
||||
} inner;
|
||||
};
|
||||
|
||||
TimePoint time_point;
|
||||
|
||||
// factors
|
||||
u64 clock_rtsc_factor{};
|
||||
u64 cpu_rtsc_factor{};
|
||||
u64 ns_rtsc_factor{};
|
||||
u64 us_rtsc_factor{};
|
||||
u64 ms_rtsc_factor{};
|
||||
|
||||
u64 rtsc_frequency;
|
||||
|
||||
std::jthread time_sync_thread;
|
||||
u64 ns_rdtsc_factor;
|
||||
u64 us_rdtsc_factor;
|
||||
u64 ms_rdtsc_factor;
|
||||
u64 cntpct_rdtsc_factor;
|
||||
u64 gputick_rdtsc_factor;
|
||||
};
|
||||
} // namespace X64
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
|
39
src/common/x64/rdtsc.cpp
Normal file
39
src/common/x64/rdtsc.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
template <u64 Nearest>
|
||||
static u64 RoundToNearest(u64 value) {
|
||||
const auto mod = value % Nearest;
|
||||
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
|
||||
}
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
FencedRDTSC();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
FencedRDTSC();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 100 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
const auto end_time = RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return RoundToNearest<100'000>(tsc_freq);
|
||||
}
|
||||
|
||||
} // namespace Common::X64
|
37
src/common/x64/rdtsc.h
Normal file
37
src/common/x64/rdtsc.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static inline u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
#endif
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common::X64
|
|
@ -14,7 +14,6 @@ add_library(core STATIC
|
|||
core.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
core_timing_util.h
|
||||
cpu_manager.cpp
|
||||
cpu_manager.h
|
||||
crypto/aes_util.cpp
|
||||
|
|
|
@ -322,11 +322,6 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
// TODO: remove when fixed in dynarmic
|
||||
config.optimizations &= ~Dynarmic::OptimizationFlag::BlockLinking;
|
||||
#endif
|
||||
|
||||
return std::make_unique<Dynarmic::A32::Jit>(config);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
constexpr s64 MAX_SLICE_LENGTH = 4000;
|
||||
constexpr s64 MAX_SLICE_LENGTH = 10000;
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
|
@ -45,9 +44,7 @@ struct CoreTiming::Event {
|
|||
}
|
||||
};
|
||||
|
||||
CoreTiming::CoreTiming()
|
||||
: cpu_clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)},
|
||||
event_clock{Common::CreateStandardWallClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
|
||||
CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {}
|
||||
|
||||
CoreTiming::~CoreTiming() {
|
||||
Reset();
|
||||
|
@ -68,7 +65,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
|||
on_thread_init = std::move(on_thread_init_);
|
||||
event_fifo_id = 0;
|
||||
shutting_down = false;
|
||||
ticks = 0;
|
||||
cpu_ticks = 0;
|
||||
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
|
@ -173,38 +170,30 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
|||
}
|
||||
|
||||
void CoreTiming::AddTicks(u64 ticks_to_add) {
|
||||
ticks += ticks_to_add;
|
||||
downcount -= static_cast<s64>(ticks);
|
||||
cpu_ticks += ticks_to_add;
|
||||
downcount -= static_cast<s64>(cpu_ticks);
|
||||
}
|
||||
|
||||
void CoreTiming::Idle() {
|
||||
if (!event_queue.empty()) {
|
||||
const u64 next_event_time = event_queue.front().time;
|
||||
const u64 next_ticks = nsToCycles(std::chrono::nanoseconds(next_event_time)) + 10U;
|
||||
if (next_ticks > ticks) {
|
||||
ticks = next_ticks;
|
||||
}
|
||||
return;
|
||||
}
|
||||
ticks += 1000U;
|
||||
cpu_ticks += 1000U;
|
||||
}
|
||||
|
||||
void CoreTiming::ResetTicks() {
|
||||
downcount = MAX_SLICE_LENGTH;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetCPUTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetCPUCycles();
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetClockCycles();
|
||||
return clock->GetCNTPCT();
|
||||
}
|
||||
return CpuCyclesToClockCycles(ticks);
|
||||
return Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetGPUTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return clock->GetGPUTick();
|
||||
}
|
||||
return Common::WallClock::CPUTickToGPUTick(cpu_ticks);
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
|
@ -297,9 +286,7 @@ void CoreTiming::ThreadLoop() {
|
|||
}
|
||||
|
||||
paused_set = true;
|
||||
event_clock->Pause(true);
|
||||
pause_event.Wait();
|
||||
event_clock->Pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,25 +302,18 @@ void CoreTiming::Reset() {
|
|||
has_started = false;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CoreTiming::GetCPUTimeNs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetTimeNS();
|
||||
}
|
||||
return CyclesToNs(ticks);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return event_clock->GetTimeNS();
|
||||
return clock->GetTimeNS();
|
||||
}
|
||||
return CyclesToNs(ticks);
|
||||
return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return event_clock->GetTimeUS();
|
||||
return clock->GetTimeUS();
|
||||
}
|
||||
return CyclesToUs(ticks);
|
||||
return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
|
|
@ -116,14 +116,11 @@ public:
|
|||
return downcount;
|
||||
}
|
||||
|
||||
/// Returns current time in emulated CPU cycles
|
||||
u64 GetCPUTicks() const;
|
||||
|
||||
/// Returns current time in emulated in Clock cycles
|
||||
/// Returns the current CNTPCT tick value.
|
||||
u64 GetClockTicks() const;
|
||||
|
||||
/// Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds GetCPUTimeNs() const;
|
||||
/// Returns the current GPU tick value.
|
||||
u64 GetGPUTicks() const;
|
||||
|
||||
/// Returns current time in microseconds.
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
|
@ -142,8 +139,7 @@ private:
|
|||
|
||||
void Reset();
|
||||
|
||||
std::unique_ptr<Common::WallClock> cpu_clock;
|
||||
std::unique_ptr<Common::WallClock> event_clock;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
s64 global_timer = 0;
|
||||
|
||||
|
@ -171,7 +167,7 @@ private:
|
|||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
u64 ticks{};
|
||||
u64 cpu_ticks{};
|
||||
s64 downcount{};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
namespace detail {
|
||||
constexpr u64 CNTFREQ_ADJUSTED = Hardware::CNTFREQ / 1000;
|
||||
constexpr u64 BASE_CLOCK_RATE_ADJUSTED = Hardware::BASE_CLOCK_RATE / 1000;
|
||||
} // namespace detail
|
||||
|
||||
[[nodiscard]] constexpr s64 msToCycles(std::chrono::milliseconds ms) {
|
||||
return ms.count() * detail::BASE_CLOCK_RATE_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr s64 usToCycles(std::chrono::microseconds us) {
|
||||
return us.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr s64 nsToCycles(std::chrono::nanoseconds ns) {
|
||||
return ns.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 msToClockCycles(std::chrono::milliseconds ms) {
|
||||
return static_cast<u64>(ms.count()) * detail::CNTFREQ_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 usToClockCycles(std::chrono::microseconds us) {
|
||||
return us.count() * detail::CNTFREQ_ADJUSTED / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 nsToClockCycles(std::chrono::nanoseconds ns) {
|
||||
return ns.count() * detail::CNTFREQ_ADJUSTED / 1000000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
return ticks * detail::CNTFREQ_ADJUSTED / detail::BASE_CLOCK_RATE_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::milliseconds CyclesToMs(s64 cycles) {
|
||||
return std::chrono::milliseconds(cycles / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::nanoseconds CyclesToNs(s64 cycles) {
|
||||
return std::chrono::nanoseconds(cycles * 1000000 / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::microseconds CyclesToUs(s64 cycles) {
|
||||
return std::chrono::microseconds(cycles * 1000 / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
|
@ -153,7 +153,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
|
||||
std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
|
||||
if (load_dir != nullptr && load_dir->GetSize() > 0) {
|
||||
if (load_dir != nullptr) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
|
@ -354,8 +354,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
|||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
((load_dir == nullptr || load_dir->GetSize() <= 0) &&
|
||||
(sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {
|
||||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -496,7 +495,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
|
|||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
if (mod_dir != nullptr) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
|
||||
|
@ -540,7 +539,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
|
|||
|
||||
// SDMC mod directory (RomFS LayeredFS)
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0) {
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
|
|
|
@ -150,23 +150,29 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
|
|||
while (cur_length > 0 && it != concatenation_map.end()) {
|
||||
// Check if we can read the file at this position.
|
||||
const auto& file = it->file;
|
||||
const u64 file_offset = it->offset;
|
||||
const u64 map_offset = it->offset;
|
||||
const u64 file_size = file->GetSize();
|
||||
|
||||
if (cur_offset >= file_offset + file_size) {
|
||||
if (cur_offset > map_offset + file_size) {
|
||||
// Entirely out of bounds read.
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the file at this position.
|
||||
const u64 intended_read_size = std::min<u64>(cur_length, file_size);
|
||||
const u64 file_seek = cur_offset - map_offset;
|
||||
const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
|
||||
const u64 actual_read_size =
|
||||
file->Read(data + (cur_offset - offset), intended_read_size, cur_offset - file_offset);
|
||||
file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
|
||||
|
||||
// Update tracking.
|
||||
cur_offset += actual_read_size;
|
||||
cur_length -= actual_read_size;
|
||||
it++;
|
||||
|
||||
// If we encountered a short read, we're done.
|
||||
if (actual_read_size < intended_read_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cur_offset - offset;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
|
||||
// For FileTimeStampRaw
|
||||
|
@ -72,8 +73,10 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
|
|||
return VfsEntryType::File;
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
|
||||
Mode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
if (auto it = cache.find(path); it != cache.end()) {
|
||||
if (auto file = it->second.lock(); file) {
|
||||
|
@ -81,23 +84,30 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!FS::Exists(path) || !FS::IsFile(path)) {
|
||||
if (!size && !FS::IsFile(path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto reference = std::make_unique<FileReference>();
|
||||
this->InsertReferenceIntoList(*reference);
|
||||
this->InsertReferenceIntoListLocked(*reference);
|
||||
|
||||
auto file =
|
||||
std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms));
|
||||
auto file = std::shared_ptr<RealVfsFile>(
|
||||
new RealVfsFile(*this, std::move(reference), path, perms, size));
|
||||
cache[path] = file;
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
return OpenFileFromEntry(path_, {}, perms);
|
||||
}
|
||||
|
||||
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
cache.erase(path);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(path);
|
||||
}
|
||||
|
||||
// Current usages of CreateFile expect to delete the contents of an existing file.
|
||||
if (FS::IsFile(path)) {
|
||||
|
@ -127,8 +137,11 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_
|
|||
VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
|
||||
const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
|
||||
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
|
||||
cache.erase(old_path);
|
||||
cache.erase(new_path);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(old_path);
|
||||
cache.erase(new_path);
|
||||
}
|
||||
if (!FS::RenameFile(old_path, new_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -137,7 +150,10 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
|
|||
|
||||
bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
cache.erase(path);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(path);
|
||||
}
|
||||
return FS::RemoveFile(path);
|
||||
}
|
||||
|
||||
|
@ -176,14 +192,17 @@ bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
|
|||
return FS::RemoveDirRecursively(path);
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms,
|
||||
FileReference& reference) {
|
||||
std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::string& path,
|
||||
Mode perms,
|
||||
FileReference& reference) {
|
||||
std::unique_lock lk{list_lock};
|
||||
|
||||
// Temporarily remove from list.
|
||||
this->RemoveReferenceFromList(reference);
|
||||
this->RemoveReferenceFromListLocked(reference);
|
||||
|
||||
// Restore file if needed.
|
||||
if (!reference.file) {
|
||||
this->EvictSingleReference();
|
||||
this->EvictSingleReferenceLocked();
|
||||
|
||||
reference.file =
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
|
||||
|
@ -193,12 +212,16 @@ void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms,
|
|||
}
|
||||
|
||||
// Reinsert into list.
|
||||
this->InsertReferenceIntoList(reference);
|
||||
this->InsertReferenceIntoListLocked(reference);
|
||||
|
||||
return lk;
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
// Remove from list.
|
||||
this->RemoveReferenceFromList(*reference);
|
||||
this->RemoveReferenceFromListLocked(*reference);
|
||||
|
||||
// Close the file.
|
||||
if (reference->file) {
|
||||
|
@ -207,14 +230,14 @@ void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference
|
|||
}
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::EvictSingleReference() {
|
||||
void RealVfsFilesystem::EvictSingleReferenceLocked() {
|
||||
if (num_open_files < MaxOpenFiles || open_references.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get and remove from list.
|
||||
auto& reference = open_references.back();
|
||||
this->RemoveReferenceFromList(reference);
|
||||
this->RemoveReferenceFromListLocked(reference);
|
||||
|
||||
// Close the file.
|
||||
if (reference.file) {
|
||||
|
@ -223,10 +246,10 @@ void RealVfsFilesystem::EvictSingleReference() {
|
|||
}
|
||||
|
||||
// Reinsert into closed list.
|
||||
this->InsertReferenceIntoList(reference);
|
||||
this->InsertReferenceIntoListLocked(reference);
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) {
|
||||
void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) {
|
||||
if (reference.file) {
|
||||
open_references.push_front(reference);
|
||||
} else {
|
||||
|
@ -234,7 +257,7 @@ void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) {
|
|||
}
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) {
|
||||
void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) {
|
||||
if (reference.file) {
|
||||
open_references.erase(open_references.iterator_to(reference));
|
||||
} else {
|
||||
|
@ -243,10 +266,10 @@ void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) {
|
|||
}
|
||||
|
||||
RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
|
||||
const std::string& path_, Mode perms_)
|
||||
const std::string& path_, Mode perms_, std::optional<u64> size_)
|
||||
: base(base_), reference(std::move(reference_)), path(path_),
|
||||
parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)),
|
||||
perms(perms_) {}
|
||||
size(size_), perms(perms_) {}
|
||||
|
||||
RealVfsFile::~RealVfsFile() {
|
||||
base.DropReference(std::move(reference));
|
||||
|
@ -257,12 +280,15 @@ std::string RealVfsFile::GetName() const {
|
|||
}
|
||||
|
||||
std::size_t RealVfsFile::GetSize() const {
|
||||
base.RefreshReference(path, perms, *reference);
|
||||
return reference->file ? reference->file->GetSize() : 0;
|
||||
if (size) {
|
||||
return *size;
|
||||
}
|
||||
return FS::GetSize(path);
|
||||
}
|
||||
|
||||
bool RealVfsFile::Resize(std::size_t new_size) {
|
||||
base.RefreshReference(path, perms, *reference);
|
||||
size.reset();
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
return reference->file ? reference->file->SetSize(new_size) : false;
|
||||
}
|
||||
|
||||
|
@ -279,7 +305,7 @@ bool RealVfsFile::IsReadable() const {
|
|||
}
|
||||
|
||||
std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
|
||||
base.RefreshReference(path, perms, *reference);
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -287,7 +313,8 @@ std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset)
|
|||
}
|
||||
|
||||
std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
|
||||
base.RefreshReference(path, perms, *reference);
|
||||
size.reset();
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -309,10 +336,11 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
|
|||
|
||||
std::vector<VirtualFile> out;
|
||||
|
||||
const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
|
||||
const auto full_path_string = FS::PathToUTF8String(full_path);
|
||||
const FS::DirEntryCallable callback = [this,
|
||||
&out](const std::filesystem::directory_entry& entry) {
|
||||
const auto full_path_string = FS::PathToUTF8String(entry.path());
|
||||
|
||||
out.emplace_back(base.OpenFile(full_path_string, perms));
|
||||
out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms));
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -330,8 +358,9 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
|
|||
|
||||
std::vector<VirtualDir> out;
|
||||
|
||||
const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
|
||||
const auto full_path_string = FS::PathToUTF8String(full_path);
|
||||
const FS::DirEntryCallable callback = [this,
|
||||
&out](const std::filesystem::directory_entry& entry) {
|
||||
const auto full_path_string = FS::PathToUTF8String(entry.path());
|
||||
|
||||
out.emplace_back(base.OpenDirectory(full_path_string, perms));
|
||||
|
||||
|
@ -483,12 +512,10 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
|
|||
|
||||
std::map<std::string, VfsEntryType, std::less<>> out;
|
||||
|
||||
const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) {
|
||||
const auto filename = FS::PathToUTF8String(full_path.filename());
|
||||
|
||||
const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
|
||||
const auto filename = FS::PathToUTF8String(entry.path().filename());
|
||||
out.insert_or_assign(filename,
|
||||
FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
|
||||
|
||||
entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include "common/intrusive_list.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
|
@ -20,6 +22,8 @@ struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
|
|||
};
|
||||
|
||||
class RealVfsFile;
|
||||
class RealVfsDirectory;
|
||||
|
||||
class RealVfsFilesystem : public VfsFilesystem {
|
||||
public:
|
||||
RealVfsFilesystem();
|
||||
|
@ -45,17 +49,24 @@ private:
|
|||
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
ReferenceListType open_references;
|
||||
ReferenceListType closed_references;
|
||||
std::mutex list_lock;
|
||||
size_t num_open_files{};
|
||||
|
||||
private:
|
||||
friend class RealVfsFile;
|
||||
void RefreshReference(const std::string& path, Mode perms, FileReference& reference);
|
||||
std::unique_lock<std::mutex> RefreshReference(const std::string& path, Mode perms,
|
||||
FileReference& reference);
|
||||
void DropReference(std::unique_ptr<FileReference>&& reference);
|
||||
void EvictSingleReference();
|
||||
|
||||
private:
|
||||
void InsertReferenceIntoList(FileReference& reference);
|
||||
void RemoveReferenceFromList(FileReference& reference);
|
||||
friend class RealVfsDirectory;
|
||||
VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size,
|
||||
Mode perms = Mode::Read);
|
||||
|
||||
private:
|
||||
void EvictSingleReferenceLocked();
|
||||
void InsertReferenceIntoListLocked(FileReference& reference);
|
||||
void RemoveReferenceFromListLocked(FileReference& reference);
|
||||
};
|
||||
|
||||
// An implementation of VfsFile that represents a file on the user's computer.
|
||||
|
@ -78,13 +89,14 @@ public:
|
|||
|
||||
private:
|
||||
RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
|
||||
const std::string& path, Mode perms = Mode::Read);
|
||||
const std::string& path, Mode perms = Mode::Read, std::optional<u64> size = {});
|
||||
|
||||
RealVfsFilesystem& base;
|
||||
std::unique_ptr<FileReference> reference;
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
std::optional<u64> size;
|
||||
Mode perms;
|
||||
};
|
||||
|
||||
|
|
|
@ -149,12 +149,16 @@ void EmulatedController::LoadDevices() {
|
|||
|
||||
camera_params[0] = right_joycon;
|
||||
camera_params[0].Set("camera", true);
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
nfc_params[1] = right_joycon;
|
||||
nfc_params[1].Set("nfc", true);
|
||||
|
||||
// Only map virtual devices to the first controller
|
||||
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
}
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
output_params[RightIndex] = right_joycon;
|
||||
output_params[2] = camera_params[1];
|
||||
|
@ -1176,10 +1180,7 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
|||
return;
|
||||
}
|
||||
|
||||
controller.nfc_state = {
|
||||
controller.nfc_values.state,
|
||||
controller.nfc_values.data,
|
||||
};
|
||||
controller.nfc_state = controller.nfc_values;
|
||||
}
|
||||
|
||||
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
|
||||
|
@ -1249,6 +1250,11 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
|
|||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
|
||||
|
||||
// Restore previous state
|
||||
if (mapped_nfc_result != Common::Input::DriverResult::Success) {
|
||||
right_output_device->SetPollingMode(Common::Input::PollingMode::Active);
|
||||
}
|
||||
|
||||
if (virtual_nfc_result == Common::Input::DriverResult::Success) {
|
||||
return virtual_nfc_result;
|
||||
}
|
||||
|
@ -1308,6 +1314,79 @@ bool EmulatedController::HasNfc() const {
|
|||
return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
|
||||
}
|
||||
|
||||
bool EmulatedController::AddNfcHandle() {
|
||||
nfc_handles++;
|
||||
return SetPollingMode(EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::NFC) ==
|
||||
Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::RemoveNfcHandle() {
|
||||
nfc_handles--;
|
||||
if (nfc_handles <= 0) {
|
||||
return SetPollingMode(EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active) ==
|
||||
Common::Input::DriverResult::Success;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulatedController::StartNfcPolling() {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
const auto device_result = nfc_output_device->StartNfcPolling();
|
||||
const auto virtual_device_result = nfc_virtual_output_device->StartNfcPolling();
|
||||
|
||||
return device_result == Common::Input::NfcState::Success ||
|
||||
virtual_device_result == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::StopNfcPolling() {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
const auto device_result = nfc_output_device->StopNfcPolling();
|
||||
const auto virtual_device_result = nfc_virtual_output_device->StopNfcPolling();
|
||||
|
||||
return device_result == Common::Input::NfcState::Success ||
|
||||
virtual_device_result == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
|
||||
Common::Input::MifareRequest& out_data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->ReadMifareData(request, out_data) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->ReadMifareData(request, out_data) ==
|
||||
Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->WriteMifareData(request) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->WriteMifareData(request) == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
|
|
@ -97,10 +97,7 @@ struct RingSensorForce {
|
|||
f32 force;
|
||||
};
|
||||
|
||||
struct NfcState {
|
||||
Common::Input::NfcState state{};
|
||||
std::vector<u8> data{};
|
||||
};
|
||||
using NfcState = Common::Input::NfcStatus;
|
||||
|
||||
struct ControllerMotion {
|
||||
Common::Vec3f accel{};
|
||||
|
@ -393,9 +390,31 @@ public:
|
|||
/// Returns true if the device has nfc support
|
||||
bool HasNfc() const;
|
||||
|
||||
/// Sets the joycon in nfc mode and increments the handle count
|
||||
bool AddNfcHandle();
|
||||
|
||||
/// Decrements the handle count if zero sets the joycon in active mode
|
||||
bool RemoveNfcHandle();
|
||||
|
||||
/// Start searching for nfc tags
|
||||
bool StartNfcPolling();
|
||||
|
||||
/// Stop searching for nfc tags
|
||||
bool StopNfcPolling();
|
||||
|
||||
/// Returns true if the nfc tag was readable
|
||||
bool ReadAmiiboData(std::vector<u8>& data);
|
||||
|
||||
/// Returns true if the nfc tag was written
|
||||
bool WriteNfc(const std::vector<u8>& data);
|
||||
|
||||
/// Returns true if the nfc tag was readable
|
||||
bool ReadMifareData(const Common::Input::MifareRequest& request,
|
||||
Common::Input::MifareRequest& out_data);
|
||||
|
||||
/// Returns true if the nfc tag was written
|
||||
bool WriteMifareData(const Common::Input::MifareRequest& request);
|
||||
|
||||
/// Returns the led pattern corresponding to this emulated controller
|
||||
LedPattern GetLedPattern() const;
|
||||
|
||||
|
@ -532,6 +551,7 @@ private:
|
|||
bool system_buttons_enabled{true};
|
||||
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
|
||||
u32 turbo_button_state{0};
|
||||
std::size_t nfc_handles{0};
|
||||
|
||||
// Temporary values to avoid doing changes while the controller is in configuring mode
|
||||
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
|
||||
|
|
|
@ -299,11 +299,7 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
|
|||
Common::Input::NfcStatus nfc{};
|
||||
switch (callback.type) {
|
||||
case Common::Input::InputType::Nfc:
|
||||
nfc = {
|
||||
.state = callback.nfc_status,
|
||||
.data = callback.raw_data,
|
||||
};
|
||||
break;
|
||||
return callback.nfc_status;
|
||||
default:
|
||||
LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
|
||||
break;
|
||||
|
|
|
@ -184,7 +184,8 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
|
|||
prev_highest_thread != highest_thread) [[likely]] {
|
||||
if (prev_highest_thread != nullptr) [[likely]] {
|
||||
IncrementScheduledCount(prev_highest_thread);
|
||||
prev_highest_thread->SetLastScheduledTick(m_kernel.System().CoreTiming().GetCPUTicks());
|
||||
prev_highest_thread->SetLastScheduledTick(
|
||||
m_kernel.System().CoreTiming().GetClockTicks());
|
||||
}
|
||||
if (m_state.should_count_idle) {
|
||||
if (highest_thread != nullptr) [[likely]] {
|
||||
|
@ -351,7 +352,7 @@ void KScheduler::SwitchThread(KThread* next_thread) {
|
|||
|
||||
// Update the CPU time tracking variables.
|
||||
const s64 prev_tick = m_last_context_switch_time;
|
||||
const s64 cur_tick = m_kernel.System().CoreTiming().GetCPUTicks();
|
||||
const s64 cur_tick = m_kernel.System().CoreTiming().GetClockTicks();
|
||||
const s64 tick_diff = cur_tick - prev_tick;
|
||||
cur_thread->AddCpuTime(m_core_id, tick_diff);
|
||||
if (cur_process != nullptr) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/k_synchronization_object.h"
|
||||
|
@ -75,7 +76,7 @@ Result KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index,
|
|||
KSynchronizationObject** objects, const s32 num_objects,
|
||||
s64 timeout) {
|
||||
// Allocate space on stack for thread nodes.
|
||||
std::vector<ThreadListNode> thread_nodes(num_objects);
|
||||
std::array<ThreadListNode, Svc::ArgumentHandleCountMax> thread_nodes;
|
||||
|
||||
// Prepare for wait.
|
||||
KThread* thread = GetCurrentThreadPointer(kernel);
|
||||
|
|
|
@ -909,7 +909,7 @@ Result KThread::SetActivity(Svc::ThreadActivity activity) {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KThread::GetThreadContext3(std::vector<u8>& out) {
|
||||
Result KThread::GetThreadContext3(Common::ScratchBuffer<u8>& out) {
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk{m_activity_pause_lock};
|
||||
|
||||
|
@ -927,15 +927,13 @@ Result KThread::GetThreadContext3(std::vector<u8>& out) {
|
|||
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
|
||||
auto context = GetContext64();
|
||||
context.pstate &= 0xFF0FFE20;
|
||||
|
||||
out.resize(sizeof(context));
|
||||
out.resize_destructive(sizeof(context));
|
||||
std::memcpy(out.data(), std::addressof(context), sizeof(context));
|
||||
} else {
|
||||
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
|
||||
auto context = GetContext32();
|
||||
context.cpsr &= 0xFF0FFE20;
|
||||
|
||||
out.resize(sizeof(context));
|
||||
out.resize_destructive(sizeof(context));
|
||||
std::memcpy(out.data(), std::addressof(context), sizeof(context));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "common/intrusive_list.h"
|
||||
|
||||
#include "common/intrusive_red_black_tree.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/hle/kernel/k_affinity_mask.h"
|
||||
|
@ -567,7 +568,7 @@ public:
|
|||
|
||||
void RemoveWaiter(KThread* thread);
|
||||
|
||||
Result GetThreadContext3(std::vector<u8>& out);
|
||||
Result GetThreadContext3(Common::ScratchBuffer<u8>& out);
|
||||
|
||||
KThread* RemoveUserWaiterByKey(bool* out_has_waiters, KProcessAddress key) {
|
||||
return this->RemoveWaiterByKey(out_has_waiters, key, false);
|
||||
|
|
|
@ -199,9 +199,9 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
|||
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
|
||||
const u64 thread_ticks = current_thread->GetCpuTime();
|
||||
|
||||
out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks);
|
||||
out_ticks = thread_ticks + (core_timing.GetClockTicks() - prev_ctx_ticks);
|
||||
} else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) {
|
||||
out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks;
|
||||
out_ticks = core_timing.GetClockTicks() - prev_ctx_ticks;
|
||||
}
|
||||
|
||||
*result = out_ticks;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_client_session.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
|
@ -45,11 +46,11 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
|
|||
handles_addr, static_cast<u64>(sizeof(Handle) * num_handles)),
|
||||
ResultInvalidPointer);
|
||||
|
||||
std::vector<Handle> handles(num_handles);
|
||||
std::array<Handle, Svc::ArgumentHandleCountMax> handles;
|
||||
GetCurrentMemory(kernel).ReadBlock(handles_addr, handles.data(), sizeof(Handle) * num_handles);
|
||||
|
||||
// Convert handle list to object table.
|
||||
std::vector<KSynchronizationObject*> objs(num_handles);
|
||||
std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> objs;
|
||||
R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles.data(),
|
||||
num_handles),
|
||||
ResultInvalidHandle);
|
||||
|
@ -80,7 +81,7 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
|
|||
// Wait for an object.
|
||||
s32 index;
|
||||
Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(),
|
||||
static_cast<s32>(objs.size()), timeout_ns);
|
||||
num_handles, timeout_ns);
|
||||
if (result == ResultTimedOut) {
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
|
@ -54,7 +55,7 @@ static Result WaitSynchronization(Core::System& system, int32_t* out_index, cons
|
|||
// Get the synchronization context.
|
||||
auto& kernel = system.Kernel();
|
||||
auto& handle_table = GetCurrentProcess(kernel).GetHandleTable();
|
||||
std::vector<KSynchronizationObject*> objs(num_handles);
|
||||
std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> objs;
|
||||
|
||||
// Copy user handles.
|
||||
if (num_handles > 0) {
|
||||
|
@ -72,8 +73,8 @@ static Result WaitSynchronization(Core::System& system, int32_t* out_index, cons
|
|||
});
|
||||
|
||||
// Wait on the objects.
|
||||
Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(),
|
||||
static_cast<s32>(objs.size()), timeout_ns);
|
||||
Result res =
|
||||
KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns);
|
||||
|
||||
R_SUCCEED_IF(res == ResultSessionClosed);
|
||||
R_RETURN(res);
|
||||
|
@ -87,8 +88,7 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
|
|||
|
||||
// Ensure number of handles is valid.
|
||||
R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange);
|
||||
|
||||
std::vector<Handle> handles(num_handles);
|
||||
std::array<Handle, Svc::ArgumentHandleCountMax> handles;
|
||||
if (num_handles > 0) {
|
||||
GetCurrentMemory(system.Kernel())
|
||||
.ReadBlock(user_handles, handles.data(), num_handles * sizeof(Handle));
|
||||
|
|
|
@ -174,7 +174,7 @@ Result GetThreadContext3(Core::System& system, u64 out_context, Handle thread_ha
|
|||
}
|
||||
|
||||
// Get the thread context.
|
||||
std::vector<u8> context;
|
||||
static thread_local Common::ScratchBuffer<u8> context;
|
||||
R_TRY(thread->GetThreadContext3(context));
|
||||
|
||||
// Copy the thread context to user space.
|
||||
|
|
|
@ -12,16 +12,8 @@ namespace Kernel::Svc {
|
|||
int64_t GetSystemTick(Core::System& system) {
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
|
||||
const u64 result{core_timing.GetClockTicks()};
|
||||
|
||||
if (!system.Kernel().IsMulticore()) {
|
||||
core_timing.AddTicks(400U);
|
||||
}
|
||||
|
||||
return static_cast<int64_t>(result);
|
||||
return static_cast<int64_t>(system.CoreTiming().GetClockTicks());
|
||||
}
|
||||
|
||||
int64_t GetSystemTick64(Core::System& system) {
|
||||
|
|
|
@ -141,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
|
|||
applet_output.device_handle = applet_input_common.device_handle;
|
||||
applet_output.result = CabinetResult::Cancel;
|
||||
const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
|
||||
const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false);
|
||||
const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
|
||||
nfp_device->Finalize();
|
||||
|
||||
if (reg_result.IsSuccess()) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "audio_core/renderer/audio_device.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
|
@ -123,19 +124,13 @@ private:
|
|||
|
||||
void GetReleasedAudioInBuffer(HLERequestContext& ctx) {
|
||||
const auto write_buffer_size = ctx.GetWriteBufferNumElements<u64>();
|
||||
std::vector<u64> released_buffers(write_buffer_size);
|
||||
tmp_buffer.resize_destructive(write_buffer_size);
|
||||
tmp_buffer[0] = 0;
|
||||
|
||||
const auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
const auto count = impl->GetReleasedBuffers(tmp_buffer);
|
||||
|
||||
[[maybe_unused]] std::string tags{};
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
tags += fmt::format("{:08X}, ", released_buffers[i]);
|
||||
}
|
||||
[[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
|
||||
LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
|
||||
tags);
|
||||
ctx.WriteBuffer(tmp_buffer);
|
||||
|
||||
ctx.WriteBuffer(released_buffers);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(count);
|
||||
|
@ -200,6 +195,7 @@ private:
|
|||
KernelHelpers::ServiceContext service_context;
|
||||
Kernel::KEvent* event;
|
||||
std::shared_ptr<AudioCore::AudioIn::In> impl;
|
||||
Common::ScratchBuffer<u64> tmp_buffer;
|
||||
};
|
||||
|
||||
AudInU::AudInU(Core::System& system_)
|
||||
|
|
|
@ -123,19 +123,13 @@ private:
|
|||
|
||||
void GetReleasedAudioOutBuffers(HLERequestContext& ctx) {
|
||||
const auto write_buffer_size = ctx.GetWriteBufferNumElements<u64>();
|
||||
std::vector<u64> released_buffers(write_buffer_size);
|
||||
tmp_buffer.resize_destructive(write_buffer_size);
|
||||
tmp_buffer[0] = 0;
|
||||
|
||||
const auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
const auto count = impl->GetReleasedBuffers(tmp_buffer);
|
||||
|
||||
[[maybe_unused]] std::string tags{};
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
tags += fmt::format("{:08X}, ", released_buffers[i]);
|
||||
}
|
||||
[[maybe_unused]] const auto sessionid{impl->GetSystem().GetSessionId()};
|
||||
LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
|
||||
tags);
|
||||
ctx.WriteBuffer(tmp_buffer);
|
||||
|
||||
ctx.WriteBuffer(released_buffers);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(count);
|
||||
|
@ -211,6 +205,7 @@ private:
|
|||
KernelHelpers::ServiceContext service_context;
|
||||
Kernel::KEvent* event;
|
||||
std::shared_ptr<AudioCore::AudioOut::Out> impl;
|
||||
Common::ScratchBuffer<u64> tmp_buffer;
|
||||
};
|
||||
|
||||
AudOutU::AudOutU(Core::System& system_)
|
||||
|
|
|
@ -116,28 +116,26 @@ private:
|
|||
// These buffers are written manually to avoid an issue with WriteBuffer throwing errors for
|
||||
// checking size 0. Performance size is 0 for most games.
|
||||
|
||||
std::vector<u8> output{};
|
||||
std::vector<u8> performance{};
|
||||
auto is_buffer_b{ctx.BufferDescriptorB()[0].Size() != 0};
|
||||
if (is_buffer_b) {
|
||||
const auto buffersB{ctx.BufferDescriptorB()};
|
||||
output.resize(buffersB[0].Size(), 0);
|
||||
performance.resize(buffersB[1].Size(), 0);
|
||||
tmp_output.resize_destructive(buffersB[0].Size());
|
||||
tmp_performance.resize_destructive(buffersB[1].Size());
|
||||
} else {
|
||||
const auto buffersC{ctx.BufferDescriptorC()};
|
||||
output.resize(buffersC[0].Size(), 0);
|
||||
performance.resize(buffersC[1].Size(), 0);
|
||||
tmp_output.resize_destructive(buffersC[0].Size());
|
||||
tmp_performance.resize_destructive(buffersC[1].Size());
|
||||
}
|
||||
|
||||
auto result = impl->RequestUpdate(input, performance, output);
|
||||
auto result = impl->RequestUpdate(input, tmp_performance, tmp_output);
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
if (is_buffer_b) {
|
||||
ctx.WriteBufferB(output.data(), output.size(), 0);
|
||||
ctx.WriteBufferB(performance.data(), performance.size(), 1);
|
||||
ctx.WriteBufferB(tmp_output.data(), tmp_output.size(), 0);
|
||||
ctx.WriteBufferB(tmp_performance.data(), tmp_performance.size(), 1);
|
||||
} else {
|
||||
ctx.WriteBufferC(output.data(), output.size(), 0);
|
||||
ctx.WriteBufferC(performance.data(), performance.size(), 1);
|
||||
ctx.WriteBufferC(tmp_output.data(), tmp_output.size(), 0);
|
||||
ctx.WriteBufferC(tmp_performance.data(), tmp_performance.size(), 1);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description);
|
||||
|
@ -235,6 +233,8 @@ private:
|
|||
Kernel::KEvent* rendered_event;
|
||||
Manager& manager;
|
||||
std::unique_ptr<Renderer> impl;
|
||||
Common::ScratchBuffer<u8> tmp_output;
|
||||
Common::ScratchBuffer<u8> tmp_performance;
|
||||
};
|
||||
|
||||
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
|
|
|
@ -68,13 +68,13 @@ private:
|
|||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferNumElements<opus_int16>());
|
||||
tmp_samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>());
|
||||
|
||||
if (extra_behavior == ExtraBehavior::ResetContext) {
|
||||
ResetDecoderContext();
|
||||
}
|
||||
|
||||
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
|
||||
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), tmp_samples, performance)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
|
@ -90,11 +90,11 @@ private:
|
|||
if (performance) {
|
||||
rb.Push<u64>(*performance);
|
||||
}
|
||||
ctx.WriteBuffer(samples);
|
||||
ctx.WriteBuffer(tmp_samples);
|
||||
}
|
||||
|
||||
bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) const {
|
||||
std::span<opus_int16> output, u64* out_performance_time) const {
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusPacketHeader) > input.size()) {
|
||||
|
@ -154,6 +154,7 @@ private:
|
|||
OpusDecoderPtr decoder;
|
||||
u32 sample_rate;
|
||||
u32 channel_count;
|
||||
Common::ScratchBuffer<opus_int16> tmp_samples;
|
||||
};
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
|
|
|
@ -93,7 +93,8 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
|
|||
const auto nfc_status = npad_device->GetNfc();
|
||||
switch (nfc_status.state) {
|
||||
case Common::Input::NfcState::NewAmiibo:
|
||||
LoadNfcTag(nfc_status.data);
|
||||
LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length,
|
||||
nfc_status.uuid);
|
||||
break;
|
||||
case Common::Input::NfcState::AmiiboRemoved:
|
||||
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
|
||||
|
@ -108,28 +109,46 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
|
|||
}
|
||||
}
|
||||
|
||||
bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
|
||||
bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) {
|
||||
if (device_state != DeviceState::SearchingForTag) {
|
||||
LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((protocol & static_cast<u8>(allowed_protocols)) == 0) {
|
||||
LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol);
|
||||
return false;
|
||||
}
|
||||
|
||||
real_tag_info = {
|
||||
.uuid = uuid,
|
||||
.uuid_length = uuid_length,
|
||||
.protocol = static_cast<NfcProtocol>(protocol),
|
||||
.tag_type = static_cast<TagType>(tag_type),
|
||||
};
|
||||
|
||||
device_state = DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NfcDevice::LoadAmiiboData() {
|
||||
std::vector<u8> data{};
|
||||
|
||||
if (!npad_device->ReadAmiiboData(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
|
||||
LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
mifare_data.resize(data.size());
|
||||
memcpy(mifare_data.data(), data.data(), data.size());
|
||||
|
||||
memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
|
||||
is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
|
||||
is_write_protected = false;
|
||||
|
||||
device_state = DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->Signal();
|
||||
|
||||
// Fallback for plain amiibos
|
||||
if (is_plain_amiibo) {
|
||||
LOG_INFO(Service_NFP, "Using plain amiibo");
|
||||
|
@ -147,6 +166,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
|
|||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO(Service_NFP, "Using encrypted amiibo");
|
||||
tag_data = {};
|
||||
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
|
||||
return true;
|
||||
|
@ -162,7 +182,6 @@ void NfcDevice::CloseNfcTag() {
|
|||
device_state = DeviceState::TagRemoved;
|
||||
encrypted_tag_data = {};
|
||||
tag_data = {};
|
||||
mifare_data = {};
|
||||
activate_event->GetReadableEvent().Clear();
|
||||
deactivate_event->Signal();
|
||||
}
|
||||
|
@ -179,8 +198,12 @@ void NfcDevice::Initialize() {
|
|||
device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
|
||||
encrypted_tag_data = {};
|
||||
tag_data = {};
|
||||
mifare_data = {};
|
||||
is_initalized = true;
|
||||
|
||||
if (device_state != DeviceState::Initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_initalized = npad_device->AddNfcHandle();
|
||||
}
|
||||
|
||||
void NfcDevice::Finalize() {
|
||||
|
@ -190,6 +213,11 @@ void NfcDevice::Finalize() {
|
|||
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
|
||||
StopDetection();
|
||||
}
|
||||
|
||||
if (device_state != DeviceState::Unavailable) {
|
||||
npad_device->RemoveNfcHandle();
|
||||
}
|
||||
|
||||
device_state = DeviceState::Unavailable;
|
||||
is_initalized = false;
|
||||
}
|
||||
|
@ -200,10 +228,8 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
|
|||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::NFC) !=
|
||||
Common::Input::DriverResult::Success) {
|
||||
LOG_ERROR(Service_NFC, "Nfc not supported");
|
||||
if (!npad_device->StartNfcPolling()) {
|
||||
LOG_ERROR(Service_NFC, "Nfc polling not supported");
|
||||
return ResultNfcDisabled;
|
||||
}
|
||||
|
||||
|
@ -213,9 +239,6 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
|
|||
}
|
||||
|
||||
Result NfcDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
@ -225,6 +248,7 @@ Result NfcDevice::StopDetection() {
|
|||
}
|
||||
|
||||
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
|
||||
npad_device->StopNfcPolling();
|
||||
device_state = DeviceState::Initialized;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
@ -233,7 +257,7 @@ Result NfcDevice::StopDetection() {
|
|||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
|
||||
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
|
||||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
|
@ -242,41 +266,15 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
|
|||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
UniqueSerialNumber uuid{};
|
||||
u8 uuid_length{};
|
||||
NfcProtocol protocol{NfcProtocol::TypeA};
|
||||
TagType tag_type{TagType::Type2};
|
||||
tag_info = real_tag_info;
|
||||
|
||||
if (is_mifare) {
|
||||
tag_type = TagType::Mifare;
|
||||
uuid_length = sizeof(NFP::NtagTagUuid);
|
||||
memcpy(uuid.data(), mifare_data.data(), uuid_length);
|
||||
} else {
|
||||
tag_type = TagType::Type2;
|
||||
uuid_length = sizeof(NFP::NtagTagUuid);
|
||||
NFP::NtagTagUuid nUuid{
|
||||
.part1 = encrypted_tag_data.uuid.part1,
|
||||
.part2 = encrypted_tag_data.uuid.part2,
|
||||
.nintendo_id = encrypted_tag_data.uuid.nintendo_id,
|
||||
};
|
||||
memcpy(uuid.data(), &nUuid, uuid_length);
|
||||
|
||||
// Generate random UUID to bypass amiibo load limits
|
||||
if (Settings::values.random_amiibo_id) {
|
||||
Common::TinyMT rng{};
|
||||
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
|
||||
rng.GenerateRandomBytes(uuid.data(), uuid_length);
|
||||
}
|
||||
// Generate random UUID to bypass amiibo load limits
|
||||
if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) {
|
||||
Common::TinyMT rng{};
|
||||
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
|
||||
rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length);
|
||||
}
|
||||
|
||||
// Protocol and tag type may change here
|
||||
tag_info = {
|
||||
.uuid = uuid,
|
||||
.uuid_length = uuid_length,
|
||||
.protocol = protocol,
|
||||
.tag_type = tag_type,
|
||||
};
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
@ -293,7 +291,7 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
|
|||
Result result = ResultSuccess;
|
||||
|
||||
TagInfo tag_info{};
|
||||
result = GetTagInfo(tag_info, true);
|
||||
result = GetTagInfo(tag_info);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
|
@ -307,6 +305,8 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
|
|||
return ResultInvalidArgument;
|
||||
}
|
||||
|
||||
Common::Input::MifareRequest request{};
|
||||
Common::Input::MifareRequest out_data{};
|
||||
const auto unknown = parameters[0].sector_key.unknown;
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
if (unknown != parameters[i].sector_key.unknown) {
|
||||
|
@ -315,25 +315,29 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
|
|||
}
|
||||
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
result = ReadMifare(parameters[i], read_block_data[i]);
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
if (parameters[i].sector_key.command == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
|
||||
request.data[i].sector = parameters[i].sector_number;
|
||||
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
|
||||
sizeof(KeyData));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
|
||||
MifareReadBlockData& read_block_data) const {
|
||||
const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
|
||||
read_block_data.sector_number = parameter.sector_number;
|
||||
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
|
||||
if (!npad_device->ReadMifareData(request, out_data)) {
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to read encrypted data
|
||||
memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock));
|
||||
for (std::size_t i = 0; i < read_block_data.size(); i++) {
|
||||
if (static_cast<MifareCmd>(out_data.data[i].command) == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
read_block_data[i] = {
|
||||
.data = out_data.data[i].data,
|
||||
.sector_number = out_data.data[i].sector,
|
||||
};
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
@ -342,7 +346,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
|
|||
Result result = ResultSuccess;
|
||||
|
||||
TagInfo tag_info{};
|
||||
result = GetTagInfo(tag_info, true);
|
||||
result = GetTagInfo(tag_info);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
|
@ -363,42 +367,25 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
|
|||
}
|
||||
}
|
||||
|
||||
Common::Input::MifareRequest request{};
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
result = WriteMifare(parameters[i]);
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
if (parameters[i].sector_key.command == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
|
||||
request.data[i].sector = parameters[i].sector_number;
|
||||
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
|
||||
sizeof(KeyData));
|
||||
memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData));
|
||||
}
|
||||
|
||||
if (!npad_device->WriteNfc(mifare_data)) {
|
||||
LOG_ERROR(Service_NFP, "Error writing to file");
|
||||
if (!npad_device->WriteMifareData(request)) {
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
|
||||
const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
|
||||
|
||||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return ResultTagRemoved;
|
||||
}
|
||||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to encrypt the data
|
||||
memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
|
||||
std::span<const u8> command_data,
|
||||
std::span<u8> out_data) {
|
||||
|
@ -412,6 +399,11 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
|
|||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
if (!LoadAmiiboData()) {
|
||||
LOG_ERROR(Service_NFP, "Not an amiibo");
|
||||
return ResultInvalidTagType;
|
||||
}
|
||||
|
||||
if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Not an amiibo");
|
||||
return ResultInvalidTagType;
|
||||
|
@ -562,7 +554,7 @@ Result NfcDevice::Restore() {
|
|||
|
||||
NFC::TagInfo tag_info{};
|
||||
std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
|
||||
Result result = GetTagInfo(tag_info, false);
|
||||
Result result = GetTagInfo(tag_info);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
|
@ -635,7 +627,7 @@ Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const {
|
|||
// TODO: Validate this data
|
||||
common_info = {
|
||||
.last_write_date = settings.write_date.GetWriteDate(),
|
||||
.write_counter = tag_data.write_counter,
|
||||
.write_counter = tag_data.application_write_counter,
|
||||
.version = tag_data.amiibo_version,
|
||||
.application_area_size = sizeof(NFP::ApplicationArea),
|
||||
};
|
||||
|
|
|
@ -42,15 +42,12 @@ public:
|
|||
Result StartDetection(NfcProtocol allowed_protocol);
|
||||
Result StopDetection();
|
||||
|
||||
Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const;
|
||||
Result GetTagInfo(TagInfo& tag_info) const;
|
||||
|
||||
Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
|
||||
std::span<MifareReadBlockData> read_block_data) const;
|
||||
Result ReadMifare(const MifareReadBlockParameter& parameter,
|
||||
MifareReadBlockData& read_block_data) const;
|
||||
|
||||
Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
|
||||
Result WriteMifare(const MifareWriteBlockParameter& parameter);
|
||||
|
||||
Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
|
||||
std::span<const u8> command_data, std::span<u8> out_data);
|
||||
|
@ -105,7 +102,8 @@ public:
|
|||
|
||||
private:
|
||||
void NpadUpdate(Core::HID::ControllerTriggerType type);
|
||||
bool LoadNfcTag(std::span<const u8> data);
|
||||
bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid);
|
||||
bool LoadAmiiboData();
|
||||
void CloseNfcTag();
|
||||
|
||||
NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
|
||||
|
@ -140,8 +138,8 @@ private:
|
|||
bool is_write_protected{};
|
||||
NFP::MountTarget mount_target{NFP::MountTarget::None};
|
||||
|
||||
TagInfo real_tag_info{};
|
||||
NFP::NTAG215File tag_data{};
|
||||
std::vector<u8> mifare_data{};
|
||||
NFP::EncryptedNTAG215File encrypted_tag_data{};
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContex
|
|||
}
|
||||
|
||||
DeviceManager ::~DeviceManager() {
|
||||
if (is_initialized) {
|
||||
Finalize();
|
||||
}
|
||||
service_context.CloseEvent(availability_change_event);
|
||||
}
|
||||
|
||||
|
@ -125,14 +128,14 @@ Result DeviceManager::StopDetection(u64 device_handle) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const {
|
||||
Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info) const {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
std::shared_ptr<NfcDevice> device = nullptr;
|
||||
auto result = GetDeviceHandle(device_handle, device);
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device->GetTagInfo(tag_info, is_mifare);
|
||||
result = device->GetTagInfo(tag_info);
|
||||
result = VerifyDeviceResult(device, result);
|
||||
}
|
||||
|
||||
|
@ -546,7 +549,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
|
|||
NFC::TagInfo tag_info{};
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device->GetTagInfo(tag_info, false);
|
||||
result = device->GetTagInfo(tag_info);
|
||||
}
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
|
@ -565,7 +568,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
|
|||
NFC::TagInfo tag_info{};
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device->GetTagInfo(tag_info, false);
|
||||
result = device->GetTagInfo(tag_info);
|
||||
}
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const;
|
||||
Result StartDetection(u64 device_handle, NfcProtocol tag_protocol);
|
||||
Result StopDetection(u64 device_handle);
|
||||
Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const;
|
||||
Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info) const;
|
||||
Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const;
|
||||
Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const;
|
||||
Result ReadMifare(u64 device_handle,
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
namespace Service::NFC {
|
||||
|
||||
enum class MifareCmd : u8 {
|
||||
None = 0x00,
|
||||
Read = 0x30,
|
||||
AuthA = 0x60,
|
||||
AuthB = 0x61,
|
||||
Read = 0x30,
|
||||
Write = 0xA0,
|
||||
Transfer = 0xB0,
|
||||
Decrement = 0xC0,
|
||||
|
@ -35,17 +36,17 @@ static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size");
|
|||
|
||||
// This is nn::nfc::MifareReadBlockParameter
|
||||
struct MifareReadBlockParameter {
|
||||
u8 sector_number;
|
||||
u8 sector_number{};
|
||||
INSERT_PADDING_BYTES(0x7);
|
||||
SectorKey sector_key;
|
||||
SectorKey sector_key{};
|
||||
};
|
||||
static_assert(sizeof(MifareReadBlockParameter) == 0x18,
|
||||
"MifareReadBlockParameter is an invalid size");
|
||||
|
||||
// This is nn::nfc::MifareReadBlockData
|
||||
struct MifareReadBlockData {
|
||||
DataBlock data;
|
||||
u8 sector_number;
|
||||
DataBlock data{};
|
||||
u8 sector_number{};
|
||||
INSERT_PADDING_BYTES(0x7);
|
||||
};
|
||||
static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size");
|
||||
|
|
|
@ -174,8 +174,7 @@ void NfcInterface::GetTagInfo(HLERequestContext& ctx) {
|
|||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
TagInfo tag_info{};
|
||||
auto result =
|
||||
GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare);
|
||||
auto result = GetManager()->GetTagInfo(device_handle, tag_info);
|
||||
result = TranslateResultToServiceError(result);
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
|
@ -216,8 +215,8 @@ void NfcInterface::ReadMifare(HLERequestContext& ctx) {
|
|||
memcpy(read_commands.data(), buffer.data(),
|
||||
number_of_commands * sizeof(MifareReadBlockParameter));
|
||||
|
||||
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}",
|
||||
device_handle, number_of_commands);
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}, read_commands_size={}", device_handle,
|
||||
number_of_commands);
|
||||
|
||||
std::vector<MifareReadBlockData> out_data(number_of_commands);
|
||||
auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data);
|
||||
|
|
|
@ -34,7 +34,7 @@ public:
|
|||
* @returns The result code of the ioctl.
|
||||
*/
|
||||
virtual NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
|
||||
std::vector<u8>& output) = 0;
|
||||
std::span<u8> output) = 0;
|
||||
|
||||
/**
|
||||
* Handles an ioctl2 request.
|
||||
|
@ -45,7 +45,7 @@ public:
|
|||
* @returns The result code of the ioctl.
|
||||
*/
|
||||
virtual NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
|
||||
std::span<const u8> inline_input, std::vector<u8>& output) = 0;
|
||||
std::span<const u8> inline_input, std::span<u8> output) = 0;
|
||||
|
||||
/**
|
||||
* Handles an ioctl3 request.
|
||||
|
@ -56,7 +56,7 @@ public:
|
|||
* @returns The result code of the ioctl.
|
||||
*/
|
||||
virtual NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input,
|
||||
std::vector<u8>& output, std::vector<u8>& inline_output) = 0;
|
||||
std::span<u8> output, std::span<u8> inline_output) = 0;
|
||||
|
||||
/**
|
||||
* Called once a device is opened
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue