From a338de785097752633eb67dacacc3d8761b9c577 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Thu, 8 Jun 2023 17:26:24 -0600 Subject: [PATCH] android: Add update support --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 13 +++ .../fragments/HomeSettingsFragment.kt | 12 ++- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 58 +++++++++++++ src/android/app/src/main/jni/native.cpp | 86 ++++++++++++++++++- .../res/drawable/ic_system_update_alt.xml | 9 ++ .../app/src/main/res/values/strings.xml | 9 ++ src/core/file_sys/submission_package.h | 1 + 7 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/android/app/src/main/res/drawable/ic_system_update_alt.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 22af9e435..4be9ade14 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 @@ -227,6 +227,8 @@ object NativeLibrary { external fun setAppDirectory(directory: String) + external fun installFileToNand(filename: String): Int + external fun initializeGpuDriver( hookLibDir: String?, customDriverDir: String?, @@ -507,4 +509,15 @@ object NativeLibrary { const val RELEASED = 0 const val PRESSED = 1 } + + /** + * Result from installFileToNand + */ + object InstallFileToNandResult { + const val Success = 0 + const val SuccessFileOverwritten = 1 + const val Error = 2 + const val ErrorBaseGame = 3 + const val ErrorFilenameExtension = 4 + } } 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 bdc337501..536163eb6 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 @@ -94,6 +94,11 @@ class HomeSettingsFragment : Fragment() { 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, @@ -103,7 +108,12 @@ class HomeSettingsFragment : Fragment() { R.string.manage_save_data, R.string.import_export_saves_description, R.drawable.ic_save - ) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) }, + ) { + ImportExportSavesFragment().show( + parentFragmentManager, + ImportExportSavesFragment.TAG + ) + }, HomeSetting( R.string.install_prod_keys, R.string.install_prod_keys_description, 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 124f62f08..82fc9e04e 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 @@ -467,4 +467,62 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } } + + val installGameUpdate = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { + if (it == null) + return@registerForActivityResult + + IndeterminateProgressDialogFragment.newInstance( + this@MainActivity, + R.string.install_game_content + ) { + val result = NativeLibrary.installFileToNand(it.toString()) + lifecycleScope.launch { + withContext(Dispatchers.Main) { + when (result) { + NativeLibrary.InstallFileToNandResult.Success -> { + Toast.makeText( + applicationContext, + R.string.install_game_content_success, + Toast.LENGTH_SHORT + ).show() + } + + NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { + Toast.makeText( + applicationContext, + R.string.install_game_content_success_overwrite, + Toast.LENGTH_SHORT + ).show() + } + + NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { + MessageDialogFragment.newInstance( + R.string.install_game_content_failure, + R.string.install_game_content_failure_base + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + + 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) + } + + 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) + } + } + } + } + return@newInstance result + }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) + } } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 7ebed5e6a..4091c23d1 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -28,7 +28,10 @@ #include "core/core.h" #include "core/cpu_manager.h" #include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/registered_cache.h" +#include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/cabinet.h" #include "core/frontend/applets/controller.h" @@ -94,6 +97,74 @@ public: m_native_window = native_window; } + int InstallFileToNand(std::string filename) { + const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, + std::size_t block_size) { + if (src == nullptr || dest == nullptr) { + return false; + } + if (!dest->Resize(src->GetSize())) { + return false; + } + + using namespace Common::Literals; + std::vector 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); + dest->Write(buffer.data(), read, i); + } + return true; + }; + + enum InstallResult { + Success = 0, + SuccessFileOverwritten = 1, + InstallError = 2, + ErrorBaseGame = 3, + ErrorFilenameExtension = 4, + }; + + m_system.SetContentProvider(std::make_unique()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); + + std::shared_ptr nsp; + if (filename.ends_with("nsp")) { + nsp = std::make_shared(m_vfs->OpenFile(filename, FileSys::Mode::Read)); + if (nsp->IsExtractedType()) { + return InstallError; + } + } else if (filename.ends_with("xci")) { + const auto xci = + std::make_shared(m_vfs->OpenFile(filename, FileSys::Mode::Read)); + nsp = xci->GetSecurePartitionNSP(); + } else { + return ErrorFilenameExtension; + } + + if (!nsp) { + return InstallError; + } + + if (nsp->GetStatus() != Loader::ResultStatus::Success) { + return InstallError; + } + + const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( + *nsp, true, copy_func); + + switch (res) { + case FileSys::InstallResult::Success: + return Success; + case FileSys::InstallResult::OverwriteExisting: + return SuccessFileOverwritten; + case FileSys::InstallResult::ErrorBaseInstall: + return ErrorBaseGame; + default: + return InstallError; + } + } + void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, const std::string& custom_driver_name, const std::string& file_redirect_dir) { @@ -154,14 +225,14 @@ public: m_window = std::make_unique(&m_input_subsystem, m_native_window, m_vulkan_library); + m_system.SetFilesystem(m_vfs); + // Initialize system. auto android_keyboard = std::make_unique(); m_software_keyboard = android_keyboard.get(); m_system.SetShuttingDown(false); m_system.ApplySettings(); m_system.HIDCore().ReloadInputDevices(); - m_system.SetContentProvider(std::make_unique()); - m_system.SetFilesystem(std::make_shared()); m_system.SetAppletFrontendSet({ nullptr, // Amiibo Settings nullptr, // Controller Selector @@ -173,7 +244,8 @@ public: std::move(android_keyboard), // Software Keyboard nullptr, // Web Browser }); - m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem()); + m_system.SetContentProvider(std::make_unique()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); // Initialize account manager m_profile_manager = std::make_unique(); @@ -398,7 +470,7 @@ private: InputCommon::InputSubsystem m_input_subsystem; Common::DetachedTasks m_detached_tasks; Core::PerfStatsResults m_perf_stats{}; - std::shared_ptr m_vfs; + std::shared_ptr m_vfs; Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; bool m_is_running{}; SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; @@ -466,6 +538,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, Common::FS::SetAppDirectory(GetJString(env, j_directory)); } +int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, + [[maybe_unused]] jclass clazz, + 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) { diff --git a/src/android/app/src/main/res/drawable/ic_system_update_alt.xml b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml new file mode 100644 index 000000000..0f6adfdb8 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_system_update_alt.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0ae69afb4..5d4636d1a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -105,6 +105,15 @@ Share debug logs Share yuzu\'s log file to debug issues No log file found + Install game content + Install game updates or DLC + Error installing file to NAND + Game content installation failed. Please ensure content is valid and that the prod.keys file is installed. + Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead. + The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid. + Game content installed successfully + Game content was overwritten successfully + https://yuzu-emu.org/help/quickstart/#dumping-installed-updates Gaia isn\'t real diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 3226b884a..27f97c725 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -8,6 +8,7 @@ #include #include #include "common/common_types.h" +#include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" namespace Core::Crypto {