android: Add update support
This commit is contained in:
		| @@ -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 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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/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<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, | ||||
|                              const std::string& custom_driver_name, | ||||
|                              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_vulkan_library); | ||||
|  | ||||
|         m_system.SetFilesystem(m_vfs); | ||||
|  | ||||
|         // Initialize system. | ||||
|         auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); | ||||
|         m_software_keyboard = android_keyboard.get(); | ||||
|         m_system.SetShuttingDown(false); | ||||
|         m_system.ApplySettings(); | ||||
|         m_system.HIDCore().ReloadInputDevices(); | ||||
|         m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | ||||
|         m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); | ||||
|         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<FileSys::ContentProviderUnion>()); | ||||
|         m_system.GetFileSystemController().CreateFactories(*m_vfs); | ||||
|  | ||||
|         // Initialize account manager | ||||
|         m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); | ||||
| @@ -398,7 +470,7 @@ private: | ||||
|     InputCommon::InputSubsystem m_input_subsystem; | ||||
|     Common::DetachedTasks m_detached_tasks; | ||||
|     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}; | ||||
|     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) { | ||||
|   | ||||
| @@ -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_description">Share yuzu\'s log file to debug issues</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 --> | ||||
|     <string name="gaia_is_not_real">Gaia isn\'t real</string> | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include <set> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
|  | ||||
| namespace Core::Crypto { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user