From e8cb8b2668c86ddad527cb8ff7de7f992080dece Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Mon, 30 Oct 2023 19:29:00 -0400 Subject: [PATCH] android: Implement applet launcher --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 32 ++++- .../yuzu/yuzu_emu/adapters/AppletAdapter.kt | 90 ++++++++++++++ .../adapters/CabinetLauncherDialogAdapter.kt | 72 +++++++++++ .../fragments/AppletLauncherFragment.kt | 113 ++++++++++++++++++ .../CabinetLauncherDialogFragment.kt | 41 +++++++ .../fragments/HomeSettingsFragment.kt | 15 +++ .../fragments/MessageDialogFragment.kt | 5 +- .../java/org/yuzu/yuzu_emu/model/Applet.kt | 55 +++++++++ .../main/java/org/yuzu/yuzu_emu/model/Game.kt | 10 +- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 3 +- .../yuzu_emu/utils/DirectoryInitialization.kt | 2 +- src/android/app/src/main/jni/native.cpp | 45 +++++++ .../app/src/main/res/drawable/ic_album.xml | 9 ++ .../app/src/main/res/drawable/ic_applet.xml | 9 ++ .../app/src/main/res/drawable/ic_edit.xml | 9 ++ .../app/src/main/res/drawable/ic_mii.xml | 18 +++ .../app/src/main/res/drawable/ic_refresh.xml | 9 ++ .../app/src/main/res/drawable/ic_restore.xml | 9 ++ .../main/res/layout/card_applet_option.xml | 57 +++++++++ .../app/src/main/res/layout/dialog_list.xml | 15 +++ .../src/main/res/layout/dialog_list_item.xml | 30 +++++ .../res/layout/fragment_applet_launcher.xml | 31 +++++ .../main/res/navigation/home_navigation.xml | 15 +++ .../app/src/main/res/values/strings.xml | 18 +++ 24 files changed, 703 insertions(+), 9 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt create mode 100644 src/android/app/src/main/res/drawable/ic_album.xml create mode 100644 src/android/app/src/main/res/drawable/ic_applet.xml create mode 100644 src/android/app/src/main/res/drawable/ic_edit.xml create mode 100644 src/android/app/src/main/res/drawable/ic_mii.xml create mode 100644 src/android/app/src/main/res/drawable/ic_refresh.xml create mode 100644 src/android/app/src/main/res/drawable/ic_restore.xml create mode 100644 src/android/app/src/main/res/layout/card_applet_option.xml create mode 100644 src/android/app/src/main/res/layout/dialog_list.xml create mode 100644 src/android/app/src/main/res/layout/dialog_list_item.xml create mode 100644 src/android/app/src/main/res/layout/fragment_applet_launcher.xml diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index e2c5b6acd..07f1b4842 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -252,7 +252,7 @@ object NativeLibrary { external fun reloadKeys(): Boolean - external fun initializeEmulation() + external fun initializeSystem() external fun defaultCPUCore(): Int @@ -505,6 +505,36 @@ object NativeLibrary { */ external fun initializeEmptyUserDirectory() + /** + * Gets the launch path for a given applet. It is the caller's responsibility to also + * set the system's current applet ID before trying to launch the nca given by this function. + * + * @param id The applet entry ID + * @return The applet's launch path + */ + external fun getAppletLaunchPath(id: Long): String + + /** + * Sets the system's current applet ID before launching. + * + * @param appletId One of the ids in the Service::AM::Applets::AppletId enum + */ + external fun setCurrentAppletId(appletId: Int) + + /** + * Sets the cabinet mode for launching the cabinet applet. + * + * @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode + */ + external fun setCabinetMode(cabinetMode: Int) + + /** + * Checks whether NAND contents are available and valid. + * + * @return 'true' if firmware is available + */ + external fun isFirmwareAvailable(): Boolean + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt new file mode 100644 index 000000000..a21a705c1 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.FragmentActivity +import androidx.navigation.findNavController +import androidx.recyclerview.widget.RecyclerView +import org.yuzu.yuzu_emu.HomeNavigationDirections +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding +import org.yuzu.yuzu_emu.model.Applet +import org.yuzu.yuzu_emu.model.AppletInfo +import org.yuzu.yuzu_emu.model.Game + +class AppletAdapter(val activity: FragmentActivity, var applets: List) : + RecyclerView.Adapter(), + 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 + ) + ) + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt new file mode 100644 index 000000000..e7b7c0f2f --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView +import org.yuzu.yuzu_emu.HomeNavigationDirections +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.databinding.DialogListItemBinding +import org.yuzu.yuzu_emu.model.CabinetMode +import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder +import org.yuzu.yuzu_emu.model.AppletInfo +import org.yuzu.yuzu_emu.model.Game + +class CabinetLauncherDialogAdapter(val fragment: Fragment) : + RecyclerView.Adapter(), + 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) + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt new file mode 100644 index 000000000..1f66b440d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.findNavController +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.transition.MaterialSharedAxis +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.adapters.AppletAdapter +import org.yuzu.yuzu_emu.databinding.FragmentAppletLauncherBinding +import org.yuzu.yuzu_emu.model.Applet +import org.yuzu.yuzu_emu.model.AppletInfo +import org.yuzu.yuzu_emu.model.HomeViewModel + +class AppletLauncherFragment : Fragment() { + private var _binding: FragmentAppletLauncherBinding? = null + private val binding get() = _binding!! + + private val homeViewModel: HomeViewModel by activityViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentAppletLauncherBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = false, animated = true) + homeViewModel.setStatusBarShadeVisibility(visible = false) + + binding.toolbarApplets.setNavigationOnClickListener { + binding.root.findNavController().popBackStack() + } + + val applets = listOf( + Applet( + R.string.album_applet, + R.string.album_applet_description, + R.drawable.ic_album, + AppletInfo.PhotoViewer + ), + Applet( + R.string.cabinet_applet, + R.string.cabinet_applet_description, + R.drawable.ic_nfc, + AppletInfo.Cabinet + ), + Applet( + R.string.mii_edit_applet, + R.string.mii_edit_applet_description, + R.drawable.ic_mii, + AppletInfo.MiiEdit + ) + ) + + binding.listApplets.apply { + layoutManager = GridLayoutManager( + requireContext(), + resources.getInteger(R.integer.grid_columns) + ) + adapter = AppletAdapter(requireActivity(), applets) + } + + setInsets() + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + val mlpAppBar = binding.toolbarApplets.layoutParams as ViewGroup.MarginLayoutParams + mlpAppBar.leftMargin = leftInsets + mlpAppBar.rightMargin = rightInsets + binding.toolbarApplets.layoutParams = mlpAppBar + + val mlpListApplets = + binding.listApplets.layoutParams as ViewGroup.MarginLayoutParams + mlpListApplets.leftMargin = leftInsets + mlpListApplets.rightMargin = rightInsets + binding.listApplets.layoutParams = mlpListApplets + + binding.listApplets.updatePadding(bottom = barInsets.bottom) + + windowInsets + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt new file mode 100644 index 000000000..5933677fd --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter +import org.yuzu.yuzu_emu.databinding.DialogListBinding + +class CabinetLauncherDialogFragment : DialogFragment() { + private lateinit var binding: DialogListBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = DialogListBinding.inflate(layoutInflater) + binding.dialogList.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = CabinetLauncherDialogAdapter(this@CabinetLauncherDialogFragment) + } + + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.cabinet_launcher) + .setView(binding.root) + .create() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return binding.root + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index f273c880a..6e19fc6c0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -30,6 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.HomeNavigationDirections +import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding @@ -131,6 +132,20 @@ class HomeSettingsFragment : Fragment() { } ) ) + add( + HomeSetting( + R.string.applets, + R.string.applets_description, + R.drawable.ic_applet, + { + binding.root.findNavController() + .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) + }, + { NativeLibrary.isFirmwareAvailable() }, + R.string.applets_error_firmware, + R.string.applets_error_description + ) + ) add( HomeSetting( R.string.select_games_folder, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 541b22f47..a6183d19e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt @@ -8,6 +8,7 @@ import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle +import android.text.Html import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.activityViewModels @@ -32,7 +33,9 @@ class MessageDialogFragment : DialogFragment() { if (titleId != 0) dialog.setTitle(titleId) if (titleString.isNotEmpty()) dialog.setTitle(titleString) - if (descriptionId != 0) dialog.setMessage(descriptionId) + if (descriptionId != 0) { + dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY)) + } if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) if (helpLinkId != 0) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt new file mode 100644 index 000000000..8677674a3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.model + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import org.yuzu.yuzu_emu.R + +data class Applet( + @StringRes val titleId: Int, + @StringRes val descriptionId: Int, + @DrawableRes val iconId: Int, + val appletInfo: AppletInfo, + val cabinetMode: CabinetMode = CabinetMode.None +) + +// Combination of Common::AM::Applets::AppletId enum and the entry id +enum class AppletInfo(val appletId: Int, val entryId: Long = 0) { + None(0x00), + Application(0x01), + OverlayDisplay(0x02), + QLaunch(0x03), + Starter(0x04), + Auth(0x0A), + Cabinet(0x0B, 0x0100000000001002), + Controller(0x0C), + DataErase(0x0D), + Error(0x0E), + NetConnect(0x0F), + ProfileSelect(0x10), + SoftwareKeyboard(0x11), + MiiEdit(0x12, 0x0100000000001009), + Web(0x13), + Shop(0x14), + PhotoViewer(0x015, 0x010000000000100D), + Settings(0x16), + OfflineWeb(0x17), + LoginShare(0x18), + WebAuth(0x19), + MyPage(0x1A) +} + +// Matches enum in Service::NFP::CabinetMode with extra metadata +enum class CabinetMode( + val id: Int, + @StringRes val titleId: Int = 0, + @DrawableRes val iconId: Int = 0 +) { + None(-1), + StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit), + StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh), + StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore), + StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index b43978fce..de84b2adb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -11,12 +11,12 @@ import kotlinx.serialization.Serializable @Parcelize @Serializable class Game( - val title: String, + val title: String = "", val path: String, - val programId: String, - val developer: String, - val version: String, - val isHomebrew: Boolean + val programId: String = "", + val developer: String = "", + val version: String = "", + val isHomebrew: Boolean = false ) : Parcelable { val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" val keyLastPlayedTime get() = "${programId}_LastPlayed" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 233aa4101..ba1177426 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -403,6 +403,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } else { firmwarePath.deleteRecursively() cacheFirmwareDir.copyRecursively(firmwarePath, true) + NativeLibrary.initializeSystem() getString(R.string.save_file_imported_success) } } catch (e: Exception) { @@ -648,7 +649,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } // Reinitialize relevant data - NativeLibrary.initializeEmulation() + NativeLibrary.initializeSystem() gamesViewModel.reloadGames(false) return@newInstance getString(R.string.user_data_import_success) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 3c9f6bad0..79a07f7ef 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt @@ -15,7 +15,7 @@ object DirectoryInitialization { fun start() { if (!areDirectoriesReady) { initializeInternalStorage() - NativeLibrary.initializeEmulation() + NativeLibrary.initializeSystem() areDirectoriesReady = true } } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 686b73588..f7931a89d 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -755,4 +755,49 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* } } +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz, + jlong jid) { + auto bis_system = + EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + return ToJString(env, ""); + } + + auto applet_nca = + bis_system->GetEntry(static_cast(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(jappletId)); +} + +void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz, + jint jcabinetMode) { + EmulationSession::GetInstance().System().GetAppletManager().SetCabinetMode( + static_cast(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" diff --git a/src/android/app/src/main/res/drawable/ic_album.xml b/src/android/app/src/main/res/drawable/ic_album.xml new file mode 100644 index 000000000..f2b63813f --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_album.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/drawable/ic_applet.xml b/src/android/app/src/main/res/drawable/ic_applet.xml new file mode 100644 index 000000000..b154e6f56 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_applet.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/drawable/ic_edit.xml b/src/android/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..ac22ce8a5 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/drawable/ic_mii.xml b/src/android/app/src/main/res/drawable/ic_mii.xml new file mode 100644 index 000000000..1271ec401 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_mii.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/android/app/src/main/res/drawable/ic_refresh.xml b/src/android/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 000000000..d0d87ecc2 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/drawable/ic_restore.xml b/src/android/app/src/main/res/drawable/ic_restore.xml new file mode 100644 index 000000000..d6d9d4017 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_restore.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/layout/card_applet_option.xml b/src/android/app/src/main/res/layout/card_applet_option.xml new file mode 100644 index 000000000..19fbec9f1 --- /dev/null +++ b/src/android/app/src/main/res/layout/card_applet_option.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/dialog_list.xml b/src/android/app/src/main/res/layout/dialog_list.xml new file mode 100644 index 000000000..7de2b2c3a --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_list.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/android/app/src/main/res/layout/dialog_list_item.xml b/src/android/app/src/main/res/layout/dialog_list_item.xml new file mode 100644 index 000000000..39f3558ff --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_list_item.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/src/android/app/src/main/res/layout/fragment_applet_launcher.xml b/src/android/app/src/main/res/layout/fragment_applet_launcher.xml new file mode 100644 index 000000000..fe8fae40f --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_applet_launcher.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 82749359d..6d4c1f86d 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml @@ -25,6 +25,9 @@ + + + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 9e4854221..b92978140 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -124,6 +124,24 @@ Share save file Failed to export save + + Applet launcher + Launch system applets using installed firmware + Firmware not installed + Applet not available + prod.keys file and firmware are installed and try again.]]> + Album + See images stored in the user screenshots folder with the system photo viewer + Mii edit + View and edit Miis with the system editor + Cabinet + Edit and delete data stored on amiibo + Cabinet launcher + Nickname and owner settings + Game data eraser + Restorer + Formatter + Gaia isn\'t real Copied to clipboard