Compare commits

..

1 Commits

Author SHA1 Message Date
7684f37daa Android #118 2023-10-31 00:57:24 +00:00
35 changed files with 124 additions and 933 deletions

View File

@ -252,7 +252,7 @@ object NativeLibrary {
external fun reloadKeys(): Boolean external fun reloadKeys(): Boolean
external fun initializeSystem() external fun initializeEmulation()
external fun defaultCPUCore(): Int external fun defaultCPUCore(): Int
@ -505,36 +505,6 @@ object NativeLibrary {
*/ */
external fun initializeEmptyUserDirectory() external fun initializeEmptyUserDirectory()
/**
* Gets the launch path for a given applet. It is the caller's responsibility to also
* set the system's current applet ID before trying to launch the nca given by this function.
*
* @param id The applet entry ID
* @return The applet's launch path
*/
external fun getAppletLaunchPath(id: Long): String
/**
* Sets the system's current applet ID before launching.
*
* @param appletId One of the ids in the Service::AM::Applets::AppletId enum
*/
external fun setCurrentAppletId(appletId: Int)
/**
* Sets the cabinet mode for launching the cabinet applet.
*
* @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode
*/
external fun setCabinetMode(cabinetMode: Int)
/**
* Checks whether NAND contents are available and valid.
*
* @return 'true' if firmware is available
*/
external fun isFirmwareAvailable(): Boolean
/** /**
* Button type for use in onTouchEvent * Button type for use in onTouchEvent
*/ */

View File

@ -45,6 +45,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.model.Game 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.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.MemoryUtil import org.yuzu.yuzu_emu.utils.MemoryUtil
@ -56,16 +57,17 @@ import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener { class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
private var controllerMappingHelper: ControllerMappingHelper? = null
var isActivityRecreated = false var isActivityRecreated = false
private lateinit var nfcReader: NfcReader private lateinit var nfcReader: NfcReader
private lateinit var inputHandler: InputHandler
private val gyro = FloatArray(3) private val gyro = FloatArray(3)
private val accel = FloatArray(3) private val accel = FloatArray(3)
private var motionTimestamp: Long = 0 private var motionTimestamp: Long = 0
private var flipMotionOrientation: Boolean = false private var flipMotionOrientation: Boolean = false
private var controllerIds = InputHandler.getGameControllerIds()
private val actionPause = "ACTION_EMULATOR_PAUSE" private val actionPause = "ACTION_EMULATOR_PAUSE"
private val actionPlay = "ACTION_EMULATOR_PLAY" private val actionPlay = "ACTION_EMULATOR_PLAY"
private val actionMute = "ACTION_EMULATOR_MUTE" private val actionMute = "ACTION_EMULATOR_MUTE"
@ -93,6 +95,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
isActivityRecreated = savedInstanceState != null isActivityRecreated = savedInstanceState != null
controllerMappingHelper = ControllerMappingHelper()
// Set these options now so that the SurfaceView the game renders into is the right size. // Set these options now so that the SurfaceView the game renders into is the right size.
enableFullscreenImmersive() enableFullscreenImmersive()
@ -101,7 +105,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this) nfcReader = NfcReader(this)
nfcReader.initialize() nfcReader.initialize()
InputHandler.initialize() inputHandler = InputHandler()
inputHandler.initialize()
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
@ -157,7 +162,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onResume() super.onResume()
nfcReader.startScanning() nfcReader.startScanning()
startMotionSensorListener() startMotionSensorListener()
InputHandler.updateControllerIds()
buildPictureInPictureParams() buildPictureInPictureParams()
} }
@ -191,7 +195,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
} }
return InputHandler.dispatchKeyEvent(event) return inputHandler.dispatchKeyEvent(event)
} }
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
@ -206,7 +210,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return true return true
} }
return InputHandler.dispatchGenericMotionEvent(event) return inputHandler.dispatchGenericMotionEvent(event)
} }
override fun onSensorChanged(event: SensorEvent) { override fun onSensorChanged(event: SensorEvent) {

View File

@ -1,90 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding
import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AppletAdapter.AppletViewHolder {
CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@AppletAdapter) }
.also { return AppletViewHolder(it) }
}
override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
holder.bind(applets[position])
override fun getItemCount(): Int = applets.size
override fun onClick(view: View) {
val applet = (view.tag as AppletViewHolder).applet
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
if (appletPath.isEmpty()) {
Toast.makeText(
YuzuApplication.appContext,
R.string.applets_error_applet,
Toast.LENGTH_SHORT
).show()
return
}
if (applet.appletInfo == AppletInfo.Cabinet) {
view.findNavController()
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
return
}
NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
val appletGame = Game(
title = YuzuApplication.appContext.getString(applet.titleId),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
view.findNavController().navigate(action)
}
inner class AppletViewHolder(val binding: CardAppletOptionBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var applet: Applet
init {
itemView.tag = this
}
fun bind(applet: Applet) {
this.applet = applet
binding.title.setText(applet.titleId)
binding.description.setText(applet.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
applet.iconId,
binding.icon.context.theme
)
)
}
}
}

View File

@ -1,72 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.DialogListItemBinding
import org.yuzu.yuzu_emu.model.CabinetMode
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
class CabinetLauncherDialogAdapter(val fragment: Fragment) :
RecyclerView.Adapter<CabinetModeViewHolder>(),
View.OnClickListener {
private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
.also { return CabinetModeViewHolder(it) }
}
override fun getItemCount(): Int = cabinetModes.size
override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
holder.bind(cabinetModes[position])
override fun onClick(view: View) {
val mode = (view.tag as CabinetModeViewHolder).cabinetMode
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
NativeLibrary.setCabinetMode(mode.id)
val appletGame = Game(
title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
fragment.findNavController().navigate(action)
}
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var cabinetMode: CabinetMode
init {
itemView.tag = this
}
fun bind(cabinetMode: CabinetMode) {
this.cabinetMode = cabinetMode
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
cabinetMode.iconId,
binding.icon.context.theme
)
)
binding.title.setText(cabinetMode.titleId)
}
}
}

View File

@ -1,113 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AppletAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAppletLauncherBinding
import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.HomeViewModel
class AppletLauncherFragment : Fragment() {
private var _binding: FragmentAppletLauncherBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAppletLauncherBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarApplets.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
val applets = listOf(
Applet(
R.string.album_applet,
R.string.album_applet_description,
R.drawable.ic_album,
AppletInfo.PhotoViewer
),
Applet(
R.string.cabinet_applet,
R.string.cabinet_applet_description,
R.drawable.ic_nfc,
AppletInfo.Cabinet
),
Applet(
R.string.mii_edit_applet,
R.string.mii_edit_applet_description,
R.drawable.ic_mii,
AppletInfo.MiiEdit
)
)
binding.listApplets.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.grid_columns)
)
adapter = AppletAdapter(requireActivity(), applets)
}
setInsets()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpAppBar = binding.toolbarApplets.layoutParams as ViewGroup.MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarApplets.layoutParams = mlpAppBar
val mlpListApplets =
binding.listApplets.layoutParams as ViewGroup.MarginLayoutParams
mlpListApplets.leftMargin = leftInsets
mlpListApplets.rightMargin = rightInsets
binding.listApplets.layoutParams = mlpListApplets
binding.listApplets.updatePadding(bottom = barInsets.bottom)
windowInsets
}
}

View File

@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter
import org.yuzu.yuzu_emu.databinding.DialogListBinding
class CabinetLauncherDialogFragment : DialogFragment() {
private lateinit var binding: DialogListBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogListBinding.inflate(layoutInflater)
binding.dialogList.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = CabinetLauncherDialogAdapter(this@CabinetLauncherDialogFragment)
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.cabinet_launcher)
.setView(binding.root)
.create()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return binding.root
}
}

View File

@ -30,7 +30,6 @@ import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
@ -132,20 +131,6 @@ class HomeSettingsFragment : Fragment() {
} }
) )
) )
add(
HomeSetting(
R.string.applets,
R.string.applets_description,
R.drawable.ic_applet,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
},
{ NativeLibrary.isFirmwareAvailable() },
R.string.applets_error_firmware,
R.string.applets_error_description
)
)
add( add(
HomeSetting( HomeSetting(
R.string.select_games_folder, R.string.select_games_folder,

View File

@ -8,7 +8,6 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Html
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -33,9 +32,7 @@ class MessageDialogFragment : DialogFragment() {
if (titleId != 0) dialog.setTitle(titleId) if (titleId != 0) dialog.setTitle(titleId)
if (titleString.isNotEmpty()) dialog.setTitle(titleString) if (titleString.isNotEmpty()) dialog.setTitle(titleString)
if (descriptionId != 0) { if (descriptionId != 0) dialog.setMessage(descriptionId)
dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
}
if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (helpLinkId != 0) { if (helpLinkId != 0) {

View File

@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.R
data class Applet(
@StringRes val titleId: Int,
@StringRes val descriptionId: Int,
@DrawableRes val iconId: Int,
val appletInfo: AppletInfo,
val cabinetMode: CabinetMode = CabinetMode.None
)
// Combination of Common::AM::Applets::AppletId enum and the entry id
enum class AppletInfo(val appletId: Int, val entryId: Long = 0) {
None(0x00),
Application(0x01),
OverlayDisplay(0x02),
QLaunch(0x03),
Starter(0x04),
Auth(0x0A),
Cabinet(0x0B, 0x0100000000001002),
Controller(0x0C),
DataErase(0x0D),
Error(0x0E),
NetConnect(0x0F),
ProfileSelect(0x10),
SoftwareKeyboard(0x11),
MiiEdit(0x12, 0x0100000000001009),
Web(0x13),
Shop(0x14),
PhotoViewer(0x015, 0x010000000000100D),
Settings(0x16),
OfflineWeb(0x17),
LoginShare(0x18),
WebAuth(0x19),
MyPage(0x1A)
}
// Matches enum in Service::NFP::CabinetMode with extra metadata
enum class CabinetMode(
val id: Int,
@StringRes val titleId: Int = 0,
@DrawableRes val iconId: Int = 0
) {
None(-1),
StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit),
StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh),
StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore),
StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear)
}

View File

@ -11,12 +11,12 @@ import kotlinx.serialization.Serializable
@Parcelize @Parcelize
@Serializable @Serializable
class Game( class Game(
val title: String = "", val title: String,
val path: String, val path: String,
val programId: String = "", val programId: String,
val developer: String = "", val developer: String,
val version: String = "", val version: String,
val isHomebrew: Boolean = false val isHomebrew: Boolean
) : Parcelable { ) : Parcelable {
val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${programId}_LastPlayed" val keyLastPlayedTime get() = "${programId}_LastPlayed"

View File

@ -403,7 +403,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} else { } else {
firmwarePath.deleteRecursively() firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true) cacheFirmwareDir.copyRecursively(firmwarePath, true)
NativeLibrary.initializeSystem()
getString(R.string.save_file_imported_success) getString(R.string.save_file_imported_success)
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -649,7 +648,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
// Reinitialize relevant data // Reinitialize relevant data
NativeLibrary.initializeSystem() NativeLibrary.initializeEmulation()
gamesViewModel.reloadGames(false) gamesViewModel.reloadGames(false)
return@newInstance getString(R.string.user_data_import_success) return@newInstance getString(R.string.user_data_import_success)

View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
/**
* Some controllers have incorrect mappings. This class has special-case fixes for them.
*/
class ControllerMappingHelper {
/**
* Some controllers report extra button presses that can be ignored.
*/
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
return if (isDualShock4(inputDevice)) {
// The two analog triggers generate analog motion events as well as a keycode.
// We always prefer to use the analog values, so throw away the button press
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
} else {
false
}
}
/**
* Scale an axis to be zero-centered with a proper range.
*/
fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
if (isDualShock4(inputDevice)) {
// Android doesn't have correct mappings for this controller's triggers. It reports them
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
// Scale them to properly zero-centered with a range of [0.0, 1.0].
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
return (value + 1) / 2.0f
}
} else if (isXboxOneWireless(inputDevice)) {
// Same as the DualShock 4, the mappings are missing.
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
return (value + 1) / 2.0f
}
if (axis == MotionEvent.AXIS_GENERIC_1) {
// This axis is stuck at ~.5. Ignore it.
return 0.0f
}
} else if (isMogaPro2Hid(inputDevice)) {
// This controller has a broken axis that reports a constant value. Ignore it.
if (axis == MotionEvent.AXIS_GENERIC_1) {
return 0.0f
}
}
return value
}
// Sony DualShock 4 controller
private fun isDualShock4(inputDevice: InputDevice): Boolean {
return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
}
// Microsoft Xbox One controller
private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
}
// Moga Pro 2 HID
private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
}
}

View File

@ -15,7 +15,7 @@ object DirectoryInitialization {
fun start() { fun start() {
if (!areDirectoriesReady) { if (!areDirectoriesReady) {
initializeInternalStorage() initializeInternalStorage()
NativeLibrary.initializeSystem() NativeLibrary.initializeEmulation()
areDirectoriesReady = true areDirectoriesReady = true
} }
} }

View File

@ -3,24 +3,17 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import kotlin.math.sqrt import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
object InputHandler { class InputHandler {
private var controllerIds = getGameControllerIds()
fun initialize() { fun initialize() {
// Connect first controller // Connect first controller
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)) NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
} }
fun updateControllerIds() {
controllerIds = getGameControllerIds()
}
fun dispatchKeyEvent(event: KeyEvent): Boolean { fun dispatchKeyEvent(event: KeyEvent): Boolean {
val button: Int = when (event.device.vendorId) { val button: Int = when (event.device.vendorId) {
0x045E -> getInputXboxButtonKey(event.keyCode) 0x045E -> getInputXboxButtonKey(event.keyCode)
@ -42,7 +35,7 @@ object InputHandler {
} }
return NativeLibrary.onGamePadButtonEvent( return NativeLibrary.onGamePadButtonEvent(
getPlayerNumber(event.device.controllerNumber, event.deviceId), getPlayerNumber(event.device.controllerNumber),
button, button,
action action
) )
@ -65,14 +58,9 @@ object InputHandler {
return true return true
} }
private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int { private fun getPlayerNumber(index: Int): Int {
var deviceIndex = index
if (deviceId != -1) {
deviceIndex = controllerIds[deviceId]!!
}
// TODO: Joycons are handled as different controllers. Find a way to merge them. // TODO: Joycons are handled as different controllers. Find a way to merge them.
return when (deviceIndex) { return when (index) {
2 -> NativeLibrary.Player2Device 2 -> NativeLibrary.Player2Device
3 -> NativeLibrary.Player3Device 3 -> NativeLibrary.Player3Device
4 -> NativeLibrary.Player4Device 4 -> NativeLibrary.Player4Device
@ -250,7 +238,7 @@ object InputHandler {
} }
private fun setGenericAxisInput(event: MotionEvent, axis: Int) { private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) val playerNumber = getPlayerNumber(event.device.controllerNumber)
when (axis) { when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@ -309,7 +297,7 @@ object InputHandler {
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
// Joycon support is half dead. Right joystick doesn't work // Joycon support is half dead. Right joystick doesn't work
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) val playerNumber = getPlayerNumber(event.device.controllerNumber)
when (axis) { when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@ -337,7 +325,7 @@ object InputHandler {
} }
private fun setRazerAxisInput(event: MotionEvent, axis: Int) { private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) val playerNumber = getPlayerNumber(event.device.controllerNumber)
when (axis) { when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@ -374,33 +362,4 @@ object InputHandler {
) )
} }
} }
fun getGameControllerIds(): Map<Int, Int> {
val gameControllerDeviceIds = mutableMapOf<Int, Int>()
val deviceIds = InputDevice.getDeviceIds()
var controllerSlot = 1
deviceIds.forEach { deviceId ->
InputDevice.getDevice(deviceId)?.apply {
// Don't over-assign controllers
if (controllerSlot >= 8) {
return gameControllerDeviceIds
}
// Verify that the device has gamepad buttons, control sticks, or both.
if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
) {
// This device is a game controller. Store its device ID.
if (deviceId and id and vendorId and productId != 0) {
// Additionally filter out devices that have no ID
gameControllerDeviceIds
.takeIf { !it.contains(deviceId) }
?.put(deviceId, controllerSlot)
controllerSlot++
}
}
}
}
return gameControllerDeviceIds
}
} }

View File

@ -247,17 +247,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
} }
} }
void EmulationSession::InitializeSystem() {
// Initialize filesystem.
m_system.SetFilesystem(m_vfs);
m_system.GetUserChannel().clear();
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
m_manual_provider.get());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
}
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) { Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
@ -265,6 +254,9 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
m_window = m_window =
std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library); std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
m_system.SetFilesystem(m_vfs);
m_system.GetUserChannel().clear();
// Initialize system. // Initialize system.
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
m_software_keyboard = android_keyboard.get(); m_software_keyboard = android_keyboard.get();
@ -285,6 +277,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
}); });
// Initialize filesystem. // Initialize filesystem.
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
m_manual_provider.get());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
ConfigureFilesystemProvider(filepath); ConfigureFilesystemProvider(filepath);
// Initialize account manager // Initialize account manager
@ -666,12 +663,11 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
} }
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
// Create the default config.ini. // Create the default config.ini.
Config{}; Config{};
// Initialize the emulated system. // Initialize the emulated system.
EmulationSession::GetInstance().System().Initialize(); EmulationSession::GetInstance().System().Initialize();
EmulationSession::GetInstance().InitializeSystem();
} }
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) { jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
@ -759,49 +755,4 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
} }
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
jlong jid) {
auto bis_system =
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
return ToJString(env, "");
}
auto applet_nca =
bis_system->GetEntry(static_cast<u64>(jid), FileSys::ContentRecordType::Program);
if (!applet_nca) {
return ToJString(env, "");
}
return ToJString(env, applet_nca->GetFullPath());
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jclass clazz,
jint jappletId) {
EmulationSession::GetInstance().System().GetAppletManager().SetCurrentAppletId(
static_cast<Service::AM::Applets::AppletId>(jappletId));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz,
jint jcabinetMode) {
EmulationSession::GetInstance().System().GetAppletManager().SetCabinetMode(
static_cast<Service::NFP::CabinetMode>(jcabinetMode));
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) {
auto bis_system =
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
return false;
}
// Query an applet to see if it's available
auto applet_nca =
bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program);
if (!applet_nca) {
return false;
}
return true;
}
} // extern "C" } // extern "C"

View File

@ -43,7 +43,6 @@ public:
const Core::PerfStatsResults& PerfStats() const; const Core::PerfStatsResults& PerfStats() const;
void ConfigureFilesystemProvider(const std::string& filepath); void ConfigureFilesystemProvider(const std::string& filepath);
void InitializeSystem();
Core::SystemResultStatus InitializeEmulation(const std::string& filepath); Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
bool IsHandheldOnly(); bool IsHandheldOnly();

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M17,16l-4,-4V8.82C14.16,8.4 15,7.3 15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6c0,1.3 0.84,2.4 2,2.82V12l-4,4H3v5h5v-3.05l4,-4.2 4,4.2V21h5v-5h-4z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>

View File

@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M9,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M20.77,8.58l-0.92,2.01c0.09,0.46 0.15,0.93 0.15,1.41 0,4.41 -3.59,8 -8,8s-8,-3.59 -8,-8c0,-0.05 0.01,-0.1 0,-0.14 2.6,-0.98 4.69,-2.99 5.74,-5.55C11.58,8.56 14.37,10 17.5,10c0.45,0 0.89,-0.04 1.33,-0.1l-0.6,-1.32 -0.88,-1.93 -1.93,-0.88 -2.79,-1.27 2.79,-1.27 0.71,-0.32C14.87,2.33 13.47,2 12,2 6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-1.47 -0.33,-2.87 -0.9,-4.13l-0.33,0.71z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M15,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M20.6,5.6L19.5,8l-1.1,-2.4L16,4.5l2.4,-1.1L19.5,1l1.1,2.4L23,4.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
</vector>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center"
android:padding="24dp">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="20dp"
android:layout_gravity="center_vertical"
app:tint="?attr/colorOnSurface" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_gravity="center_vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
tools:text="@string/applets" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/description"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
tools:text="@string/applets_description" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:fadeScrollbars="false"
android:paddingVertical="12dp"
android:scrollbars="vertical" />
</androidx.appcompat.widget.LinearLayoutCompat>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
tools:src="@drawable/ic_nfc" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical|start"
android:textAlignment="viewStart"
tools:text="List option" />
</LinearLayout>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_applets"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_applets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_applets"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_back"
app:title="@string/applets" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_applets"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -25,9 +25,6 @@
<action <action
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment" android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
app:destination="@id/driverManagerFragment" /> app:destination="@id/driverManagerFragment" />
<action
android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
app:destination="@id/appletLauncherFragment" />
</fragment> </fragment>
<fragment <fragment
@ -105,17 +102,5 @@
android:id="@+id/driverManagerFragment" android:id="@+id/driverManagerFragment"
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
android:label="DriverManagerFragment" /> android:label="DriverManagerFragment" />
<fragment
android:id="@+id/appletLauncherFragment"
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
android:label="AppletLauncherFragment" >
<action
android:id="@+id/action_appletLauncherFragment_to_cabinetLauncherDialogFragment"
app:destination="@id/cabinetLauncherDialogFragment" />
</fragment>
<dialog
android:id="@+id/cabinetLauncherDialogFragment"
android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
android:label="CabinetLauncherDialogFragment" />
</navigation> </navigation>

View File

@ -124,24 +124,6 @@
<string name="share_save_file">Share save file</string> <string name="share_save_file">Share save file</string>
<string name="export_save_failed">Failed to export save</string> <string name="export_save_failed">Failed to export save</string>
<!-- Applet launcher strings -->
<string name="applets">Applet launcher</string>
<string name="applets_description">Launch system applets using installed firmware</string>
<string name="applets_error_firmware">Firmware not installed</string>
<string name="applets_error_applet">Applet not available</string>
<string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and <a href="https://yuzu-emu.org/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.]]></string>
<string name="album_applet">Album</string>
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string>
<string name="mii_edit_applet">Mii edit</string>
<string name="mii_edit_applet_description">View and edit Miis with the system editor</string>
<string name="cabinet_applet">Cabinet</string>
<string name="cabinet_applet_description">Edit and delete data stored on amiibo</string>
<string name="cabinet_launcher">Cabinet launcher</string>
<string name="cabinet_nickname_and_owner">Nickname and owner settings</string>
<string name="cabinet_game_data_eraser">Game data eraser</string>
<string name="cabinet_restorer">Restorer</string>
<string name="cabinet_formatter">Formatter</string>
<!-- About screen strings --> <!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string> <string name="gaia_is_not_real">Gaia isn\'t real</string>
<string name="copied_to_clipboard">Copied to clipboard</string> <string name="copied_to_clipboard">Copied to clipboard</string>

View File

@ -23,7 +23,6 @@
#include "core/hle/service/am/applets/applet_cabinet.h" #include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/am/applets/applet_mii_edit_types.h" #include "core/hle/service/am/applets/applet_mii_edit_types.h"
#include "core/hle/service/am/applets/applet_profile_select.h" #include "core/hle/service/am/applets/applet_profile_select.h"
#include "core/hle/service/am/applets/applet_software_keyboard_types.h"
#include "core/hle/service/am/applets/applet_web_browser.h" #include "core/hle/service/am/applets/applet_web_browser.h"
#include "core/hle/service/am/applets/applets.h" #include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/idle.h" #include "core/hle/service/am/idle.h"
@ -32,7 +31,6 @@
#include "core/hle/service/apm/apm_controller.h" #include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/apm/apm_interface.h" #include "core/hle/service/apm/apm_interface.h"
#include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h" #include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
@ -704,17 +702,9 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(HLERequestContext& c
void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) { void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto report_option = rp.PopEnum<Capture::AlbumReportOption>(); const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>();
LOG_INFO(Service_AM, "called, report_option={}", report_option); LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option);
const auto screenshot_service =
system.ServiceManager().GetService<Service::Capture::IScreenShotApplicationService>(
"caps:su");
if (screenshot_service) {
screenshot_service->CaptureAndSaveScreenshot(report_option);
}
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1572,7 +1562,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{16, nullptr, "GetMainAppletStorageId"}, {16, nullptr, "GetMainAppletStorageId"},
{17, nullptr, "GetCallerAppletIdentityInfoStack"}, {17, nullptr, "GetCallerAppletIdentityInfoStack"},
{18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"}, {18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"},
{19, &ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout, "GetDesirableKeyboardLayout"}, {19, nullptr, "GetDesirableKeyboardLayout"},
{20, nullptr, "PopExtraStorage"}, {20, nullptr, "PopExtraStorage"},
{25, nullptr, "GetPopExtraStorageEvent"}, {25, nullptr, "GetPopExtraStorageEvent"},
{30, nullptr, "UnpopInData"}, {30, nullptr, "UnpopInData"},
@ -1591,7 +1581,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{120, nullptr, "GetLaunchStorageInfoForDebug"}, {120, nullptr, "GetLaunchStorageInfoForDebug"},
{130, nullptr, "GetGpuErrorDetectedSystemEvent"}, {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
{140, nullptr, "SetApplicationMemoryReservation"}, {140, nullptr, "SetApplicationMemoryReservation"},
{150, &ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually, "ShouldSetGpuTimeSliceManually"}, {150, nullptr, "ShouldSetGpuTimeSliceManually"},
}; };
// clang-format on // clang-format on
RegisterHandlers(functions); RegisterHandlers(functions);
@ -1606,9 +1596,6 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
case Applets::AppletId::PhotoViewer: case Applets::AppletId::PhotoViewer:
PushInShowAlbum(); PushInShowAlbum();
break; break;
case Applets::AppletId::SoftwareKeyboard:
PushInShowSoftwareKeyboard();
break;
default: default:
break; break;
} }
@ -1685,14 +1672,6 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
rb.PushRaw(applet_info); rb.PushRaw(applet_info);
} }
void ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(0);
}
void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) { void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
const Service::Account::ProfileManager manager{}; const Service::Account::ProfileManager manager{};
bool is_empty{true}; bool is_empty{true};
@ -1712,14 +1691,6 @@ void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext&
rb.Push(user_count); rb.Push(user_count);
} }
void ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
rb.Push<u8>(0);
}
void ILibraryAppletSelfAccessor::PushInShowAlbum() { void ILibraryAppletSelfAccessor::PushInShowAlbum() {
const Applets::CommonArguments arguments{ const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3, .arguments_version = Applets::CommonArgumentVersion::Version3,
@ -1788,61 +1759,6 @@ void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
queue_data.emplace_back(std::move(argument_data)); queue_data.emplace_back(std::move(argument_data));
} }
void ILibraryAppletSelfAccessor::PushInShowSoftwareKeyboard() {
const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3,
.size = Applets::CommonArgumentSize::Version3,
.library_version = static_cast<u32>(Applets::SwkbdAppletVersion::Version524301),
.theme_color = Applets::ThemeColor::BasicBlack,
.play_startup_sound = true,
.system_tick = system.CoreTiming().GetClockTicks(),
};
std::vector<char16_t> initial_string(0);
const Applets::SwkbdConfigCommon swkbd_config{
.type = Applets::SwkbdType::Qwerty,
.ok_text{},
.left_optional_symbol_key{},
.right_optional_symbol_key{},
.use_prediction = false,
.key_disable_flags{},
.initial_cursor_position = Applets::SwkbdInitialCursorPosition::Start,
.header_text{},
.sub_text{},
.guide_text{},
.max_text_length = 500,
.min_text_length = 0,
.password_mode = Applets::SwkbdPasswordMode::Disabled,
.text_draw_type = Applets::SwkbdTextDrawType::Box,
.enable_return_button = true,
.use_utf8 = false,
.use_blur_background = true,
.initial_string_offset{},
.initial_string_length = static_cast<u32>(initial_string.size()),
.user_dictionary_offset{},
.user_dictionary_entries{},
.use_text_check = false,
};
Applets::SwkbdConfigNew swkbd_config_new{};
std::vector<u8> argument_data(sizeof(arguments));
std::vector<u8> swkbd_data(sizeof(swkbd_config) + sizeof(swkbd_config_new));
std::vector<u8> work_buffer(swkbd_config.initial_string_length * sizeof(char16_t));
std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
std::memcpy(swkbd_data.data(), &swkbd_config, sizeof(swkbd_config));
std::memcpy(swkbd_data.data() + sizeof(swkbd_config), &swkbd_config_new,
sizeof(Applets::SwkbdConfigNew));
std::memcpy(work_buffer.data(), initial_string.data(),
swkbd_config.initial_string_length * sizeof(char16_t));
queue_data.emplace_back(std::move(argument_data));
queue_data.emplace_back(std::move(swkbd_data));
queue_data.emplace_back(std::move(work_buffer));
}
IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_) IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
: ServiceFramework{system_, "IAppletCommonFunctions"} { : ServiceFramework{system_, "IAppletCommonFunctions"} {
// clang-format off // clang-format off

View File

@ -347,14 +347,11 @@ private:
void GetLibraryAppletInfo(HLERequestContext& ctx); void GetLibraryAppletInfo(HLERequestContext& ctx);
void ExitProcessAndReturn(HLERequestContext& ctx); void ExitProcessAndReturn(HLERequestContext& ctx);
void GetCallerAppletIdentityInfo(HLERequestContext& ctx); void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
void GetDesirableKeyboardLayout(HLERequestContext& ctx);
void GetMainAppletAvailableUsers(HLERequestContext& ctx); void GetMainAppletAvailableUsers(HLERequestContext& ctx);
void ShouldSetGpuTimeSliceManually(HLERequestContext& ctx);
void PushInShowAlbum(); void PushInShowAlbum();
void PushInShowCabinetData(); void PushInShowCabinetData();
void PushInShowMiiEditData(); void PushInShowMiiEditData();
void PushInShowSoftwareKeyboard();
std::deque<std::vector<u8>> queue_data; std::deque<std::vector<u8>> queue_data;
}; };

View File

@ -228,14 +228,12 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute, const ScreenShotAttribute& attribute,
AlbumReportOption report_option, std::span<const u8> image_data, std::span<const u8> image_data, u64 aruid) {
u64 aruid) { return SaveScreenShot(out_entry, attribute, {}, image_data, aruid);
return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
} }
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute, const ScreenShotAttribute& attribute,
AlbumReportOption report_option,
const ApplicationData& app_data, std::span<const u8> image_data, const ApplicationData& app_data, std::span<const u8> image_data,
u64 aruid) { u64 aruid) {
const u64 title_id = system.GetApplicationProcessProgramID(); const u64 title_id = system.GetApplicationProcessProgramID();
@ -409,14 +407,10 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
return ResultSuccess; return ResultSuccess;
} }
void AlbumManager::FlipVerticallyOnWrite(bool flip) { static void PNGToMemory(void* context, void* png, int len) {
stbi_flip_vertically_on_write(flip);
}
static void PNGToMemory(void* context, void* data, int len) {
std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context); std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context);
unsigned char* png = static_cast<unsigned char*>(data); png_image->reserve(len);
png_image->insert(png_image->end(), png, png + len); std::memcpy(png_image->data(), png, len);
} }
Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image,

View File

@ -59,17 +59,14 @@ public:
const ScreenShotDecodeOption& decoder_options) const; const ScreenShotDecodeOption& decoder_options) const;
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
AlbumReportOption report_option, std::span<const u8> image_data,
u64 aruid);
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
AlbumReportOption report_option, const ApplicationData& app_data,
std::span<const u8> image_data, u64 aruid); std::span<const u8> image_data, u64 aruid);
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
const ApplicationData& app_data, std::span<const u8> image_data,
u64 aruid);
Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute, const AlbumFileId& file_id, const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
std::span<const u8> image_data); std::span<const u8> image_data);
void FlipVerticallyOnWrite(bool flip);
private: private:
static constexpr std::size_t NandAlbumFileLimit = 1000; static constexpr std::size_t NandAlbumFileLimit = 1000;
static constexpr std::size_t SdAlbumFileLimit = 10000; static constexpr std::size_t SdAlbumFileLimit = 10000;

View File

@ -34,7 +34,7 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
struct Parameters { struct Parameters {
ScreenShotAttribute attribute{}; ScreenShotAttribute attribute{};
AlbumReportOption report_option{}; u32 report_option{};
INSERT_PADDING_BYTES(0x4); INSERT_PADDING_BYTES(0x4);
u64 applet_resource_user_id{}; u64 applet_resource_user_id{};
}; };
@ -49,16 +49,13 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
parameters.applet_resource_user_id); parameters.applet_resource_user_id);
ApplicationAlbumEntry entry{}; ApplicationAlbumEntry entry{};
manager->FlipVerticallyOnWrite(false); const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
const auto result = parameters.applet_resource_user_id);
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
image_data_buffer, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10}; IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result); rb.Push(result);
rb.PushRaw(entry); rb.PushRaw(entry);
} }
void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) { void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
struct Parameters { struct Parameters {
@ -86,7 +83,6 @@ void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
image_data_buffer.size(), thumbnail_image_data_buffer.size()); image_data_buffer.size(), thumbnail_image_data_buffer.size());
ApplicationAlbumEntry entry{}; ApplicationAlbumEntry entry{};
manager->FlipVerticallyOnWrite(false);
const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute, const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
parameters.file_id, image_data_buffer); parameters.file_id, image_data_buffer);

View File

@ -2,12 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/caps/caps_manager.h" #include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_su.h" #include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h" #include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "video_core/renderer_base.h"
namespace Service::Capture { namespace Service::Capture {
@ -60,10 +58,8 @@ void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
parameters.applet_resource_user_id); parameters.applet_resource_user_id);
ApplicationAlbumEntry entry{}; ApplicationAlbumEntry entry{};
manager->FlipVerticallyOnWrite(false); const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
const auto result = parameters.applet_resource_user_id);
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
image_data_buffer, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10}; IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result); rb.Push(result);
@ -92,43 +88,13 @@ void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
ApplicationAlbumEntry entry{}; ApplicationAlbumEntry entry{};
ApplicationData app_data{}; ApplicationData app_data{};
std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData)); std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
manager->FlipVerticallyOnWrite(false);
const auto result = const auto result =
manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, app_data, manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer,
image_data_buffer, parameters.applet_resource_user_id); parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10}; IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result); rb.Push(result);
rb.PushRaw(entry); rb.PushRaw(entry);
} }
void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption report_option) {
auto& renderer = system.Renderer();
Layout::FramebufferLayout layout =
Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
const Capture::ScreenShotAttribute attribute{
.unknown_0{},
.orientation = Capture::AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
};
renderer.RequestScreenshot(
image_data.data(),
[attribute, report_option, this](bool invert_y) {
// Convert from BGRA to RGBA
for (std::size_t i = 0; i < image_data.size(); i += bytes_per_pixel) {
const u8 temp = image_data[i];
image_data[i] = image_data[i + 2];
image_data[i + 2] = temp;
}
Capture::ApplicationAlbumEntry entry{};
manager->FlipVerticallyOnWrite(invert_y);
manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
},
layout);
}
} // namespace Service::Capture } // namespace Service::Capture

View File

@ -10,7 +10,6 @@ class System;
} }
namespace Service::Capture { namespace Service::Capture {
enum class AlbumReportOption : s32;
class AlbumManager; class AlbumManager;
class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> { class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
@ -19,19 +18,11 @@ public:
std::shared_ptr<AlbumManager> album_manager); std::shared_ptr<AlbumManager> album_manager);
~IScreenShotApplicationService() override; ~IScreenShotApplicationService() override;
void CaptureAndSaveScreenshot(AlbumReportOption report_option);
private: private:
static constexpr std::size_t screenshot_width = 1280;
static constexpr std::size_t screenshot_height = 720;
static constexpr std::size_t bytes_per_pixel = 4;
void SetShimLibraryVersion(HLERequestContext& ctx); void SetShimLibraryVersion(HLERequestContext& ctx);
void SaveScreenShotEx0(HLERequestContext& ctx); void SaveScreenShotEx0(HLERequestContext& ctx);
void SaveScreenShotEx1(HLERequestContext& ctx); void SaveScreenShotEx1(HLERequestContext& ctx);
std::array<u8, screenshot_width * screenshot_height * bytes_per_pixel> image_data;
std::shared_ptr<AlbumManager> manager; std::shared_ptr<AlbumManager> manager;
}; };