android: Add update support
This commit is contained in:
		| @@ -227,6 +227,8 @@ object NativeLibrary { | |||||||
|  |  | ||||||
|     external fun setAppDirectory(directory: String) |     external fun setAppDirectory(directory: String) | ||||||
|  |  | ||||||
|  |     external fun installFileToNand(filename: String): Int | ||||||
|  |  | ||||||
|     external fun initializeGpuDriver( |     external fun initializeGpuDriver( | ||||||
|         hookLibDir: String?, |         hookLibDir: String?, | ||||||
|         customDriverDir: String?, |         customDriverDir: String?, | ||||||
| @@ -507,4 +509,15 @@ object NativeLibrary { | |||||||
|         const val RELEASED = 0 |         const val RELEASED = 0 | ||||||
|         const val PRESSED = 1 |         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 | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -94,6 +94,11 @@ class HomeSettingsFragment : Fragment() { | |||||||
|                 R.string.install_amiibo_keys_description, |                 R.string.install_amiibo_keys_description, | ||||||
|                 R.drawable.ic_nfc |                 R.drawable.ic_nfc | ||||||
|             ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }, |             ) { 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( |             HomeSetting( | ||||||
|                 R.string.select_games_folder, |                 R.string.select_games_folder, | ||||||
|                 R.string.select_games_folder_description, |                 R.string.select_games_folder_description, | ||||||
| @@ -103,7 +108,12 @@ class HomeSettingsFragment : Fragment() { | |||||||
|                 R.string.manage_save_data, |                 R.string.manage_save_data, | ||||||
|                 R.string.import_export_saves_description, |                 R.string.import_export_saves_description, | ||||||
|                 R.drawable.ic_save |                 R.drawable.ic_save | ||||||
|             ) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) }, |             ) { | ||||||
|  |                 ImportExportSavesFragment().show( | ||||||
|  |                     parentFragmentManager, | ||||||
|  |                     ImportExportSavesFragment.TAG | ||||||
|  |                 ) | ||||||
|  |             }, | ||||||
|             HomeSetting( |             HomeSetting( | ||||||
|                 R.string.install_prod_keys, |                 R.string.install_prod_keys, | ||||||
|                 R.string.install_prod_keys_description, |                 R.string.install_prod_keys_description, | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |         } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,7 +28,10 @@ | |||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/cpu_manager.h" | #include "core/cpu_manager.h" | ||||||
| #include "core/crypto/key_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/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/file_sys/vfs_real.h" | ||||||
| #include "core/frontend/applets/cabinet.h" | #include "core/frontend/applets/cabinet.h" | ||||||
| #include "core/frontend/applets/controller.h" | #include "core/frontend/applets/controller.h" | ||||||
| @@ -94,6 +97,74 @@ public: | |||||||
|         m_native_window = native_window; |         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<u8> buffer(1_MiB); | ||||||
|  |  | ||||||
|  |             for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { | ||||||
|  |                 const auto read = src->Read(buffer.data(), buffer.size(), i); | ||||||
|  |                 dest->Write(buffer.data(), read, i); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         enum InstallResult { | ||||||
|  |             Success = 0, | ||||||
|  |             SuccessFileOverwritten = 1, | ||||||
|  |             InstallError = 2, | ||||||
|  |             ErrorBaseGame = 3, | ||||||
|  |             ErrorFilenameExtension = 4, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | ||||||
|  |         m_system.GetFileSystemController().CreateFactories(*m_vfs); | ||||||
|  |  | ||||||
|  |         std::shared_ptr<FileSys::NSP> nsp; | ||||||
|  |         if (filename.ends_with("nsp")) { | ||||||
|  |             nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); | ||||||
|  |             if (nsp->IsExtractedType()) { | ||||||
|  |                 return InstallError; | ||||||
|  |             } | ||||||
|  |         } else if (filename.ends_with("xci")) { | ||||||
|  |             const auto xci = | ||||||
|  |                 std::make_shared<FileSys::XCI>(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, |     void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, | ||||||
|                              const std::string& custom_driver_name, |                              const std::string& custom_driver_name, | ||||||
|                              const std::string& file_redirect_dir) { |                              const std::string& file_redirect_dir) { | ||||||
| @@ -154,14 +225,14 @@ public: | |||||||
|         m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, |         m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, | ||||||
|                                                        m_vulkan_library); |                                                        m_vulkan_library); | ||||||
|  |  | ||||||
|  |         m_system.SetFilesystem(m_vfs); | ||||||
|  |  | ||||||
|         // Initialize system. |         // Initialize system. | ||||||
|         auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); |         auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); | ||||||
|         m_software_keyboard = android_keyboard.get(); |         m_software_keyboard = android_keyboard.get(); | ||||||
|         m_system.SetShuttingDown(false); |         m_system.SetShuttingDown(false); | ||||||
|         m_system.ApplySettings(); |         m_system.ApplySettings(); | ||||||
|         m_system.HIDCore().ReloadInputDevices(); |         m_system.HIDCore().ReloadInputDevices(); | ||||||
|         m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |  | ||||||
|         m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); |  | ||||||
|         m_system.SetAppletFrontendSet({ |         m_system.SetAppletFrontendSet({ | ||||||
|             nullptr,                     // Amiibo Settings |             nullptr,                     // Amiibo Settings | ||||||
|             nullptr,                     // Controller Selector |             nullptr,                     // Controller Selector | ||||||
| @@ -173,7 +244,8 @@ public: | |||||||
|             std::move(android_keyboard), // Software Keyboard |             std::move(android_keyboard), // Software Keyboard | ||||||
|             nullptr,                     // Web Browser |             nullptr,                     // Web Browser | ||||||
|         }); |         }); | ||||||
|         m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem()); |         m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | ||||||
|  |         m_system.GetFileSystemController().CreateFactories(*m_vfs); | ||||||
|  |  | ||||||
|         // Initialize account manager |         // Initialize account manager | ||||||
|         m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); |         m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); | ||||||
| @@ -398,7 +470,7 @@ private: | |||||||
|     InputCommon::InputSubsystem m_input_subsystem; |     InputCommon::InputSubsystem m_input_subsystem; | ||||||
|     Common::DetachedTasks m_detached_tasks; |     Common::DetachedTasks m_detached_tasks; | ||||||
|     Core::PerfStatsResults m_perf_stats{}; |     Core::PerfStatsResults m_perf_stats{}; | ||||||
|     std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs; |     std::shared_ptr<FileSys::VfsFilesystem> m_vfs; | ||||||
|     Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; |     Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | ||||||
|     bool m_is_running{}; |     bool m_is_running{}; | ||||||
|     SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; |     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)); |     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( | void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver( | ||||||
|     JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir, |     JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir, | ||||||
|     jstring custom_driver_name, jstring file_redirect_dir) { |     jstring custom_driver_name, jstring file_redirect_dir) { | ||||||
|   | |||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="48dp" | ||||||
|  |     android:height="48dp" | ||||||
|  |     android:viewportWidth="960" | ||||||
|  |     android:viewportHeight="960"> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="#FF000000" | ||||||
|  |       android:pathData="M140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h250v60L140,220v520h680v-520L570,220v-60h250q24,0 42,18t18,42v520q0,24 -18,42t-42,18L140,800ZM480,615L280,415l43,-43 127,127v-339h60v339l127,-127 43,43 -200,200Z"/> | ||||||
|  | </vector> | ||||||
| @@ -105,6 +105,15 @@ | |||||||
|     <string name="share_log">Share debug logs</string> |     <string name="share_log">Share debug logs</string> | ||||||
|     <string name="share_log_description">Share yuzu\'s log file to debug issues</string> |     <string name="share_log_description">Share yuzu\'s log file to debug issues</string> | ||||||
|     <string name="share_log_missing">No log file found</string> |     <string name="share_log_missing">No log file found</string> | ||||||
|  |     <string name="install_game_content">Install game content</string> | ||||||
|  |     <string name="install_game_content_description">Install game updates or DLC</string> | ||||||
|  |     <string name="install_game_content_failure">Error installing file to NAND</string> | ||||||
|  |     <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string> | ||||||
|  |     <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string> | ||||||
|  |     <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string> | ||||||
|  |     <string name="install_game_content_success">Game content installed successfully</string> | ||||||
|  |     <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string> | ||||||
|  |     <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</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> | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
| #include <set> | #include <set> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
|  | #include "core/file_sys/nca_metadata.h" | ||||||
| #include "core/file_sys/vfs.h" | #include "core/file_sys/vfs.h" | ||||||
|  |  | ||||||
| namespace Core::Crypto { | namespace Core::Crypto { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user